aboutsummaryrefslogtreecommitdiff
path: root/engines/sword2/mouse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sword2/mouse.cpp')
-rw-r--r--engines/sword2/mouse.cpp1437
1 files changed, 1437 insertions, 0 deletions
diff --git a/engines/sword2/mouse.cpp b/engines/sword2/mouse.cpp
new file mode 100644
index 0000000000..f8c315a47f
--- /dev/null
+++ b/engines/sword2/mouse.cpp
@@ -0,0 +1,1437 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/console.h"
+#include "sword2/controls.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+namespace Sword2 {
+
+// Pointer resource id's
+
+enum {
+ CROSHAIR = 18,
+ EXIT0 = 788,
+ EXIT1 = 789,
+ EXIT2 = 790,
+ EXIT3 = 791,
+ EXIT4 = 792,
+ EXIT5 = 793,
+ EXIT6 = 794,
+ EXIT7 = 795,
+ EXITDOWN = 796,
+ EXITUP = 797,
+ MOUTH = 787,
+ NORMAL = 17,
+ PICKUP = 3099,
+ SCROLL_L = 1440,
+ SCROLL_R = 1441,
+ USE = 3100
+};
+
+Mouse::Mouse(Sword2Engine *vm) {
+ _vm = vm;
+
+ setPos(0, 0);
+ resetMouseList();
+
+ _mouseTouching = 0;
+ _oldMouseTouching = 0;
+ _menuSelectedPos = 0;
+ _examiningMenuIcon = false;
+ _mousePointerRes = 0;
+ _mouseMode = 0;
+ _mouseStatus = false;
+ _mouseModeLocked = false;
+ _currentLuggageResource = 0;
+ _oldButton = 0;
+ _buttonClick = 0;
+ _pointerTextBlocNo = 0;
+ _playerActivityDelay = 0;
+ _realLuggageItem = 0;
+
+ _mouseAnim.data = NULL;
+ _luggageAnim.data = NULL;
+
+ // For the menus
+ _totalTemp = 0;
+ memset(_tempList, 0, sizeof(_tempList));
+
+ _totalMasters = 0;
+ memset(_masterMenuList, 0, sizeof(_masterMenuList));
+ memset(_mouseList, 0, sizeof(_mouseList));
+ memset(_subjectList, 0, sizeof(_subjectList));
+
+ _defaultResponseId = 0;
+ _choosing = false;
+
+ _iconCount = 0;
+
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < RDMENU_MAXPOCKETS; j++) {
+ _icons[i][j] = NULL;
+ _pocketStatus[i][j] = 0;
+ }
+
+ _menuStatus[i] = RDMENU_HIDDEN;
+ }
+}
+
+Mouse::~Mouse() {
+ free(_mouseAnim.data);
+ free(_luggageAnim.data);
+ for (int i = 0; i < 2; i++)
+ for (int j = 0; j < RDMENU_MAXPOCKETS; j++)
+ free(_icons[i][j]);
+}
+
+void Mouse::getPos(int &x, int &y) {
+ x = _pos.x;
+ y = _pos.y;
+}
+
+void Mouse::setPos(int x, int y) {
+ _pos.x = x;
+ _pos.y = y;
+}
+
+/**
+ * Call at beginning of game loop
+ */
+
+void Mouse::resetMouseList() {
+ _curMouse = 0;
+}
+
+void Mouse::registerMouse(byte *ob_mouse, BuildUnit *build_unit) {
+ assert(_curMouse < TOTAL_mouse_list);
+
+ ObjectMouse mouse;
+
+ mouse.read(ob_mouse);
+
+ if (!mouse.pointer)
+ return;
+
+ if (build_unit) {
+ _mouseList[_curMouse].rect.left = build_unit->x;
+ _mouseList[_curMouse].rect.top = build_unit->y;
+ _mouseList[_curMouse].rect.right = 1 + build_unit->x + build_unit->scaled_width;
+ _mouseList[_curMouse].rect.bottom = 1 + build_unit->y + build_unit->scaled_height;
+ } else {
+ _mouseList[_curMouse].rect.left = mouse.x1;
+ _mouseList[_curMouse].rect.top = mouse.y1;
+ _mouseList[_curMouse].rect.right = 1 + mouse.x2;
+ _mouseList[_curMouse].rect.bottom = 1 + mouse.y2;
+ }
+
+ _mouseList[_curMouse].priority = mouse.priority;
+ _mouseList[_curMouse].pointer = mouse.pointer;
+
+ // Change all COGS pointers to CROSHAIR. I'm guessing that this was a
+ // design decision made in mid-development and they didn't want to go
+ // back and re-generate the resource files.
+
+ if (_mouseList[_curMouse].pointer == USE)
+ _mouseList[_curMouse].pointer = CROSHAIR;
+
+ // Check if pointer text field is set due to previous object using this
+ // slot (ie. not correct for this one)
+
+ // If 'pointer_text' field is set, but the 'id' field isn't same is
+ // current id then we don't want this "left over" pointer text
+
+ if (_mouseList[_curMouse].pointer_text && _mouseList[_curMouse].id != (int32)_vm->_logic->readVar(ID))
+ _mouseList[_curMouse].pointer_text = 0;
+
+ // Get id from system variable 'id' which is correct for current object
+ _mouseList[_curMouse].id = _vm->_logic->readVar(ID);
+
+ _curMouse++;
+}
+
+void Mouse::registerPointerText(int32 text_id) {
+ assert(_curMouse < TOTAL_mouse_list);
+
+ // current object id - used for checking pointer_text when mouse area
+ // registered (in fnRegisterMouse and fnRegisterFrame)
+
+ _mouseList[_curMouse].id = _vm->_logic->readVar(ID);
+ _mouseList[_curMouse].pointer_text = text_id;
+}
+
+/**
+ * This function is called every game cycle.
+ */
+
+void Mouse::mouseEngine() {
+ monitorPlayerActivity();
+ clearPointerText();
+
+ // If George is dead, the system menu is visible all the time, and is
+ // the only thing that can be used.
+
+ if (_vm->_logic->readVar(DEAD)) {
+ if (_mouseMode != MOUSE_system_menu) {
+ _mouseMode = MOUSE_system_menu;
+
+ if (_mouseTouching) {
+ _oldMouseTouching = 0;
+ _mouseTouching = 0;
+ }
+
+ setMouse(NORMAL_MOUSE_ID);
+ buildSystemMenu();
+ }
+ systemMenuMouse();
+ return;
+ }
+
+ // If the mouse is not visible, do nothing
+
+ if (_mouseStatus)
+ return;
+
+ switch (_mouseMode) {
+ case MOUSE_normal:
+ normalMouse();
+ break;
+ case MOUSE_menu:
+ menuMouse();
+ break;
+ case MOUSE_drag:
+ dragMouse();
+ break;
+ case MOUSE_system_menu:
+ systemMenuMouse();
+ break;
+ case MOUSE_holding:
+ if (_pos.y < 400) {
+ _mouseMode = MOUSE_normal;
+ debug(5, " releasing");
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+#if RIGHT_CLICK_CLEARS_LUGGAGE
+bool Mouse::heldIsInInventory() {
+ int32 object_held = (int32)_vm->_logic->readVar(OBJECT_HELD);
+
+ for (uint i = 0; i < _totalMasters; i++) {
+ if (_masterMenuList[i].icon_resource == object_held)
+ return true;
+ }
+ return false;
+}
+#endif
+
+int Mouse::menuClick(int menu_items) {
+ if (_pos.x < RDMENU_ICONSTART)
+ return -1;
+
+ if (_pos.x > RDMENU_ICONSTART + menu_items * (RDMENU_ICONWIDE + RDMENU_ICONSPACING) - RDMENU_ICONSPACING)
+ return -1;
+
+ return (_pos.x - RDMENU_ICONSTART) / (RDMENU_ICONWIDE + RDMENU_ICONSPACING);
+}
+
+void Mouse::systemMenuMouse() {
+ uint32 safe_looping_music_id;
+ MouseEvent *me;
+ int hit;
+ byte *icon;
+ int32 pars[2];
+ uint32 icon_list[5] = {
+ OPTIONS_ICON,
+ QUIT_ICON,
+ SAVE_ICON,
+ RESTORE_ICON,
+ RESTART_ICON
+ };
+
+ // If the mouse is moved off the menu, close it. Unless the player is
+ // dead, in which case the menu should always be visible.
+
+ if (_pos.y > 0 && !_vm->_logic->readVar(DEAD)) {
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_TOP);
+ return;
+ }
+
+ // Check if the user left-clicks anywhere in the menu area.
+
+ me = _vm->mouseEvent();
+
+ if (!me || !(me->buttons & RD_LEFTBUTTONDOWN))
+ return;
+
+ if (_pos.y > 0)
+ return;
+
+ hit = menuClick(ARRAYSIZE(icon_list));
+
+ if (hit < 0)
+ return;
+
+ // No save when dead
+
+ if (icon_list[hit] == SAVE_ICON && _vm->_logic->readVar(DEAD))
+ return;
+
+ // Gray out all he icons, except the one that was clicked
+
+ for (int i = 0; i < ARRAYSIZE(icon_list); i++) {
+ if (i != hit) {
+ icon = _vm->_resman->openResource(icon_list[i]) + ResHeader::size();
+ setMenuIcon(RDMENU_TOP, i, icon);
+ _vm->_resman->closeResource(icon_list[i]);
+ }
+ }
+
+ _vm->_sound->pauseFx();
+
+ // NB. Need to keep a safe copy of '_loopingMusicId' for savegame & for
+ // playing when returning from control panels because control panel
+ // music will overwrite it!
+
+ safe_looping_music_id = _vm->_sound->getLoopingMusicId();
+
+ pars[0] = 221;
+ pars[1] = FX_LOOP;
+ _vm->_logic->fnPlayMusic(pars);
+
+ // HACK: Restore proper looping_music_id
+ _vm->_sound->setLoopingMusicId(safe_looping_music_id);
+
+ processMenu();
+
+ // call the relevant screen
+
+ switch (hit) {
+ case 0:
+ {
+ OptionsDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ case 1:
+ {
+ QuitDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ case 2:
+ {
+ SaveDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ case 3:
+ {
+ RestoreDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ case 4:
+ {
+ RestartDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ }
+
+ // Menu stays open on death screen. Otherwise it's closed.
+
+ if (!_vm->_logic->readVar(DEAD)) {
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_TOP);
+ } else {
+ setMouse(NORMAL_MOUSE_ID);
+ buildSystemMenu();
+ }
+
+ // Back to the game again
+
+ processMenu();
+
+ // Reset game palette, but not after a successful restore or restart!
+ // See RestoreFromBuffer() in save_rest.cpp
+
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ if (screenInfo->new_palette != 99) {
+ // 0 means put back game screen palette; see build_display.cpp
+ _vm->_screen->setFullPalette(0);
+
+ // Stop the engine fading in the restored screens palette
+ screenInfo->new_palette = 0;
+ } else
+ screenInfo->new_palette = 1;
+
+ _vm->_sound->unpauseFx();
+
+ // If there was looping music before coming into the control panels
+ // then restart it! NB. If a game has been restored the music will be
+ // restarted twice, but this shouldn't cause any harm.
+
+ if (_vm->_sound->getLoopingMusicId()) {
+ pars[0] = _vm->_sound->getLoopingMusicId();
+ pars[1] = FX_LOOP;
+ _vm->_logic->fnPlayMusic(pars);
+ } else
+ _vm->_logic->fnStopMusic(NULL);
+}
+
+void Mouse::dragMouse() {
+ byte buf1[NAME_LEN], buf2[NAME_LEN];
+ MouseEvent *me;
+ int hit;
+
+ // We can use dragged object both on other inventory objects, or on
+ // objects in the scene, so if the mouse moves off the inventory menu,
+ // then close it.
+
+ if (_pos.y < 400) {
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_BOTTOM);
+ return;
+ }
+
+ // Handles cursors and the luggage on/off according to type
+
+ mouseOnOff();
+
+ // Now do the normal click stuff
+
+ me = _vm->mouseEvent();
+
+ if (!me)
+ return;
+
+#if RIGHT_CLICK_CLEARS_LUGGAGE
+ if ((me->buttons & RD_RIGHTBUTTONDOWN) && heldIsInInventory()) {
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+ _menuSelectedPos = 0;
+ _mouseMode = MOUSE_menu;
+ setLuggage(0);
+ buildMenu();
+ return;
+ }
+#endif
+
+ if (!(me->buttons & RD_LEFTBUTTONDOWN))
+ return;
+
+ // there's a mouse event to be processed
+
+ // could be clicking on an on screen object or on the menu
+ // which is currently displayed
+
+ if (_mouseTouching) {
+ // mouse is over an on screen object - and we have luggage
+
+ // Depending on type we'll maybe kill the object_held - like
+ // for exits
+
+ // Set global script variable 'button'. We know that it was the
+ // left button, not the right one.
+
+ _vm->_logic->writeVar(LEFT_BUTTON, 1);
+ _vm->_logic->writeVar(RIGHT_BUTTON, 0);
+
+ // These might be required by the action script about to be run
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ _vm->_logic->writeVar(MOUSE_X, _pos.x + screenInfo->scroll_offset_x);
+ _vm->_logic->writeVar(MOUSE_Y, _pos.y + screenInfo->scroll_offset_y);
+
+ // For scripts to know what's been clicked. First used for
+ // 'room_13_turning_script' in object 'biscuits_13'
+
+ _vm->_logic->writeVar(CLICKED_ID, _mouseTouching);
+
+ _vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching);
+
+ debug(2, "Used \"%s\" on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf1),
+ _vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf2));
+
+ // Hide menu - back to normal menu mode
+
+ hideMenu(RDMENU_BOTTOM);
+ _mouseMode = MOUSE_normal;
+
+ return;
+ }
+
+ // Better check for combine/cancel. Cancel puts us back in MOUSE_menu
+ // mode
+
+ hit = menuClick(TOTAL_engine_pockets);
+
+ if (hit < 0 || !_masterMenuList[hit].icon_resource)
+ return;
+
+ // Always back into menu mode. Remove the luggage as well.
+
+ _mouseMode = MOUSE_menu;
+ setLuggage(0);
+
+ if ((uint)hit == _menuSelectedPos) {
+ // If we clicked on the same icon again, reset the first icon
+
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+ _menuSelectedPos = 0;
+ } else {
+ // Otherwise, combine the two icons
+
+ _vm->_logic->writeVar(COMBINE_BASE, _masterMenuList[hit].icon_resource);
+ _vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, MENU_MASTER_OBJECT);
+
+ // Turn off mouse now, to prevent player trying to click
+ // elsewhere BUT leave the bottom menu open
+
+ hideMouse();
+
+ debug(2, "Used \"%s\" on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf1),
+ _vm->_resman->fetchName(_vm->_logic->readVar(COMBINE_BASE), buf2));
+ }
+
+ // Refresh the menu
+
+ buildMenu();
+}
+
+void Mouse::menuMouse() {
+ byte buf[NAME_LEN];
+ MouseEvent *me;
+ int hit;
+
+ // If the mouse is moved off the menu, close it.
+
+ if (_pos.y < 400) {
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_BOTTOM);
+ return;
+ }
+
+ me = _vm->mouseEvent();
+
+ if (!me)
+ return;
+
+ hit = menuClick(TOTAL_engine_pockets);
+
+ // Check if we clicked on an actual icon.
+
+ if (hit < 0 || !_masterMenuList[hit].icon_resource)
+ return;
+
+ if (me->buttons & RD_RIGHTBUTTONDOWN) {
+ // Right button - examine an object, identified by its icon
+ // resource id.
+
+ _examiningMenuIcon = true;
+ _vm->_logic->writeVar(OBJECT_HELD, _masterMenuList[hit].icon_resource);
+
+ // Must clear this so next click on exit becomes 1st click
+ // again
+
+ _vm->_logic->writeVar(EXIT_CLICK_ID, 0);
+
+ _vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, MENU_MASTER_OBJECT);
+
+ // Refresh the menu
+
+ buildMenu();
+
+ // Turn off mouse now, to prevent player trying to click
+ // elsewhere BUT leave the bottom menu open
+
+ hideMouse();
+
+ debug(2, "Right-click on \"%s\" icon",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf));
+
+ return;
+ }
+
+ if (me->buttons & RD_LEFTBUTTONDOWN) {
+ // Left button - bung us into drag luggage mode. The object is
+ // identified by its icon resource id. We need the luggage
+ // resource id for mouseOnOff
+
+ _mouseMode = MOUSE_drag;
+
+ _menuSelectedPos = hit;
+ _vm->_logic->writeVar(OBJECT_HELD, _masterMenuList[hit].icon_resource);
+ _currentLuggageResource = _masterMenuList[hit].luggage_resource;
+
+ // Must clear this so next click on exit becomes 1st click
+ // again
+
+ _vm->_logic->writeVar(EXIT_CLICK_ID, 0);
+
+ // Refresh the menu
+
+ buildMenu();
+
+ setLuggage(_masterMenuList[hit].luggage_resource);
+
+ debug(2, "Left-clicked on \"%s\" icon - switch to drag mode",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf));
+ }
+}
+
+void Mouse::normalMouse() {
+ // The gane is playing and none of the menus are activated - but, we
+ // need to check if a menu is to start. Note, won't have luggage
+
+ MouseEvent *me;
+
+ // Check if the cursor has moved onto the system menu area. No save in
+ // big-object menu lock situation, of if the player is dragging an
+ // object.
+
+ if (_pos.y < 0 && !_mouseModeLocked && !_vm->_logic->readVar(OBJECT_HELD)) {
+ _mouseMode = MOUSE_system_menu;
+
+ if (_mouseTouching) {
+ // We were on something, but not anymore
+ _oldMouseTouching = 0;
+ _mouseTouching = 0;
+ }
+
+ // Reset mouse cursor - in case we're between mice
+
+ setMouse(NORMAL_MOUSE_ID);
+ buildSystemMenu();
+ return;
+ }
+
+ // Check if the cursor has moved onto the inventory menu area. No
+ // inventory in big-object menu lock situation,
+
+ if (_pos.y > 399 && !_mouseModeLocked) {
+ // If an object is being held, i.e. if the mouse cursor has a
+ // luggage, go to drag mode instead of menu mode, but the menu
+ // is still opened.
+ //
+ // That way, we can still use an object on another inventory
+ // object, even if the inventory menu was closed after the
+ // first object was selected.
+
+ if (!_vm->_logic->readVar(OBJECT_HELD))
+ _mouseMode = MOUSE_menu;
+ else
+ _mouseMode = MOUSE_drag;
+
+ // If mouse is moving off an object and onto the menu then do a
+ // standard get-off
+
+ if (_mouseTouching) {
+ _oldMouseTouching = 0;
+ _mouseTouching = 0;
+ }
+
+ // Reset mouse cursor
+
+ setMouse(NORMAL_MOUSE_ID);
+ buildMenu();
+ return;
+ }
+
+ // Check for moving the mouse on or off things
+
+ mouseOnOff();
+
+ me = _vm->mouseEvent();
+
+ if (!me)
+ return;
+
+ bool button_down = (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN)) != 0;
+
+ // For debugging. We can draw a rectangle on the screen and see its
+ // coordinates. This was probably used to help defining hit areas.
+
+ if (_vm->_debugger->_definingRectangles) {
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ if (_vm->_debugger->_draggingRectangle == 0) {
+ // Not yet dragging a rectangle, so need click to start
+
+ if (button_down) {
+ // set both (x1,y1) and (x2,y2) to this point
+ _vm->_debugger->_rectX1 = _vm->_debugger->_rectX2 = (uint32)_pos.x + screenInfo->scroll_offset_x;
+ _vm->_debugger->_rectY1 = _vm->_debugger->_rectY2 = (uint32)_pos.y + screenInfo->scroll_offset_y;
+ _vm->_debugger->_draggingRectangle = 1;
+ }
+ } else if (_vm->_debugger->_draggingRectangle == 1) {
+ // currently dragging a rectangle - click means reset
+
+ if (button_down) {
+ // lock rectangle, so you can let go of mouse
+ // to type in the coords
+ _vm->_debugger->_draggingRectangle = 2;
+ } else {
+ // drag rectangle
+ _vm->_debugger->_rectX2 = (uint32)_pos.x + screenInfo->scroll_offset_x;
+ _vm->_debugger->_rectY2 = (uint32)_pos.y + screenInfo->scroll_offset_y;
+ }
+ } else {
+ // currently locked to avoid knocking out of place
+ // while reading off the coords
+
+ if (button_down) {
+ // click means reset - back to start again
+ _vm->_debugger->_draggingRectangle = 0;
+ }
+ }
+
+ return;
+ }
+
+#if RIGHT_CLICK_CLEARS_LUGGAGE
+ if (_vm->_logic->readVar(OBJECT_HELD) && (me->buttons & RD_RIGHTBUTTONDOWN) && heldIsInInventory()) {
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+ _menuSelectedPos = 0;
+ setLuggage(0);
+ return;
+ }
+#endif
+
+ // Now do the normal click stuff
+
+ // We only care about down clicks when the mouse is over an object.
+
+ if (!_mouseTouching || !button_down)
+ return;
+
+ // There's a mouse event to be processed and the mouse is on something.
+ // Notice that the floor itself is considered an object.
+
+ // There are no menus about so its nice and simple. This is as close to
+ // the old advisor_188 script as we get, I'm sorry to say.
+
+ // If player is walking or relaxing then those need to terminate
+ // correctly. Otherwise set player run the targets action script or, do
+ // a special walk if clicking on the scroll-more icon
+
+ // PLAYER_ACTION script variable - whatever catches this must reset to
+ // 0 again
+ // _vm->_logic->writeVar(PLAYER_ACTION, _mouseTouching);
+
+ // Idle or router-anim will catch it
+
+ // Set global script variable 'button'
+
+ if (me->buttons & RD_LEFTBUTTONDOWN) {
+ _vm->_logic->writeVar(LEFT_BUTTON, 1);
+ _vm->_logic->writeVar(RIGHT_BUTTON, 0);
+ _buttonClick = 0; // for re-click
+ } else {
+ _vm->_logic->writeVar(LEFT_BUTTON, 0);
+ _vm->_logic->writeVar(RIGHT_BUTTON, 1);
+ _buttonClick = 1; // for re-click
+ }
+
+ // These might be required by the action script about to be run
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ _vm->_logic->writeVar(MOUSE_X, _pos.x + screenInfo->scroll_offset_x);
+ _vm->_logic->writeVar(MOUSE_Y, _pos.y + screenInfo->scroll_offset_y);
+
+ if (_mouseTouching == _vm->_logic->readVar(EXIT_CLICK_ID) && (me->buttons & RD_LEFTBUTTONDOWN)) {
+ // It's the exit double click situation. Let the existing
+ // interaction continue and start fading down. Switch the human
+ // off too
+
+ noHuman();
+ _vm->_logic->fnFadeDown(NULL);
+
+ // Tell the walker
+
+ _vm->_logic->writeVar(EXIT_FADING, 1);
+ } else if (_oldButton == _buttonClick && _mouseTouching == _vm->_logic->readVar(CLICKED_ID) && _mousePointerRes != NORMAL_MOUSE_ID) {
+ // Re-click. Do nothing, except on floors
+ } else {
+ // For re-click
+
+ _oldButton = _buttonClick;
+
+ // For scripts to know what's been clicked. First used for
+ // 'room_13_turning_script' in object 'biscuits_13'
+
+ _vm->_logic->writeVar(CLICKED_ID, _mouseTouching);
+
+ // Must clear these two double-click control flags - do it here
+ // so reclicks after exit clicks are cleared up
+
+ _vm->_logic->writeVar(EXIT_CLICK_ID, 0);
+ _vm->_logic->writeVar(EXIT_FADING, 0);
+
+ _vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching);
+
+ byte buf1[NAME_LEN], buf2[NAME_LEN];
+
+ if (_vm->_logic->readVar(OBJECT_HELD))
+ debug(2, "Used \"%s\" on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf1),
+ _vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf2));
+ else if (_vm->_logic->readVar(LEFT_BUTTON))
+ debug(2, "Left-clicked on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf1));
+ else // RIGHT BUTTON
+ debug(2, "Right-clicked on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf1));
+ }
+}
+
+uint32 Mouse::chooseMouse() {
+ // Unlike the other mouse "engines", this one is called directly by the
+ // fnChoose() opcode.
+
+ uint i;
+
+ _vm->_logic->writeVar(AUTO_SELECTED, 0);
+
+ uint32 in_subject = _vm->_logic->readVar(IN_SUBJECT);
+ uint32 object_held = _vm->_logic->readVar(OBJECT_HELD);
+
+ if (object_held) {
+ // The player used an object on a person. In this case it
+ // triggered a conversation menu. Act as if the user tried to
+ // talk to the person about that object. If the person doesn't
+ // know anything about it, use the default response.
+
+ uint32 response = _defaultResponseId;
+
+ for (i = 0; i < in_subject; i++) {
+ if (_subjectList[i].res == object_held) {
+ response = _subjectList[i].ref;
+ break;
+ }
+ }
+
+ // The user won't be holding the object any more, and the
+ // conversation menu will be closed.
+
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+ _vm->_logic->writeVar(IN_SUBJECT, 0);
+ return response;
+ }
+
+ if (_vm->_logic->readVar(CHOOSER_COUNT_FLAG) == 0 && in_subject == 1 && _subjectList[0].res == EXIT_ICON) {
+ // This is the first time the chooser is coming up in this
+ // conversation, there is only one subject and that's the
+ // EXIT icon.
+ //
+ // In other words, the player doesn't have anything to talk
+ // about. Skip it.
+
+ // The conversation menu will be closed. We set AUTO_SELECTED
+ // because the speech script depends on it.
+
+ _vm->_logic->writeVar(AUTO_SELECTED, 1);
+ _vm->_logic->writeVar(IN_SUBJECT, 0);
+ return _subjectList[0].ref;
+ }
+
+ byte *icon;
+
+ if (!_choosing) {
+ // This is a new conversation menu.
+
+ if (!in_subject)
+ error("fnChoose with no subjects");
+
+ for (i = 0; i < in_subject; i++) {
+ icon = _vm->_resman->openResource(_subjectList[i].res) + ResHeader::size() + RDMENU_ICONWIDE * RDMENU_ICONDEEP;
+ setMenuIcon(RDMENU_BOTTOM, i, icon);
+ _vm->_resman->closeResource(_subjectList[i].res);
+ }
+
+ for (; i < 15; i++)
+ setMenuIcon(RDMENU_BOTTOM, (uint8) i, NULL);
+
+ showMenu(RDMENU_BOTTOM);
+ setMouse(NORMAL_MOUSE_ID);
+ _choosing = true;
+ return (uint32)-1;
+ }
+
+ // The menu is there - we're just waiting for a click. We only care
+ // about left clicks.
+
+ MouseEvent *me = _vm->mouseEvent();
+ int mouseX, mouseY;
+
+ getPos(mouseX, mouseY);
+
+ if (!me || !(me->buttons & RD_LEFTBUTTONDOWN) || mouseY < 400)
+ return (uint32)-1;
+
+ // Check for click on a menu.
+
+ int hit = _vm->_mouse->menuClick(in_subject);
+ if (hit < 0)
+ return (uint32)-1;
+
+ // Hilight the clicked icon by greying the others. This can look a bit
+ // odd when you click on the exit icon, but there are also cases when
+ // it looks strange if you don't do it.
+
+ for (i = 0; i < in_subject; i++) {
+ if ((int)i != hit) {
+ icon = _vm->_resman->openResource(_subjectList[i].res) + ResHeader::size();
+ _vm->_mouse->setMenuIcon(RDMENU_BOTTOM, i, icon);
+ _vm->_resman->closeResource(_subjectList[i].res);
+ }
+ }
+
+ // For non-speech scripts that manually call the chooser
+ _vm->_logic->writeVar(RESULT, _subjectList[hit].res);
+
+ // The conversation menu will be closed
+
+ _choosing = false;
+ _vm->_logic->writeVar(IN_SUBJECT, 0);
+ setMouse(0);
+
+ return _subjectList[hit].ref;
+}
+
+void Mouse::mouseOnOff() {
+ // this handles the cursor graphic when moving on and off mouse areas
+ // it also handles the luggage thingy
+
+ uint32 pointer_type;
+ static uint8 mouse_flicked_off = 0;
+
+ _oldMouseTouching = _mouseTouching;
+
+ // don't detect objects that are hidden behind the menu bars (ie. in
+ // the scrolled-off areas of the screen)
+
+ if (_pos.y < 0 || _pos.y > 399) {
+ pointer_type = 0;
+ _mouseTouching = 0;
+ } else {
+ // set '_mouseTouching' & return pointer_type
+ pointer_type = checkMouseList();
+ }
+
+ // same as previous cycle?
+ if (!mouse_flicked_off && _oldMouseTouching == _mouseTouching) {
+ // yes, so nothing to do
+ // BUT CARRY ON IF MOUSE WAS FLICKED OFF!
+ return;
+ }
+
+ // can reset this now
+ mouse_flicked_off = 0;
+
+ //the cursor has moved onto something
+ if (!_oldMouseTouching && _mouseTouching) {
+ // make a copy of the object we've moved onto because one day
+ // we'll move back off again! (but the list positioning could
+ // theoretically have changed)
+
+ // we can only move onto something from being on nothing - we
+ // stop the system going from one to another when objects
+ // overlap
+
+ _oldMouseTouching = _mouseTouching;
+
+ // run get on
+
+ if (pointer_type) {
+ // 'pointer_type' holds the resource id of the
+ // pointer anim
+
+ setMouse(pointer_type);
+
+ // setup luggage icon
+ if (_vm->_logic->readVar(OBJECT_HELD)) {
+ setLuggage(_currentLuggageResource);
+ }
+ } else {
+ byte buf[NAME_LEN];
+
+ error("ERROR: mouse.pointer==0 for object %d (%s) - update logic script!", _mouseTouching, _vm->_resman->fetchName(_mouseTouching, buf));
+ }
+ } else if (_oldMouseTouching && !_mouseTouching) {
+ // the cursor has moved off something - reset cursor to
+ // normal pointer
+
+ _oldMouseTouching = 0;
+ setMouse(NORMAL_MOUSE_ID);
+
+ // reset luggage only when necessary
+ } else if (_oldMouseTouching && _mouseTouching) {
+ // The cursor has moved off something and onto something
+ // else. Flip to a blank cursor for a cycle.
+
+ // ignore the new id this cycle - should hit next cycle
+ _mouseTouching = 0;
+ _oldMouseTouching = 0;
+ setMouse(0);
+
+ // so we know to set the mouse pointer back to normal if 2nd
+ // hot-spot doesn't register because mouse pulled away
+ // quickly (onto nothing)
+
+ mouse_flicked_off = 1;
+
+ // reset luggage only when necessary
+ } else {
+ // Mouse was flicked off for one cycle, but then moved onto
+ // nothing before 2nd hot-spot registered
+
+ // both '_oldMouseTouching' & '_mouseTouching' will be zero
+ // reset cursor to normal pointer
+
+ setMouse(NORMAL_MOUSE_ID);
+ }
+
+ // possible check for edge of screen more-to-scroll here on large
+ // screens
+}
+
+void Mouse::setMouse(uint32 res) {
+ // high level - whats the mouse - for the engine
+ _mousePointerRes = res;
+
+ if (res) {
+ byte *icon = _vm->_resman->openResource(res) + ResHeader::size();
+ uint32 len = _vm->_resman->fetchLen(res) - ResHeader::size();
+
+ // don't pulse the normal pointer - just do the regular anim
+ // loop
+
+ if (res == NORMAL_MOUSE_ID)
+ setMouseAnim(icon, len, RDMOUSE_NOFLASH);
+ else
+ setMouseAnim(icon, len, RDMOUSE_FLASH);
+
+ _vm->_resman->closeResource(res);
+ } else {
+ // blank cursor
+ setMouseAnim(NULL, 0, 0);
+ }
+}
+
+void Mouse::setLuggage(uint32 res) {
+ _realLuggageItem = res;
+
+ if (res) {
+ byte *icon = _vm->_resman->openResource(res) + ResHeader::size();
+ uint32 len = _vm->_resman->fetchLen(res) - ResHeader::size();
+
+ setLuggageAnim(icon, len);
+ _vm->_resman->closeResource(res);
+ } else
+ setLuggageAnim(NULL, 0);
+}
+
+void Mouse::setObjectHeld(uint32 res) {
+ setLuggage(res);
+
+ _vm->_logic->writeVar(OBJECT_HELD, res);
+ _currentLuggageResource = res;
+
+ // mode locked - no menu available
+ _mouseModeLocked = true;
+}
+
+uint32 Mouse::checkMouseList() {
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ Common::Point mousePos(_pos.x + screenInfo->scroll_offset_x, _pos.y + screenInfo->scroll_offset_y);
+
+ // Number of priorities subject to implementation needs
+ for (int priority = 0; priority < 10; priority++) {
+ for (uint i = 0; i < _curMouse; i++) {
+ // If the mouse pointer is over this
+ // mouse-detection-box
+
+ if (_mouseList[i].priority == priority && _mouseList[i].rect.contains(mousePos)) {
+ // Record id
+ _mouseTouching = _mouseList[i].id;
+
+ createPointerText(_mouseList[i].pointer_text, _mouseList[i].pointer);
+
+ // Return pointer type
+ return _mouseList[i].pointer;
+ }
+ }
+ }
+
+ // Touching nothing; no pointer to return
+ _mouseTouching = 0;
+ return 0;
+}
+
+#define POINTER_TEXT_WIDTH 640 // just in case!
+#define POINTER_TEXT_PEN 184 // white
+
+void Mouse::createPointerText(uint32 text_id, uint32 pointer_res) {
+ uint32 local_text;
+ uint32 text_res;
+ byte *text;
+ // offsets for pointer text sprite from pointer position
+ int16 xOffset, yOffset;
+ uint8 justification;
+
+ if (!_objectLabels || !text_id)
+ return;
+
+ // Check what the pointer is, to set offsets correctly for text
+ // position
+
+ switch (pointer_res) {
+ case CROSHAIR:
+ yOffset = -7;
+ xOffset = +10;
+ break;
+ case EXIT0:
+ yOffset = +15;
+ xOffset = +20;
+ break;
+ case EXIT1:
+ yOffset = +16;
+ xOffset = -10;
+ break;
+ case EXIT2:
+ yOffset = +10;
+ xOffset = -22;
+ break;
+ case EXIT3:
+ yOffset = -16;
+ xOffset = -10;
+ break;
+ case EXIT4:
+ yOffset = -15;
+ xOffset = +15;
+ break;
+ case EXIT5:
+ yOffset = -12;
+ xOffset = +10;
+ break;
+ case EXIT6:
+ yOffset = +10;
+ xOffset = +25;
+ break;
+ case EXIT7:
+ yOffset = +16;
+ xOffset = +20;
+ break;
+ case EXITDOWN:
+ yOffset = -20;
+ xOffset = -10;
+ break;
+ case EXITUP:
+ yOffset = +20;
+ xOffset = +20;
+ break;
+ case MOUTH:
+ yOffset = -10;
+ xOffset = +15;
+ break;
+ case NORMAL:
+ yOffset = -10;
+ xOffset = +15;
+ break;
+ case PICKUP:
+ yOffset = -40;
+ xOffset = +10;
+ break;
+ case SCROLL_L:
+ yOffset = -20;
+ xOffset = +20;
+ break;
+ case SCROLL_R:
+ yOffset = -20;
+ xOffset = -20;
+ break;
+ case USE:
+ yOffset = -8;
+ xOffset = +20;
+ break;
+ default:
+ // Shouldn't happen if we cover all the different mouse
+ // pointers above
+ yOffset = -10;
+ xOffset = +10;
+ break;
+ }
+
+ // Set up justification for text sprite, based on its offsets from the
+ // pointer position
+
+ if (yOffset < 0) {
+ // Above pointer
+ if (xOffset < 0) {
+ // Above left
+ justification = POSITION_AT_RIGHT_OF_BASE;
+ } else if (xOffset > 0) {
+ // Above right
+ justification = POSITION_AT_LEFT_OF_BASE;
+ } else {
+ // Above centre
+ justification = POSITION_AT_CENTRE_OF_BASE;
+ }
+ } else if (yOffset > 0) {
+ // Below pointer
+ if (xOffset < 0) {
+ // Below left
+ justification = POSITION_AT_RIGHT_OF_TOP;
+ } else if (xOffset > 0) {
+ // Below right
+ justification = POSITION_AT_LEFT_OF_TOP;
+ } else {
+ // Below centre
+ justification = POSITION_AT_CENTRE_OF_TOP;
+ }
+ } else {
+ // Same y-coord as pointer
+ if (xOffset < 0) {
+ // Centre left
+ justification = POSITION_AT_RIGHT_OF_CENTRE;
+ } else if (xOffset > 0) {
+ // Centre right
+ justification = POSITION_AT_LEFT_OF_CENTRE;
+ } else {
+ // Centre centre - shouldn't happen anyway!
+ justification = POSITION_AT_LEFT_OF_CENTRE;
+ }
+ }
+
+ // Text resource number, and line number within the resource
+
+ text_res = text_id / SIZE;
+ local_text = text_id & 0xffff;
+
+ // open text file & get the line
+ text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
+
+ // 'text+2' to skip the first 2 bytes which form the
+ // line reference number
+
+ _pointerTextBlocNo = _vm->_fontRenderer->buildNewBloc(
+ text + 2, _pos.x + xOffset,
+ _pos.y + yOffset,
+ POINTER_TEXT_WIDTH, POINTER_TEXT_PEN,
+ RDSPR_TRANS | RDSPR_DISPLAYALIGN,
+ _vm->_speechFontId, justification);
+
+ // now ok to close the text file
+ _vm->_resman->closeResource(text_res);
+}
+
+void Mouse::clearPointerText() {
+ if (_pointerTextBlocNo) {
+ _vm->_fontRenderer->killTextBloc(_pointerTextBlocNo);
+ _pointerTextBlocNo = 0;
+ }
+}
+
+void Mouse::hideMouse() {
+ // leaves the menus open
+ // used by the system when clicking right on a menu item to examine
+ // it and when combining objects
+
+ // for logic scripts
+ _vm->_logic->writeVar(MOUSE_AVAILABLE, 0);
+
+ // human/mouse off
+ _mouseStatus = true;
+
+ setMouse(0);
+ setLuggage(0);
+}
+
+void Mouse::noHuman() {
+ hideMouse();
+ clearPointerText();
+
+ // Must be normal mouse situation or a largely neutral situation -
+ // special menus use hideMouse()
+
+ // Don't hide menu in conversations
+ if (_vm->_logic->readVar(TALK_FLAG) == 0)
+ hideMenu(RDMENU_BOTTOM);
+
+ if (_mouseMode == MOUSE_system_menu) {
+ // Close menu
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_TOP);
+ }
+}
+
+void Mouse::addHuman() {
+ // For logic scripts
+ _vm->_logic->writeVar(MOUSE_AVAILABLE, 1);
+
+ if (_mouseStatus) {
+ // Force engine to choose a cursor
+ _mouseStatus = false;
+ _mouseTouching = 1;
+ }
+
+ // Clear this to reset no-second-click system
+ _vm->_logic->writeVar(CLICKED_ID, 0);
+
+ // This is now done outside the OBJECT_HELD check in case it's set to
+ // zero before now!
+
+ // Unlock the mouse from possible large object lock situtations - see
+ // syphon in rm 3
+
+ _mouseModeLocked = false;
+
+ if (_vm->_logic->readVar(OBJECT_HELD)) {
+ // Was dragging something around - need to clear this again
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+
+ // And these may also need clearing, just in case
+ _examiningMenuIcon = false;
+ _vm->_logic->writeVar(COMBINE_BASE, 0);
+
+ setLuggage(0);
+ }
+
+ // If mouse is over menu area
+ if (_pos.y > 399) {
+ if (_mouseMode != MOUSE_holding) {
+ // VITAL - reset things & rebuild the menu
+ _mouseMode = MOUSE_normal;
+ }
+ setMouse(NORMAL_MOUSE_ID);
+ }
+
+ // Enabled/disabled from console; status printed with on-screen debug
+ // info
+
+ if (_vm->_debugger->_testingSnR) {
+ uint8 black[4] = { 0, 0, 0, 0 };
+ uint8 white[4] = { 255, 255, 255, 0 };
+
+ // Testing logic scripts by simulating instant Save & Restore
+
+ _vm->_screen->setPalette(0, 1, white, RDPAL_INSTANT);
+
+ // Stops all fx & clears the queue - eg. when leaving a room
+ _vm->_sound->clearFxQueue();
+
+ // Trash all object resources so they load in fresh & restart
+ // their logic scripts
+
+ _vm->_resman->killAllObjects(false);
+
+ _vm->_screen->setPalette(0, 1, black, RDPAL_INSTANT);
+ }
+}
+
+void Mouse::refreshInventory() {
+ // Can reset this now
+ _vm->_logic->writeVar(COMBINE_BASE, 0);
+
+ // Cause 'object_held' icon to be greyed. The rest are coloured.
+ _examiningMenuIcon = true;
+ buildMenu();
+ _examiningMenuIcon = false;
+}
+
+void Mouse::startConversation() {
+ if (_vm->_logic->readVar(TALK_FLAG) == 0) {
+ // See fnChooser & speech scripts
+ _vm->_logic->writeVar(CHOOSER_COUNT_FLAG, 0);
+ }
+
+ noHuman();
+}
+
+void Mouse::endConversation() {
+ hideMenu(RDMENU_BOTTOM);
+
+ if (_pos.y > 399) {
+ // Will wait for cursor to move off the bottom menu
+ _mouseMode = MOUSE_holding;
+ }
+
+ // In case DC forgets
+ _vm->_logic->writeVar(TALK_FLAG, 0);
+}
+
+void Mouse::monitorPlayerActivity() {
+ // if there is at least one mouse event outstanding
+ if (_vm->checkForMouseEvents()) {
+ // reset activity delay counter
+ _playerActivityDelay = 0;
+ } else {
+ // no. of game cycles since mouse event queue last empty
+ _playerActivityDelay++;
+ }
+}
+
+void Mouse::checkPlayerActivity(uint32 seconds) {
+ // Convert seconds to game cycles
+ uint32 threshold = seconds * 12;
+
+ // If the actual delay is at or above the given threshold, reset the
+ // activity delay counter now that we've got a positive check.
+
+ if (_playerActivityDelay >= threshold) {
+ _playerActivityDelay = 0;
+ _vm->_logic->writeVar(RESULT, 1);
+ } else
+ _vm->_logic->writeVar(RESULT, 0);
+}
+
+void Mouse::pauseGame() {
+ // Make the mouse cursor normal. This is the only place where we are
+ // allowed to clear the luggage this way.
+
+ clearPointerText();
+ setLuggageAnim(NULL, 0);
+ setMouse(0);
+ setMouseTouching(1);
+}
+
+void Mouse::unpauseGame() {
+ if (_vm->_logic->readVar(OBJECT_HELD) && _realLuggageItem)
+ setLuggage(_realLuggageItem);
+}
+
+} // End of namespace Sword2