/* 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 3 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, see . * */ #include "common/system.h" #include "graphics/cursorman.h" #include "engines/nancy/nancy.h" #include "engines/nancy/cursor.h" #include "engines/nancy/graphics.h" #include "engines/nancy/resource.h" #include "engines/nancy/util.h" namespace Nancy { CursorManager::CursorManager() : _isInitialized(false), _curItemID(-1), _curCursorType(kNormal), _curCursorID(0), _lastCursorID(10000), // nonsense default value to ensure cursor is drawn the first time _hasItem(false), _numCursorTypes(0), _puzzleExitCursor((g_nancy->getGameType() >= kGameTypeNancy4) ? kMoveBackward : kExit), _warpedMousePos(-500, -500) {} void CursorManager::init(Common::SeekableReadStream *chunkStream) { assert(chunkStream); chunkStream->seek(0); // First, we need to figure out the number of possible CursorTypes in the current game _numCursorTypes = g_nancy->getStaticData().numCursorTypes; // The structure of CURS is weird: // The data is divided in half: first half is source rectangles, second half is hotspots (all of which are identical...) // However, each of those halves are divided into a number of arrays, each one of size _numCursorTypes. // The first few arrays are the following: // - an array of cursors used when the mouse is in the VIEWPORT (hourglass, directional arrows, etc.) // - an array of cursors used in the FRAME // - an array of cursors used in MENUS (not present in TVD) // The only frame cursors used are the first two: the classic arrow cursor, and its hotspot variant, which is slightly shorter // The same applies to the menu cursors; however, we completely ignore those (technically the arrow cursor has sliiiiightly // different shading from the one in the frame array, but I don't care enough to implement it). // Following those are the ITEM arrays; these cursors are used to indicate that the player is holding an item. // Their number is the same as the number of items described in INV, and their size is also _numCursorTypes. // Out of those arrays, the only cursors that get used are the kNormal and kHotspot ones. The first few games also // had kMove item cursors, but the Move cursors quickly fell out of use. // Due to the logic in setCursor(), directional arrow cursors found in the VIEWPORT array take precedence over // the ones in the item arrays. As a result, most of the CURS data is effectively junk that never gets used. // Perhaps in the future the class could be modified so we no longer have to store or care about all of the junk cursors; // however, this cannot happen until the engine is more mature and I'm more aware of what changes they made to the // cursor code in later games. uint numCursors = _numCursorTypes * (g_nancy->getGameType() == kGameTypeVampire ? 2 : 3) + g_nancy->getStaticData().numItems * _numCursorTypes; _cursors.resize(numCursors); for (uint i = 0; i < numCursors; ++i) { readRect(*chunkStream, _cursors[i].bounds); } for (uint i = 0; i < numCursors; ++i) { _cursors[i].hotspot.x = chunkStream->readUint32LE(); _cursors[i].hotspot.y = chunkStream->readUint32LE(); } readRect(*chunkStream, _primaryVideoInactiveZone); _primaryVideoInitialPos.x = chunkStream->readUint16LE(); _primaryVideoInitialPos.y = chunkStream->readUint16LE(); auto *inventoryData = GetEngineData(INV); assert(inventoryData); g_nancy->_resource->loadImage(inventoryData->inventoryCursorsImageName, _invCursorsSurface); setCursor(kNormalArrow, -1); showCursor(false); _isInitialized = true; adjustCursorHotspot(); delete chunkStream; } void CursorManager::setCursor(CursorType type, int16 itemID) { if (!_isInitialized) { return; } Nancy::GameType gameType = g_nancy->getGameType(); if (type == _curCursorType && itemID == _curItemID) { return; } else { _curCursorType = type; _curItemID = itemID; } _hasItem = false; // For all cases below, the selected cursor is _always_ shown, regardless // of whether or not an item is held. All other types of cursor // are overridable when holding an item. Every item cursor has // _numItemCursor variants, one corresponding to every numbered // value of the CursorType enum. switch (type) { case kNormalArrow: _curCursorID = _numCursorTypes; return; case kHotspotArrow: _curCursorID = _numCursorTypes + 1; return; case kInvertedRotateLeft: // Only valid for nancy6 and up if (gameType >= kGameTypeNancy6) { _curCursorID = kInvertedRotateLeft; return; } // fall through case kRotateLeft: // Only valid for nancy6 and up if (gameType >= kGameTypeNancy6) { _curCursorID = kRotateLeft; return; } // fall through case kMoveLeft: // Only valid for nancy3 and up if (gameType >= kGameTypeNancy3) { _curCursorID = kMoveLeft; return; } else { type = kMove; } break; case kInvertedRotateRight: // Only valid for nancy6 and up if (gameType >= kGameTypeNancy6) { _curCursorID = kInvertedRotateRight; return; } // fall through case kRotateRight: // Only valid for nancy6 and up if (gameType >= kGameTypeNancy6) { _curCursorID = kRotateRight; return; } // fall through case kMoveRight: // Only valid for nancy3 and up if (gameType >= kGameTypeNancy3) { _curCursorID = kMoveRight; return; } else { type = kMove; } break; case kMoveUp: // Only valid for nancy4 and up if (gameType >= kGameTypeNancy4) { _curCursorID = kMoveUp; return; } else { type = kMove; } break; case kMoveDown: // Only valid for nancy4 and up if (gameType >= kGameTypeNancy4) { _curCursorID = kMoveDown; return; } else { type = kMove; } break; case kMoveForward: // Only valid for nancy4 and up if (gameType >= kGameTypeNancy4) { _curCursorID = kMoveForward; return; } else { type = kHotspot; } break; case kMoveBackward: // Only valid for nancy4 and up if (gameType >= kGameTypeNancy4) { _curCursorID = kMoveBackward; return; } else { type = kHotspot; } break; case kExit: // Not valid in TVD if (gameType != kGameTypeVampire) { _curCursorID = 3; return; } break; case kRotateCW: _curCursorID = kRotateCW; return; case kRotateCCW: _curCursorID = kRotateCCW; return; default: break; } // Special cases have been handled, now choose correct // item cursor if holding something uint itemsOffset = 0; if (itemID == -1) { // No item held, set to eyeglass itemID = 0; } else { // Item held itemsOffset = _numCursorTypes * (g_nancy->getGameType() == kGameTypeVampire ? 2 : 3); _hasItem = true; } _curCursorID = (itemID * _numCursorTypes) + itemsOffset + type; } void CursorManager::setCursorType(CursorType type) { setCursor(type, _curItemID); } void CursorManager::setCursorItemID(int16 itemID) { setCursor(_curCursorType, itemID); } void CursorManager::warpCursor(const Common::Point &pos) { _warpedMousePos = pos; } void CursorManager::applyCursor() { if (_curCursorID != _lastCursorID) { Graphics::ManagedSurface *surf; Common::Rect bounds = _cursors[_curCursorID].bounds; Common::Point hotspot = _cursors[_curCursorID].hotspot; if (_hasItem) { surf = &_invCursorsSurface; } else { surf = &g_nancy->_graphics->_object0; } Graphics::ManagedSurface temp(*surf, bounds); CursorMan.replaceCursor(temp, hotspot.x, hotspot.y, g_nancy->_graphics->getTransColor(), false); if (g_nancy->getGameType() == kGameTypeVampire) { byte palette[3 * 256]; surf->grabPalette(palette, 0, 256); CursorMan.replaceCursorPalette(palette, 0, 256); } _lastCursorID = _curCursorID; } if (_warpedMousePos.x != -500 && _warpedMousePos.y != -500) { g_system->warpMouse(_warpedMousePos.x, _warpedMousePos.y); _warpedMousePos.x = -500; _warpedMousePos.y = -500; } } void CursorManager::showCursor(bool shouldShow) { CursorMan.showMouse(shouldShow); } void CursorManager::adjustCursorHotspot() { if (g_nancy->getGameType() == kGameTypeVampire) { return; } // Improvement: the arrow cursor in the Nancy games has an atrocious hotspot that's // right in the middle of the graphic, instead of in the top left where // it would make sense to be. This function fixes that. // The hotspot is still a few pixels lower than it should be to account // for the different graphic when hovering UI elements // TODO: Make this optional? uint startID = _curCursorID; setCursorType(kNormalArrow); _cursors[_curCursorID].hotspot = {3, 4}; setCursorType(kHotspotArrow); _cursors[_curCursorID].hotspot = {3, 4}; _curCursorID = startID; } } // End of namespace Nancy