diff options
Diffstat (limited to 'sword2')
-rw-r--r-- | sword2/driver/driver96.h | 264 | ||||
-rw-r--r-- | sword2/mouse.cpp | 1076 | ||||
-rw-r--r-- | sword2/mouse.h | 5 | ||||
-rw-r--r-- | sword2/sword2.h | 2 |
4 files changed, 696 insertions, 651 deletions
diff --git a/sword2/driver/driver96.h b/sword2/driver/driver96.h index 510b8cef37..47253ac03a 100644 --- a/sword2/driver/driver96.h +++ b/sword2/driver/driver96.h @@ -32,126 +32,128 @@ namespace Sword2 { // ------- // -// defines specific to windows headers... #ifndef WIN32 +// FIXME: Get rid of these + #define FILE_ATTRIBUTE_NORMAL 0x80 #define _MAX_PATH 260 #endif -//Generic error codes -#define RD_OK 0x00000000 -#define RDERR_UNKNOWN 0x00000001 -#define RDERR_INVALIDPOINTER 0x00000002 -#define RDERR_OUTOFMEMORY 0x00000003 -#define RDERR_INVALIDFILENAME 0x00000004 -#define RDERR_READERROR 0x00000005 -#define RDERR_WRITEERROR 0x00000006 -#define RDERR_NOEMULATION 0x00000007 -#define RDERR_LOCKFAILED 0x00000008 - -//Drawing error codes -#define RDERR_VIDEOMODE 0x00010000 -#define RDERR_COLOURDEPTH 0x00010001 -#define RDERR_CANNOTFLIP 0x00010002 -#define RDERR_RESTORELAYERS 0x00010003 -#define RDERR_DDRAWNOEMULATION 0X00010004 -#define RDERR_NOHARDWARE 0x00010005 -#define RDERR_ALREADYON 0x00010006 -#define RDERR_DECOMPRESSION 0x00010007 - -//Operating system error codes -#define RDERR_CREATEWINDOW 0x00020000 -#define RDERR_APPCLOSED 0x00020001 -#define RDERR_GOFULLSCREEN 0x00020002 - -//Language and version error codes -#define RDERR_OPENVERSIONFILE 0x00030000 -#define RDERR_INVALIDVERSION 0x00030001 - -//Keyboard error codes -#define RDERR_NOKEYWAITING 0x00040000 - -//Sprite drawing error codes -#define RDERR_NOCLIPPING 0x00050000 -#define RDERR_NOTIMPLEMENTED 0x00050001 -#define RDERR_UNKNOWNTYPE 0x00050002 -#define RDERR_INVALIDSCALING 0x00050003 -#define RDERR_SURFACELOST 0x00050004 -#define RDERR_NOTCLOSED 0x00050005 -#define RDERR_NOTOPEN 0x00050006 -#define RDERR_ALREADYCLOSED 0x00050007 // added for _console.cpp by khalek - -//Menubar error codes -#define RDERR_INVALIDMENU 0x00060000 -#define RDERR_INVALIDPOCKET 0x00060001 -#define RDERR_INVALIDCOMMAND 0x00060002 - -//Palette fading error codes -#define RDERR_FADEINCOMPLETE 0x00070000 - -//Sound engine error codes -#define RDERR_DSOUNDCREATE 0x00080000 -#define RDERR_DSOUNDCOOPERATE 0x00080001 -#define RDERR_DSOUNDPBUFFER 0x00080002 -#define RDERR_PRIMARYFORMAT 0x00080003 -#define RDERR_SPEECHPLAYING 0x00080004 -#define RDERR_SPEECHNOTPLAYING 0x00080005 -#define RDERR_INVALIDWAV 0x00080006 -#define RDERR_CREATESOUNDBUFFER 0x00080007 -#define RDERR_LOCKSPEECHBUFFER 0x00080008 -#define RDERR_FXALREADYOPEN 0x00080009 -#define RDERR_NOFREEBUFFERS 0x0008000A -#define RDERR_FXNOTOPEN 0x0008000B -#define RDERR_FXFUCKED 0x0008000C -#define RDERR_INVALIDID 0x0008000D +enum { + // Generic error codes + + RD_OK, + RDERR_UNKNOWN, + RDERR_INVALIDPOINTER, + RDERR_OUTOFMEMORY, + RDERR_INVALIDFILENAME, + + // Drawing error codes + + RDERR_DECOMPRESSION, + + // Language and version error codes + + RDERR_OPENVERSIONFILE, + + // Keyboard error codes + + RDERR_NOKEYWAITING, + + // Sprite drawing error codes + + RDERR_NOTIMPLEMENTED, + RDERR_UNKNOWNTYPE, + RDERR_INVALIDSCALING, + RDERR_NOTCLOSED, + RDERR_NOTOPEN, + + // Menubar error codes + RDERR_INVALIDMENU, + RDERR_INVALIDPOCKET, + RDERR_INVALIDCOMMAND, + + // Palette fading error codes + + RDERR_FADEINCOMPLETE, + + // Sound engine error codes + + RDERR_SPEECHPLAYING, + RDERR_SPEECHNOTPLAYING, + RDERR_INVALIDWAV, + RDERR_FXALREADYOPEN, + RDERR_NOFREEBUFFERS, + RDERR_FXNOTOPEN, + RDERR_FXFUCKED, + RDERR_INVALIDID +}; // Key codes -#define RDKEY_ESCAPE 27 +enum { +// RDKEY_ESCAPE = 27 +}; // Mouse button defines -#define RD_LEFTBUTTONDOWN 0x01 -#define RD_LEFTBUTTONUP 0x02 -#define RD_RIGHTBUTTONDOWN 0x04 -#define RD_RIGHTBUTTONUP 0x08 - - -//Sprite defines -#define RDSPR_TRANS 0x0001 -#define RDSPR_BLEND 0x0004 -#define RDSPR_FLIP 0x0008 -#define RDSPR_SHADOW 0x0010 -#define RDSPR_DISPLAYALIGN 0x0020 -#define RDSPR_NOCOMPRESSION 0x0040 -#define RDSPR_EDGEBLEND 0x0080 -//This is the high byte part of the sprite type which defines what type of -// compression is used, as long as RDSPR_NOCOMPRESSION is not defined. -#define RDSPR_RLE16 0x0000 -#define RDSPR_RLE256 0x0100 -#define RDSPR_RLE256FAST 0x0200 - - -//Fading defines -#define RDFADE_NONE 0x00 -#define RDFADE_UP 0x01 -#define RDFADE_DOWN 0x02 -#define RDFADE_BLACK 0x03 - -//Mouse defines -#define RDMOUSE_NOFLASH 0x00 -#define RDMOUSE_FLASH 0x01 - -//Menubar defines. -#define RDMENU_TOP 0x00 -#define RDMENU_BOTTOM 0x01 - -#define RDMENU_HIDDEN 0x00 -#define RDMENU_SHOWN 0x01 -#define RDMENU_OPENING 0x02 -#define RDMENU_CLOSING 0x03 + +enum { + RD_LEFTBUTTONDOWN = 0x01, + RD_LEFTBUTTONUP = 0x02, + RD_RIGHTBUTTONDOWN = 0x04, + RD_RIGHTBUTTONUP = 0x08 +}; + +// Sprite defines + +enum { + RDSPR_TRANS = 0x0001, + RDSPR_BLEND = 0x0004, + RDSPR_FLIP = 0x0008, + RDSPR_SHADOW = 0x0010, + RDSPR_DISPLAYALIGN = 0x0020, + RDSPR_NOCOMPRESSION = 0x0040, + RDSPR_EDGEBLEND = 0x0080, // Unused + + // This is the high byte part of the sprite type which defines what + // type of compression is used, as long as RDSPR_NOCOMPRESSION is not + // defined. + + RDSPR_RLE16 = 0x0000, + RDSPR_RLE256 = 0x0100, + RDSPR_RLE256FAST = 0x0200 +}; + +// Fading defines + +enum { + RDFADE_NONE, + RDFADE_UP, + RDFADE_DOWN, + RDFADE_BLACK +}; + +// Mouse defines + +enum { + RDMOUSE_NOFLASH, + RDMOUSE_FLASH +}; + +// Menubar defines. + +#define RDMENU_TOP 0 +#define RDMENU_BOTTOM 1 + +enum { + RDMENU_HIDDEN, + RDMENU_SHOWN, + RDMENU_OPENING, + RDMENU_CLOSING +}; #define RDMENU_ICONWIDE 35 #define RDMENU_ICONDEEP 30 @@ -160,25 +162,35 @@ namespace Sword2 { #define RDMENU_MAXPOCKETS 15 #define RDMENU_MENUDEEP 40 -#define RDSE_SAMPLEFINISHED 0 -#define RDSE_SAMPLEPLAYING 1 -#define RDSE_FXTOCLEAR 0 -#define RDSE_FXCACHED 1 -#define RDSE_FXSPOT 0 -#define RDSE_FXLOOP 1 -#define RDSE_FXLEADIN 2 -#define RDSE_FXLEADOUT 3 -#define RDSE_QUIET 1 -#define RDSE_SPEAKING 0 - - -#define RDPAL_FADE 0 -#define RDPAL_INSTANT 1 - -//Blitting FX defines -#define RDBLTFX_SPRITEBLEND 0x01 -#define RDBLTFX_SHADOWBLEND 0x02 -#define RDBLTFX_EDGEBLEND 0x04 +// Sound defines + +enum { + RDSE_SAMPLEFINISHED = 0, + RDSE_SAMPLEPLAYING = 1, + RDSE_FXTOCLEAR = 0, // Unused + RDSE_FXCACHED = 1, // Unused + RDSE_FXSPOT = 0, + RDSE_FXLOOP = 1, + RDSE_FXLEADIN = 2, + RDSE_FXLEADOUT = 3, + RDSE_QUIET = 1, + RDSE_SPEAKING = 0 +}; + +// Palette defines + +enum { + RDPAL_FADE, + RDPAL_INSTANT +}; + +// Blitting FX defines + +enum { + RDBLTFX_SPRITEBLEND = 0x01, + RDBLTFX_SHADOWBLEND = 0x02, + RDBLTFX_EDGEBLEND = 0x04 +}; // // Structure definitions diff --git a/sword2/mouse.cpp b/sword2/mouse.cpp index eae9cd1673..24a6e439bd 100644 --- a/sword2/mouse.cpp +++ b/sword2/mouse.cpp @@ -59,8 +59,11 @@ enum { USE = 3100 }; +/** + * Call at beginning of game loop + */ + void Sword2Engine::resetMouseList(void) { - // call at beginning of gameloop _curMouse = 1; } @@ -120,10 +123,20 @@ void Sword2Engine::mouseEngine(void) { } } +int Sword2Engine::menuClick(int menu_items) { + if (_input->_mouseX < RDMENU_ICONSTART) + return -1; + + if (_input->_mouseX > RDMENU_ICONSTART + menu_items * (RDMENU_ICONWIDE + RDMENU_ICONSPACING) - RDMENU_ICONSPACING) + return -1; + + return (_input->_mouseX - RDMENU_ICONSTART) / (RDMENU_ICONWIDE + RDMENU_ICONSPACING); +} + void Sword2Engine::systemMenuMouse(void) { uint32 safe_looping_music_id; _mouseEvent *me; - int j, hit; + int hit; uint8 *icon; int32 pars[2]; uint32 icon_list[5] = { @@ -134,333 +147,327 @@ void Sword2Engine::systemMenuMouse(void) { RESTART_ICON }; - // can't close when player is dead + // 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 (_input->_mouseY > 0 && !DEAD) { - // close menu _mouseMode = MOUSE_normal; - g_graphics->hideMenu(RDMENU_TOP); + _graphics->hideMenu(RDMENU_TOP); return; } - me = g_input->mouseEvent(); - - if (me && (me->buttons & RD_LEFTBUTTONDOWN)) { - // clicked on a top mouse pointer? - - if (_input->_mouseX >= 24 && _input->_mouseX < 640 - 24 && _input->_mouseY < 0) { - // which are we over? - hit = (_input->_mouseX - 24) / 40; - - // no save when dead - if (icon_list[hit] == SAVE_ICON && DEAD) - return; - - // there are 5 system menu icons - if (hit < ARRAYSIZE(icon_list)) { - // build them all high in full colour - when - // one is clicked on all the rest will grey out - for (j = 0; j < ARRAYSIZE(icon_list); j++) { - // change all others to grey - if (j != hit) { - icon = res_man->openResource(icon_list[j]) + sizeof(_standardHeader); - g_graphics->setMenuIcon(RDMENU_TOP, j, icon); - res_man->closeResource(icon_list[j]); - } - } - - g_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 = _loopingMusicId; - - pars[0] = 221; // SystemM234 (M234.wav) - pars[1] = FX_LOOP; - g_logic->fnPlayMusic(pars); - - // restore proper looping_music_id - _loopingMusicId = safe_looping_music_id; - - // clear the screen & set up the new palette - // for the menus - - g_graphics->clearScene(); - g_graphics->processMenu(); - g_graphics->resetRenderEngine(); - - // call the relevent screen - switch (hit) { - case 0: - gui->optionControl(); - break; - case 1: - gui->quitControl(); - break; - case 2: - gui->saveControl(); - break; - case 3: - gui->restoreControl(); - break; - case 4: - gui->restartControl(); - break; - } - - // Menu stays open on death screen - if (!DEAD) { - _mouseMode = MOUSE_normal; - g_graphics->hideMenu(RDMENU_TOP); - } else { - setMouse(NORMAL_MOUSE_ID); - buildSystemMenu(); - } - - // clear the screen & restore the location - // palette - - g_graphics->clearScene(); - g_graphics->processMenu(); - - // reset game palette, but not after a - // successful restore or restart! - - // see RestoreFromBuffer() in save_rest.cpp - if (_thisScreen.new_palette != 99) { - // '0' means put back game screen - // palette; see Build_display.cpp - - setFullPalette(0); - - // stop the engine fading in the - // restored screens palette - - _thisScreen.new_palette = 0; - } else - _thisScreen.new_palette = 1; - - g_sound->unpauseFx(); - - // If there was looping music before coming - // into the control panels then restart it! - // NB. This will also start music required - // when a game has been restored - - if (_loopingMusicId) { - pars[0] = _loopingMusicId; - pars[1] = FX_LOOP; - g_logic->fnPlayMusic(pars); - - // cross-fades into the required music: - // - either a restored game tune - // - or music playing prior to - // entering control panels - } else { - // stop the control panel music - g_logic->fnStopMusic(NULL); - } - } + // Check if the user left-clicks anywhere in the menu area. + + me = _input->mouseEvent(); + + if (!me || !(me->buttons & RD_LEFTBUTTONDOWN)) + return; + + if (_input->_mouseY > 0) + return; + + hit = menuClick(ARRAYSIZE(icon_list)); + + if (hit < 0) + return; + + // No save when dead + + if (icon_list[hit] == SAVE_ICON && 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 = res_man->openResource(icon_list[i]) + sizeof(_standardHeader); + _graphics->setMenuIcon(RDMENU_TOP, i, icon); + res_man->closeResource(icon_list[i]); } } + + _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 = _loopingMusicId; + + pars[0] = 221; + pars[1] = FX_LOOP; + g_logic->fnPlayMusic(pars); + + // restore proper looping_music_id + _loopingMusicId = safe_looping_music_id; + + // clear the screen & set up the new palette for the menus + + _graphics->clearScene(); + _graphics->processMenu(); + _graphics->resetRenderEngine(); + + // call the relevent screen + + switch (hit) { + case 0: + gui->optionControl(); + break; + case 1: + gui->quitControl(); + break; + case 2: + gui->saveControl(); + break; + case 3: + gui->restoreControl(); + break; + case 4: + gui->restartControl(); + break; + } + + // Menu stays open on death screen. Otherwise it's closed. + + if (!DEAD) { + _mouseMode = MOUSE_normal; + _graphics->hideMenu(RDMENU_TOP); + } else { + setMouse(NORMAL_MOUSE_ID); + buildSystemMenu(); + } + + // Clear the screen & restore the location palette + + _graphics->clearScene(); + _graphics->processMenu(); + + // Reset game palette, but not after a successful restore or restart! + // See RestoreFromBuffer() in save_rest.cpp + + if (_thisScreen.new_palette != 99) { + // 0 means put back game screen palette; see build_display.cpp + setFullPalette(0); + + // Stop the engine fading in the restored screens palette + _thisScreen.new_palette = 0; + } else + _thisScreen.new_palette = 1; + + _sound->unpauseFx(); + + // If there was looping music before coming into the control panels + // then restart it! NB. This will also start music required when a game + // has been restored + + if (_loopingMusicId) { + pars[0] = _loopingMusicId; + pars[1] = FX_LOOP; + g_logic->fnPlayMusic(pars); + + // cross-fades into the required music: either a restored game + // tune, or music playing prior to entering control panels + } else { + // stop the control panel music + g_logic->fnStopMusic(NULL); + } } void Sword2Engine::dragMouse(void) { _mouseEvent *me; - uint32 pos; + 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 (_input->_mouseY < 400) { - // close menu _mouseMode = MOUSE_normal; - g_graphics->hideMenu(RDMENU_BOTTOM); + _graphics->hideMenu(RDMENU_BOTTOM); return; } - // handles cursors and the luggage on/off according to type + // Handles cursors and the luggage on/off according to type + mouseOnOff(); - // now do the normal click stuff + // Now do the normal click stuff - me = g_input->mouseEvent(); + me = _input->mouseEvent(); - // we only care about left clicks when the mouse is over an object - // we ignore mouse releases + if (!me) + return; - if (me && (me->buttons & RD_LEFTBUTTONDOWN)) { - // there's a mouse event to be processed +#if 0 + // If the user right-clicks, cancel drag mode. The original code did + // not do this, but it feels natural to me. - // could be clicking on an on screen object or on the menu - // which is currently displayed + if (me->buttons & RD_RIGHTBUTTONDOWN) { + OBJECT_HELD = 0; + _menuSelectedPos = 0; + _mouseMode = MOUSE_menu; + setLuggage(0); + buildMenu(); + return; + } +#endif - if (_mouseTouching) { - // mouse is over an on screen object - and we have - // luggage + if (!(me->buttons & RD_LEFTBUTTONDOWN)) + return; - // depending on type we'll maybe kill the object_held - // - like for exits + // there's a mouse event to be processed - // Set global script variable 'button'. We know that - // it was the left button, not the right one. + // could be clicking on an on screen object or on the menu + // which is currently displayed - LEFT_BUTTON = 1; - RIGHT_BUTTON = 0; + if (_mouseTouching) { + // mouse is over an on screen object - and we have luggage - // these might be required by the action script about - // to be run + // Depending on type we'll maybe kill the object_held - like + // for exits - MOUSE_X = (uint32) _input->_mouseX + _thisScreen.scroll_offset_x; - MOUSE_Y = (uint32) _input->_mouseY + _thisScreen.scroll_offset_y; + // Set global script variable 'button'. We know that it was the + // left button, not the right one. - // for scripts to know what's been clicked (21jan97). - // First used for 'room_13_turning_script' in object - // 'biscuits_13' + LEFT_BUTTON = 1; + RIGHT_BUTTON = 0; - CLICKED_ID = _mouseTouching; + // These might be required by the action script about to be run - g_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching); + MOUSE_X = _input->_mouseX + _thisScreen.scroll_offset_x; + MOUSE_Y = _input->_mouseY + _thisScreen.scroll_offset_y; - debug(5, "USED \"%s\" ICON ON %s", fetchObjectName(OBJECT_HELD), fetchObjectName(CLICKED_ID)); + // For scripts to know what's been clicked. First used for + // 'room_13_turning_script' in object 'biscuits_13' - // Hide menu - back to normal menu mode + CLICKED_ID = _mouseTouching; - g_graphics->hideMenu(RDMENU_BOTTOM); - _mouseMode = MOUSE_normal; - } else { - // better check for combine/cancel - // cancel puts us back in Menu_mouse mode - if (_input->_mouseX >= 24 && _input->_mouseX < 640 - 24) { - // which are we over? - pos = (_input->_mouseX - 24) / 40; - - //clicked on something - what button? - if (_masterMenuList[pos].icon_resource) { - // always back into menu mode - _mouseMode = MOUSE_menu; - - // remove luggage - setLuggage(0); - - // if we've clicked on the same icon - // as the one we're dragging - - if (pos == _menuSelectedPos) { - // reset first icon - OBJECT_HELD = 0; - _menuSelectedPos = 0; - } else { - // combine the 2 icons - debug(5, "combine"); - - // what we clicked on, not what - // we're dragging - - COMBINE_BASE = _masterMenuList[pos].icon_resource; - g_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 - - noHuman(); - - debug(5, "USED \"%s\" ICON ON \"%s\" ICON", fetchObjectName(OBJECT_HELD), fetchObjectName(COMBINE_BASE)); - } - - // refresh the menu - buildMenu(); - debug(5, "switch to menu mode"); - } - } - } + g_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching); + + debug(5, "Used \"%s\" on \"%s\"", fetchObjectName(OBJECT_HELD), fetchObjectName(CLICKED_ID)); + + // Hide menu - back to normal menu mode + + _graphics->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 + + OBJECT_HELD = 0; + _menuSelectedPos = 0; + } else { + // Otherwise, combine the two icons + + COMBINE_BASE = _masterMenuList[hit].icon_resource; + g_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 + + noHuman(); + + debug(2, "Used \"%s\" on \"%s\"", fetchObjectName(OBJECT_HELD), fetchObjectName(COMBINE_BASE)); } + + // Refresh the menu + + buildMenu(); } void Sword2Engine::menuMouse(void) { - // menu is down - _mouseEvent *me; - uint32 pos; + int hit; + + // If the mouse is moved off the menu, close it. if (_input->_mouseY < 400) { - // close menu _mouseMode = MOUSE_normal; - g_graphics->hideMenu(RDMENU_BOTTOM); + _graphics->hideMenu(RDMENU_BOTTOM); return; } - me = g_input->mouseEvent(); + me = _input->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; - // we only care about left clicks when the mouse is over an object - // we ignore mouse releases + if (me->buttons & RD_RIGHTBUTTONDOWN) { + // Right button - examine an object, identified by its icon + // resource id. - if (me) { - // there's a mouse event to be processed - // now check if we've clicked on an actual icon + _examiningMenuIcon = true; + OBJECT_HELD = _masterMenuList[hit].icon_resource; - if (_input->_mouseX >= 24 && _input->_mouseX < 640 - 24) { - // which are we over? - pos = (_input->_mouseX - 24) / 40; + // Must clear this so next click on exit becomes 1st click + // again - // clicked on something - what button? - if (_masterMenuList[pos].icon_resource) { - if (me->buttons & RD_RIGHTBUTTONDOWN) { - // right button look - _examiningMenuIcon = true; + EXIT_CLICK_ID = 0; - // id the object via its graphic - OBJECT_HELD = _masterMenuList[pos].icon_resource; + g_logic->setPlayerActionEvent(CUR_PLAYER_ID, MENU_MASTER_OBJECT); - // Must clear this so next click on - // exit becomes 1st click again + // Refresh the menu - EXIT_CLICK_ID = 0; + buildMenu(); - debug(5, "RIGHT-CLICKED ON \"%s\" ICON", fetchObjectName(OBJECT_HELD)); + // Turn off mouse now, to prevent player trying to click + // elsewhere BUT leave the bottom menu open - g_logic->setPlayerActionEvent(CUR_PLAYER_ID, MENU_MASTER_OBJECT); + noHuman(); - // refresh the menu - buildMenu(); + debug(2, "Right-click on \"%s\" icon", + fetchObjectName(OBJECT_HELD)); - // turn off mouse now, to prevent - // player trying to click elsewhere - // BUT leave the bottom menu open + return; + } - noHuman(); - } else if (me->buttons & RD_LEFTBUTTONDOWN) { - // left button - highlight the object - // and bung us into drag luggage mode + 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 - // menu slot we clicked on - derive - // luggage resource from this in - // mouse_on_off() + _mouseMode = MOUSE_drag; - _menuSelectedPos = pos; - _currentLuggageResource = _masterMenuList[pos].luggage_resource; + _menuSelectedPos = hit; + OBJECT_HELD = _masterMenuList[hit].icon_resource; + _currentLuggageResource = _masterMenuList[hit].luggage_resource; - _mouseMode = MOUSE_drag; - debug(5, "setting OH in menu"); + // Must clear this so next click on exit becomes 1st click + // again - // id the object via its graphic - OBJECT_HELD = _masterMenuList[pos].icon_resource; + EXIT_CLICK_ID = 0; - // must clear this so next click on - // exit becomes 1st click again + // Refresh the menu - EXIT_CLICK_ID = 0; + buildMenu(); - // refresh the menu - buildMenu(); + setLuggage(_masterMenuList[hit].luggage_resource); - setLuggage(_masterMenuList[pos].luggage_resource); - debug(5, "switch to drag mode"); - } - } - } + debug(2, "Left-clicked on \"%s\" icon - switch to drag mode", + fetchObjectName(OBJECT_HELD)); } } @@ -470,7 +477,10 @@ void Sword2Engine::normalMouse(void) { _mouseEvent *me; - // no save in big-object menu lock situation + // 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 (_input->_mouseY < 0 && !_mouseModeLocked && !OBJECT_HELD) { _mouseMode = MOUSE_system_menu; @@ -480,16 +490,20 @@ void Sword2Engine::normalMouse(void) { _mouseTouching = 0; } - // reset mouse cursor - in case we're between mice + // 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 (_input->_mouseY > 399 && !_mouseModeLocked) { // If an object is being held, i.e. if the mouse cursor has a - // luggage, we should be use dragging mode instead of inventory - // menu mode. + // 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 @@ -504,31 +518,36 @@ void Sword2Engine::normalMouse(void) { // standard get-off if (_mouseTouching) { - // We were on something, but not anymore - _oldMouseTouching = 0; _mouseTouching = 0; } - // reset mouse cursor + // Reset mouse cursor + setMouse(NORMAL_MOUSE_ID); buildMenu(); return; } - // check for moving the mouse on or off things + // Check for moving the mouse on or off things mouseOnOff(); - // now do the normal click stuff + me = _input->mouseEvent(); + + if (!me) + return; + + bool button_down = (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN)) != 0; - me = g_input->mouseEvent(); + // For debugging. We can draw a rectangle on the screen and see its + // coordinates. This was probably used to help defining hit areas. if (_debugger->_definingRectangles) { if (_debugger->_draggingRectangle == 0) { - // not yet dragging a rectangle, so need click to start + // Not yet dragging a rectangle, so need click to start - if (me && (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN))) { + if (button_down) { // set both (x1,y1) and (x2,y2) to this point _debugger->_rectX1 = _debugger->_rectX2 = (uint32) _input->_mouseX + _thisScreen.scroll_offset_x; _debugger->_rectY1 = _debugger->_rectY2 = (uint32) _input->_mouseY + _thisScreen.scroll_offset_y; @@ -537,7 +556,7 @@ void Sword2Engine::normalMouse(void) { } else if (_debugger->_draggingRectangle == 1) { // currently dragging a rectangle - click means reset - if (me && (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN))) { + if (button_down) { // lock rectangle, so you can let go of mouse // to type in the coords _debugger->_draggingRectangle = 2; @@ -550,91 +569,105 @@ void Sword2Engine::normalMouse(void) { // currently locked to avoid knocking out of place // while reading off the coords - if (me && (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN))) { + if (button_down) { // click means reset - back to start again _debugger->_draggingRectangle = 0; } } - } else { - // We only care about down clicks when the mouse is over an - // object. We ignore mouse releases - if (me && (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN)) && _mouseTouching) { - // there's a mouse event to be processed and the mouse - // is on something + return; + } - // ok, 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 0 + // If user right-clicks while holding an object, release it. The + // original code did not do this, but it feels natural to me. - // if player is walking or relaxing then those need to - // terminate correctly + if (OBJECT_HELD && (me->buttons & RD_RIGHTBUTTONDOWN)) { + OBJECT_HELD = 0; + _menuSelectedPos = 0; + setLuggage(0); + return; + } +#endif - // otherwise set player run the targets action script - // or, do a special walk if clicking on the scroll-more - // icon + // Now do the normal click stuff - // PLAYER_ACTION script variable - whatever catches - // this must reset to 0 again - // PLAYER_ACTION = _mouseTouching; + // We only care about down clicks when the mouse is over an object. We + // ignore mouse releases - // idle or router-anim will catch it + if (!_mouseTouching || !button_down) + return; - // set global script variable 'button' + // There's a mouse event to be processed and the mouse is on something. + // Notice that the floor itself is considered an object. - if (me->buttons & RD_LEFTBUTTONDOWN) { - LEFT_BUTTON = 1; - RIGHT_BUTTON = 0; - _buttonClick = 0; // for re-click - } else { - LEFT_BUTTON = 0; - RIGHT_BUTTON = 1; - _buttonClick = 1; // for re-click - } + // 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. - // these might be required by the action script about - // to be run - MOUSE_X = (uint32) _input->_mouseX + _thisScreen.scroll_offset_x; - MOUSE_Y = (uint32) _input->_mouseY + _thisScreen.scroll_offset_y; - - // only left button - if (_mouseTouching == EXIT_CLICK_ID && (me->buttons & RD_LEFTBUTTONDOWN)) { - // its the exit double click situation - // let the existing interaction continue and - // start fading down - switch the human off too - - g_logic->fnNoHuman(NULL); - g_logic->fnFadeDown(NULL); - EXIT_FADING = 1; // tell the walker - } else if (_oldButton == _buttonClick && _mouseTouching == CLICKED_ID && _mousePointerRes != NORMAL_MOUSE_ID) { - // re-click - do nothing - except on floors - } else { - // allow the click - _oldButton = _buttonClick; // for re-click + // 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 - // for scripts to know what's been clicked - // First used for 'room_13_turning_script' in - // object 'biscuits_13' + // PLAYER_ACTION script variable - whatever catches this must reset to + // 0 again + // PLAYER_ACTION = _mouseTouching; - CLICKED_ID = _mouseTouching; + // Idle or router-anim will catch it - // must clear these two double-click control - // flags - do it here so reclicks after exit - // clicks are cleared up + // Set global script variable 'button' - EXIT_CLICK_ID = 0; - EXIT_FADING = 0; + if (me->buttons & RD_LEFTBUTTONDOWN) { + LEFT_BUTTON = 1; + RIGHT_BUTTON = 0; + _buttonClick = 0; // for re-click + } else { + LEFT_BUTTON = 0; + RIGHT_BUTTON = 1; + _buttonClick = 1; // for re-click + } - g_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching); + // These might be required by the action script about to be run - if (OBJECT_HELD) - debug(5, "USED \"%s\" ICON ON %s", fetchObjectName(OBJECT_HELD), fetchObjectName(CLICKED_ID)); - else if (LEFT_BUTTON) - debug(5, "LEFT-CLICKED ON %s", fetchObjectName(CLICKED_ID)); - else // RIGHT BUTTON - debug(5, "RIGHT-CLICKED ON %s", fetchObjectName(CLICKED_ID)); - } - } + MOUSE_X = _input->_mouseX + _thisScreen.scroll_offset_x; + MOUSE_Y = _input->_mouseY + _thisScreen.scroll_offset_y; + + if (_mouseTouching == 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 + + g_logic->fnNoHuman(NULL); + g_logic->fnFadeDown(NULL); + + // Tell the walker + + EXIT_FADING = 1; + } else if (_oldButton == _buttonClick && _mouseTouching == 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' + + CLICKED_ID = _mouseTouching; + + // Must clear these two double-click control flags - do it here + // so reclicks after exit clicks are cleared up + + EXIT_CLICK_ID = 0; + EXIT_FADING = 0; + + g_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching); + + if (OBJECT_HELD) + debug(2, "Used \"%s\" on \"%s\"", fetchObjectName(OBJECT_HELD), fetchObjectName(CLICKED_ID)); + else if (LEFT_BUTTON) + debug(2, "Left-clicked on \"%s\"", fetchObjectName(CLICKED_ID)); + else // RIGHT BUTTON + debug(2, "Right-clicked on \"%s\"", fetchObjectName(CLICKED_ID)); } } @@ -747,14 +780,14 @@ void Sword2Engine::setMouse(uint32 res) { // loop if (res == NORMAL_MOUSE_ID) - g_graphics->setMouseAnim(icon, len, RDMOUSE_NOFLASH); + _graphics->setMouseAnim(icon, len, RDMOUSE_NOFLASH); else - g_graphics->setMouseAnim(icon, len, RDMOUSE_FLASH); + _graphics->setMouseAnim(icon, len, RDMOUSE_FLASH); res_man->closeResource(res); } else { // blank cursor - g_graphics->setMouseAnim(NULL, 0, 0); + _graphics->setMouseAnim(NULL, 0, 0); } } @@ -768,11 +801,11 @@ void Sword2Engine::setLuggage(uint32 res) { icon = res_man->openResource(res) + sizeof(_standardHeader); len = res_man->_resList[res]->size - sizeof(_standardHeader); - g_graphics->setLuggageAnim(icon, len); + _graphics->setLuggageAnim(icon, len); res_man->closeResource(res); } else - g_graphics->setLuggageAnim(NULL, 0); + _graphics->setLuggageAnim(NULL, 0); } uint32 Sword2Engine::checkMouseList(void) { @@ -780,33 +813,34 @@ uint32 Sword2Engine::checkMouseList(void) { uint32 j = 1; if (_curMouse > 1) { - //number of priorities subject to implementation needs + // Number of priorities subject to implementation needs + while (priority < 10) { - // if the mouse pointer is over this + // If the mouse pointer is over this // mouse-detection-box if (_mouseList[j].priority == priority && - _input->_mouseX + _thisScreen.scroll_offset_x >= _mouseList[j].x1 && - _input->_mouseX + _thisScreen.scroll_offset_x <= _mouseList[j].x2 && - _input->_mouseY + _thisScreen.scroll_offset_y >= _mouseList[j].y1 && - _input->_mouseY + _thisScreen.scroll_offset_y <= _mouseList[j].y2) { - // record id + _input->_mouseX + _thisScreen.scroll_offset_x >= _mouseList[j].x1 && + _input->_mouseX + _thisScreen.scroll_offset_x <= _mouseList[j].x2 && + _input->_mouseY + _thisScreen.scroll_offset_y >= _mouseList[j].y1 && + _input->_mouseY + _thisScreen.scroll_offset_y <= _mouseList[j].y2) { + // Record id _mouseTouching = _mouseList[j].id; - // change all COGS pointers to CROSHAIR + // Change all COGS pointers to CROSHAIR if (_mouseList[j].pointer == USE) _mouseList[j].pointer = CROSHAIR; createPointerText(_mouseList[j].pointer_text, _mouseList[j].pointer); - // return pointer type + // Return pointer type return _mouseList[j].pointer; } j++; if (j == _curMouse) { j = 0; - // next priority - 0 being the highest, 9 the + // Next priority - 0 being the highest, 9 the // lowest priority++; } @@ -828,153 +862,150 @@ void Sword2Engine::createPointerText(uint32 text_id, uint32 pointer_res) { int16 xOffset, yOffset; uint8 justification; - if (gui->_pointerTextSelected) { - if (text_id) { - // 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; - } + if (!gui->_pointerTextSelected || !text_id) + return; - // 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; - } - } + // Check what the pointer is, to set offsets correctly for text + // position - // text resource number, and line number within the - // resource + 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; + } - text_res = text_id / SIZE; - local_text = text_id & 0xffff; + // 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; + } + } - // open text file & get the line - text = fetchTextLine(res_man->openResource(text_res), local_text); + // Text resource number, and line number within the resource - // 'text+2' to skip the first 2 bytes which form the - // line reference number + text_res = text_id / SIZE; + local_text = text_id & 0xffff; - _pointerTextBlocNo = fontRenderer->buildNewBloc( - text + 2, _input->_mouseX + xOffset, - _input->_mouseY + yOffset, - POINTER_TEXT_WIDTH, POINTER_TEXT_PEN, - RDSPR_TRANS | RDSPR_DISPLAYALIGN, - _speechFontId, justification); + // open text file & get the line + text = fetchTextLine(res_man->openResource(text_res), local_text); - // now ok to close the text file - res_man->closeResource(text_res); - } - } + // 'text+2' to skip the first 2 bytes which form the + // line reference number + + _pointerTextBlocNo = fontRenderer->buildNewBloc( + text + 2, _input->_mouseX + xOffset, + _input->_mouseY + yOffset, + POINTER_TEXT_WIDTH, POINTER_TEXT_PEN, + RDSPR_TRANS | RDSPR_DISPLAYALIGN, + _speechFontId, justification); + + // now ok to close the text file + res_man->closeResource(text_res); } void Sword2Engine::clearPointerText(void) { @@ -1002,47 +1033,44 @@ void Sword2Engine::noHuman(void) { void Sword2Engine::registerMouse(Object_mouse *ob_mouse) { debug(5, "_curMouse = %d", _curMouse); - // only if 'pointer' isn't NULL - if (ob_mouse->pointer) { - assert(_curMouse < TOTAL_mouse_list); + if (!ob_mouse->pointer) + return; - _mouseList[_curMouse].x1 = ob_mouse->x1; - _mouseList[_curMouse].y1 = ob_mouse->y1; - _mouseList[_curMouse].x2 = ob_mouse->x2; - _mouseList[_curMouse].y2 = ob_mouse->y2; + assert(_curMouse < TOTAL_mouse_list); - _mouseList[_curMouse].priority = ob_mouse->priority; - _mouseList[_curMouse].pointer = ob_mouse->pointer; + _mouseList[_curMouse].x1 = ob_mouse->x1; + _mouseList[_curMouse].y1 = ob_mouse->y1; + _mouseList[_curMouse].x2 = ob_mouse->x2; + _mouseList[_curMouse].y2 = ob_mouse->y2; - // Check if pointer text field is set due to previous object - // using this slot (ie. not correct for this one) + _mouseList[_curMouse].priority = ob_mouse->priority; + _mouseList[_curMouse].pointer = ob_mouse->pointer; - // 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 + // Check if pointer text field is set due to previous object using this + // slot (ie. not correct for this one) - if (_mouseList[_curMouse].pointer_text && _mouseList[_curMouse].id != (int32) ID) - _mouseList[_curMouse].pointer_text = 0; + // 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 - // get id from system variable 'id' which is correct for - // current object + if (_mouseList[_curMouse].pointer_text && _mouseList[_curMouse].id != (int32) ID) + _mouseList[_curMouse].pointer_text = 0; - _mouseList[_curMouse].id = ID; + // Get id from system variable 'id' which is correct for current object - // not using sprite as mask - this is only done from - // fnRegisterFrame() + _mouseList[_curMouse].id = ID; - _mouseList[_curMouse].anim_resource = 0; - _mouseList[_curMouse].anim_pc = 0; + // Not using sprite as mask - this is only done from fnRegisterFrame() - debug(5, "mouse id %d", _mouseList[_curMouse].id); - _curMouse++; - } + _mouseList[_curMouse].anim_resource = 0; + _mouseList[_curMouse].anim_pc = 0; + + debug(5, "mouse id %d", _mouseList[_curMouse].id); + _curMouse++; } void Sword2Engine::monitorPlayerActivity(void) { // if there is at least one mouse event outstanding - if (g_input->checkForMouseEvents()) { + if (_input->checkForMouseEvents()) { // reset activity delay counter _playerActivityDelay = 0; } else { diff --git a/sword2/mouse.h b/sword2/mouse.h index 8af8e6eddf..08a6d0dedb 100644 --- a/sword2/mouse.h +++ b/sword2/mouse.h @@ -31,9 +31,12 @@ enum { MOUSE_menu = 1, // menu chooser MOUSE_drag = 2, // dragging luggage MOUSE_system_menu = 3, // system menu chooser - MOUSE_holding = 4 // speech chooser + MOUSE_holding = 4 // special }; +// The MOUSE_holding mode is entered when the conversation menu is closed, and +// exited when the mouse cursor moves off that menu area. I don't know why yet. + // mouse unit - like Object_mouse, but with anim resource & pc (needed if // sprite is to act as mouse detection mask) diff --git a/sword2/sword2.h b/sword2/sword2.h index dfa4219b84..0bc3535973 100644 --- a/sword2/sword2.h +++ b/sword2/sword2.h @@ -126,6 +126,8 @@ private: void startNewPalette(void); void processLayer(uint32 layer_number); + int menuClick(int menu_items); + void getPlayerStructures(void); void putPlayerStructures(void); |