1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 /*
24  * This code is based on original Hugo Trilogy source code
25  *
26  * Copyright (c) 1989-1995 David P. Gray
27  *
28  */
29 
30 // mouse.cpp : Handle all mouse activity
31 
32 #include "common/debug.h"
33 #include "common/system.h"
34 
35 #include "hugo/hugo.h"
36 #include "hugo/dialogs.h"
37 #include "hugo/game.h"
38 #include "hugo/mouse.h"
39 #include "hugo/schedule.h"
40 #include "hugo/display.h"
41 #include "hugo/inventory.h"
42 #include "hugo/route.h"
43 #include "hugo/util.h"
44 #include "hugo/object.h"
45 #include "hugo/text.h"
46 
47 namespace Hugo {
48 
MouseHandler(HugoEngine * vm)49 MouseHandler::MouseHandler(HugoEngine *vm) : _vm(vm) {
50 	_hotspots = nullptr;
51 	_leftButtonFl  = false;
52 	_rightButtonFl = false;
53 	_jumpExitFl = false;                            // Can't jump to a screen exit
54 	_mouseX = kXPix / 2;
55 	_mouseY = kYPix / 2;
56 }
57 
resetLeftButton()58 void MouseHandler::resetLeftButton() {
59 	_leftButtonFl = false;
60 }
61 
resetRightButton()62 void MouseHandler::resetRightButton() {
63 	_rightButtonFl = false;
64 }
65 
setLeftButton()66 void MouseHandler::setLeftButton() {
67 	_leftButtonFl = true;
68 }
69 
setRightButton()70 void MouseHandler::setRightButton() {
71 	_rightButtonFl = true;
72 }
73 
setJumpExitFl(bool fl)74 void MouseHandler::setJumpExitFl(bool fl) {
75 	_jumpExitFl = fl;
76 }
77 
setMouseX(int x)78 void MouseHandler::setMouseX(int x) {
79 	_mouseX = x;
80 }
81 
setMouseY(int y)82 void MouseHandler::setMouseY(int y) {
83 	_mouseY = y;
84 }
85 
freeHotspots()86 void MouseHandler::freeHotspots() {
87 	free(_hotspots);
88 	_hotspots = nullptr;
89 }
90 
getJumpExitFl() const91 bool MouseHandler::getJumpExitFl() const {
92 	return _jumpExitFl;
93 }
94 
getMouseX() const95 int MouseHandler::getMouseX() const {
96 	return _mouseX;
97 }
98 
getMouseY() const99 int MouseHandler::getMouseY() const {
100 	return _mouseY;
101 }
102 
getDirection(const int16 hotspotId) const103 int16 MouseHandler::getDirection(const int16 hotspotId) const {
104 	return _hotspots[hotspotId]._direction;
105 }
106 
getHotspotActIndex(const int16 hotspotId) const107 int16 MouseHandler::getHotspotActIndex(const int16 hotspotId) const {
108 	return _hotspots[hotspotId]._actIndex;
109 }
110 
111 /**
112  * Shadow-blit supplied string into dib_a at cx,cy and add to display list
113  */
cursorText(const char * buffer,const int16 cx,const int16 cy,const Uif fontId,const int16 color)114 void MouseHandler::cursorText(const char *buffer, const int16 cx, const int16 cy, const Uif fontId, const int16 color) {
115 	debugC(1, kDebugMouse, "cursorText(%s, %d, %d, %d, %d)", buffer, cx, cy, fontId, color);
116 
117 	_vm->_screen->loadFont(fontId);
118 
119 	// Find bounding rect for string
120 	int16 sdx = _vm->_screen->stringLength(buffer);
121 	int16 sdy = _vm->_screen->fontHeight() + 1;     // + 1 for shadow
122 	int16 sx, sy;
123 	if (cx < kXPix / 2) {
124 		sx = cx + kCursorNameOffX;
125 		if (_vm->_inventory->getInventoryObjId() == -1) {
126 			sy = cy + kCursorNameOffY;
127 		} else {
128 			sy = cy + kCursorNameOffY - (_vm->_screen->fontHeight() + 1);
129 			if (sy < 0) {
130 				sx = cx + kCursorNameOffX + 25;
131 				sy = cy + kCursorNameOffY;
132 			}
133 		}
134 	} else {
135 		sx = cx - sdx - kCursorNameOffX / 2;
136 		sy = cy + kCursorNameOffY;
137 	}
138 
139 	if (sy < 0) {
140 		sy = 0;
141 	}
142 
143 	// Display the string and add rect to display list
144 	_vm->_screen->shadowStr(sx, sy, buffer, _TBRIGHTWHITE);
145 	_vm->_screen->displayList(kDisplayAdd, sx, sy, sdx, sdy);
146 }
147 
148 /**
149  * Find the exit hotspot containing cx, cy.
150  * Return hotspot index or -1 if not found.
151  */
findExit(const int16 cx,const int16 cy,byte screenId)152 int16 MouseHandler::findExit(const int16 cx, const int16 cy, byte screenId) {
153 	debugC(2, kDebugMouse, "findExit(%d, %d, %d)", cx, cy, screenId);
154 
155 	for (int i = 0; _hotspots[i]._screenIndex >= 0; i++) {
156 		if (_hotspots[i]._screenIndex == screenId) {
157 			if (cx >= _hotspots[i]._x1 && cx <= _hotspots[i]._x2 && cy >= _hotspots[i]._y1 && cy <= _hotspots[i]._y2)
158 				return i;
159 		}
160 	}
161 	return -1;
162 }
163 
164 /**
165  * Process a mouse right click at coord cx, cy over object objid
166  */
processRightClick(const int16 objId,const int16 cx,const int16 cy)167 void MouseHandler::processRightClick(const int16 objId, const int16 cx, const int16 cy) {
168 	debugC(1, kDebugMouse, "ProcessRightClick(%d, %d, %d)", objId, cx, cy);
169 
170 	Status &gameStatus = _vm->getGameStatus();
171 
172 	if (gameStatus._storyModeFl || _vm->_hero->_pathType == kPathQuiet) // Make sure user has control
173 		return;
174 
175 	int16 inventObjId = _vm->_inventory->getInventoryObjId();
176 	// Check if this was over iconbar
177 	if ((_vm->_inventory->getInventoryState() == kInventoryActive) && (cy < kInvDy + kDibOffY)) { // Clicked over iconbar object
178 		if (inventObjId == -1)
179 			_vm->_screen->selectInventoryObjId(objId);
180 		else if (inventObjId == objId)
181 			_vm->_screen->resetInventoryObjId();
182 		else
183 			_vm->_object->useObject(objId);         // Use status.objid on object
184 	} else {                                        // Clicked over viewport object
185 		Object *obj = &_vm->_object->_objects[objId];
186 		int16 x, y;
187 		switch (obj->_viewx) {                      // Where to walk to
188 		case -1: {                                  // Walk to object position
189 			bool foundFl = false;
190 			if (_vm->_object->findObjectSpace(obj, &x, &y))
191 				foundFl = _vm->_route->startRoute(kRouteGet, objId, x, y);  // TRUE if route found to object
192 			if (!foundFl)                           // Can't get there, try to use from here
193 				_vm->_object->useObject(objId);
194 			}
195 			break;
196 		case 0:                                     // Immediate use
197 			_vm->_object->useObject(objId);         // Pick up or use object
198 			break;
199 		default:                                    // Walk to view point if possible
200 			if (!_vm->_route->startRoute(kRouteGet, objId, obj->_viewx, obj->_viewy)) {
201 				if (_vm->_hero->_cycling == kCycleInvisible) // If invisible do
202 					_vm->_object->useObject(objId); // immediate use
203 				else
204 					Utils::notifyBox(_vm->_text->getTextMouse(kMsNoWayText)); // Can't get there
205 			}
206 			break;
207 		}
208 	}
209 }
210 
211 /** Process a left mouse click over:
212  * 1.  An icon - show description
213  * 2.  An object - walk to and show description
214  * 3.  An icon scroll arrow - scroll the iconbar
215  * 4.  Nothing - attempt to walk there
216  * 5.  Exit - walk to exit hotspot
217  */
processLeftClick(const int16 objId,const int16 cx,const int16 cy)218 void MouseHandler::processLeftClick(const int16 objId, const int16 cx, const int16 cy) {
219 	debugC(1, kDebugMouse, "ProcessLeftClick(%d, %d, %d)", objId, cx, cy);
220 
221 	int16 i, x, y;
222 	Object *obj;
223 
224 	Status &gameStatus = _vm->getGameStatus();
225 
226 	if (gameStatus._storyModeFl || _vm->_hero->_pathType == kPathQuiet) // Make sure user has control
227 		return;
228 
229 	switch (objId) {
230 	case -1:                                        // Empty space - attempt to walk there
231 		_vm->_route->startRoute(kRouteSpace, 0, cx, cy);
232 		break;
233 	case kLeftArrow:                                // A scroll arrow - scroll the iconbar
234 	case kRightArrow:
235 		// Scroll the iconbar and display results
236 		_vm->_inventory->processInventory((objId == kLeftArrow) ? kInventoryActionLeft : kInventoryActionRight);
237 		_vm->_screen->moveImage(_vm->_screen->getIconBuffer(), 0, 0, kXPix, kInvDy, kXPix, _vm->_screen->getFrontBuffer(), 0, kDibOffY, kXPix);
238 		_vm->_screen->moveImage(_vm->_screen->getIconBuffer(), 0, 0, kXPix, kInvDy, kXPix, _vm->_screen->getBackBuffer(), 0, kDibOffY, kXPix);
239 		_vm->_screen->displayList(kDisplayAdd, 0, kDibOffY, kXPix, kInvDy);
240 		break;
241 	case kExitHotspot:                              // Walk to exit hotspot
242 		i = findExit(cx, cy, *_vm->_screenPtr);
243 		x = _hotspots[i]._viewx;
244 		y = _hotspots[i]._viewy;
245 		if (x >= 0) {                               // Hotspot refers to an exit
246 			// Special case of immediate exit
247 			if (_jumpExitFl) {
248 				// Get rid of iconbar if necessary
249 				if (_vm->_inventory->getInventoryState() != kInventoryOff)
250 					_vm->_inventory->setInventoryState(kInventoryUp);
251 				_vm->_scheduler->insertActionList(_hotspots[i]._actIndex);
252 			} else {    // Set up route to exit spot
253 				if (_hotspots[i]._direction == Common::KEYCODE_RIGHT)
254 					x -= kHeroMaxWidth;
255 				else if (_hotspots[i]._direction == Common::KEYCODE_LEFT)
256 					x += kHeroMaxWidth;
257 				if (!_vm->_route->startRoute(kRouteExit, i, x, y))
258 					Utils::notifyBox(_vm->_text->getTextMouse(kMsNoWayText)); // Can't get there
259 			}
260 
261 			// Get rid of any attached icon
262 			_vm->_screen->resetInventoryObjId();
263 		}
264 		break;
265 	default:                                        // Look at an icon or object
266 		obj = &_vm->_object->_objects[objId];
267 
268 		// Over iconbar - immediate description
269 		if ((_vm->_inventory->getInventoryState() == kInventoryActive) && (cy < kInvDy + kDibOffY)) {
270 			_vm->_object->lookObject(obj);
271 		} else {
272 			bool foundFl = false;                   // TRUE if route found to object
273 			switch (obj->_viewx) {                   // Clicked over viewport object
274 			case -1:                                // Walk to object position
275 				if (_vm->_object->findObjectSpace(obj, &x, &y))
276 					foundFl = _vm->_route->startRoute(kRouteLook, objId, x, y);
277 				if (!foundFl)                       // Can't get there, immediate description
278 					_vm->_object->lookObject(obj);
279 				break;
280 			case 0:                                 // Immediate description
281 				_vm->_object->lookObject(obj);
282 				break;
283 			default:                                // Walk to view point if possible
284 				if (!_vm->_route->startRoute(kRouteLook, objId, obj->_viewx, obj->_viewy)) {
285 					if (_vm->_hero->_cycling == kCycleInvisible) // If invisible do
286 						_vm->_object->lookObject(obj);          // immediate decription
287 					else
288 						Utils::notifyBox(_vm->_text->getTextMouse(kMsNoWayText));  // Can't get there
289 				}
290 				break;
291 			}
292 		}
293 		break;
294 	}
295 }
296 
297 /**
298  * Process mouse activity
299  */
mouseHandler()300 void MouseHandler::mouseHandler() {
301 	debugC(2, kDebugMouse, "mouseHandler");
302 
303 	Status &gameStatus = _vm->getGameStatus();
304 	Istate inventState = _vm->_inventory->getInventoryState();
305 	if ((gameStatus._viewState != kViewPlay) && (inventState != kInventoryActive))
306 		return;
307 
308 	int16 cx = getMouseX();
309 	int16 cy = getMouseY();
310 
311 //	gameStatus._cx = cx;                             // Save cursor coords
312 //	gameStatus._cy = cy;
313 
314 	// Don't process if outside client area
315 	if ((cx < 0) || (cx > kXPix) || (cy < kDibOffY) || (cy > kViewSizeY + kDibOffY))
316 		return;
317 
318 	int16 objId = -1;                               // Current source object
319 	// Process cursor over an object or icon
320 	if (inventState == kInventoryActive) { // Check inventory icon bar first
321 		objId = _vm->_inventory->processInventory(kInventoryActionGet, cx, cy);
322 	} else {
323 		if (cy < 5 && cy > 0) {
324 			_vm->_topMenu->runModal();
325 		}
326 	}
327 
328 	if (!gameStatus._gameOverFl) {
329 		if (objId == -1)                            // No match, check rest of view
330 			objId = _vm->_object->findObject(cx, cy);
331 
332 		if (objId >= 0) {                           // Got a match
333 			// Display object name next to cursor (unless CURSOR_NOCHAR)
334 			// Note test for swapped hero name
335 			const char *name = _vm->_text->getNoun(_vm->_object->_objects[(objId == kHeroIndex) ? _vm->_heroImage : objId]._nounIndex, kCursorNameIndex);
336 			if (name[0] != kCursorNochar)
337 				cursorText(name, cx, cy, U_FONT8, _TBRIGHTWHITE);
338 
339 			// Process right click over object in view or iconbar
340 			if (_rightButtonFl)
341 				processRightClick(objId, cx, cy);
342 		}
343 
344 		// Process cursor over an exit hotspot
345 		if (objId == -1) {
346 			int i = findExit(cx, cy, *_vm->_screenPtr);
347 			if (i != -1 && _hotspots[i]._viewx >= 0) {
348 				objId = kExitHotspot;
349 				cursorText(_vm->_text->getTextMouse(kMsExit), cx, cy, U_FONT8, _TBRIGHTWHITE);
350 			}
351 		}
352 	}
353 	// Left click over icon, object or to move somewhere
354 	if (_leftButtonFl)
355 		processLeftClick(objId, cx, cy);
356 
357 	// Clear mouse click states
358 	resetLeftButton();
359 	resetRightButton();
360 }
361 
readHotspot(Common::ReadStream & in,Hotspot & hotspot)362 void MouseHandler::readHotspot(Common::ReadStream &in, Hotspot &hotspot) {
363 	hotspot._screenIndex = in.readSint16BE();
364 	hotspot._x1 = in.readSint16BE();
365 	hotspot._y1 = in.readSint16BE();
366 	hotspot._x2 = in.readSint16BE();
367 	hotspot._y2 = in.readSint16BE();
368 	hotspot._actIndex = in.readUint16BE();
369 	hotspot._viewx = in.readSint16BE();
370 	hotspot._viewy = in.readSint16BE();
371 	hotspot._direction = in.readSint16BE();
372 }
373 
374 /**
375  * Load hotspots data from hugo.dat
376  */
loadHotspots(Common::ReadStream & in)377 void MouseHandler::loadHotspots(Common::ReadStream &in) {
378 	Hotspot *wrkHotspots = 0;
379 	Hotspot tmp;
380 	memset(&tmp, 0, sizeof(tmp));
381 	for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
382 		int numRows = in.readUint16BE();
383 		if (varnt == _vm->_gameVariant)
384 			_hotspots = wrkHotspots = (Hotspot *)malloc(sizeof(Hotspot) * numRows);
385 
386 		for (int i = 0; i < numRows; i++)
387 			readHotspot(in, (varnt == _vm->_gameVariant) ? wrkHotspots[i] : tmp);
388 	}
389 }
390 
391 /**
392  * Display hotspot boundaries for the current screen
393  */
drawHotspots() const394 void MouseHandler::drawHotspots() const {
395 	for (int i = 0; _hotspots[i]._screenIndex >= 0; i++) {
396 		Hotspot *hotspot = &_hotspots[i];
397 		if (hotspot->_screenIndex == _vm->_hero->_screenIndex)
398 			_vm->_screen->drawRectangle(false, hotspot->_x1, hotspot->_y1, hotspot->_x2, hotspot->_y2, _TLIGHTRED);
399 	}
400 }
401 } // End of namespace Hugo
402