/* 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/system.h" #include "common/textconsole.h" #include "graphics/cursorman.h" #include "parallaction/exec.h" #include "parallaction/input.h" #include "parallaction/parallaction.h" #include "parallaction/debug.h" namespace Parallaction { #define MOUSEARROW_WIDTH_NS 16 #define MOUSEARROW_HEIGHT_NS 16 #define MOUSECOMBO_WIDTH_NS 32 // sizes for cursor + selected inventory item #define MOUSECOMBO_HEIGHT_NS 32 struct MouseComboProperties { int _xOffset; int _yOffset; int _width; int _height; }; /* // TODO: improve NS's handling of normal cursor before merging cursor code. MouseComboProperties _mouseComboProps_NS = { 7, // combo x offset (the icon from the inventory will be rendered from here) 7, // combo y offset (ditto) 32, // combo (arrow + icon) width 32 // combo (arrow + icon) height }; */ MouseComboProperties _mouseComboProps_BR = { 8, // combo x offset (the icon from the inventory will be rendered from here) 8, // combo y offset (ditto) 68, // combo (arrow + icon) width 68 // combo (arrow + icon) height }; Input::Input(Parallaction *vm) : _vm(vm) { _gameType = _vm->getGameType(); _transCurrentHoverItem = 0; _hasDelayedAction = false; // actived when the character needs to move before taking an action _mouseState = MOUSE_DISABLED; _activeItem._index = 0; _activeItem._id = 0; _mouseButtons = 0; _delayedActionZone.reset(); _inputMode = 0; _hasKeyPressEvent = false; _dinoCursor = 0; _dougCursor = 0; _donnaCursor = 0; _comboArrow = 0; _mouseArrow = 0; initCursors(); } Input::~Input() { if (_gameType == GType_Nippon) { delete _mouseArrow; } delete _comboArrow; delete _dinoCursor; delete _dougCursor; delete _donnaCursor; } // FIXME: the engine has 3 event loops. The following routine hosts the main one, // and it's called from 8 different places in the code. There exist 2 more specialised // loops which could possibly be merged into this one with some effort in changing // caller code, i.e. adding condition checks. // void Input::readInput() { bool updateMousePos = false; Common::Event e; _mouseButtons = kMouseNone; _hasKeyPressEvent = false; Common::EventManager *eventMan = _vm->_system->getEventManager(); while (eventMan->pollEvent(e)) { updateMousePos = true; switch (e.type) { case Common::EVENT_KEYDOWN: _hasKeyPressEvent = true; _keyPressed = e.kbd; if (e.kbd.hasFlags(Common::KBD_CTRL) && e.kbd.keycode == Common::KEYCODE_d) _vm->_debugger->attach(); updateMousePos = false; break; case Common::EVENT_LBUTTONDOWN: _mouseButtons = kMouseLeftDown; break; case Common::EVENT_LBUTTONUP: _mouseButtons = kMouseLeftUp; break; case Common::EVENT_RBUTTONDOWN: _mouseButtons = kMouseRightDown; break; case Common::EVENT_RBUTTONUP: _mouseButtons = kMouseRightUp; break; case Common::EVENT_RTL: case Common::EVENT_QUIT: return; default: break; } } if (updateMousePos) { setCursorPos(e.mouse); } _vm->_debugger->onFrame(); return; } bool Input::getLastKeyDown(uint16 &ascii) { ascii = _keyPressed.ascii; return (_hasKeyPressEvent); } // FIXME: see comment for readInput() void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) { if (buttonEventMask == kMouseNone) { _mouseButtons = kMouseNone; // don't wait on nothing return; } const int32 LOOP_RESOLUTION = 30; if (timeout <= 0) { do { readInput(); _vm->_system->delayMillis(LOOP_RESOLUTION); } while ((_mouseButtons & buttonEventMask) == 0); } else { do { readInput(); _vm->_system->delayMillis(LOOP_RESOLUTION); timeout -= LOOP_RESOLUTION; } while ((timeout > 0) && (_mouseButtons & buttonEventMask) == 0); } } int Input::updateGameInput() { int event = kEvNone; if (!isMouseEnabled() || (g_engineFlags & kEngineBlockInput) || (g_engineFlags & kEngineWalking) || (g_engineFlags & kEngineChangeLocation)) { debugC(3, kDebugInput, "updateGameInput: input flags (mouse: %i, block: %i, walking: %i, changeloc: %i)", isMouseEnabled(), (g_engineFlags & kEngineBlockInput) == 0, (g_engineFlags & kEngineWalking) == 0, (g_engineFlags & kEngineChangeLocation) == 0 ); return event; } if (_gameType == GType_Nippon) { if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) { if (_keyPressed.keycode == Common::KEYCODE_l) event = kEvLoadGame; if (_keyPressed.keycode == Common::KEYCODE_s) event = kEvSaveGame; } } else if (_gameType == GType_BRA) { if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) { if (_keyPressed.keycode == Common::KEYCODE_F5) event = kEvIngameMenu; } } else { error("unsupported gametype in updateGameInput"); } if (event == kEvNone) { translateGameInput(); } return event; } int Input::updateInput() { int oldMode = _inputMode; int event = kEvNone; readInput(); switch (_inputMode) { case kInputModeGame: event = updateGameInput(); break; case kInputModeInventory: updateInventoryInput(); break; default: break; } // when mode changes, then consider any input consumed // for the current frame if (oldMode != _inputMode) { _mouseButtons = kEvNone; _hasKeyPressEvent = false; } return event; } void Input::trackMouse(ZonePtr z) { if ((z != _hoverZone) && (_hoverZone)) { stopHovering(); return; } if (!z) { return; } if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) { _hoverZone = z; _vm->_gfx->showFloatingLabel(_hoverZone->_label); return; } } void Input::stopHovering() { _hoverZone.reset(); _vm->_gfx->hideFloatingLabel(); } void Input::takeAction(ZonePtr z) { stopHovering(); _vm->pauseJobs(); _vm->runZone(z); _vm->resumeJobs(); } void Input::walkTo(const Common::Point &dest) { stopHovering(); setArrowCursor(); _vm->scheduleWalk(dest.x, dest.y, true); } bool Input::translateGameInput() { if (g_engineFlags & kEnginePauseJobs) { return false; } if (_hasDelayedAction) { // if walking is over, then take programmed action takeAction(_delayedActionZone); _hasDelayedAction = false; _delayedActionZone.reset(); return true; } if (_mouseButtons == kMouseRightDown) { // right button down shows inventory enterInventoryMode(); return true; } Common::Point mousePos; getAbsoluteCursorPos(mousePos); // test if mouse is hovering on an interactive zone for the currently selected inventory item ZonePtr z = _vm->hitZone(_activeItem._id, mousePos.x, mousePos.y); if (((_mouseButtons == kMouseLeftUp) && (_activeItem._id == 0) && ((g_engineFlags & kEngineWalking) == 0)) && ((!z) || (ACTIONTYPE(z) != kZoneCommand))) { walkTo(mousePos); return true; } trackMouse(z); if (!z) { return true; } if ((_mouseButtons == kMouseLeftUp) && ((_activeItem._id != 0) || (ACTIONTYPE(z) == kZoneCommand))) { bool noWalk = z->_flags & kFlagsNoWalk; // check the explicit no-walk flag if (_gameType == GType_BRA) { // action performed on object marked for self-use do not need walk in BRA noWalk |= ((z->_flags & kFlagsYourself) != 0); } if (noWalk) { takeAction(z); } else { // action delayed: if Zone defined a moveto position the character is programmed to move there, // else it will move to the mouse position _delayedActionZone = z; _hasDelayedAction = true; if (z->_moveTo.y != 0) { mousePos = z->_moveTo; } walkTo(mousePos); } _vm->beep(); setArrowCursor(); return true; } return true; } void Input::enterInventoryMode() { Common::Point mousePos; getAbsoluteCursorPos(mousePos); bool hitCharacter = _vm->hitZone(kZoneYou, mousePos.x, mousePos.y); if (hitCharacter) { if (_activeItem._id != 0) { _activeItem._index = (_activeItem._id >> 16) & 0xFFFF; g_engineFlags |= kEngineDragging; } else { setArrowCursor(); } } stopHovering(); _vm->pauseJobs(); _vm->openInventory(); _transCurrentHoverItem = -1; _inputMode = kInputModeInventory; } void Input::exitInventoryMode() { // right up hides inventory Common::Point mousePos; getCursorPos(mousePos); int pos = _vm->getHoverInventoryItem(mousePos.x, mousePos.y); _vm->highlightInventoryItem(-1); // disable if ((g_engineFlags & kEngineDragging)) { g_engineFlags &= ~kEngineDragging; ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(pos)); if (z) { _vm->dropItem(z->u._mergeObj1); _vm->dropItem(z->u._mergeObj2); _vm->addInventoryItem(z->u._mergeObj3); _vm->_cmdExec->run(z->_commands); // commands might set a new _inputMode } } _vm->closeInventory(); if (pos == -1) { setArrowCursor(); } else { const InventoryItem *item = _vm->getInventoryItem(pos); if (item->_index != 0) { _activeItem._id = item->_id; setInventoryCursor(item->_index); } } _vm->resumeJobs(); // in case the input mode was not changed by the code above (especially by the commands // executed in case of a merge), then assume we are going back to game mode if (_inputMode == kInputModeInventory) { _inputMode = kInputModeGame; } } bool Input::updateInventoryInput() { if (_mouseButtons == kMouseRightUp) { exitInventoryMode(); return true; } Common::Point mousePos; getCursorPos(mousePos); int16 _si = _vm->getHoverInventoryItem(mousePos.x, mousePos.y); if (_si != _transCurrentHoverItem) { _transCurrentHoverItem = _si; _vm->highlightInventoryItem(_si); // enable } return true; } void Input::setMouseState(MouseTriState state) { assert(state == MOUSE_ENABLED_SHOW || state == MOUSE_ENABLED_HIDE || state == MOUSE_DISABLED); _mouseState = state; switch (_mouseState) { case MOUSE_ENABLED_HIDE: case MOUSE_DISABLED: CursorMan.showMouse(false); break; case MOUSE_ENABLED_SHOW: CursorMan.showMouse(true); break; default: break; } } MouseTriState Input::getMouseState() { return _mouseState; } bool Input::isMouseEnabled() { return (_mouseState == MOUSE_ENABLED_SHOW) || (_mouseState == MOUSE_ENABLED_HIDE); } void Input::getAbsoluteCursorPos(Common::Point& p) const { _vm->_gfx->getScrollPos(p); p.x += _mousePos.x; p.y += _mousePos.y; } void Input::initCursors() { _dinoCursor = _donnaCursor = _dougCursor = 0; switch (_gameType) { case GType_Nippon: _comboArrow = _vm->_disk->loadPointer("pointer"); _mouseArrow = new Cnv(1, MOUSEARROW_WIDTH_NS, MOUSEARROW_HEIGHT_NS, _resMouseArrow_NS, false); break; case GType_BRA: if (_vm->getPlatform() == Common::kPlatformDOS) { _dinoCursor = _vm->_disk->loadPointer("pointer1"); _dougCursor = _vm->_disk->loadPointer("pointer2"); _donnaCursor = _vm->_disk->loadPointer("pointer3"); Graphics::Surface *surf = new Graphics::Surface; surf->create(_mouseComboProps_BR._width, _mouseComboProps_BR._height, Graphics::PixelFormat::createFormatCLUT8()); _comboArrow = new SurfaceToFrames(surf); // TODO: choose the pointer depending on the active character // For now, we pick Donna's _mouseArrow = _donnaCursor; } else { // TODO: Where are the Amiga cursors? Graphics::Surface *surf1 = new Graphics::Surface; surf1->create(_mouseComboProps_BR._width, _mouseComboProps_BR._height, Graphics::PixelFormat::createFormatCLUT8()); _comboArrow = new SurfaceToFrames(surf1); // TODO: scale mouse cursor (see staticres.cpp) Graphics::Surface *surf2 = new Graphics::Surface; surf2->create(32, 16, Graphics::PixelFormat::createFormatCLUT8()); memcpy(surf2->getPixels(), _resMouseArrow_BR_Amiga, 32*16); _mouseArrow = new SurfaceToFrames(surf2); } break; default: warning("Input::initCursors: unknown gametype"); } } void Input::setArrowCursor() { switch (_gameType) { case GType_Nippon: debugC(1, kDebugInput, "setting mouse cursor to arrow"); // this stuff is needed to avoid artifacts with labels and selected items when switching cursors stopHovering(); _activeItem._id = 0; CursorMan.replaceCursor(_mouseArrow->getData(0), MOUSEARROW_WIDTH_NS, MOUSEARROW_HEIGHT_NS, 0, 0, 0); break; case GType_BRA: { Common::Rect r; _mouseArrow->getRect(0, r); CursorMan.replaceCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0); CursorMan.showMouse(true); _activeItem._id = 0; break; } default: warning("Input::setArrowCursor: unknown gametype"); } } void Input::setInventoryCursor(ItemName name) { assert(name > 0); switch (_gameType) { case GType_Nippon: { byte *v8 = _comboArrow->getData(0); // FIXME: destination offseting is not clear _vm->_inventoryRenderer->drawItem(name, v8 + 7 * MOUSECOMBO_WIDTH_NS + 7, MOUSECOMBO_WIDTH_NS); CursorMan.replaceCursor(v8, MOUSECOMBO_WIDTH_NS, MOUSECOMBO_HEIGHT_NS, 0, 0, 0); break; } case GType_BRA: { byte *src = _mouseArrow->getData(0); byte *dst = _comboArrow->getData(0); // FIXME: destination offseting is not clear Common::Rect srcRect, dstRect; _mouseArrow->getRect(0, srcRect); _comboArrow->getRect(0, dstRect); for (uint y = 0; y < (uint)srcRect.height(); y++) memcpy(dst + y * dstRect.width(), src + y * srcRect.width(), srcRect.width()); _vm->_inventoryRenderer->drawItem(name, dst + _mouseComboProps_BR._yOffset * _mouseComboProps_BR._width + _mouseComboProps_BR._xOffset, _mouseComboProps_BR._width); CursorMan.replaceCursor(dst, _mouseComboProps_BR._width, _mouseComboProps_BR._height, 0, 0, 0); break; } default: warning("Input::setInventoryCursor: unknown gametype"); } } } // namespace Parallaction