scummvm/engines/nancy/cursor.cpp
2024-02-19 20:46:17 +01:00

331 lines
9.2 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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