File: cursor.cpp

package info (click to toggle)
scummvm 2.9.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 450,268 kB
  • sloc: cpp: 4,297,604; asm: 28,322; python: 12,901; sh: 11,219; java: 8,477; xml: 7,843; perl: 2,633; ansic: 2,465; yacc: 1,670; javascript: 1,020; makefile: 933; lex: 578; awk: 275; objc: 82; sed: 11; php: 1
file content (331 lines) | stat: -rw-r--r-- 9,444 bytes parent folder | download | duplicates (2)
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