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