diff options
Diffstat (limited to 'engines/sword2/mouse.cpp')
-rw-r--r-- | engines/sword2/mouse.cpp | 1437 |
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 |