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