/* 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 .
*
*/
#include "common/system.h"
#include "audio/mixer.h"
#include "graphics/cursorman.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
#include "graphics/managed_surface.h"
#include "graphics/palette.h"
#include "graphics/surface.h"
#include "dgds/includes.h"
#include "dgds/font.h"
#include "dgds/globals.h"
#include "dgds/menu.h"
#include "dgds/request.h"
#include "dgds/scene.h"
#include "dgds/sound.h"
namespace Dgds {
// TODO: These are the IDs for Dragon, this code needs updates for China/Beamish/etc
enum MenuButtonIds {
kMenuMainPlay = 120,
kMenuMainControls = 20,
kMenuMainOptions = 121,
kMenuMainCalibrate = 118,
kMenuMainFiles = 119,
kMenuMainQuit = 122,
kMenuControlsVCR = 127,
kMenuControlsPlay = 128,
kMenuSliderControlsDifficulty = 123,
kMenuSliderControlsTextSpeed = 125,
kMenuSliderControlsDetailLevel = 131,
kMenuOptionsJoystickOnOff = 139,
kMenuOptionsJoystickOnOffHoC = 174,
kMenuOptionsMouseOnOff = 138,
kMenuOptionsMouseOnOffHoC = 173,
kMenuOptionsSoundsOnOff = 137,
kMenuOptionsMusicOnOff = 140,
kMenuOptionsSoundsOnOffHoC = 175,
kMenuOptionsSoundsOnOffDE = 172, // German version
kMenuOptionsMusicOnOffHoC = 171,
kMenuOptionsVCR = 135,
kMenuOptionsPlay = 136,
//kMenuCalibrateJoystickBtn = 145,
//kMenuCalibrateMouseBtn = 146,
//kMenuCalibrateVCR = 144,
//kMenuCalibratePlay = 147,
//kMenuCalibrateVCRHoC = 159,
//kMenuCalibratePlayHoC = 158,
kMenuFilesSave = 107,
kMenuFilesRestore = 106,
kMenuFilesRestart = 105,
kMenuFilesVCR = 103,
kMenuFilesPlay = 130,
//kMenuSavePrevious = 58,
//kMenuSaveNext = 59,
//kMenuSaveSave = 53,
//kMenuSaveCancel = 54,
//kMenuSaveChangeDirectory = 55,
//kMenuChangeDirectoryOK = 95,
//kMenuChangeDirectoryCancel = 96,
//kMenuMouseCalibrationCalibrate = 157,
//kMenuMouseCalibrationPlay = 155,
//kMenuJoystickCalibrationOK = 132,
kMenuQuitYes = 134,
kMenuQuitNo = 133,
kMenuMaybeBetterSaveYes = 137,
kMenuMaybeBetterSaveNo = 138,
// Intro menu in Rise of the Dragon
kMenuIntroSkip = 143,
kMenuIntroPlay = 144,
// Intro menu in Heart of China / Willy Beamish
kMenuIntroJumpToIntroduction = 156,
kMenuIntroJumpToGame = 157,
kMenuIntroRestore = 150,
kMenuRestartYes = 163,
kMenuRestartNo = 164,
// For Dragon arcade
kMenuDragonReplayArcadeYes = 139,
kMenuDragonReplayArcadeNo = 140,
kMenuDragonFrustratedArcadeWin = 147,
kMenuDragonFrustratedArcadeKeepTrying = 148,
kMenuGameOverQuit = 169,
kMenuGameOverRestart = 168,
kMenuGameOverRestore = 170,
// Tank/train menu in Heart of China
kMenuTankTrainSkipArcade = 153,
kMenuTankTrainPlayArcade = 154,
};
Menu::Menu() : _curMenu(kMenuNone), _dragGadget(nullptr), _selectedItem(0), _numSelectable(0) {
_screenBuffer.create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8());
}
Menu::~Menu() {
_screenBuffer.free();
}
void Menu::setRequestData(const REQFileData &data) {
for (auto &req : data._requests) {
_menuRequests[req._fileNum] = req;
}
}
void Menu::setScreenBuffer() {
Graphics::Surface *dst = g_system->lockScreen();
_screenBuffer.copyFrom(*dst);
g_system->unlockScreen();
}
bool Menu::updateOptionsGadget(Gadget *gadget) {
Audio::Mixer *mixer = DgdsEngine::getInstance()->_mixer;
const char *mouseStr, *soundStr, *musicStr, *onStr, *offStr;
if (DgdsEngine::getInstance()->getGameLang() == Common::EN_ANY) {
mouseStr = "MOUSE";
soundStr = "SOUND";
musicStr = "MUSIC";
onStr = "ON";
offStr = "OFF";
} else if (DgdsEngine::getInstance()->getGameLang() == Common::DE_DEU) {
mouseStr = "MAUS";
soundStr = "TON";
musicStr = "MUSIK";
onStr = "AN";
offStr = "AUS";
} else {
error("Unsupported language %d", DgdsEngine::getInstance()->getGameLang());
}
switch (gadget->_gadgetNo) {
case kMenuOptionsJoystickOnOff:
case kMenuOptionsJoystickOnOffHoC:
gadget->_buttonName = Common::String::format("JOYSTICK %s", onStr);
return true;
case kMenuOptionsMouseOnOff:
case kMenuOptionsMouseOnOffHoC:
gadget->_buttonName = Common::String::format("%s %s", mouseStr, onStr);
return true;
case kMenuOptionsSoundsOnOff: // same id as kMenuMaybeBetterSaveYes
case kMenuOptionsSoundsOnOffDE:
case kMenuOptionsSoundsOnOffHoC: {
bool isMuted = mixer->isSoundTypeMuted(Audio::Mixer::kSFXSoundType);
gadget->_buttonName = Common::String::format("%s %s", soundStr, isMuted ? offStr : onStr);
return true;
}
case kMenuOptionsMusicOnOff:
case kMenuOptionsMusicOnOffHoC: {
bool isMuted = mixer->isSoundTypeMuted(Audio::Mixer::kMusicSoundType);
gadget->_buttonName = Common::String::format("%s %s", musicStr, isMuted ? offStr : onStr);
return true;
}
default:
return false;
}
}
void Menu::configureGadget(MenuId menu, Gadget *gadget) {
DgdsEngine *engine = DgdsEngine::getInstance();
// a bit of a hack - set up the gadget with the correct value before we draw it.
if (menu == kMenuControls) {
SliderGadget *slider = dynamic_cast(gadget);
if (!slider)
return;
switch (gadget->_gadgetNo) {
case kMenuSliderControlsDifficulty:
slider->setSteps(3, false);
slider->setValue(engine->getDifficulty()); // TODO: set a difficulty value
break;
case kMenuSliderControlsTextSpeed:
slider->setSteps(10, false);
slider->setValue(9 - engine->getTextSpeed());
break;
case kMenuSliderControlsDetailLevel:
slider->setSteps(2, true);
slider->setValue(engine->getDetailLevel());
break;
default:
break;
// do nothing.
}
} else if (menu == kMenuOptions) {
updateOptionsGadget(gadget);
}
}
void Menu::drawMenu(MenuId menu) {
bool firstDraw = (_curMenu != menu);
_curMenu = menu;
Common::Array > gadgets = _menuRequests[_curMenu]._gadgets;
// Restore background when drawing submenus
g_system->copyRectToScreen(_screenBuffer.getPixels(), _screenBuffer.pitch, 0, 0, _screenBuffer.w, _screenBuffer.h);
// This is not very efficient, but it only happens once when the menu is opened.
Graphics::Surface *screen = g_system->lockScreen();
Graphics::ManagedSurface managed(screen->w, screen->h, screen->format);
managed.blitFrom(*screen);
_menuRequests[_curMenu].drawBg(&managed);
_numSelectable = 0;
for (Common::SharedPtr &gptr : gadgets) {
Gadget *gadget = gptr.get();
if (gadget->_gadgetType == kGadgetButton || gadget->_gadgetType == kGadgetSlider) {
if (firstDraw)
configureGadget(menu, gadget);
gadget->draw(&managed);
_numSelectable++;
}
}
if (firstDraw) {
_selectedItem = _numSelectable - 1;
putMouseOnSelectedItem();
}
drawMenuText(managed);
// Can't use transparent blit here as the font is often color 0.
screen->copyRectToSurface(*managed.surfacePtr(), 0, 0, Common::Rect(screen->w, screen->h));
g_system->unlockScreen();
g_system->updateScreen();
}
Gadget *Menu::getSelectedItem() {
int item = 0;
Common::Array > gadgets = _menuRequests[_curMenu]._gadgets;
for (Common::SharedPtr &gptr : gadgets) {
Gadget *gadget = gptr.get();
if (gadget->_gadgetType == kGadgetButton || gadget->_gadgetType == kGadgetSlider) {
if (item == _selectedItem)
return gadget;
item++;
}
}
return nullptr;
}
void Menu::putMouseOnSelectedItem() {
Gadget *selected = getSelectedItem();
if (!selected)
return;
const Common::Point midPt = selected->midPoint();
// put the mouse on the first button/slider
g_system->warpMouse(midPt.x, midPt.y);
return;
}
void Menu::drawMenuText(Graphics::ManagedSurface &dst) {
Common::Array > gadgets = _menuRequests[_curMenu]._gadgets;
Common::Array textItems = _menuRequests[_curMenu]._textItemList;
if (gadgets.empty())
return;
// TODO: Get the parent coordinates properly
uint16 parentX = gadgets[0].get()->_parentX;
uint16 parentY = gadgets[0].get()->_parentY;
uint16 pos = 0;
for (TextItem &textItem : textItems) {
// HACK: Skip the first entry, which corresponds to the header
if (pos == 0) {
pos++;
continue;
}
const DgdsFont *font = RequestData::getMenuFont();
int w = font->getStringWidth(textItem._txt);
font->drawString(dst.surfacePtr(), textItem._txt, parentX + textItem._x, parentY + textItem._y, w, 0);
pos++;
}
}
Gadget *Menu::getClickedMenuItem(const Common::Point &mouseClick) {
if (_curMenu == kMenuNone)
return nullptr;
Common::Array > gadgets = _menuRequests[_curMenu]._gadgets;
for (Common::SharedPtr &gptr : gadgets) {
Gadget *gadget = gptr.get();
if (gadget->_gadgetType == kGadgetButton || gadget->_gadgetType == kGadgetSlider) {
if (gadget->containsPoint(mouseClick)) {
return gadget;
}
}
}
return nullptr;
}
void Menu::onMouseLDown(const Common::Point &mouse) {
SliderGadget *slider = dynamic_cast(getClickedMenuItem(mouse));
if (slider) {
_dragGadget = slider;
_dragStartPt = mouse;
}
}
void Menu::onMouseMove(const Common::Point &mouse) {
if (!_dragGadget)
return;
_dragGadget->onDrag(mouse);
drawMenu(_curMenu);
}
void Menu::onMouseLUp(const Common::Point &mouse) {
DgdsEngine *engine = DgdsEngine::getInstance();
if (_dragGadget && mouse != _dragStartPt) {
int16 setting = _dragGadget->onDragFinish(mouse);
switch (_dragGadget->_gadgetNo) {
case kMenuSliderControlsDifficulty:
engine->setDifficulty(setting);
break;
case kMenuSliderControlsTextSpeed:
engine->setTextSpeed(9 - setting);
break;
case kMenuSliderControlsDetailLevel:
engine->setDetailLevel(static_cast(setting));
break;
}
drawMenu(_curMenu);
_dragGadget = nullptr;
_dragStartPt = Common::Point();
return;
}
_dragGadget = nullptr;
Gadget *gadget = getClickedMenuItem(mouse);
bool isToggle = false;
if (!gadget)
return;
// Click animation
if (dynamic_cast(gadget)) {
gadget->toggle(false);
if (_curMenu == kMenuOptions)
isToggle = updateOptionsGadget(gadget);
drawMenu(_curMenu);
g_system->delayMillis(500);
gadget->toggle(true);
}
if (_curMenu == kMenuOptions)
handleClickOptionsMenu(mouse);
else if (_curMenu == kMenuSkipPlayIntro)
handleClickSkipPlayIntroMenu(mouse);
else
handleClick(mouse);
if (isToggle)
drawMenu(_curMenu);
}
void Menu::handleClick(const Common::Point &mouse) {
DgdsEngine *engine = DgdsEngine::getInstance();
int currentScene = engine->getScene()->getNum();
Gadget *gadget = getClickedMenuItem(mouse);
int16 clickedMenuItem = gadget->_gadgetNo;
switch (clickedMenuItem) {
case kMenuMainPlay:
case kMenuControlsPlay:
case kMenuOptionsPlay:
case kMenuFilesPlay:
case kMenuMaybeBetterSaveNo:
//case kMenuCalibratePlay:
//case kMenuCalibratePlayHoC:
//case kMenuMouseCalibrationPlay:
_curMenu = kMenuNone;
CursorMan.showMouse(false);
break;
case kMenuMainControls:
drawMenu(kMenuControls);
break;
case kMenuMainOptions:
drawMenu(kMenuOptions);
break;
case kMenuMainCalibrate:
//case kMenuJoystickCalibrationOK:
//case kMenuMouseCalibrationCalibrate: // NOTE: same ID as kMenuIntroJumpToGame (for HOC)
case kMenuIntroJumpToGame:
if (_curMenu == kMenuSkipPlayIntro) {
hideMenu();
engine->setShowClock(true);
engine->changeScene(24);
} else {
// Do nothing - the calibrate menu doesn't offer
// any functionality in ScummVM
//drawMenu(kMenuCalibrate);
drawMenu(_curMenu);
}
break;
case kMenuMainFiles:
//case kMenuSaveCancel:
drawMenu(kMenuFiles);
break;
case kMenuMainQuit:
drawMenu(kMenuReallyQuit);
break;
//case kMenuCalibrateVCR: // NOTE: same ID as kMenuIntroPlay
case kMenuIntroPlay:
drawMenu(kMenuMain);
break;
case kMenuControlsVCR:
case kMenuOptionsVCR:
//case kMenuCalibrateVCRHoC:
case kMenuFilesVCR:
case kMenuQuitNo:
case kMenuRestartNo:
drawMenu(kMenuMain);
break;
//case kMenuCalibrateJoystickBtn:
// drawMenu(kMenuCalibrateJoystick);
// break;
//case kMenuCalibrateMouseBtn:
// drawMenu(kMenuCalibrateMouse);
// break;
//case kMenuChangeDirectoryCancel:
// drawMenu(kMenuSaveDlg);
// break;
case kMenuFilesRestore:
case kMenuGameOverRestore:
case kMenuIntroRestore:
if (g_engine->loadGameDialog())
hideMenu();
else
drawMenu(_curMenu);
break;
case kMenuFilesRestart:
drawMenu(kMenuRestart);
break;
case kMenuFilesSave: // TODO: Add an option to support original save/load dialogs?
//case kMenuSavePrevious:
//case kMenuSaveNext:
//case kMenuSaveSave:
case kMenuMaybeBetterSaveYes:
if (g_engine->saveGameDialog())
hideMenu();
else
drawMenu(_curMenu);
break;
//case kMenuSaveChangeDirectory:
// drawMenu(kMenuChangeDir);
// break;
//case kMenuChangeDirectoryOK:
// // Do nothing - the change directory menu doesn't offer
// // any functionality in ScummVM
// drawMenu(_curMenu);
// break;
case kMenuQuitYes:
g_engine->quitGame();
break;
case kMenuRestartYes:
engine->restartGame();
break;
case kMenuGameOverQuit:
drawMenu(kMenuReallyQuit);
break;
case kMenuGameOverRestart:
drawMenu(kMenuRestart);
break;
case kMenuSliderControlsDifficulty: {
SliderGadget *slider = dynamic_cast(gadget);
if (!slider)
break;
int16 setting = slider->onClick(mouse);
engine->setDifficulty(setting);
// redraw for update.
drawMenu(_curMenu);
break;
}
case kMenuSliderControlsTextSpeed: {
SliderGadget *slider = dynamic_cast(gadget);
if (!slider)
break;
int16 setting = slider->onClick(mouse);
engine->setTextSpeed(9 - setting);
drawMenu(_curMenu);
break;
}
case kMenuSliderControlsDetailLevel: {
SliderGadget *slider = dynamic_cast(gadget);
if (!slider)
break;
int16 setting = slider->onClick(mouse);
engine->setDetailLevel(static_cast(setting));
drawMenu(_curMenu);
break;
}
case kMenuDragonReplayArcadeYes:
case kMenuDragonReplayArcadeNo:
if (engine->getGameId() == GID_DRAGON && _curMenu == kMenuReplayArcade) {
DragonGlobals *dragonGlobals = static_cast(engine->getGameGlobals());
dragonGlobals->setArcadeState(clickedMenuItem == kMenuDragonReplayArcadeYes ? 20 : 10);
_curMenu = kMenuNone;
}
break;
case kMenuDragonFrustratedArcadeWin:
case kMenuDragonFrustratedArcadeKeepTrying:
if (engine->getGameId() == GID_DRAGON && _curMenu == kMenuArcadeFrustrated) {
DragonGlobals *dragonGlobals = static_cast(engine->getGameGlobals());
dragonGlobals->setArcadeState(clickedMenuItem == kMenuDragonFrustratedArcadeWin ? 6 : 20);
_curMenu = kMenuNone;
}
break;
case kMenuTankTrainSkipArcade:
hideMenu();
if (currentScene == 73)
engine->changeScene(12); // skip tank mini-game
else if (currentScene == 84)
engine->changeScene(106); // skip train mini-game
break;
case kMenuTankTrainPlayArcade:
// TODO
if (currentScene == 73)
warning("Play tank mini-game");
else if (currentScene == 84)
warning("Play train mini-game");
drawMenu(_curMenu);
break;
default:
debug("Clicked ID %d", clickedMenuItem);
break;
}
}
void Menu::handleClickOptionsMenu(const Common::Point &mouse) {
DgdsEngine *engine = DgdsEngine::getInstance();
Audio::Mixer *mixer = engine->_mixer;
Gadget *gadget = getClickedMenuItem(mouse);
int16 clickedMenuItem = gadget->_gadgetNo;
Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType;
switch (clickedMenuItem) {
case kMenuOptionsJoystickOnOff:
case kMenuOptionsJoystickOnOffHoC:
case kMenuOptionsMouseOnOff: // same id as kMenuMaybeBetterSaveNo
case kMenuOptionsMouseOnOffHoC:
// Do nothing - we don't toggle joystick or mouse functionality
break;
case kMenuOptionsSoundsOnOff: // same id as kMenuMaybeBetterSaveYes
case kMenuOptionsSoundsOnOffDE:
case kMenuOptionsSoundsOnOffHoC:
soundType = Audio::Mixer::kSFXSoundType;
// fall through
case kMenuOptionsMusicOnOff:
case kMenuOptionsMusicOnOffHoC:
if (!mixer->isSoundTypeMuted(soundType)) {
mixer->muteSoundType(soundType, true);
warning("TODO: Sync volume and pause music");
//midiPlayer->syncVolume();
//engine->_soundPlayer->pauseMusic();
} else {
mixer->muteSoundType(soundType, false);
warning("TODO: Sync volume and resume music");
//midiPlayer->syncVolume();
//engine->_soundPlayer->resumeMusic();
}
updateOptionsGadget(gadget);
break;
default:
handleClick(mouse);
break;
}
}
void Menu::handleClickSkipPlayIntroMenu(const Common::Point &mouse) {
DgdsEngine *engine = DgdsEngine::getInstance();
Gadget *gadget = getClickedMenuItem(mouse);
int16 clickedMenuItem = gadget->_gadgetNo;
switch (clickedMenuItem) {
case kMenuIntroPlay:
hideMenu();
break;
case kMenuIntroSkip:
hideMenu();
engine->setShowClock(true);
engine->changeScene(5);
break;
case kMenuIntroJumpToIntroduction:
hideMenu();
if (engine->getGameId() == GID_HOC)
engine->changeScene(100);
else if (engine->getGameId() == GID_WILLY)
engine->changeScene(24);
break;
case kMenuIntroJumpToGame:
hideMenu();
if (engine->getGameId() == GID_HOC)
engine->changeScene(24);
else if (engine->getGameId() == GID_WILLY)
engine->changeScene(4);
break;
default:
handleClick(mouse);
break;
}
}
void Menu::toggleGadget(int16 gadgetId, bool enable) {
Common::Array > gadgets = _menuRequests[_curMenu]._gadgets;
for (Common::SharedPtr &gptr : gadgets) {
Gadget *gadget = gptr.get();
if (gadget->_gadgetNo == gadgetId) {
gadget->toggle(enable);
return;
}
}
}
/**
* Choose the next menu item via keyboard - items are numbered backwards
* so this *decreases* the counter.
*/
void Menu::nextChoice() {
_selectedItem--;
if (_selectedItem < 0)
_selectedItem = _numSelectable - 1;
putMouseOnSelectedItem();
}
void Menu::prevChoice() {
_selectedItem++;
if (_selectedItem >= _numSelectable)
_selectedItem = 0;
putMouseOnSelectedItem();
}
void Menu::activateChoice() {
Gadget *selected = getSelectedItem();
if (!selected)
return;
handleClick(selected->midPoint());
}
} // End of namespace Dgds