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