1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
|
/* 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
|