diff options
Diffstat (limited to 'engines/neverhood/menumodule.cpp')
-rw-r--r-- | engines/neverhood/menumodule.cpp | 1116 |
1 files changed, 1116 insertions, 0 deletions
diff --git a/engines/neverhood/menumodule.cpp b/engines/neverhood/menumodule.cpp new file mode 100644 index 0000000000..a8631cb0d6 --- /dev/null +++ b/engines/neverhood/menumodule.cpp @@ -0,0 +1,1116 @@ +/* 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 "neverhood/menumodule.h" +#include "neverhood/gamemodule.h" + +#include "engines/savestate.h" + +namespace Neverhood { + +enum { + MAIN_MENU = 0, + CREDITS_SCENE = 1, + MAKING_OF = 2, + LOAD_GAME_MENU = 3, + SAVE_GAME_MENU = 4, + DELETE_GAME_MENU = 5, + QUERY_OVR_MENU = 6 +}; + +enum { + kMainMenuRestartGame = 0, + kMainMenuLoadGame = 1, + kMainMenuSaveGame = 2, + kMainMenuResumeGame = 3, + kMainMenuQuitGame = 4, + kMainMenuCredits = 5, + kMainMenuMakingOf = 6, + kMainMenuToggleMusic = 7, + kMainMenuDeleteGame = 8 +}; + +static const uint32 kMakingOfSmackerFileHashList[] = { + 0x21082409, + 0x21082809, + 0x21083009, + 0x21080009, + 0x21086009, + 0x2108A009, + 0x21092009, + 0x210A2009, + 0x210C2009, + 0x21082411, + 0x21082811, + 0x21083011, + 0x21080011, + 0 +}; + +MenuModule::MenuModule(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule), _savegameList(NULL) { + + SetMessageHandler(&MenuModule::handleMessage); + + _savedPaletteData = _vm->_screen->getPaletteData(); + _vm->_mixer->pauseAll(true); + + createScene(MAIN_MENU, -1); +} + +MenuModule::~MenuModule() { + _vm->_mixer->pauseAll(false); + _vm->_screen->setPaletteData(_savedPaletteData); +} + +void MenuModule::setLoadgameInfo(uint index) { + _savegameSlot = (*_savegameList)[index].slotNum; +} + +void MenuModule::setSavegameInfo(const Common::String &description, uint index, bool newSavegame) { + _savegameDescription = description; + _savegameSlot = newSavegame ? -1 : (*_savegameList)[index].slotNum; +} + +void MenuModule::setDeletegameInfo(uint index) { + _savegameSlot = (*_savegameList)[index].slotNum; +} + +void MenuModule::createScene(int sceneNum, int which) { + _sceneNum = sceneNum; + switch (_sceneNum) { + case MAIN_MENU: + _childObject = new MainMenu(_vm, this); + break; + case CREDITS_SCENE: + _childObject = new CreditsScene(_vm, this, true); + break; + case MAKING_OF: + createSmackerScene(kMakingOfSmackerFileHashList, false, true, true); + break; + case LOAD_GAME_MENU: + createLoadGameMenu(); + break; + case SAVE_GAME_MENU: + createSaveGameMenu(); + break; + case DELETE_GAME_MENU: + createDeleteGameMenu(); + break; + case QUERY_OVR_MENU: + _childObject = new QueryOverwriteMenu(_vm, this, _savegameDescription); + break; + } + SetUpdateHandler(&MenuModule::updateScene); + _childObject->handleUpdate(); +} + +void MenuModule::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case MAIN_MENU: + switch (_moduleResult) { + case kMainMenuRestartGame: + _vm->_gameModule->requestRestartGame(false); + leaveModule(0); + break; + case kMainMenuLoadGame: + createScene(LOAD_GAME_MENU, -1); + break; + case kMainMenuSaveGame: + createScene(SAVE_GAME_MENU, -1); + break; + case kMainMenuResumeGame: + leaveModule(0); + break; + case kMainMenuQuitGame: + leaveModule(0); + _vm->quitGame(); + break; + case kMainMenuCredits: + createScene(CREDITS_SCENE, -1); + break; + case kMainMenuMakingOf: + createScene(MAKING_OF, -1); + break; + case kMainMenuToggleMusic: + // TODO Toggle music 0048A367 + createScene(MAIN_MENU, -1); + break; + case kMainMenuDeleteGame: + createScene(DELETE_GAME_MENU, -1); + break; + default: + createScene(MAIN_MENU, -1); + break; + } + break; + case CREDITS_SCENE: + case MAKING_OF: + createScene(MAIN_MENU, -1); + break; + case LOAD_GAME_MENU: + handleLoadGameMenuAction(_moduleResult != 1); + break; + case SAVE_GAME_MENU: + handleSaveGameMenuAction(_moduleResult != 1, true); + break; + case DELETE_GAME_MENU: + handleDeleteGameMenuAction(_moduleResult != 1); + break; + case QUERY_OVR_MENU: + handleSaveGameMenuAction(_moduleResult != 1, false); + break; + default: + break; + } + } +} + +uint32 MenuModule::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + return Module::handleMessage(messageNum, param, sender);; +} + +void MenuModule::createLoadGameMenu() { + _savegameSlot = -1; + _savegameList = new SavegameList(); + loadSavegameList(); + _childObject = new LoadGameMenu(_vm, this, _savegameList); +} + +void MenuModule::createSaveGameMenu() { + _savegameSlot = -1; + _savegameList = new SavegameList(); + loadSavegameList(); + _childObject = new SaveGameMenu(_vm, this, _savegameList); +} + +void MenuModule::createDeleteGameMenu() { + _savegameSlot = -1; + _savegameList = new SavegameList(); + loadSavegameList(); + _childObject = new DeleteGameMenu(_vm, this, _savegameList); +} + +void MenuModule::handleLoadGameMenuAction(bool doLoad) { + createScene(MAIN_MENU, -1); + if (doLoad && _savegameSlot >= 0) { + _vm->loadGameState(_savegameSlot); + leaveModule(0); + } + delete _savegameList; + _savegameList = NULL; +} + +void MenuModule::handleSaveGameMenuAction(bool doSave, bool doQuery) { + if (doSave && doQuery && _savegameSlot >= 0) { + createScene(QUERY_OVR_MENU, -1); + } else if (doSave) { + // Get a new slot number if it's a new savegame + if (_savegameSlot < 0) + _savegameSlot = _savegameList->size() > 0 ? _savegameList->back().slotNum + 1 : 0; + // Restore the scene palette and background so that the correct thumbnail is saved + byte *menuPaletteData = _vm->_screen->getPaletteData(); + _vm->_screen->setPaletteData(_savedPaletteData); + _vm->_gameModule->redrawPrevChildObject(); + _vm->saveGameState(_savegameSlot, _savegameDescription); + _vm->_screen->setPaletteData(menuPaletteData); + createScene(MAIN_MENU, -1); + } else { + createScene(MAIN_MENU, -1); + } + delete _savegameList; + _savegameList = NULL; +} + +void MenuModule::handleDeleteGameMenuAction(bool doDelete) { + createScene(MAIN_MENU, -1); + if (doDelete && _savegameSlot >= 0) { + _vm->removeGameState(_savegameSlot); + } + delete _savegameList; + _savegameList = NULL; +} + +void MenuModule::loadSavegameList() { + + Common::SaveFileManager *saveFileMan = _vm->_system->getSavefileManager(); + Neverhood::NeverhoodEngine::SaveHeader header; + Common::String pattern = _vm->getTargetName(); + pattern += ".???"; + + Common::StringArray filenames; + filenames = saveFileMan->listSavefiles(pattern.c_str()); + Common::sort(filenames.begin(), filenames.end()); + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) { + int slotNum = atoi(file->c_str() + file->size() - 3); + if (slotNum >= 0 && slotNum <= 999) { + Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str()); + if (in) { + if (Neverhood::NeverhoodEngine::readSaveHeader(in, false, header) == Neverhood::NeverhoodEngine::kRSHENoError) { + SavegameItem savegameItem; + savegameItem.slotNum = slotNum; + savegameItem.description = header.description; + _savegameList->push_back(savegameItem); + } + delete in; + } + } + } + +} + +MenuButton::MenuButton(NeverhoodEngine *vm, Scene *parentScene, uint buttonIndex, uint32 fileHash, const NRect &collisionBounds) + : StaticSprite(vm, 900), _parentScene(parentScene), _buttonIndex(buttonIndex), _countdown(0) { + + loadSprite(fileHash, kSLFDefDrawOffset | kSLFDefPosition, 100); + _collisionBounds = collisionBounds; + setVisible(false); + SetUpdateHandler(&MenuButton::update); + SetMessageHandler(&MenuButton::handleMessage); +} + +void MenuButton::update() { + updatePosition(); + if (_countdown != 0 && (--_countdown) == 0) { + setVisible(false); + sendMessage(_parentScene, 0x2000, _buttonIndex); + } +} + +uint32 MenuButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown == 0) { + setVisible(true); + _countdown = 4; + } + messageResult = 1; + break; + } + return messageResult; +} + +MainMenu::MainMenu(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule) { + + static const uint32 kMenuButtonFileHashes[] = { + 0x36C62120, + 0x56C62120, + 0x96C62120, + 0x16C62121, + 0x16C62122, + 0x16C62124, + 0x16C62128, + 0x16C62130, + 0x16C62100 + }; + + static const NRect kMenuButtonCollisionBounds[] = { + NRect(52, 121, 110, 156), + NRect(52, 192, 109, 222), + NRect(60, 257, 119, 286), + NRect(67, 326, 120, 354), + NRect(70, 389, 128, 416), + NRect(523, 113, 580, 144), + NRect(525, 176, 577, 206), + NRect(527, 384, 580, 412), + NRect(522, 255, 580, 289) + }; + + setBackground(0x08C0020C); + setPalette(0x08C0020C); + insertScreenMouse(0x00208084); + + insertStaticSprite(0x41137051, 100); + insertStaticSprite(0xC10B2015, 100); + + // TODO Only if music is enabled + _musicOnButton = insertStaticSprite(0x0C24C0EE, 100); + + for (uint buttonIndex = 0; buttonIndex < 9; ++buttonIndex) { + Sprite *menuButton = insertSprite<MenuButton>(this, buttonIndex, + kMenuButtonFileHashes[buttonIndex], kMenuButtonCollisionBounds[buttonIndex]); + addCollisionSprite(menuButton); + } + + SetUpdateHandler(&Scene::update); + SetMessageHandler(&MainMenu::handleMessage); + +} + +uint32 MainMenu::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + leaveScene(param.asInteger()); + break; + } + return 0; +} + +static const uint32 kCreditsSceneFileHashes[] = { + 0x6081128C, 0x608112BC, 0x608112DC, + 0x6081121C, 0x6081139C, 0x6081109C, + 0x6081169C, 0x60811A9C, 0x6081029C, + 0x0081128C, 0x008112BC, 0x008012BC, + 0x008112DC, 0x0081121C, 0x0081139C, + 0x0081109C, 0x0081169C, 0x00811A9C, + 0x0081029C, 0x0081329C, 0xC08112BC, + 0xC08112DC, 0xC081121C, 0xC081139C, + 0 +}; + +CreditsScene::CreditsScene(NeverhoodEngine *vm, Module *parentModule, bool canAbort) + : Scene(vm, parentModule), _canAbort(canAbort), _screenIndex(0), _ticksDuration(0), + _countdown(216) { + + SetUpdateHandler(&CreditsScene::update); + SetMessageHandler(&CreditsScene::handleMessage); + + setBackground(0x6081128C); + setPalette(0x6081128C); + + _ticksTime = _vm->_system->getMillis() + 202100; + + _musicResource = new MusicResource(_vm); + _musicResource->load(0x30812225); + _musicResource->play(0); + +} + +CreditsScene::~CreditsScene() { + _musicResource->unload(); + delete _musicResource; +} + +void CreditsScene::update() { + Scene::update(); + if (_countdown != 0) { + if (_screenIndex == 23 && _vm->_system->getMillis() > _ticksTime) + leaveScene(0); + else if ((--_countdown) == 0) { + ++_screenIndex; + if (kCreditsSceneFileHashes[_screenIndex] == 0) + leaveScene(0); + else { + _background->load(kCreditsSceneFileHashes[_screenIndex]); + _palette->addPalette(kCreditsSceneFileHashes[_screenIndex], 0, 256, 0); + if (_screenIndex < 5) + _countdown = 192; + else if (_screenIndex < 15) + _countdown = 144; + else if (_screenIndex < 16) + _countdown = 216; + else if (_screenIndex < 23) + _countdown = 144; + else + _countdown = 1224; + } + } + } +} + +uint32 CreditsScene::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0009: + leaveScene(0); + break; + case 0x000B: + if (param.asInteger() == Common::KEYCODE_ESCAPE && _canAbort) + leaveScene(0); + break; + case 0x101D: + _ticksDuration = _ticksTime - _vm->_system->getMillis(); + break; + case 0x101E: + _ticksTime = _ticksDuration + _vm->_system->getMillis(); + break; + } + return 0; +} + +Widget::Widget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + int baseObjectPriority, int baseSurfacePriority) + : StaticSprite(vm, baseObjectPriority), _parentScene(parentScene), + _baseObjectPriority(baseObjectPriority), _baseSurfacePriority(baseSurfacePriority) { + + SetUpdateHandler(&Widget::update); + SetMessageHandler(&Widget::handleMessage); + + setPosition(x, y); +} + +void Widget::onClick() { + _parentScene->setCurrWidget(this); +} + +void Widget::setPosition(int16 x, int16 y) { + _x = x; + _y = y; + updateBounds(); +} + +void Widget::refreshPosition() { + _needRefresh = true; + StaticSprite::updatePosition(); + _collisionBoundsOffset.set(0, 0, + _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + updateBounds(); +} + +void Widget::initialize() { + // Empty +} + +int16 Widget::getWidth() { + return _spriteResource.getDimensions().width; +} + +int16 Widget::getHeight() { + return _spriteResource.getDimensions().height; +} + +void Widget::enterWidget() { + // Empty +} + +void Widget::exitWidget() { + // Empty +} + +void Widget::update() { + handleSpriteUpdate(); + StaticSprite::updatePosition(); +} + +uint32 Widget::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + onClick(); + messageResult = 1; + break; + } + return messageResult; +} + +TextLabelWidget::TextLabelWidget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + int baseObjectPriority, int baseSurfacePriority, + const byte *string, int stringLen, BaseSurface *drawSurface, int16 tx, int16 ty, FontSurface *fontSurface) + : Widget(vm, x, y, parentScene, baseObjectPriority, baseSurfacePriority), + _string(string), _stringLen(stringLen), _drawSurface(drawSurface), _tx(tx), _ty(ty), _fontSurface(fontSurface) { + +} + +void TextLabelWidget::initialize() { + _parentScene->addSprite(this); + _parentScene->addCollisionSprite(this); +} + +int16 TextLabelWidget::getWidth() { + return _fontSurface->getStringWidth(_string, _stringLen); +} + +int16 TextLabelWidget::getHeight() { + return _fontSurface->getCharHeight(); +} + +void TextLabelWidget::drawString(int maxStringLength) { + _fontSurface->drawString(_drawSurface, _x, _y, _string, MIN(_stringLen, maxStringLength)); + _collisionBoundsOffset.set(_tx, _ty, getWidth(), getHeight()); + updateBounds(); +} + +void TextLabelWidget::clear() { + _collisionBoundsOffset.set(0, 0, 0, 0); + updateBounds(); +} + +void TextLabelWidget::setString(const byte *string, int stringLen) { + _string = string; + _stringLen = stringLen; +} + +TextEditWidget::TextEditWidget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + int maxStringLength, FontSurface *fontSurface, uint32 fileHash, const NRect &rect) + : Widget(vm, x, y, parentScene, 1000, 1000), + _maxStringLength(maxStringLength), _fontSurface(fontSurface), _fileHash(fileHash), _rect(rect), + _cursorSurface(NULL), _cursorTicks(0), _cursorPos(0), _cursorFileHash(0), _cursorWidth(0), _cursorHeight(0), + _modified(false), _readOnly(false) { + + _maxVisibleChars = (_rect.x2 - _rect.x1) / _fontSurface->getCharWidth(); + _cursorPos = 0; + + SetUpdateHandler(&TextEditWidget::update); + SetMessageHandler(&TextEditWidget::handleMessage); +} + +TextEditWidget::~TextEditWidget() { + delete _cursorSurface; +} + +void TextEditWidget::onClick() { + NPoint mousePos = _parentScene->getMousePos(); + mousePos.x -= _x + _rect.x1; + mousePos.y -= _y + _rect.y1; + if (mousePos.x >= 0 && mousePos.x <= _rect.x2 - _rect.x1 && + mousePos.y >= 0 && mousePos.y <= _rect.y2 - _rect.y1) { + if (_entryString.size() == 1) + _cursorPos = 0; + else { + int newCursorPos = mousePos.x / _fontSurface->getCharWidth(); + if (mousePos.x % _fontSurface->getCharWidth() > _fontSurface->getCharWidth() / 2 && newCursorPos <= (int)_entryString.size()) + ++newCursorPos; + _cursorPos = MIN((int)_entryString.size(), newCursorPos); + } + _cursorSurface->setVisible(true); + refresh(); + } + Widget::onClick(); +} + +void TextEditWidget::initialize() { + SpriteResource cursorSpriteResource(_vm); + + _spriteResource.load(_fileHash, true); + createSurface(_baseSurfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + refreshPosition(); + _parentScene->addSprite(this); + _parentScene->addCollisionSprite(this); + _surface->setVisible(true); + _textLabelWidget = new TextLabelWidget(_vm, _rect.x1, _rect.y1 + (_rect.y2 - _rect.y1 + 1 - _fontSurface->getCharHeight()) / 2, + _parentScene, _baseObjectPriority + 1, _baseSurfacePriority + 1, + (const byte*)_entryString.c_str(), _entryString.size(), _surface, _x, _y, _fontSurface); + _textLabelWidget->initialize(); + if (_cursorFileHash != 0) { + cursorSpriteResource.load(_cursorFileHash, true); + _cursorSurface = new BaseSurface(_vm, 0, cursorSpriteResource.getDimensions().width, cursorSpriteResource.getDimensions().height); + _cursorSurface->drawSpriteResourceEx(cursorSpriteResource, false, false, cursorSpriteResource.getDimensions().width, cursorSpriteResource.getDimensions().height); + _cursorSurface->setVisible(!_readOnly); + } + refresh(); +} + +void TextEditWidget::enterWidget() { + if (!_readOnly) { + _cursorSurface->setVisible(true); + _vm->_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true); + } + refresh(); +} + +void TextEditWidget::exitWidget() { + if (!_readOnly) { + _cursorSurface->setVisible(false); + _vm->_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false); + } + refresh(); +} + +void TextEditWidget::setCursor(uint32 cursorFileHash, int16 cursorWidth, int16 cursorHeight) { + _cursorFileHash = cursorFileHash; + _cursorWidth = cursorWidth; + _cursorHeight = cursorHeight; +} + +void TextEditWidget::drawCursor() { + if (_cursorSurface->getVisible() && _cursorPos >= 0 && _cursorPos <= _maxVisibleChars) { + NDrawRect sourceRect(0, 0, _cursorWidth, _cursorHeight); + _surface->copyFrom(_cursorSurface->getSurface(), _rect.x1 + _cursorPos * _fontSurface->getCharWidth(), + _rect.y1 + (_rect.y2 - _cursorHeight - _rect.y1 + 1) / 2, sourceRect); + } else if (!_readOnly) + _cursorSurface->setVisible(false); +} + +void TextEditWidget::updateString() { + _textLabelWidget->setString((const byte *)_entryString.c_str(), _entryString.size()); + _textLabelWidget->drawString(_maxVisibleChars); +} + +Common::String& TextEditWidget::getString() { + return _entryString; +} + +void TextEditWidget::setString(const Common::String &string) { + _entryString = string; + _cursorPos = _entryString.size(); + _modified = false; + refresh(); +} + +void TextEditWidget::handleAsciiKey(char ch) { + if ((int)_entryString.size() < _maxStringLength && + ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == ' ')) { + _entryString.insertChar(ch, _cursorPos); + ++_cursorPos; + _modified = true; + refresh(); + } +} + +void TextEditWidget::handleKeyDown(Common::KeyCode keyCode) { + bool doRefresh = true; + switch (keyCode) { + case Common::KEYCODE_HOME: + _cursorPos = 0; + break; + case Common::KEYCODE_END: + _cursorPos = _entryString.size(); + break; + case Common::KEYCODE_LEFT: + if (_entryString.size() > 0 && _cursorPos > 0) + --_cursorPos; + break; + case Common::KEYCODE_RIGHT: + if (_cursorPos < (int)_entryString.size()) + ++_cursorPos; + break; + case Common::KEYCODE_DELETE: + if (_entryString.size() > 0 && _cursorPos < (int)_entryString.size()) { + _entryString.deleteChar(_cursorPos); + _modified = true; + } + break; + case Common::KEYCODE_BACKSPACE: + if (_entryString.size() > 0 && _cursorPos > 0) { + _entryString.deleteChar(--_cursorPos); + _modified = true; + } + break; + default: + doRefresh = false; + break; + } + if (doRefresh) { + _cursorSurface->setVisible(!_readOnly); + _cursorTicks = 0; + refresh(); + } +} + +void TextEditWidget::refresh() { + refreshPosition(); + updateString(); + if (_cursorFileHash != 0) + drawCursor(); +} + +void TextEditWidget::update() { + Widget::update(); + if (!_readOnly && _parentScene->getCurrWidget() == this && _cursorTicks++ == 10) { + _cursorSurface->setVisible(!_cursorSurface->getVisible()); + refresh(); + _cursorTicks = 0; + } +} + +uint32 TextEditWidget::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Widget::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x000A: + handleAsciiKey(param.asInteger()); + break; + case 0x000B: + handleKeyDown((Common::KeyCode)param.asInteger()); + break; + } + return messageResult; +} + +SavegameListBox::SavegameListBox(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + SavegameList *savegameList, FontSurface *fontSurface, uint32 bgFileHash, const NRect &rect) + : Widget(vm, x, y, parentScene, 1000, 1000), + _savegameList(savegameList), _fontSurface(fontSurface), _bgFileHash(bgFileHash), _rect(rect), + _maxStringLength(0), _firstVisibleItem(0), _lastVisibleItem(0), _currIndex(0) { + + _maxVisibleItemsCount = (_rect.y2 - _rect.y1) / _fontSurface->getCharHeight(); + _maxStringLength = (_rect.x2 - _rect.x1) / _fontSurface->getCharWidth(); +} + +void SavegameListBox::onClick() { + NPoint mousePos = _parentScene->getMousePos(); + mousePos.x -= _x + _rect.x1; + mousePos.y -= _y + _rect.y1; + if (mousePos.x >= 0 && mousePos.x <= _rect.x2 - _rect.x1 && + mousePos.y >= 0 && mousePos.y <= _rect.y2 - _rect.y1) { + // We add 1 to the char height to ensure that the correct entry is chosen if the + // user clicks at the bottom the text entry + int newIndex = _firstVisibleItem + mousePos.y / (_fontSurface->getCharHeight() + 1); + if (newIndex <= _lastVisibleItem) { + _currIndex = newIndex; + refresh(); + _parentScene->setCurrWidget(this); + _parentScene->refreshDescriptionEdit(); + } + } +} + +void SavegameListBox::initialize() { + _spriteResource.load(_bgFileHash, true); + createSurface(_baseSurfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + refreshPosition(); + _parentScene->addSprite(this); + _parentScene->addCollisionSprite(this); + _surface->setVisible(true); + buildItems(); + _firstVisibleItem = 0; + _lastVisibleItem = MIN(_maxVisibleItemsCount, (int)_textLabelItems.size()) - 1; + refresh(); +} + +void SavegameListBox::buildItems() { + SavegameList &savegameList = *_savegameList; + int16 itemX = _rect.x1, itemY = 0; + for (uint i = 0; i < savegameList.size(); ++i) { + const byte *string = (const byte*)savegameList[i].description.c_str(); + int stringLen = (int)savegameList[i].description.size(); + TextLabelWidget *label = new TextLabelWidget(_vm, itemX, itemY, _parentScene, _baseObjectPriority + 1, + _baseSurfacePriority + 1, string, MIN(stringLen, _maxStringLength), _surface, _x, _y, _fontSurface); + label->initialize(); + _textLabelItems.push_back(label); + } +} + +void SavegameListBox::drawItems() { + for (int i = 0; i < (int)_textLabelItems.size(); ++i) { + TextLabelWidget *label = _textLabelItems[i]; + if (i >= _firstVisibleItem && i <= _lastVisibleItem) { + label->setY(_rect.y1 + (i - _firstVisibleItem) * _fontSurface->getCharHeight()); + label->updateBounds(); + label->drawString(_maxStringLength); + } else + label->clear(); + } +} + +void SavegameListBox::refresh() { + refreshPosition(); + drawItems(); +} + +void SavegameListBox::scrollUp() { + if (_firstVisibleItem > 0) { + --_firstVisibleItem; + --_lastVisibleItem; + refresh(); + } +} + +void SavegameListBox::scrollDown() { + if (_lastVisibleItem < (int)_textLabelItems.size()) { + ++_firstVisibleItem; + ++_lastVisibleItem; + refresh(); + } +} + +void SavegameListBox::pageUp() { + int amount = MIN(_firstVisibleItem, _maxVisibleItemsCount); + if (amount > 0) { + _firstVisibleItem -= amount; + _lastVisibleItem -= amount; + refresh(); + } +} + +void SavegameListBox::pageDown() { + int amount = MIN((int)_textLabelItems.size() - _lastVisibleItem, _maxVisibleItemsCount); + if (amount > 0) { + _firstVisibleItem += amount; + _lastVisibleItem += amount; + refresh(); + } +} + +GameStateMenu::GameStateMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList, + const uint32 *buttonFileHashes, const NRect *buttonCollisionBounds, + uint32 backgroundFileHash, uint32 fontFileHash, + uint32 mouseFileHash, const NRect *mouseRect, + uint32 listBoxBackgroundFileHash, int16 listBoxX, int16 listBoxY, const NRect &listBoxRect, + uint32 textEditBackgroundFileHash, uint32 textEditCursorFileHash, int16 textEditX, int16 textEditY, const NRect &textEditRect, + uint32 textFileHash1, uint32 textFileHash2) + : Scene(vm, parentModule), _currWidget(NULL), _savegameList(savegameList) { + + _fontSurface = new FontSurface(_vm, fontFileHash, 32, 7, 32, 11, 17); + + setBackground(backgroundFileHash); + setPalette(backgroundFileHash); + insertScreenMouse(mouseFileHash, mouseRect); + insertStaticSprite(textFileHash1, 200); + insertStaticSprite(textFileHash2, 200); + + _listBox = new SavegameListBox(_vm, listBoxX, listBoxY, this, + _savegameList, _fontSurface, listBoxBackgroundFileHash, listBoxRect); + _listBox->initialize(); + + _textEditWidget = new TextEditWidget(_vm, textEditX, textEditY, this, 29, + _fontSurface, textEditBackgroundFileHash, textEditRect); + if (textEditCursorFileHash != 0) + _textEditWidget->setCursor(textEditCursorFileHash, 2, 13); + else + _textEditWidget->setReadOnly(true); + _textEditWidget->initialize(); + setCurrWidget(_textEditWidget); + + for (uint buttonIndex = 0; buttonIndex < 6; ++buttonIndex) { + Sprite *menuButton = insertSprite<MenuButton>(this, buttonIndex, + buttonFileHashes[buttonIndex], buttonCollisionBounds[buttonIndex]); + addCollisionSprite(menuButton); + } + + SetUpdateHandler(&Scene::update); + SetMessageHandler(&GameStateMenu::handleMessage); + +} + +GameStateMenu::~GameStateMenu() { + delete _fontSurface; +} + +NPoint GameStateMenu::getMousePos() { + NPoint pt; + pt.x = _mouseCursor->getX(); + pt.y = _mouseCursor->getY(); + return pt; +} + +void GameStateMenu::setCurrWidget(Widget *newWidget) { + if (newWidget && newWidget != _currWidget) { + if (_currWidget) + _currWidget->exitWidget(); + newWidget->enterWidget(); + _currWidget = newWidget; + } +} + +void GameStateMenu::refreshDescriptionEdit() { + uint currIndex = _listBox->getCurrIndex(); + _textEditWidget->setString((*_savegameList)[currIndex].description); + setCurrWidget(_textEditWidget); +} + +void GameStateMenu::performAction() { + // Empty +} + +uint32 GameStateMenu::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x000A: + if (!_textEditWidget->isReadOnly()) { + sendMessage(_textEditWidget, 0x000A, param.asInteger()); + setCurrWidget(_textEditWidget); + } + break; + case 0x000B: + if (param.asInteger() == Common::KEYCODE_RETURN) + performAction(); + else if (param.asInteger() == Common::KEYCODE_ESCAPE) + leaveScene(1); + else if (!_textEditWidget->isReadOnly()) { + sendMessage(_textEditWidget, 0x000B, param.asInteger()); + setCurrWidget(_textEditWidget); + } + break; + case 0x2000: + // Handle menu button click + switch (param.asInteger()) { + case 0: + performAction(); + break; + case 1: + leaveScene(1); + break; + case 2: + _listBox->pageUp(); + break; + case 3: + _listBox->scrollUp(); + break; + case 4: + _listBox->scrollDown(); + break; + case 5: + _listBox->pageDown(); + break; + } + break; + } + return 0; +} + +static const uint32 kSaveGameMenuButtonFileHashes[] = { + 0x8359A824, 0x0690E260, 0x0352B050, + 0x1392A223, 0x13802260, 0x0B32B200 +}; + +static const NRect kSaveGameMenuButtonCollisionBounds[] = { + NRect(518, 106, 602, 160), + NRect(516, 378, 596, 434), + NRect(394, 108, 458, 206), + NRect(400, 204, 458, 276), + NRect(398, 292, 456, 352), + NRect(396, 352, 460, 444) +}; + +static const NRect kSaveGameMenuListBoxRect(0, 0, 320, 272); +static const NRect kSaveGameMenuTextEditRect(0, 0, 377, 17); +static const NRect kSaveGameMenuMouseRect(50, 47, 427, 64); + +SaveGameMenu::SaveGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList) + : GameStateMenu(vm, parentModule, savegameList, kSaveGameMenuButtonFileHashes, kSaveGameMenuButtonCollisionBounds, + 0x30084E25, 0x2328121A, + 0x84E21308, &kSaveGameMenuMouseRect, + 0x1115A223, 60, 142, kSaveGameMenuListBoxRect, + 0x3510A868, 0x8290AC20, 50, 47, kSaveGameMenuTextEditRect, + 0x1340A5C2, 0x1301A7EA) { + +} + +void SaveGameMenu::performAction() { + ((MenuModule*)_parentModule)->setSavegameInfo(_textEditWidget->getString(), + _listBox->getCurrIndex(), _textEditWidget->isModified()); + leaveScene(0); +} + +static const uint32 kLoadGameMenuButtonFileHashes[] = { + 0x100B2091, 0x84822B03, 0x20E22087, + 0x04040107, 0x04820122, 0x24423047 +}; + +static const NRect kLoadGameMenuButtonCollisionBounds[] = { + NRect( 44, 115, 108, 147), + NRect( 52, 396, 112, 426), + NRect(188, 116, 245, 196), + NRect(189, 209, 239, 269), + NRect(187, 301, 233, 349), + NRect(182, 358, 241, 433) +}; + +static const NRect kLoadGameMenuListBoxRect(0, 0, 320, 271); +static const NRect kLoadGameMenuTextEditRect(0, 0, 320, 17); +static const NRect kLoadGameMenuMouseRect(263, 48, 583, 65); + +LoadGameMenu::LoadGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList) + : GameStateMenu(vm, parentModule, savegameList, kLoadGameMenuButtonFileHashes, kLoadGameMenuButtonCollisionBounds, + 0x98620234, 0x201C2474, + 0x2023098E, &kLoadGameMenuMouseRect, + 0x04040409, 263, 142, kLoadGameMenuListBoxRect, + 0x10924C03, 0, 263, 48, kLoadGameMenuTextEditRect, + 0x0BC600A3, 0x0F960021) { + +} + +void LoadGameMenu::performAction() { + ((MenuModule*)_parentModule)->setLoadgameInfo(_listBox->getCurrIndex()); + leaveScene(0); +} + +static const uint32 kDeleteGameMenuButtonFileHashes[] = { + 0x8198E268, 0xDD0C4620, 0x81296520, + 0x8D284211, 0x8C004621, 0x07294020 +}; + +static const NRect kDeleteGameMenuButtonCollisionBounds[] = { + NRect(518, 46, 595, 91), + NRect(524, 322, 599, 369), + NRect(395, 40, 462, 127), + NRect(405, 126, 460, 185), + NRect(397, 205, 456, 273), + NRect(395, 278, 452, 372) +}; + +static const NRect kDeleteGameMenuListBoxRect(0, 0, 320, 271); +static const NRect kDeleteGameMenuTextEditRect(0, 0, 320, 17); + +DeleteGameMenu::DeleteGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList) + : GameStateMenu(vm, parentModule, savegameList, kDeleteGameMenuButtonFileHashes, kDeleteGameMenuButtonCollisionBounds, + 0x4080E01C, 0x728523ED, + 0x0E018400, NULL, + 0xA5584211, 61, 64, kDeleteGameMenuListBoxRect, + 0x250A3060, 0, 49, 414, kDeleteGameMenuTextEditRect, + 0x80083C01, 0x84181E81) { + +} + +void DeleteGameMenu::performAction() { + ((MenuModule*)_parentModule)->setDeletegameInfo(_listBox->getCurrIndex()); + leaveScene(0); +} + +QueryOverwriteMenu::QueryOverwriteMenu(NeverhoodEngine *vm, Module *parentModule, const Common::String &description) + : Scene(vm, parentModule) { + + static const uint32 kQueryOverwriteMenuButtonFileHashes[] = { + 0x90312400, + 0x94C22A22 + }; + + static const NRect kQueryOverwriteMenuCollisionBounds[] = { + NRect(145, 334, 260, 385), + NRect(365, 340, 477, 388) + }; + + setBackground(0x043692C4); + setPalette(0x043692C4); + insertScreenMouse(0x692C004B); + insertStaticSprite(0x08C0AC24, 200); + + for (uint buttonIndex = 0; buttonIndex < 2; ++buttonIndex) { + Sprite *menuButton = insertSprite<MenuButton>(this, buttonIndex, + kQueryOverwriteMenuButtonFileHashes[buttonIndex], kQueryOverwriteMenuCollisionBounds[buttonIndex]); + addCollisionSprite(menuButton); + } + + // Draw the query text to the background, each text line is centered + // NOTE The original had this text in its own class + FontSurface *fontSurface = new FontSurface(_vm, 0x94188D4D, 32, 7, 32, 11, 17); + Common::StringArray textLines; + textLines.push_back(description); + textLines.push_back("Game exists."); + textLines.push_back("Overwrite it?"); + for (uint i = 0; i < textLines.size(); ++i) + fontSurface->drawString(_background->getSurface(), 106 + (423 - textLines[i].size() * 11) / 2, + 127 + 31 + i * 17, (const byte*)textLines[i].c_str()); + delete fontSurface; + + SetUpdateHandler(&Scene::update); + SetMessageHandler(&QueryOverwriteMenu::handleMessage); +} + +uint32 QueryOverwriteMenu::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + // Handle menu button click + leaveScene(param.asInteger()); + break; + } + return 0; +} + +} // End of namespace Neverhood |