From 2731ae228795ca1a8d823f147212ef5c22261598 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Wed, 16 May 2018 14:05:42 -0400 Subject: STARTREK: Refactor text and menus Moved them out of the Graphics class and into their own files. --- engines/startrek/menu.cpp | 569 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 569 insertions(+) create mode 100644 engines/startrek/menu.cpp (limited to 'engines/startrek/menu.cpp') diff --git a/engines/startrek/menu.cpp b/engines/startrek/menu.cpp new file mode 100644 index 0000000000..f10e784a35 --- /dev/null +++ b/engines/startrek/menu.cpp @@ -0,0 +1,569 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/events.h" +#include "common/stream.h" +#include "graphics/cursorman.h" + +#include "startrek/graphics.h" + +namespace StarTrek { + +/** + * Returns the index of the button at the given position, or -1 if none. + */ +int StarTrekEngine::getMenuButtonAt(const Menu &menu, int x, int y) { + for (int i = 0; i < menu.numButtons; i++) { + const Sprite &spr = menu.sprites[i]; + + if (spr.drawMode != 2) + continue; + + int left = spr.pos.x - spr.bitmap->xoffset; + int top = spr.pos.y - spr.bitmap->yoffset; + + // Oddly, this doesn't account for x/yoffset... + int right = spr.pos.x + spr.bitmap->width - 1; + int bottom = spr.pos.y + spr.bitmap->height - 1; + + if (x >= left && x <= right && y >= top && y <= bottom) + return i; + } + + return -1; +} + +/** + * This chooses a sprite from the list to place the mouse cursor at. The sprite it chooses + * may be, for example, the top-leftmost one in the list. Exact behaviour is determined by + * the "mode" parameter. + * + * If "containMouseSprite" is a valid index, it's ensured that the mouse is contained + * within it. "mode" should be -1 in this case. + */ +void StarTrekEngine::chooseMousePositionFromSprites(Sprite *sprites, int numSprites, int containMouseSprite, int mode) { + uint16 mouseX1 = 0x7fff; // Candidate positions to warp mouse to + uint16 mouseY1 = 0x7fff; + uint16 mouseX2 = 0x7fff; + uint16 mouseY2 = 0x7fff; + + Common::Point mousePos = _gfx->getMousePos(); + + // Ensure the cursor is contained within one of the sprites + if (containMouseSprite >= 0 && containMouseSprite < numSprites) { + Common::Rect rect = sprites[containMouseSprite].getRect(); + + if (mousePos.x < rect.left || mousePos.x >= rect.right + || mousePos.y < rect.top || mousePos.y >= rect.bottom) { + mousePos.x = (rect.left + rect.right) / 2; + mousePos.y = (rect.top + rect.bottom) / 2; + } + } + + // Choose a sprite to warp the cursor to + for (int i = 0; i < numSprites; i++) { + Sprite *sprite = &sprites[i]; + if (sprite->drawMode != 2) // Skip hidden buttons + continue; + + Common::Rect rect = sprite->getRect(); + + int hCenter = (rect.left + rect.right) / 2; + int vCenter = (rect.top + rect.bottom) / 2; + + // Choose which sprite is closest based on certain criteria? + switch(mode) { + case 0: // Choose topmost, leftmost sprite that's below the cursor + if (((vCenter == mousePos.y && hCenter > mousePos.x) || vCenter > mousePos.y) + && (vCenter < mouseY1 || (vCenter == mouseY1 && hCenter < mouseX1))) { + mouseX1 = hCenter; + mouseY1 = vCenter; + } + // fall through + + case 4: // Choose topmost, leftmost sprite + if (vCenter < mouseY2 || (vCenter == mouseY2 && hCenter < mouseX2)) { + mouseX2 = hCenter; + mouseY2 = vCenter; + } + break; + + case 1: // Choose bottommost, rightmost sprite that's above the cursor + if (((vCenter == mousePos.y && hCenter < mousePos.x) || vCenter < mousePos.y) + && (mouseY1 == 0x7fff || vCenter > mouseY1 + || (vCenter == mouseY1 && hCenter > mouseX1))) { + mouseX1 = hCenter; + mouseY1 = vCenter; + } + // fall through + + case 5: // Choose bottommost, rightmost sprite + if (mouseY2 == 0x7fff || vCenter > mouseY2 + || (vCenter == mouseY2 && hCenter > mouseX2)) { + mouseX2 = hCenter; + mouseY2 = vCenter; + } + break; + + case 2: + // This seems broken... OR condition on first line has no affect on the logic... + if ((vCenter < mousePos.y || (vCenter == mouseY1 && hCenter == mousePos.x)) + && (mouseX1 == 0x7fff || vCenter >= mouseY1)) { + mouseX1 = hCenter; + mouseY1 = vCenter; + debug("Try %d %d", mouseX1, mouseY1); + } + if (mouseX2 == 0x7fff || vCenter > mouseY2 + || (hCenter == mouseX2 && vCenter == mouseY2)) { + mouseX2 = hCenter; + mouseY2 = vCenter; + } + break; + + case 3: + // Similar to above... + if ((vCenter > mousePos.y || (vCenter == mouseY1 && hCenter == mousePos.x)) + && (mouseX1 == 0x7fff || vCenter <= mouseY1)) { + mouseX1 = hCenter; + mouseY1 = vCenter; + } + if (mouseX2 == 0x7fff || vCenter < mouseY2 + || (hCenter == mouseX2 && vCenter == mouseY2)) { + mouseX2 = hCenter; + mouseY2 = vCenter; + } + break; + } + } + + // Warp mouse to one of the coordinates, if one is valid + if (mouseX1 != 0x7fff) { + mousePos.x = mouseX1; + mousePos.y = mouseY1; + } + else if (mouseX2 != 0x7fff) { + mousePos.x = mouseX2; + mousePos.y = mouseY2; + } + + _system->warpMouse(mousePos.x, mousePos.y); + +} + +/** + * Draws or removes the outline on menu buttons when the cursor hovers on them, or leaves + * them. + */ +void StarTrekEngine::drawMenuButtonOutline(SharedPtr bitmap, byte color) { + int lineWidth = bitmap->width-2; + int offsetToBottom = (bitmap->height-3)*bitmap->width; + + byte *dest = bitmap->pixels + bitmap->width + 1; + + while (lineWidth--) { + *dest = color; + *(dest+offsetToBottom) = color; + dest++; + } + + int lineHeight = bitmap->height - 2; + int offsetToRight = bitmap->width - 3; + + dest = bitmap->pixels + bitmap->width + 1; + + while (lineHeight--) { + *dest = color; + *(dest+offsetToRight) = color; + dest += bitmap->width; + } +} + +void StarTrekEngine::showOptionsMenu(int x, int y) { + bool tmpMouseControllingShip = _mouseControllingShip; + _mouseControllingShip = false; + + Common::Point oldMousePos = _gfx->getMousePos(); + SharedPtr oldMouseBitmap = _gfx->_mouseBitmap; + + _gfx->setMouseCursor(_gfx->loadBitmap("options")); + loadMenuButtons("options", x, y); + + uint32 disabledButtons = 0; + if (_musicWorking) { + if (_musicEnabled) + disabledButtons |= (1 << OPTIONBUTTON_ENABLEMUSIC); + else + disabledButtons |= (1 << OPTIONBUTTON_DISABLEMUSIC); + } + else + disabledButtons |= (1 << OPTIONBUTTON_ENABLEMUSIC) | (1 << OPTIONBUTTON_DISABLEMUSIC); + + if (_sfxWorking) { + if (_sfxEnabled) + disabledButtons |= (1 << OPTIONBUTTON_ENABLESFX); + else + disabledButtons |= (1 << OPTIONBUTTON_DISABLESFX); + } + else + disabledButtons |= (1 << OPTIONBUTTON_ENABLESFX) | (1 << OPTIONBUTTON_DISABLESFX); + + disableMenuButtons(disabledButtons); + chooseMousePositionFromSprites(_activeMenu->sprites, _activeMenu->numButtons, -1, 4); + int event = handleMenuEvents(0, false); + + unloadMenuButtons(); + _mouseControllingShip = tmpMouseControllingShip; + _gfx->setMouseCursor(oldMouseBitmap); + + if (event != MENUEVENT_LCLICK_OFFBUTTON && event != MENUEVENT_RCLICK_OFFBUTTON) + _system->warpMouse(oldMousePos.x, oldMousePos.y); + + + // Can't use OPTIONBUTTON constants since the button retvals differ from the button + // indices... + switch(event) { + case 0: // Save + showSaveMenu(); + break; + case 1: // Load + showLoadMenu(); + break; + case 2: // Enable music + _sound->setMusicEnabled(true); + break; + case 3: // Disable music + _sound->setMusicEnabled(false); + break; + case 4: // Enable sfx + _sound->setSfxEnabled(true); + break; + case 5: // Disable sfx + _sound->setSfxEnabled(false); + break; + case 6: // Quit + showQuitGamePrompt(20, 20); + break; + case 7: // Text + showTextConfigurationMenu(true); + break; + default: + break; + } +} + +/** + * Loads a .MNU file, which is a list of buttons to display. + */ +void StarTrekEngine::loadMenuButtons(String mnuFilename, int xpos, int ypos) { + if (_activeMenu == nullptr) + _keyboardControlsMouseOutsideMenu = _keyboardControlsMouse; + + SharedPtr oldMenu = _activeMenu; + _activeMenu = SharedPtr(new Menu()); + _activeMenu->nextMenu = oldMenu; + + SharedPtr stream = loadFile(mnuFilename + ".MNU"); + + _activeMenu->menuFile = stream; + _activeMenu->numButtons = _activeMenu->menuFile->size() / 16; + + for (int i = 0; i < _activeMenu->numButtons; i++) { + memset(&_activeMenu->sprites[i], 0, sizeof(Sprite)); + _gfx->addSprite(&_activeMenu->sprites[i]); + _activeMenu->sprites[i].drawMode = 2; + + char bitmapBasename[11]; + stream->seek(i * 16, SEEK_SET); + stream->read(bitmapBasename, 10); + for (int j = 0; j < 10; j++) { + if (bitmapBasename[j] == ' ') + bitmapBasename[j] = '\0'; + } + bitmapBasename[10] = '\0'; + + _activeMenu->sprites[i].bitmap = _gfx->loadBitmap(bitmapBasename); + _activeMenu->sprites[i].pos.x = stream->readUint16() + xpos; + _activeMenu->sprites[i].pos.y = stream->readUint16() + ypos; + _activeMenu->retvals[i] = stream->readUint16(); + + _activeMenu->sprites[i].field6 = 8; + } + + if (_activeMenu->retvals[_activeMenu->numButtons - 1] == 0) { + // Set default retvals for buttons + for (int i = 0; i < _activeMenu->numButtons; i++) + _activeMenu->retvals[i] = i; + } + + _activeMenu->selectedButton = -1; + _activeMenu->disabledButtons = 0; + _keyboardControlsMouse = false; +} + +/** + * Sets which buttons are visible based on the given bitmask. + */ +void StarTrekEngine::setVisibleMenuButtons(uint32 bits) { + for (int i = 0; i < _activeMenu->numButtons; i++) { + Sprite *sprite = &_activeMenu->sprites[i]; + uint32 spriteBitmask = (1 << i); + if (spriteBitmask == 0) + break; + + if ((bits & spriteBitmask) == 0 || sprite->drawMode != 0) { + if ((bits & spriteBitmask) == 0 && sprite->drawMode == 2) { + if (i == _activeMenu->selectedButton) { + drawMenuButtonOutline(sprite->bitmap, 0x00); + _activeMenu->selectedButton = -1; + } + + sprite->field16 = true; + sprite->bitmapChanged = true; + } + } + else { + _gfx->addSprite(sprite); + sprite->drawMode = 2; + sprite->bitmapChanged = true; + } + } + + _gfx->drawAllSprites(); + + for (int i = 0; i < _activeMenu->numButtons; i++) { + Sprite *sprite = &_activeMenu->sprites[i]; + uint32 spriteBitmask = (1 << i); + if (spriteBitmask == 0) + break; + + if ((bits & spriteBitmask) == 0 && sprite->drawMode == 2) { + _gfx->delSprite(sprite); + + // Setting drawMode to 0 is the game's way of saying that the menu button is + // hidden (since it would normally be 2). + sprite->drawMode = 0; + } + } +} + +/** + * Disables the given bitmask of buttons. + */ +void StarTrekEngine::disableMenuButtons(uint32 bits) { + _activeMenu->disabledButtons |= bits; + if (_activeMenu->selectedButton != -1 + && (_activeMenu->disabledButtons & (1 << _activeMenu->selectedButton))) { + Sprite *sprite = &_activeMenu->sprites[_activeMenu->selectedButton]; + drawMenuButtonOutline(sprite->bitmap, 0x00); + + sprite->bitmapChanged = true; + _activeMenu->selectedButton = -1; + } +} + +void StarTrekEngine::enableMenuButtons(uint32 bits) { + _activeMenu->disabledButtons &= ~bits; +} + +/** + * This returns either a special menu event (negative number) or the retval of the button + * clicked (usually an index, always positive). + */ +int StarTrekEngine::handleMenuEvents(uint32 ticksUntilClickingEnabled, bool arg4) { + // TODO: finish + + uint32 tickWhenClickingEnabled = _clockTicks + ticksUntilClickingEnabled; + + while (true) { + TrekEvent event; + while (popNextEvent(&event)) { + switch(event.type) { + + case TREKEVENT_TICK: { + case TREKEVENT_MOUSEMOVE: // FIXME: actual game only uses TICK event here + Common::Point mousePos = _gfx->getMousePos(); + int buttonIndex = getMenuButtonAt(*_activeMenu, mousePos.x, mousePos.y); + if (buttonIndex != -1) { + if (_activeMenu->disabledButtons & (1<selectedButton) { + if (_activeMenu->selectedButton != -1) { + Sprite &spr = _activeMenu->sprites[_activeMenu->selectedButton]; + drawMenuButtonOutline(spr.bitmap, 0x00); + spr.bitmapChanged = true; + } + if (buttonIndex != -1) { + Sprite &spr = _activeMenu->sprites[buttonIndex]; + drawMenuButtonOutline(spr.bitmap, 0xda); + spr.bitmapChanged = true; + } + _activeMenu->selectedButton = buttonIndex; + } + // Not added: updating mouse position (scummvm handles that) + + // sub_10492(); + // sub_10A91(); + _gfx->drawAllSprites(); + // sub_10BE7(); + // sub_2A4B1(); + + if (_finishedPlayingSpeech != 0) { + _finishedPlayingSpeech = 0; + if (_textDisplayMode != TEXTDISPLAY_WAIT) { + return TEXTBUTTON_SPEECH_DONE; + } + } + // sub_1E88C(); + _frameIndex++; + + if (ticksUntilClickingEnabled != 0 && _clockTicks >= tickWhenClickingEnabled) + return MENUEVENT_ENABLEINPUT; + break; + } + + case TREKEVENT_LBUTTONDOWN: + if (_activeMenu->selectedButton != -1) { + playSoundEffectIndex(0x10); + return _activeMenu->retvals[_activeMenu->selectedButton]; + } + else { + Common::Point mouse = _gfx->getMousePos(); + if (getMenuButtonAt(*_activeMenu, mouse.x, mouse.y) == -1) { + playSoundEffectIndex(0x10); + return MENUEVENT_LCLICK_OFFBUTTON; + } + } + break; + + case TREKEVENT_RBUTTONDOWN: + // TODO + break; + + case TREKEVENT_KEYDOWN: + // TODO + break; + + default: + break; + } + } + } +} + +void StarTrekEngine::unloadMenuButtons() { + if (_activeMenu->selectedButton != -1) + drawMenuButtonOutline(_activeMenu->sprites[_activeMenu->selectedButton].bitmap, 0x00); + + for (int i = 0; i < _activeMenu->numButtons; i++) { + Sprite *sprite = &_activeMenu->sprites[i]; + if (sprite->drawMode == 2) { + sprite->field16 = true; + sprite->bitmapChanged = true; + } + } + + _gfx->drawAllSprites(); + + for (int i = 0; i < _activeMenu->numButtons; i++) { + Sprite *sprite = &_activeMenu->sprites[i]; + sprite->bitmap.reset(); + if (sprite->drawMode == 2) + _gfx->delSprite(sprite); + } + + _activeMenu = _activeMenu->nextMenu; + + if (_activeMenu == nullptr) + _keyboardControlsMouse = _keyboardControlsMouseOutsideMenu; +} + +void StarTrekEngine::showSaveMenu() { + // TODO +} + +void StarTrekEngine::showLoadMenu() { + // TODO +} + +void StarTrekEngine::showQuitGamePrompt(int x, int y) { + const char *options[] = { + "Quit Game", + "#GENE\\GENER028#Yes, quit the game.", + "#GENE\\GENER008#No, do not quit the game.", + "" + }; + + if (_inQuitGameMenu) + return; + + _inQuitGameMenu = true; + int val = showText(&StarTrekEngine::readTextFromArray, (uintptr)options, x, y, 0xb0, true, 0, 1); + _inQuitGameMenu = false; + + if (val == 0) { + // sub_1e70d(); + _system->quit(); + } +} + +/** + * This can be called from startup or from the options menu. + * On startup, this tries to load the setting without user input. + */ +void StarTrekEngine::showTextConfigurationMenu(bool fromOptionMenu) { + const char *options[] = { // TODO: languages... + "Text display", + "Text subtitles.", + "Display text until you press enter.", + "No text displayed.", + "" + }; + + int val; + if (fromOptionMenu || (val = loadTextDisplayMode()) == -1) { + val = showText(&StarTrekEngine::readTextFromArray, (uintptr)options, 20, 30, 0xb0, true, 0, 1); + saveTextDisplayMode(val); + } + + switch(val) { + case 0: + _textDisplayMode = TEXTDISPLAY_SUBTITLES; + break; + case 1: + _textDisplayMode = TEXTDISPLAY_WAIT; + break; + case 2: + _textDisplayMode = TEXTDISPLAY_NONE; + break; + } +} + +int StarTrekEngine::loadTextDisplayMode() { + return -1; // TODO +} +void StarTrekEngine::saveTextDisplayMode(int value) { + // TODO; +} + +} -- cgit v1.2.3