scummvm/engines/nancy/cursor.cpp
Kaloyan Chehlarski ccca59475c NANCY: Support modified nancy.dat format
Added support for the freshly modified nancy.dat. The
previous version is also supported and will be kept that way
until the next time nancy.dat gets updated in master
(most likely after work on nancy8/9 is finished)
2024-01-16 22:26:55 +01:00

332 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->_graphicsManager->_object0;
}
Graphics::ManagedSurface temp(*surf, bounds);
CursorMan.replaceCursor(temp, hotspot.x, hotspot.y, g_nancy->_graphicsManager->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