/* 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. * * $URL$ * $Id$ * */ #include "common/events.h" #include "common/system.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 = nullZonePtr; 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.flags == Common::KBD_CTRL && e.kbd.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); } if (_vm->_debugger->isAttached()) _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() || (_engineFlags & kEngineWalking) || (_engineFlags & kEngineChangeLocation)) { debugC(3, kDebugInput, "updateGameInput: input flags (mouse: %i, walking: %i, changeloc: %i)", isMouseEnabled(), (_engineFlags & kEngineWalking) == 0, (_engineFlags & kEngineChangeLocation) == 0 ); return event; } if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) { if (_keyPressed.keycode == Common::KEYCODE_l) event = kEvLoadGame; if (_keyPressed.keycode == Common::KEYCODE_s) event = kEvSaveGame; } 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; } // 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 = nullZonePtr; _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->_char.scheduleWalk(dest.x, dest.y); } bool Input::translateGameInput() { if (_engineFlags & kEnginePauseJobs) { return false; } if (_hasDelayedAction) { // if walking is over, then take programmed action takeAction(_delayedActionZone); _hasDelayedAction = false; _delayedActionZone = nullZonePtr; 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) && ((_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))) { if (z->_flags & kFlagsNoWalk) { // character doesn't need to walk to take specified action 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; getCursorPos(mousePos); bool hitCharacter = _vm->hitZone(kZoneYou, mousePos.x, mousePos.y); if (hitCharacter) { if (_activeItem._id != 0) { _activeItem._index = (_activeItem._id >> 16) & 0xFFFF; _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 ((_engineFlags & kEngineDragging)) { _engineFlags &= ~kEngineDragging; ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(pos)); if (z) { _vm->dropItem(z->u.merge->_obj1); _vm->dropItem(z->u.merge->_obj2); _vm->addInventoryItem(z->u.merge->_obj3); _vm->_cmdExec->run(z->_commands); } } _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(); _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: _vm->_system->showMouse(false); break; case MOUSE_ENABLED_SHOW: _vm->_system->showMouse(true); 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 { p = _mousePos; p.x += _vm->_gfx->getScrollPos(); } 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::kPlatformPC) { _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, 1); _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? _mouseArrow = 0; } 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; _vm->_system->setMouseCursor(_mouseArrow->getData(0), MOUSEARROW_WIDTH_NS, MOUSEARROW_HEIGHT_NS, 0, 0, 0); break; case GType_BRA: { if (_vm->getPlatform() == Common::kPlatformAmiga) return; Common::Rect r; _mouseArrow->getRect(0, r); _vm->_system->setMouseCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0); _vm->_system->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); _vm->_system->setMouseCursor(v8, MOUSECOMBO_WIDTH_NS, MOUSECOMBO_HEIGHT_NS, 0, 0, 0); break; } case GType_BRA: { byte *src = _mouseArrow->getData(0); byte *dst = _comboArrow->getData(0); memcpy(dst, src, _comboArrow->getSize(0)); // FIXME: destination offseting is not clear _vm->_inventoryRenderer->drawItem(name, dst + _mouseComboProps_BR._yOffset * _mouseComboProps_BR._width + _mouseComboProps_BR._xOffset, _mouseComboProps_BR._width); _vm->_system->setMouseCursor(dst, _mouseComboProps_BR._width, _mouseComboProps_BR._height, 0, 0, 0); break; } default: warning("Input::setInventoryCursor: unknown gametype"); } } } // namespace Parallaction