aboutsummaryrefslogtreecommitdiff
path: root/engines/startrek/menu.cpp
diff options
context:
space:
mode:
authorMatthew Stewart2018-05-16 14:05:42 -0400
committerEugene Sandulenko2018-08-09 08:37:30 +0200
commit2731ae228795ca1a8d823f147212ef5c22261598 (patch)
treeb6b3316d7db1dda62a4ea83f0a955b5df311179a /engines/startrek/menu.cpp
parentbe8e807a4bbf28959e6e07b505d27b8ae622e0ef (diff)
downloadscummvm-rg350-2731ae228795ca1a8d823f147212ef5c22261598.tar.gz
scummvm-rg350-2731ae228795ca1a8d823f147212ef5c22261598.tar.bz2
scummvm-rg350-2731ae228795ca1a8d823f147212ef5c22261598.zip
STARTREK: Refactor text and menus
Moved them out of the Graphics class and into their own files.
Diffstat (limited to 'engines/startrek/menu.cpp')
-rw-r--r--engines/startrek/menu.cpp569
1 files changed, 569 insertions, 0 deletions
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> 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<Bitmap> 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<Menu> oldMenu = _activeMenu;
+ _activeMenu = SharedPtr<Menu>(new Menu());
+ _activeMenu->nextMenu = oldMenu;
+
+ SharedPtr<FileStream> 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<<buttonIndex))
+ buttonIndex = -1;
+ }
+
+ if (buttonIndex != _activeMenu->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;
+}
+
+}