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  * Additional copyright for this file:
8  * Copyright (C) 1994-1998 Revolution Software Ltd.
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  */
24 
25 
26 #include "common/system.h"
27 #include "common/events.h"
28 #include "common/memstream.h"
29 #include "common/textconsole.h"
30 
31 #include "graphics/cursorman.h"
32 
33 #include "sword2/sword2.h"
34 #include "sword2/console.h"
35 #include "sword2/controls.h"
36 #include "sword2/defs.h"
37 #include "sword2/header.h"
38 #include "sword2/logic.h"
39 #include "sword2/maketext.h"
40 #include "sword2/mouse.h"
41 #include "sword2/object.h"
42 #include "sword2/resman.h"
43 #include "sword2/screen.h"
44 #include "sword2/sound.h"
45 
46 namespace Sword2 {
47 
48 // Pointer resource id's
49 
50 enum {
51 	CROSHAIR	= 18,
52 	EXIT0		= 788,
53 	EXIT1		= 789,
54 	EXIT2		= 790,
55 	EXIT3		= 791,
56 	EXIT4		= 792,
57 	EXIT5		= 793,
58 	EXIT6		= 794,
59 	EXIT7		= 795,
60 	EXITDOWN	= 796,
61 	EXITUP		= 797,
62 	MOUTH		= 787,
63 	NORMAL		= 17,
64 	PICKUP		= 3099,
65 	SCROLL_L	= 1440,
66 	SCROLL_R	= 1441,
67 	USE		= 3100
68 };
69 
Mouse(Sword2Engine * vm)70 Mouse::Mouse(Sword2Engine *vm) {
71 	_vm = vm;
72 
73 	resetMouseList();
74 
75 	_mouseTouching = 0;
76 	_oldMouseTouching = 0;
77 	_menuSelectedPos = 0;
78 	_examiningMenuIcon = false;
79 	_mousePointerRes = 0;
80 	_mouseMode = 0;
81 	_mouseStatus = false;
82 	_mouseModeLocked = false;
83 	_currentLuggageResource = 0;
84 	_oldButton = 0;
85 	_buttonClick = 0;
86 	_pointerTextBlocNo = 0;
87 	_playerActivityDelay = 0;
88 	_realLuggageItem = 0;
89 
90 	_mouseAnim.data = NULL;
91 	_luggageAnim.data = NULL;
92 
93 	// For the menus
94 	_totalTemp = 0;
95 	memset(_tempList, 0, sizeof(_tempList));
96 
97 	_totalMasters = 0;
98 	memset(_masterMenuList, 0, sizeof(_masterMenuList));
99 	for (uint i = 0; i < ARRAYSIZE(_mouseList); i++) {
100 		_mouseList[i].clear();
101 	}
102 	memset(_subjectList, 0, sizeof(_subjectList));
103 
104 	_defaultResponseId = 0;
105 	_choosing = false;
106 
107 	_iconCount = 0;
108 
109 	for (int i = 0; i < 2; i++) {
110 		for (int j = 0; j < RDMENU_MAXPOCKETS; j++) {
111 			_icons[i][j] = NULL;
112 			_pocketStatus[i][j] = 0;
113 		}
114 
115 		_menuStatus[i] = RDMENU_HIDDEN;
116 	}
117 }
118 
~Mouse()119 Mouse::~Mouse() {
120 	free(_mouseAnim.data);
121 	free(_luggageAnim.data);
122 	for (int i = 0; i < 2; i++)
123 		for (int j = 0; j < RDMENU_MAXPOCKETS; j++)
124 			free(_icons[i][j]);
125 }
126 
getPos(int & x,int & y)127 void Mouse::getPos(int &x, int &y) {
128 	Common::EventManager *eventMan = _vm->_system->getEventManager();
129 	Common::Point pos = eventMan->getMousePos();
130 
131 	x = pos.x;
132 	y = pos.y - MENUDEEP;
133 }
134 
getX()135 int Mouse::getX() {
136 	int x, y;
137 
138 	getPos(x, y);
139 	return x;
140 }
141 
getY()142 int Mouse::getY() {
143 	int x, y;
144 
145 	getPos(x, y);
146 	return y;
147 }
148 
149 /**
150  * Call at beginning of game loop
151  */
152 
resetMouseList()153 void Mouse::resetMouseList() {
154 	_curMouse = 0;
155 }
156 
registerMouse(byte * ob_mouse,BuildUnit * build_unit)157 void Mouse::registerMouse(byte *ob_mouse, BuildUnit *build_unit) {
158 	assert(_curMouse < TOTAL_mouse_list);
159 
160 	ObjectMouse mouse;
161 
162 	mouse.read(ob_mouse);
163 
164 	if (!mouse.pointer)
165 		return;
166 
167 	if (build_unit) {
168 		_mouseList[_curMouse].rect.left = build_unit->x;
169 		_mouseList[_curMouse].rect.top = build_unit->y;
170 		_mouseList[_curMouse].rect.right = 1 + build_unit->x + build_unit->scaled_width;
171 		_mouseList[_curMouse].rect.bottom = 1 + build_unit->y + build_unit->scaled_height;
172 	} else {
173 		_mouseList[_curMouse].rect.left = mouse.x1;
174 		_mouseList[_curMouse].rect.top = mouse.y1;
175 		_mouseList[_curMouse].rect.right = 1 + mouse.x2;
176 		_mouseList[_curMouse].rect.bottom = 1 + mouse.y2;
177 	}
178 
179 	_mouseList[_curMouse].priority = mouse.priority;
180 	_mouseList[_curMouse].pointer = mouse.pointer;
181 
182 	// Change all COGS pointers to CROSHAIR. I'm guessing that this was a
183 	// design decision made in mid-development and they didn't want to go
184 	// back and re-generate the resource files.
185 
186 	if (_mouseList[_curMouse].pointer == USE)
187 		_mouseList[_curMouse].pointer = CROSHAIR;
188 
189 	// Check if pointer text field is set due to previous object using this
190 	// slot (ie. not correct for this one)
191 
192 	// If 'pointer_text' field is set, but the 'id' field isn't same is
193 	// current id then we don't want this "left over" pointer text
194 
195 	if (_mouseList[_curMouse].pointer_text && _mouseList[_curMouse].id != (int32)_vm->_logic->readVar(ID))
196 		_mouseList[_curMouse].pointer_text = 0;
197 
198 	// Get id from system variable 'id' which is correct for current object
199 	_mouseList[_curMouse].id = _vm->_logic->readVar(ID);
200 
201 	_curMouse++;
202 }
203 
registerPointerText(int32 text_id)204 void Mouse::registerPointerText(int32 text_id) {
205 	assert(_curMouse < TOTAL_mouse_list);
206 
207 	// current object id - used for checking pointer_text when mouse area
208 	// registered (in fnRegisterMouse and fnRegisterFrame)
209 
210 	_mouseList[_curMouse].id = _vm->_logic->readVar(ID);
211 	_mouseList[_curMouse].pointer_text = text_id;
212 }
213 
214 /**
215  * This function is called every game cycle.
216  */
217 
mouseEngine()218 void Mouse::mouseEngine() {
219 	monitorPlayerActivity();
220 	clearPointerText();
221 
222 	// If George is dead, the system menu is visible all the time, and is
223 	// the only thing that can be used.
224 
225 	if (_vm->_logic->readVar(DEAD)) {
226 		if (_mouseMode != MOUSE_system_menu) {
227 			_mouseMode = MOUSE_system_menu;
228 
229 			if (_mouseTouching) {
230 				_oldMouseTouching = 0;
231 				_mouseTouching = 0;
232 			}
233 
234 			setMouse(NORMAL_MOUSE_ID);
235 			buildSystemMenu();
236 		}
237 		systemMenuMouse();
238 		return;
239 	}
240 
241 	// If the mouse is not visible, do nothing
242 
243 	if (_mouseStatus)
244 		return;
245 
246 	switch (_mouseMode) {
247 	case MOUSE_normal:
248 		normalMouse();
249 		break;
250 	case MOUSE_menu:
251 		menuMouse();
252 		break;
253 	case MOUSE_drag:
254 		dragMouse();
255 		break;
256 	case MOUSE_system_menu:
257 		systemMenuMouse();
258 		break;
259 	case MOUSE_holding:
260 		if (getY() < 400) {
261 			_mouseMode = MOUSE_normal;
262 			debug(5, "   releasing");
263 		}
264 		break;
265 	default:
266 		break;
267 	}
268 }
269 
270 #if RIGHT_CLICK_CLEARS_LUGGAGE
heldIsInInventory()271 bool Mouse::heldIsInInventory() {
272 	int32 object_held = (int32)_vm->_logic->readVar(OBJECT_HELD);
273 
274 	for (uint i = 0; i < _totalMasters; i++) {
275 		if (_masterMenuList[i].icon_resource == object_held)
276 			return true;
277 	}
278 	return false;
279 }
280 #endif
281 
menuClick(int menu_items)282 int Mouse::menuClick(int menu_items) {
283 	int x = getX();
284 	byte menuIconWidth;
285 
286 	if (Sword2Engine::isPsx())
287 		menuIconWidth = RDMENU_PSXICONWIDE;
288 	else
289 		menuIconWidth = RDMENU_ICONWIDE;
290 
291 
292 	if (x < RDMENU_ICONSTART)
293 		return -1;
294 
295 	if (x > RDMENU_ICONSTART + menu_items * (menuIconWidth + RDMENU_ICONSPACING) - RDMENU_ICONSPACING)
296 		return -1;
297 
298 	return (x - RDMENU_ICONSTART) / (menuIconWidth + RDMENU_ICONSPACING);
299 }
300 
systemMenuMouse()301 void Mouse::systemMenuMouse() {
302 	uint32 safe_looping_music_id;
303 	MouseEvent *me;
304 	int hit;
305 	byte *icon;
306 	int32 pars[2];
307 	uint32 icon_list[5] = {
308 		OPTIONS_ICON,
309 		QUIT_ICON,
310 		SAVE_ICON,
311 		RESTORE_ICON,
312 		RESTART_ICON
313 	};
314 
315 	// If the mouse is moved off the menu, close it. Unless the player is
316 	// dead, in which case the menu should always be visible.
317 
318 	int y = getY();
319 
320 	if (y > 0 && !_vm->_logic->readVar(DEAD)) {
321 		_mouseMode = MOUSE_normal;
322 		hideMenu(RDMENU_TOP);
323 		return;
324 	}
325 
326 	// Check if the user left-clicks anywhere in the menu area.
327 
328 	me = _vm->mouseEvent();
329 
330 	if (!me || !(me->buttons & RD_LEFTBUTTONDOWN))
331 		return;
332 
333 	if (y > 0)
334 		return;
335 
336 	hit = menuClick(ARRAYSIZE(icon_list));
337 
338 	if (hit < 0)
339 		return;
340 
341 	// Do nothing if using PSX version and are on TOP menu.
342 
343 	if ((icon_list[hit] == OPTIONS_ICON || icon_list[hit] == QUIT_ICON
344 		|| icon_list[hit] == SAVE_ICON || icon_list[hit] == RESTORE_ICON
345 		|| icon_list[hit] == RESTART_ICON) && Sword2Engine::isPsx())
346 		return;
347 
348 	// No save when dead
349 
350 	if (icon_list[hit] == SAVE_ICON && _vm->_logic->readVar(DEAD))
351 		return;
352 
353 	// Gray out all he icons, except the one that was clicked
354 
355 	for (int i = 0; i < ARRAYSIZE(icon_list); i++) {
356 		if (i != hit) {
357 			icon = _vm->_resman->openResource(icon_list[i]) + ResHeader::size();
358 			setMenuIcon(RDMENU_TOP, i, icon);
359 			_vm->_resman->closeResource(icon_list[i]);
360 		}
361 	}
362 
363 	_vm->_sound->pauseFx();
364 
365 	// NB. Need to keep a safe copy of '_loopingMusicId' for savegame & for
366 	// playing when returning from control panels because control panel
367 	// music will overwrite it!
368 
369 	safe_looping_music_id = _vm->_sound->getLoopingMusicId();
370 
371 	pars[0] = 221;
372 	pars[1] = FX_LOOP;
373 	_vm->_logic->fnPlayMusic(pars);
374 
375 	// HACK: Restore proper looping_music_id
376 	_vm->_sound->setLoopingMusicId(safe_looping_music_id);
377 
378 	processMenu();
379 
380 	// call the relevant screen
381 
382 	switch (hit) {
383 	case 0:
384 		{
385 			OptionsDialog dialog(_vm);
386 			dialog.runModal();
387 		}
388 		break;
389 	case 1:
390 		{
391 			QuitDialog dialog(_vm);
392 			dialog.runModal();
393 		}
394 		break;
395 	case 2:
396 		{
397 			SaveDialog dialog(_vm);
398 			dialog.runModal();
399 		}
400 		break;
401 	case 3:
402 		{
403 			RestoreDialog dialog(_vm);
404 			dialog.runModal();
405 		}
406 		break;
407 	case 4:
408 		{
409 			RestartDialog dialog(_vm);
410 			dialog.runModal();
411 		}
412 		break;
413 	default:
414 		break;
415 	}
416 
417 	// Menu stays open on death screen. Otherwise it's closed.
418 
419 	if (!_vm->_logic->readVar(DEAD)) {
420 		_mouseMode = MOUSE_normal;
421 		hideMenu(RDMENU_TOP);
422 	} else {
423 		setMouse(NORMAL_MOUSE_ID);
424 		buildSystemMenu();
425 	}
426 
427 	// Back to the game again
428 
429 	processMenu();
430 
431 	// Reset game palette, but not after a successful restore or restart!
432 	// See RestoreFromBuffer() in saveload.cpp
433 
434 	ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
435 
436 	if (screenInfo->new_palette != 99) {
437 		// 0 means put back game screen palette; see build_display.cpp
438 		_vm->_screen->setFullPalette(0);
439 
440 		// Stop the engine fading in the restored screens palette
441 		screenInfo->new_palette = 0;
442 	} else
443 		screenInfo->new_palette = 1;
444 
445 	_vm->_sound->unpauseFx();
446 
447 	// If there was looping music before coming into the control panels
448 	// then restart it! NB. If a game has been restored the music will be
449 	// restarted twice, but this shouldn't cause any harm.
450 
451 	if (_vm->_sound->getLoopingMusicId()) {
452 		pars[0] = _vm->_sound->getLoopingMusicId();
453 		pars[1] = FX_LOOP;
454 		_vm->_logic->fnPlayMusic(pars);
455 	} else
456 		_vm->_logic->fnStopMusic(NULL);
457 }
458 
dragMouse()459 void Mouse::dragMouse() {
460 	byte buf1[NAME_LEN], buf2[NAME_LEN];
461 	MouseEvent *me;
462 	int hit;
463 
464 	// We can use dragged object both on other inventory objects, or on
465 	// objects in the scene, so if the mouse moves off the inventory menu,
466 	// then close it.
467 
468 	int x, y;
469 
470 	getPos(x, y);
471 
472 	if (y < 400) {
473 		_mouseMode = MOUSE_normal;
474 		hideMenu(RDMENU_BOTTOM);
475 		return;
476 	}
477 
478 	// Handles cursors and the luggage on/off according to type
479 
480 	mouseOnOff();
481 
482 	// Now do the normal click stuff
483 
484 	me = _vm->mouseEvent();
485 
486 	if (!me)
487 		return;
488 
489 #if RIGHT_CLICK_CLEARS_LUGGAGE
490 	if ((me->buttons & RD_RIGHTBUTTONDOWN) && heldIsInInventory()) {
491 		_vm->_logic->writeVar(OBJECT_HELD, 0);
492 		_menuSelectedPos = 0;
493 		_mouseMode = MOUSE_menu;
494 		setLuggage(0);
495 		buildMenu();
496 		return;
497 	}
498 #endif
499 
500 	if (!(me->buttons & RD_LEFTBUTTONDOWN))
501 		return;
502 
503 	// there's a mouse event to be processed
504 
505 	// could be clicking on an on screen object or on the menu
506 	// which is currently displayed
507 
508 	if (_mouseTouching) {
509 		// mouse is over an on screen object - and we have luggage
510 
511 		// Depending on type we'll maybe kill the object_held - like
512 		// for exits
513 
514 		// Set global script variable 'button'. We know that it was the
515 		// left button, not the right one.
516 
517 		_vm->_logic->writeVar(LEFT_BUTTON, 1);
518 		_vm->_logic->writeVar(RIGHT_BUTTON, 0);
519 
520 		// These might be required by the action script about to be run
521 		ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
522 
523 		_vm->_logic->writeVar(MOUSE_X, x + screenInfo->scroll_offset_x);
524 		_vm->_logic->writeVar(MOUSE_Y, y + screenInfo->scroll_offset_y);
525 
526 		// For scripts to know what's been clicked. First used for
527 		// 'room_13_turning_script' in object 'biscuits_13'
528 
529 		_vm->_logic->writeVar(CLICKED_ID, _mouseTouching);
530 
531 		_vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching);
532 
533 		debug(2, "Used \"%s\" on \"%s\"",
534 			_vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf1),
535 			_vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf2));
536 
537 		// Hide menu - back to normal menu mode
538 
539 		hideMenu(RDMENU_BOTTOM);
540 		_mouseMode = MOUSE_normal;
541 
542 		return;
543 	}
544 
545 	// Better check for combine/cancel. Cancel puts us back in MOUSE_menu
546 	// mode
547 
548 	hit = menuClick(TOTAL_engine_pockets);
549 
550 	if (hit < 0 || !_masterMenuList[hit].icon_resource)
551 		return;
552 
553 	// Always back into menu mode. Remove the luggage as well.
554 
555 	_mouseMode = MOUSE_menu;
556 	setLuggage(0);
557 
558 	if ((uint)hit == _menuSelectedPos) {
559 		// If we clicked on the same icon again, reset the first icon
560 
561 		_vm->_logic->writeVar(OBJECT_HELD, 0);
562 		_menuSelectedPos = 0;
563 	} else {
564 		// Otherwise, combine the two icons
565 
566 		_vm->_logic->writeVar(COMBINE_BASE, _masterMenuList[hit].icon_resource);
567 		_vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, MENU_MASTER_OBJECT);
568 
569 		// Turn off mouse now, to prevent player trying to click
570 		// elsewhere BUT leave the bottom menu open
571 
572 		hideMouse();
573 
574 		debug(2, "Used \"%s\" on \"%s\"",
575 			_vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf1),
576 			_vm->_resman->fetchName(_vm->_logic->readVar(COMBINE_BASE), buf2));
577 	}
578 
579 	// Refresh the menu
580 
581 	buildMenu();
582 }
583 
menuMouse()584 void Mouse::menuMouse() {
585 	MouseEvent *me;
586 	int hit;
587 
588 	// If the mouse is moved off the menu, close it.
589 
590 	if (getY() < 400) {
591 		_mouseMode = MOUSE_normal;
592 		hideMenu(RDMENU_BOTTOM);
593 		return;
594 	}
595 
596 	me = _vm->mouseEvent();
597 
598 	if (!me)
599 		return;
600 
601 	hit = menuClick(TOTAL_engine_pockets);
602 
603 	// Check if we clicked on an actual icon.
604 
605 	if (hit < 0 || !_masterMenuList[hit].icon_resource)
606 		return;
607 
608 	if (me->buttons & RD_RIGHTBUTTONDOWN) {
609 		// Right button - examine an object, identified by its icon
610 		// resource id.
611 
612 		_examiningMenuIcon = true;
613 		_vm->_logic->writeVar(OBJECT_HELD, _masterMenuList[hit].icon_resource);
614 
615 		// Must clear this so next click on exit becomes 1st click
616 		// again
617 
618 		_vm->_logic->writeVar(EXIT_CLICK_ID, 0);
619 
620 		_vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, MENU_MASTER_OBJECT);
621 
622 		// Refresh the menu
623 
624 		buildMenu();
625 
626 		// Turn off mouse now, to prevent player trying to click
627 		// elsewhere BUT leave the bottom menu open
628 
629 		hideMouse();
630 
631 		debug(2, "Right-click on \"%s\" icon",
632 			_vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD)));
633 
634 		return;
635 	}
636 
637 	if (me->buttons & RD_LEFTBUTTONDOWN) {
638 		// Left button - bung us into drag luggage mode. The object is
639 		// identified by its icon resource id. We need the luggage
640 		// resource id for mouseOnOff
641 
642 		_mouseMode = MOUSE_drag;
643 
644 		_menuSelectedPos = hit;
645 		_vm->_logic->writeVar(OBJECT_HELD, _masterMenuList[hit].icon_resource);
646 		_currentLuggageResource = _masterMenuList[hit].luggage_resource;
647 
648 		// Must clear this so next click on exit becomes 1st click
649 		// again
650 
651 		_vm->_logic->writeVar(EXIT_CLICK_ID, 0);
652 
653 		// Refresh the menu
654 
655 		buildMenu();
656 
657 		setLuggage(_masterMenuList[hit].luggage_resource);
658 
659 		debug(2, "Left-clicked on \"%s\" icon - switch to drag mode",
660 			_vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD)));
661 	}
662 }
663 
normalMouse()664 void Mouse::normalMouse() {
665 	// The game is playing and none of the menus are activated - but, we
666 	// need to check if a menu is to start. Note, won't have luggage
667 
668 	MouseEvent *me;
669 
670 	// Check if the cursor has moved onto the system menu area. No save in
671 	// big-object menu lock situation, of if the player is dragging an
672 	// object.
673 
674 	int x, y;
675 
676 	getPos(x, y);
677 
678 	if (y < 0 && !_mouseModeLocked && !_vm->_logic->readVar(OBJECT_HELD)) {
679 		_mouseMode = MOUSE_system_menu;
680 
681 		if (_mouseTouching) {
682 			// We were on something, but not anymore
683 			_oldMouseTouching = 0;
684 			_mouseTouching = 0;
685 		}
686 
687 		// Reset mouse cursor - in case we're between mice
688 
689 		setMouse(NORMAL_MOUSE_ID);
690 		buildSystemMenu();
691 		return;
692 	}
693 
694 	// Check if the cursor has moved onto the inventory menu area. No
695 	// inventory in big-object menu lock situation,
696 
697 	if (y > 399 && !_mouseModeLocked) {
698 		// If an object is being held, i.e. if the mouse cursor has a
699 		// luggage, go to drag mode instead of menu mode, but the menu
700 		// is still opened.
701 		//
702 		// That way, we can still use an object on another inventory
703 		// object, even if the inventory menu was closed after the
704 		// first object was selected.
705 
706 		if (!_vm->_logic->readVar(OBJECT_HELD))
707 			_mouseMode = MOUSE_menu;
708 		else
709 			_mouseMode = MOUSE_drag;
710 
711 		// If mouse is moving off an object and onto the menu then do a
712 		// standard get-off
713 
714 		if (_mouseTouching) {
715 			_oldMouseTouching = 0;
716 			_mouseTouching = 0;
717 		}
718 
719 		// Reset mouse cursor
720 
721 		setMouse(NORMAL_MOUSE_ID);
722 		buildMenu();
723 		return;
724 	}
725 
726 	// Check for moving the mouse on or off things
727 
728 	mouseOnOff();
729 
730 	me = _vm->mouseEvent();
731 
732 	if (!me)
733 		return;
734 
735 	bool button_down = (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN)) != 0;
736 
737 	// For debugging. We can draw a rectangle on the screen and see its
738 	// coordinates. This was probably used to help defining hit areas.
739 
740 	if (_vm->_debugger->_definingRectangles) {
741 		ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
742 
743 		if (_vm->_debugger->_draggingRectangle == 0) {
744 			// Not yet dragging a rectangle, so need click to start
745 
746 			if (button_down) {
747 				// set both (x1,y1) and (x2,y2) to this point
748 				_vm->_debugger->_rectX1 = _vm->_debugger->_rectX2 = (uint32)x + screenInfo->scroll_offset_x;
749 				_vm->_debugger->_rectY1 = _vm->_debugger->_rectY2 = (uint32)y + screenInfo->scroll_offset_y;
750 				_vm->_debugger->_draggingRectangle = 1;
751 			}
752 		} else if (_vm->_debugger->_draggingRectangle == 1) {
753 			// currently dragging a rectangle - click means reset
754 
755 			if (button_down) {
756 				// lock rectangle, so you can let go of mouse
757 				// to type in the coords
758 				_vm->_debugger->_draggingRectangle = 2;
759 			} else {
760 				// drag rectangle
761 				_vm->_debugger->_rectX2 = (uint32)x + screenInfo->scroll_offset_x;
762 				_vm->_debugger->_rectY2 = (uint32)y + screenInfo->scroll_offset_y;
763 			}
764 		} else {
765 			// currently locked to avoid knocking out of place
766 			// while reading off the coords
767 
768 			if (button_down) {
769 				// click means reset - back to start again
770 				_vm->_debugger->_draggingRectangle = 0;
771 			}
772 		}
773 
774 		return;
775 	}
776 
777 #if RIGHT_CLICK_CLEARS_LUGGAGE
778 	if (_vm->_logic->readVar(OBJECT_HELD) && (me->buttons & RD_RIGHTBUTTONDOWN) && heldIsInInventory()) {
779 		_vm->_logic->writeVar(OBJECT_HELD, 0);
780 		_menuSelectedPos = 0;
781 		setLuggage(0);
782 		return;
783 	}
784 #endif
785 
786 	// Now do the normal click stuff
787 
788 	// We only care about down clicks when the mouse is over an object.
789 
790 	if (!_mouseTouching || !button_down)
791 		return;
792 
793 	// There's a mouse event to be processed and the mouse is on something.
794 	// Notice that the floor itself is considered an object.
795 
796 	// There are no menus about so its nice and simple. This is as close to
797 	// the old advisor_188 script as we get, I'm sorry to say.
798 
799 	// If player is walking or relaxing then those need to terminate
800 	// correctly. Otherwise set player run the targets action script or, do
801 	// a special walk if clicking on the scroll-more icon
802 
803 	// PLAYER_ACTION script variable - whatever catches this must reset to
804 	// 0 again
805 	// _vm->_logic->writeVar(PLAYER_ACTION, _mouseTouching);
806 
807 	// Idle or router-anim will catch it
808 
809 	// Set global script variable 'button'
810 
811 	if (me->buttons & RD_LEFTBUTTONDOWN) {
812 		_vm->_logic->writeVar(LEFT_BUTTON, 1);
813 		_vm->_logic->writeVar(RIGHT_BUTTON, 0);
814 		_buttonClick = 0;	// for re-click
815 	} else {
816 		_vm->_logic->writeVar(LEFT_BUTTON, 0);
817 		_vm->_logic->writeVar(RIGHT_BUTTON, 1);
818 		_buttonClick = 1;	// for re-click
819 	}
820 
821 	// These might be required by the action script about to be run
822 	ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
823 
824 	_vm->_logic->writeVar(MOUSE_X, x + screenInfo->scroll_offset_x);
825 	_vm->_logic->writeVar(MOUSE_Y, y + screenInfo->scroll_offset_y);
826 
827 	if (_mouseTouching == _vm->_logic->readVar(EXIT_CLICK_ID) && (me->buttons & RD_LEFTBUTTONDOWN) && _oldButton == _buttonClick) {
828 		// It's the exit double click situation. Let the existing
829 		// interaction continue and start fading down. Switch the human
830 		// off too
831 
832 		noHuman();
833 		_vm->_logic->fnFadeDown(NULL);
834 
835 		// Tell the walker
836 
837 		_vm->_logic->writeVar(EXIT_FADING, 1);
838 	} else if (_oldButton == _buttonClick && _mouseTouching == _vm->_logic->readVar(CLICKED_ID) && _mousePointerRes != NORMAL_MOUSE_ID) {
839 		// Re-click. Do nothing, except on floors
840 	} else {
841 		// For re-click
842 
843 		_oldButton = _buttonClick;
844 
845 		// For scripts to know what's been clicked. First used for
846 		// 'room_13_turning_script' in object 'biscuits_13'
847 
848 		_vm->_logic->writeVar(CLICKED_ID, _mouseTouching);
849 
850 		// Must clear these two double-click control flags - do it here
851 		// so reclicks after exit clicks are cleared up
852 
853 		_vm->_logic->writeVar(EXIT_CLICK_ID, 0);
854 		_vm->_logic->writeVar(EXIT_FADING, 0);
855 
856 		_vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching);
857 
858 		byte buf1[NAME_LEN], buf2[NAME_LEN];
859 
860 		if (_vm->_logic->readVar(OBJECT_HELD))
861 			debug(2, "Used \"%s\" on \"%s\"",
862 				_vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf1),
863 				_vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf2));
864 		else if (_vm->_logic->readVar(LEFT_BUTTON))
865 			debug(2, "Left-clicked on \"%s\"",
866 				_vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID)));
867 		else	// RIGHT BUTTON
868 			debug(2, "Right-clicked on \"%s\"",
869 				_vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID)));
870 	}
871 }
872 
chooseMouse()873 uint32 Mouse::chooseMouse() {
874 	// Unlike the other mouse "engines", this one is called directly by the
875 	// fnChoose() opcode.
876 
877 	byte menuIconWidth;
878 
879 	if (Sword2Engine::isPsx())
880 		menuIconWidth = RDMENU_PSXICONWIDE;
881 	else
882 		menuIconWidth = RDMENU_ICONWIDE;
883 
884 
885 	uint i;
886 
887 	_vm->_logic->writeVar(AUTO_SELECTED, 0);
888 
889 	uint32 in_subject = _vm->_logic->readVar(IN_SUBJECT);
890 	uint32 object_held = _vm->_logic->readVar(OBJECT_HELD);
891 
892 	if (object_held) {
893 		// The player used an object on a person. In this case it
894 		// triggered a conversation menu. Act as if the user tried to
895 		// talk to the person about that object. If the person doesn't
896 		// know anything about it, use the default response.
897 
898 		uint32 response = _defaultResponseId;
899 
900 		for (i = 0; i < in_subject; i++) {
901 			if (_subjectList[i].res == object_held) {
902 				response = _subjectList[i].ref;
903 				break;
904 			}
905 		}
906 
907 		// The user won't be holding the object any more, and the
908 		// conversation menu will be closed.
909 
910 		_vm->_logic->writeVar(OBJECT_HELD, 0);
911 		_vm->_logic->writeVar(IN_SUBJECT, 0);
912 		return response;
913 	}
914 
915 	if (_vm->_logic->readVar(CHOOSER_COUNT_FLAG) == 0 && in_subject == 1 && _subjectList[0].res == EXIT_ICON) {
916 		// This is the first time the chooser is coming up in this
917 		// conversation, there is only one subject and that's the
918 		// EXIT icon.
919 		//
920 		// In other words, the player doesn't have anything to talk
921 		// about. Skip it.
922 
923 		// The conversation menu will be closed. We set AUTO_SELECTED
924 		// because the speech script depends on it.
925 
926 		_vm->_logic->writeVar(AUTO_SELECTED, 1);
927 		_vm->_logic->writeVar(IN_SUBJECT, 0);
928 		return _subjectList[0].ref;
929 	}
930 
931 	byte *icon;
932 
933 	if (!_choosing) {
934 		// This is a new conversation menu.
935 
936 		if (!in_subject)
937 			error("fnChoose with no subjects");
938 
939 		for (i = 0; i < in_subject; i++) {
940 			icon = _vm->_resman->openResource(_subjectList[i].res) + ResHeader::size() + menuIconWidth * RDMENU_ICONDEEP;
941 			setMenuIcon(RDMENU_BOTTOM, i, icon);
942 			_vm->_resman->closeResource(_subjectList[i].res);
943 		}
944 
945 		for (; i < 15; i++)
946 			setMenuIcon(RDMENU_BOTTOM, (uint8) i, NULL);
947 
948 		showMenu(RDMENU_BOTTOM);
949 		setMouse(NORMAL_MOUSE_ID);
950 		_choosing = true;
951 		return (uint32)-1;
952 	}
953 
954 	// The menu is there - we're just waiting for a click. We only care
955 	// about left clicks.
956 
957 	MouseEvent *me = _vm->mouseEvent();
958 	int mouseX, mouseY;
959 
960 	getPos(mouseX, mouseY);
961 
962 	if (!me || !(me->buttons & RD_LEFTBUTTONDOWN) || mouseY < 400)
963 		return (uint32)-1;
964 
965 	// Check for click on a menu.
966 
967 	int hit = _vm->_mouse->menuClick(in_subject);
968 	if (hit < 0)
969 		return (uint32)-1;
970 
971 	// Hilight the clicked icon by greying the others. This can look a bit
972 	// odd when you click on the exit icon, but there are also cases when
973 	// it looks strange if you don't do it.
974 
975 	for (i = 0; i < in_subject; i++) {
976 		if ((int)i != hit) {
977 			icon = _vm->_resman->openResource(_subjectList[i].res) + ResHeader::size();
978 			_vm->_mouse->setMenuIcon(RDMENU_BOTTOM, i, icon);
979 			_vm->_resman->closeResource(_subjectList[i].res);
980 		}
981 	}
982 
983 	// For non-speech scripts that manually call the chooser
984 	_vm->_logic->writeVar(RESULT, _subjectList[hit].res);
985 
986 	// The conversation menu will be closed
987 
988 	_choosing = false;
989 	_vm->_logic->writeVar(IN_SUBJECT, 0);
990 	setMouse(0);
991 
992 	return _subjectList[hit].ref;
993 }
994 
mouseOnOff()995 void Mouse::mouseOnOff() {
996 	// this handles the cursor graphic when moving on and off mouse areas
997 	// it also handles the luggage thingy
998 
999 	uint32 pointer_type;
1000 	static uint8 mouse_flicked_off = 0;
1001 
1002 	_oldMouseTouching = _mouseTouching;
1003 
1004 	// don't detect objects that are hidden behind the menu bars (ie. in
1005 	// the scrolled-off areas of the screen)
1006 
1007 	int y = getY();
1008 
1009 	if (y < 0 || y > 399) {
1010 		pointer_type = 0;
1011 		_mouseTouching = 0;
1012 	} else {
1013 		// set '_mouseTouching' & return pointer_type
1014 		pointer_type = checkMouseList();
1015 	}
1016 
1017 	// same as previous cycle?
1018 	if (!mouse_flicked_off && _oldMouseTouching == _mouseTouching) {
1019 		// yes, so nothing to do
1020 		// BUT CARRY ON IF MOUSE WAS FLICKED OFF!
1021 		return;
1022 	}
1023 
1024 	// can reset this now
1025 	mouse_flicked_off = 0;
1026 
1027 	//the cursor has moved onto something
1028 	if (!_oldMouseTouching && _mouseTouching) {
1029 		// make a copy of the object we've moved onto because one day
1030 		// we'll move back off again! (but the list positioning could
1031 		// theoretically have changed)
1032 
1033 		// we can only move onto something from being on nothing - we
1034 		// stop the system going from one to another when objects
1035 		// overlap
1036 
1037 		_oldMouseTouching = _mouseTouching;
1038 
1039 		// run get on
1040 
1041 		if (pointer_type) {
1042 			// 'pointer_type' holds the resource id of the
1043 			// pointer anim
1044 
1045 			setMouse(pointer_type);
1046 
1047 			// setup luggage icon
1048 			if (_vm->_logic->readVar(OBJECT_HELD)) {
1049 				setLuggage(_currentLuggageResource);
1050 			}
1051 		} else {
1052 			error("ERROR: mouse.pointer==0 for object %d (%s) - update logic script", _mouseTouching, _vm->_resman->fetchName(_mouseTouching));
1053 		}
1054 	} else if (_oldMouseTouching && !_mouseTouching) {
1055 		// the cursor has moved off something - reset cursor to
1056 		// normal pointer
1057 
1058 		_oldMouseTouching = 0;
1059 		setMouse(NORMAL_MOUSE_ID);
1060 
1061 		// reset luggage only when necessary
1062 	} else if (_oldMouseTouching && _mouseTouching) {
1063 		// The cursor has moved off something and onto something
1064 		// else. Flip to a blank cursor for a cycle.
1065 
1066 		// ignore the new id this cycle - should hit next cycle
1067 		_mouseTouching = 0;
1068 		_oldMouseTouching = 0;
1069 		setMouse(0);
1070 
1071 		// so we know to set the mouse pointer back to normal if 2nd
1072 		// hot-spot doesn't register because mouse pulled away
1073 		// quickly (onto nothing)
1074 
1075 		mouse_flicked_off = 1;
1076 
1077 		// reset luggage only when necessary
1078 	} else {
1079 		// Mouse was flicked off for one cycle, but then moved onto
1080 		// nothing before 2nd hot-spot registered
1081 
1082 		// both '_oldMouseTouching' & '_mouseTouching' will be zero
1083 		// reset cursor to normal pointer
1084 
1085 		setMouse(NORMAL_MOUSE_ID);
1086 	}
1087 
1088 	// possible check for edge of screen more-to-scroll here on large
1089 	// screens
1090 }
1091 
setMouse(uint32 res)1092 void Mouse::setMouse(uint32 res) {
1093 	// high level - whats the mouse - for the engine
1094 	_mousePointerRes = res;
1095 
1096 	if (res) {
1097 		byte *icon = _vm->_resman->openResource(res) + ResHeader::size();
1098 		uint32 len = _vm->_resman->fetchLen(res) - ResHeader::size();
1099 
1100 		// don't pulse the normal pointer - just do the regular anim
1101 		// loop
1102 
1103 		if (res == NORMAL_MOUSE_ID)
1104 			setMouseAnim(icon, len, RDMOUSE_NOFLASH);
1105 		else
1106 			setMouseAnim(icon, len, RDMOUSE_FLASH);
1107 
1108 		_vm->_resman->closeResource(res);
1109 	} else {
1110 		// blank cursor
1111 		setMouseAnim(NULL, 0, 0);
1112 	}
1113 }
1114 
setLuggage(uint32 res)1115 void Mouse::setLuggage(uint32 res) {
1116 	_realLuggageItem = res;
1117 
1118 	if (res) {
1119 		byte *icon = _vm->_resman->openResource(res) + ResHeader::size();
1120 		uint32 len = _vm->_resman->fetchLen(res) - ResHeader::size();
1121 
1122 		setLuggageAnim(icon, len);
1123 		_vm->_resman->closeResource(res);
1124 	} else
1125 		setLuggageAnim(NULL, 0);
1126 }
1127 
setObjectHeld(uint32 res)1128 void Mouse::setObjectHeld(uint32 res) {
1129 	setLuggage(res);
1130 
1131 	_vm->_logic->writeVar(OBJECT_HELD, res);
1132 	_currentLuggageResource = res;
1133 
1134 	// mode locked - no menu available
1135 	_mouseModeLocked = true;
1136 }
1137 
checkMouseList()1138 uint32 Mouse::checkMouseList() {
1139 	ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
1140 	int x, y;
1141 
1142 	getPos(x, y);
1143 
1144 	Common::Point mousePos(x + screenInfo->scroll_offset_x, y + screenInfo->scroll_offset_y);
1145 
1146 	// Number of priorities subject to implementation needs
1147 	for (int priority = 0; priority < 10; priority++) {
1148 		for (uint i = 0; i < _curMouse; i++) {
1149 			// If the mouse pointer is over this
1150 			// mouse-detection-box
1151 
1152 			if (_mouseList[i].priority == priority && _mouseList[i].rect.contains(mousePos)) {
1153 				// Record id
1154 				_mouseTouching = _mouseList[i].id;
1155 
1156 				createPointerText(_mouseList[i].pointer_text, _mouseList[i].pointer);
1157 
1158 				// Return pointer type
1159 				return _mouseList[i].pointer;
1160 			}
1161 		}
1162 	}
1163 
1164 	// Touching nothing; no pointer to return
1165 	_mouseTouching = 0;
1166 	return 0;
1167 }
1168 
1169 #define POINTER_TEXT_WIDTH	640		// just in case!
1170 #define POINTER_TEXT_PEN	184		// white
1171 
createPointerText(uint32 text_id,uint32 pointer_res)1172 void Mouse::createPointerText(uint32 text_id, uint32 pointer_res) {
1173 	uint32 local_text;
1174 	uint32 text_res;
1175 	byte *text;
1176 	// offsets for pointer text sprite from pointer position
1177 	int16 xOffset, yOffset;
1178 	uint8 justification;
1179 
1180 	if (!_objectLabels || !text_id)
1181 		return;
1182 
1183 	// Check what the pointer is, to set offsets correctly for text
1184 	// position
1185 
1186 	switch (pointer_res) {
1187 	case CROSHAIR:
1188 		yOffset = -7;
1189 		xOffset = +10;
1190 		break;
1191 	case EXIT0:
1192 		yOffset = +15;
1193 		xOffset = +20;
1194 		break;
1195 	case EXIT1:
1196 		yOffset = +16;
1197 		xOffset = -10;
1198 		break;
1199 	case EXIT2:
1200 		yOffset = +10;
1201 		xOffset = -22;
1202 		break;
1203 	case EXIT3:
1204 		yOffset = -16;
1205 		xOffset = -10;
1206 		break;
1207 	case EXIT4:
1208 		yOffset = -15;
1209 		xOffset = +15;
1210 		break;
1211 	case EXIT5:
1212 		yOffset = -12;
1213 		xOffset = +10;
1214 		break;
1215 	case EXIT6:
1216 		yOffset = +10;
1217 		xOffset = +25;
1218 		break;
1219 	case EXIT7:
1220 		yOffset = +16;
1221 		xOffset = +20;
1222 		break;
1223 	case EXITDOWN:
1224 		yOffset = -20;
1225 		xOffset = -10;
1226 		break;
1227 	case EXITUP:
1228 		yOffset = +20;
1229 		xOffset = +20;
1230 		break;
1231 	case MOUTH:
1232 		yOffset = -10;
1233 		xOffset = +15;
1234 		break;
1235 	case NORMAL:
1236 		yOffset = -10;
1237 		xOffset = +15;
1238 		break;
1239 	case PICKUP:
1240 		yOffset = -40;
1241 		xOffset = +10;
1242 		break;
1243 	case SCROLL_L:
1244 		yOffset = -20;
1245 		xOffset = +20;
1246 		break;
1247 	case SCROLL_R:
1248 		yOffset = -20;
1249 		xOffset = -20;
1250 		break;
1251 	case USE:
1252 		yOffset = -8;
1253 		xOffset = +20;
1254 		break;
1255 	default:
1256 		// Shouldn't happen if we cover all the different mouse
1257 		// pointers above
1258 		yOffset = -10;
1259 		xOffset = +10;
1260 		break;
1261 	}
1262 
1263 	// Set up justification for text sprite, based on its offsets from the
1264 	// pointer position
1265 
1266 	if (yOffset < 0) {
1267 		// Above pointer
1268 		if (xOffset < 0) {
1269 			// Above left
1270 			justification = POSITION_AT_RIGHT_OF_BASE;
1271 		} else if (xOffset > 0) {
1272 			// Above right
1273 			justification = POSITION_AT_LEFT_OF_BASE;
1274 		} else {
1275 			// Above center
1276 			justification = POSITION_AT_CENTER_OF_BASE;
1277 		}
1278 	} else if (yOffset > 0) {
1279 		// Below pointer
1280 		if (xOffset < 0) {
1281 			// Below left
1282 			justification = POSITION_AT_RIGHT_OF_TOP;
1283 		} else if (xOffset > 0) {
1284 			// Below right
1285 			justification = POSITION_AT_LEFT_OF_TOP;
1286 		} else {
1287 			// Below center
1288 			justification = POSITION_AT_CENTER_OF_TOP;
1289 		}
1290 	} else {
1291 		// Same y-coord as pointer
1292 		if (xOffset < 0) {
1293 			// Center left
1294 			justification = POSITION_AT_RIGHT_OF_CENTER;
1295 		} else if (xOffset > 0) {
1296 			// Center right
1297 			justification = POSITION_AT_LEFT_OF_CENTER;
1298 		} else {
1299 			// Center center - shouldn't happen anyway!
1300 			justification = POSITION_AT_LEFT_OF_CENTER;
1301 		}
1302 	}
1303 
1304 	// Text resource number, and line number within the resource
1305 
1306 	text_res = text_id / SIZE;
1307 	local_text = text_id & 0xffff;
1308 
1309 	// open text file & get the line
1310 	text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
1311 
1312 	// 'text+2' to skip the first 2 bytes which form the
1313 	// line reference number
1314 
1315 	int x, y;
1316 
1317 	getPos(x, y);
1318 
1319 	_pointerTextBlocNo = _vm->_fontRenderer->buildNewBloc(
1320 		text + 2, x + xOffset,
1321 		y + yOffset,
1322 		POINTER_TEXT_WIDTH, POINTER_TEXT_PEN,
1323 		RDSPR_TRANS | RDSPR_DISPLAYALIGN,
1324 		_vm->_speechFontId, justification);
1325 
1326 	// now ok to close the text file
1327 	_vm->_resman->closeResource(text_res);
1328 }
1329 
clearPointerText()1330 void Mouse::clearPointerText() {
1331 	if (_pointerTextBlocNo) {
1332 		_vm->_fontRenderer->killTextBloc(_pointerTextBlocNo);
1333 		_pointerTextBlocNo = 0;
1334 	}
1335 }
1336 
hideMouse()1337 void Mouse::hideMouse() {
1338 	// leaves the menus open
1339 	// used by the system when clicking right on a menu item to examine
1340 	// it and when combining objects
1341 
1342 	// for logic scripts
1343 	_vm->_logic->writeVar(MOUSE_AVAILABLE, 0);
1344 
1345 	// human/mouse off
1346 	_mouseStatus = true;
1347 
1348 	setMouse(0);
1349 	setLuggage(0);
1350 }
1351 
noHuman()1352 void Mouse::noHuman() {
1353 	hideMouse();
1354 	clearPointerText();
1355 
1356 	// Must be normal mouse situation or a largely neutral situation -
1357 	// special menus use hideMouse()
1358 
1359 	// Don't hide menu in conversations
1360 	if (_vm->_logic->readVar(TALK_FLAG) == 0)
1361 		hideMenu(RDMENU_BOTTOM);
1362 
1363 	if (_mouseMode == MOUSE_system_menu) {
1364 		// Close menu
1365 		_mouseMode = MOUSE_normal;
1366 		hideMenu(RDMENU_TOP);
1367 	}
1368 }
1369 
addHuman()1370 void Mouse::addHuman() {
1371 	// For logic scripts
1372 	_vm->_logic->writeVar(MOUSE_AVAILABLE, 1);
1373 
1374 	if (_mouseStatus) {
1375 		// Force engine to choose a cursor
1376 		_mouseStatus = false;
1377 		_mouseTouching = 1;
1378 	}
1379 
1380 	// Clear this to reset no-second-click system
1381 	_vm->_logic->writeVar(CLICKED_ID, 0);
1382 
1383 	// This is now done outside the OBJECT_HELD check in case it's set to
1384 	// zero before now!
1385 
1386 	// Unlock the mouse from possible large object lock situtations - see
1387 	// syphon in rm 3
1388 
1389 	_mouseModeLocked = false;
1390 
1391 	if (_vm->_logic->readVar(OBJECT_HELD)) {
1392 		// Was dragging something around - need to clear this again
1393 		_vm->_logic->writeVar(OBJECT_HELD, 0);
1394 
1395 		// And these may also need clearing, just in case
1396 		_examiningMenuIcon = false;
1397 		_vm->_logic->writeVar(COMBINE_BASE, 0);
1398 
1399 		setLuggage(0);
1400 	}
1401 
1402 	// If mouse is over menu area
1403 	if (getY() > 399) {
1404 		if (_mouseMode != MOUSE_holding) {
1405 			// VITAL - reset things & rebuild the menu
1406 			_mouseMode = MOUSE_normal;
1407 		}
1408 		setMouse(NORMAL_MOUSE_ID);
1409 	}
1410 
1411 	// Enabled/disabled from console; status printed with on-screen debug
1412 	// info
1413 
1414 	if (_vm->_debugger->_testingSnR) {
1415 		uint8 black[3] = {   0,  0,    0 };
1416 		uint8 white[3] = { 255, 255, 255 };
1417 
1418 		// Testing logic scripts by simulating instant Save & Restore
1419 
1420 		_vm->_screen->setPalette(0, 1, white, RDPAL_INSTANT);
1421 
1422 		// Stops all fx & clears the queue - eg. when leaving a room
1423 		_vm->_sound->clearFxQueue(false);
1424 
1425 		// Trash all object resources so they load in fresh & restart
1426 		// their logic scripts
1427 
1428 		_vm->_resman->killAllObjects(false);
1429 
1430 		_vm->_screen->setPalette(0, 1, black, RDPAL_INSTANT);
1431 	}
1432 }
1433 
refreshInventory()1434 void Mouse::refreshInventory() {
1435 	// Can reset this now
1436 	_vm->_logic->writeVar(COMBINE_BASE, 0);
1437 
1438 	// Cause 'object_held' icon to be greyed. The rest are colored.
1439 	_examiningMenuIcon = true;
1440 	buildMenu();
1441 	_examiningMenuIcon = false;
1442 }
1443 
startConversation()1444 void Mouse::startConversation() {
1445 	if (_vm->_logic->readVar(TALK_FLAG) == 0) {
1446 		// See fnChooser & speech scripts
1447 		_vm->_logic->writeVar(CHOOSER_COUNT_FLAG, 0);
1448 	}
1449 
1450 	noHuman();
1451 }
1452 
endConversation()1453 void Mouse::endConversation() {
1454 	hideMenu(RDMENU_BOTTOM);
1455 
1456 	if (getY() > 399) {
1457 		// Will wait for cursor to move off the bottom menu
1458 		_mouseMode = MOUSE_holding;
1459 	}
1460 
1461 	// In case DC forgets
1462 	_vm->_logic->writeVar(TALK_FLAG, 0);
1463 }
1464 
monitorPlayerActivity()1465 void Mouse::monitorPlayerActivity() {
1466 	// if there is at least one mouse event outstanding
1467 	if (_vm->checkForMouseEvents()) {
1468 		// reset activity delay counter
1469 		_playerActivityDelay = 0;
1470 	} else {
1471 		// no. of game cycles since mouse event queue last empty
1472 		_playerActivityDelay++;
1473 	}
1474 }
1475 
checkPlayerActivity(uint32 seconds)1476 void Mouse::checkPlayerActivity(uint32 seconds) {
1477 	// Convert seconds to game cycles
1478 	uint32 threshold = seconds * 12;
1479 
1480 	// If the actual delay is at or above the given threshold, reset the
1481 	// activity delay counter now that we've got a positive check.
1482 
1483 	if (_playerActivityDelay >= threshold) {
1484 		_playerActivityDelay = 0;
1485 		_vm->_logic->writeVar(RESULT, 1);
1486 	} else
1487 		_vm->_logic->writeVar(RESULT, 0);
1488 }
1489 
pauseEngine(bool pause)1490 void Mouse::pauseEngine(bool pause) {
1491 	if (pause) {
1492 		// Make the mouse cursor normal. This is the only place where
1493 		// we are allowed to clear the luggage this way.
1494 
1495 		clearPointerText();
1496 		setLuggageAnim(NULL, 0);
1497 		setMouse(0);
1498 		setMouseTouching(1);
1499 	} else {
1500 		if (_vm->_logic->readVar(OBJECT_HELD) && _realLuggageItem)
1501 			setLuggage(_realLuggageItem);
1502 	}
1503 }
1504 
1505 #define MOUSEFLASHFRAME 6
1506 
decompressMouse(byte * decomp,byte * comp,uint8 frame,int width,int height,int pitch,int xOff,int yOff)1507 void Mouse::decompressMouse(byte *decomp, byte *comp, uint8 frame, int width, int height, int pitch, int xOff, int yOff) {
1508 	int32 size = width * height;
1509 	int32 i = 0;
1510 	int x = 0;
1511 	int y = 0;
1512 
1513 	if (Sword2Engine::isPsx()) {
1514 		comp = comp + READ_LE_UINT32(comp + 2 + frame * 4) - MOUSE_ANIM_HEADER_SIZE;
1515 
1516 		yOff /= 2; // Without this, distance of object from cursor is too big.
1517 
1518 		byte *buffer;
1519 
1520 		buffer = (byte *)malloc(size);
1521 		Screen::decompressHIF(comp, buffer);
1522 
1523 		for (int line = 0; line < height; line++) {
1524 			memcpy(decomp + (line + yOff) * pitch + xOff, buffer + line * width, width);
1525 		}
1526 
1527 		free(buffer);
1528 
1529 	} else {
1530 		comp = comp + READ_LE_UINT32(comp + frame * 4) - MOUSE_ANIM_HEADER_SIZE;
1531 
1532 		while (i < size) {
1533 			if (*comp > 183) {
1534 				decomp[(y + yOff) * pitch + x + xOff] = *comp++;
1535 				if (++x >= width) {
1536 					x = 0;
1537 					y++;
1538 				}
1539 				i++;
1540 			} else {
1541 				x += *comp;
1542 				while (x >= width) {
1543 					y++;
1544 					x -= width;
1545 				}
1546 				i += *comp++;
1547 			}
1548 		}
1549 	}
1550 }
1551 
drawMouse()1552 void Mouse::drawMouse() {
1553 	if (!_mouseAnim.data && !_luggageAnim.data)
1554 		return;
1555 
1556 	// When an object is used in the game, the mouse cursor should be a
1557 	// combination of a standard mouse cursor and a luggage cursor.
1558 	//
1559 	// However, judging by the original code luggage cursors can also
1560 	// appear on their own. I have no idea which cases though.
1561 
1562 	uint16 mouse_width = 0;
1563 	uint16 mouse_height = 0;
1564 	uint16 hotspot_x = 0;
1565 	uint16 hotspot_y = 0;
1566 	int deltaX = 0;
1567 	int deltaY = 0;
1568 
1569 	if (_mouseAnim.data) {
1570 		hotspot_x = _mouseAnim.xHotSpot;
1571 		hotspot_y = _mouseAnim.yHotSpot;
1572 		mouse_width = _mouseAnim.mousew;
1573 		mouse_height = _mouseAnim.mouseh;
1574 	}
1575 
1576 	if (_luggageAnim.data) {
1577 		if (!_mouseAnim.data) {
1578 			hotspot_x = _luggageAnim.xHotSpot;
1579 			hotspot_y = _luggageAnim.yHotSpot;
1580 		}
1581 		if (_luggageAnim.mousew > mouse_width)
1582 			mouse_width = _luggageAnim.mousew;
1583 		if (_luggageAnim.mouseh > mouse_height)
1584 			mouse_height = _luggageAnim.mouseh;
1585 	}
1586 
1587 	if (_mouseAnim.data && _luggageAnim.data) {
1588 		deltaX = _mouseAnim.xHotSpot - _luggageAnim.xHotSpot;
1589 		deltaY = _mouseAnim.yHotSpot - _luggageAnim.yHotSpot;
1590 	}
1591 
1592 	assert(deltaX >= 0);
1593 	assert(deltaY >= 0);
1594 
1595 	mouse_width += deltaX;
1596 	mouse_height += deltaY;
1597 
1598 	byte *mouseData = (byte *)calloc(mouse_height, mouse_width);
1599 
1600 	if (_luggageAnim.data)
1601 		decompressMouse(mouseData, _luggageAnim.data, 0,
1602 			_luggageAnim.mousew, _luggageAnim.mouseh,
1603 			mouse_width, deltaX, deltaY);
1604 
1605 	if (_mouseAnim.data)
1606 		decompressMouse(mouseData, _mouseAnim.data, _mouseFrame,
1607 			_mouseAnim.mousew, _mouseAnim.mouseh, mouse_width);
1608 
1609 	// Fix height for mouse sprite in PSX version
1610 	if (Sword2Engine::isPsx()) {
1611 		mouse_height *= 2;
1612 
1613 		byte *buffer = (byte *)malloc(mouse_width * mouse_height);
1614 		Screen::resizePsxSprite(buffer, mouseData, mouse_width, mouse_height);
1615 
1616 		free(mouseData);
1617 		mouseData = buffer;
1618 	}
1619 
1620 	CursorMan.replaceCursor(mouseData, mouse_width, mouse_height, hotspot_x, hotspot_y, 0);
1621 
1622 	free(mouseData);
1623 }
1624 
1625 /**
1626  * Animates the current mouse pointer
1627  */
1628 
animateMouse()1629 int32 Mouse::animateMouse() {
1630 	uint8 prevMouseFrame = _mouseFrame;
1631 
1632 	if (!_mouseAnim.data)
1633 		return RDERR_UNKNOWN;
1634 
1635 	if (++_mouseFrame == _mouseAnim.noAnimFrames)
1636 		_mouseFrame = MOUSEFLASHFRAME;
1637 
1638 	if (_mouseFrame != prevMouseFrame)
1639 		drawMouse();
1640 
1641 	return RD_OK;
1642 }
1643 
1644 /**
1645  * Sets the mouse cursor animation.
1646  * @param ma a pointer to the animation data, or NULL to clear the current one
1647  * @param size the size of the mouse animation data
1648  * @param mouseFlash RDMOUSE_FLASH or RDMOUSE_NOFLASH, depending on whether
1649  * or not there is a lead-in animation
1650  */
1651 
setMouseAnim(byte * ma,int32 size,int32 mouseFlash)1652 int32 Mouse::setMouseAnim(byte *ma, int32 size, int32 mouseFlash) {
1653 	free(_mouseAnim.data);
1654 	_mouseAnim.data = NULL;
1655 
1656 	if (ma)	{
1657 		if (mouseFlash == RDMOUSE_FLASH)
1658 			_mouseFrame = 0;
1659 		else
1660 			_mouseFrame = MOUSEFLASHFRAME;
1661 
1662 		Common::MemoryReadStream readS(ma, size);
1663 
1664 		_mouseAnim.runTimeComp = readS.readByte();
1665 		_mouseAnim.noAnimFrames = readS.readByte();
1666 		_mouseAnim.xHotSpot = readS.readSByte();
1667 		_mouseAnim.yHotSpot = readS.readSByte();
1668 		_mouseAnim.mousew = readS.readByte();
1669 		_mouseAnim.mouseh = readS.readByte();
1670 
1671 		_mouseAnim.data = (byte *)malloc(size - MOUSE_ANIM_HEADER_SIZE);
1672 		if (!_mouseAnim.data)
1673 			return RDERR_OUTOFMEMORY;
1674 
1675 		readS.read(_mouseAnim.data, size - MOUSE_ANIM_HEADER_SIZE);
1676 
1677 		animateMouse();
1678 		drawMouse();
1679 
1680 		CursorMan.showMouse(true);
1681 	} else {
1682 		if (_luggageAnim.data)
1683 			drawMouse();
1684 		else
1685 			CursorMan.showMouse(false);
1686 	}
1687 
1688 	return RD_OK;
1689 }
1690 
1691 /**
1692  * Sets the "luggage" animation to accompany the mouse animation. Luggage
1693  * sprites are of the same format as mouse sprites.
1694  * @param ma a pointer to the animation data, or NULL to clear the current one
1695  * @param size the size of the animation data
1696  */
1697 
setLuggageAnim(byte * ma,int32 size)1698 int32 Mouse::setLuggageAnim(byte *ma, int32 size) {
1699 	free(_luggageAnim.data);
1700 	_luggageAnim.data = NULL;
1701 
1702 	if (ma)	{
1703 		Common::MemoryReadStream readS(ma, size);
1704 
1705 		_luggageAnim.runTimeComp = readS.readByte();
1706 		_luggageAnim.noAnimFrames = readS.readByte();
1707 		_luggageAnim.xHotSpot = readS.readSByte();
1708 		_luggageAnim.yHotSpot = readS.readSByte();
1709 		_luggageAnim.mousew = readS.readByte();
1710 		_luggageAnim.mouseh = readS.readByte();
1711 
1712 		_luggageAnim.data = (byte *)malloc(size - MOUSE_ANIM_HEADER_SIZE);
1713 		if (!_luggageAnim.data)
1714 			return RDERR_OUTOFMEMORY;
1715 
1716 		readS.read(_luggageAnim.data, size - MOUSE_ANIM_HEADER_SIZE);
1717 
1718 		animateMouse();
1719 		drawMouse();
1720 
1721 		CursorMan.showMouse(true);
1722 	} else {
1723 		if (_mouseAnim.data)
1724 			drawMouse();
1725 		else
1726 			CursorMan.showMouse(false);
1727 	}
1728 
1729 	return RD_OK;
1730 }
1731 
getMouseMode()1732 int Mouse::getMouseMode() {
1733 	return _mouseMode;
1734 }
1735 
setMouseMode(int mouseMode)1736 void Mouse::setMouseMode(int mouseMode) {
1737 	_mouseMode = mouseMode;
1738 }
1739 
1740 } // End of namespace Sword2
1741