Subversion Repositories Games.Chess Giants

Rev

Rev 1 | Rev 24 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. // window_main.cpp
  2.  
  3. #include "../common.h"
  4.  
  5.  
  6. // prototypes of local functions
  7. static bool Button_IsHovered (guibutton_t *button, int gui_x, int gui_y);
  8. static bool Button_UpdateHoverState (guibutton_t *button, int gui_x, int gui_y);
  9.  
  10.  
  11. LRESULT CALLBACK WindowProc_Main (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  12. {
  13.    // this is the main message handler for the program
  14.  
  15.    static wchar_t fen_string[128];
  16.    static char selectable_parts[14] = "PRNBQK kqbnrp";
  17.    static int prevgui_x = 0;
  18.    static int prevgui_y = 0;
  19.    static bool ctrl_pressed = false;
  20.    static bool rbutton_pushed = false;
  21.  
  22.    unsigned short wParam_hiword;
  23.    unsigned short wParam_loword;
  24.    int prev_hovered_position[2];
  25.    player_t *current_player;
  26.    player_t *opposite_player;
  27.    player_t *local_player;
  28.    player_t *remote_player;
  29.    ccreply_t *entered_ccreply;
  30.    boardmove_t *current_move;
  31.    float board_x;
  32.    float board_y;
  33.    int gui_x;
  34.    int gui_y;
  35.    int line;
  36.    int column;
  37.    int index_line;
  38.    int index_column;
  39.    int part_index;
  40.    int part_color;
  41.  
  42.    // filter out the commonly used message values
  43.    wParam_hiword = HIWORD (wParam);
  44.    wParam_loword = LOWORD (wParam);
  45.  
  46.    ////////////////////////////////////////////////////////////////////////////////////////////////
  47.    // has the window just been fired up ?
  48.    if (message == WM_CREATE)
  49.    {
  50.       the_scene.gui.is_entering_text = false; // we are NOT entering text yet
  51.  
  52.       // call the default window message processing function to keep things going
  53.       return (DefWindowProc (hWnd, message, wParam, lParam));
  54.    }
  55.  
  56.    ////////////////////////////////////////////////////////////////////////////////////////////////
  57.    // else have we clicked on the close button ?
  58.    else if (message == WM_CLOSE)
  59.    {
  60.       if (the_board.game_state == STATE_PLAYING)
  61.          DialogBox_Quit (); // if a game has started, ask for confirmation
  62.       else
  63.          is_dialogbox_quit_validated = true; // if game hasn't started yet, quit without question
  64.  
  65.       return (0); // don't let Windows do the default processing on this message
  66.    }
  67.  
  68.    ////////////////////////////////////////////////////////////////////////////////////////////////
  69.    // else is the user closing its session OR the window is being destroyed ?
  70.    else if ((message == WM_QUERYENDSESSION) || (message == WM_ENDSESSION) || (message == WM_DESTROY))
  71.       terminate_everything = true; // suicide (for some reason, PostQuitMessage() is unreliable !?)
  72.  
  73.    ////////////////////////////////////////////////////////////////////////////////////////////////
  74.    // menu event
  75.    else if (message == WM_COMMAND)
  76.    {
  77.       // game menu, new game
  78.       if (wParam_loword == MENUID_GAME_NEWGAME)
  79.       {
  80.          if ((the_board.game_state == STATE_PLAYING) && (the_board.move_count > 1))
  81.             DialogBox_Resign (); // if a game is playing, ask to resign first
  82.          else
  83.             DialogBox_NewGame (); // game not started yet, show the new game dialog box
  84.       }
  85.  
  86.       // game menu, setup start position
  87.       else if (wParam_loword == MENUID_GAME_SETUPPOSITION)
  88.       {
  89.          if ((the_board.game_state == STATE_PLAYING) && (the_board.move_count > 1))
  90.             DialogBox_Resign (); // if a game is playing, ask to resign first
  91.          else
  92.          {
  93.             current_move = &the_board.moves[0]; // quick access to start move
  94.             memset (&current_move->slots, 0, sizeof (current_move->slots)); // erase all slots
  95.  
  96.             the_board.game_state = STATE_SETUPPOSITION; // game not started yet, enter board setup mode
  97.             the_board.reevaluate = true; // evaluate board again
  98.  
  99.             // display the "please choose start position" phrase in the middle of the screen
  100.             Scene_SetText (&the_scene.gui.central_text, 50.0f, 50.0f, -1, ALIGN_CENTER, ALIGN_CENTER, ALIGN_CENTER, centermsg_fontindex, RGBA_TO_RGBACOLOR (255, 255, 255, 191),
  101.                            999999.0f, true, LOCALIZE (L"SetupMode"));
  102.             the_scene.gui.partspick_selectedpart = ' '; // no selected part yet
  103.             the_scene.update = true; // and update the 3D scene
  104.          }
  105.       }
  106.  
  107.       // game menu, load game
  108.       else if (wParam_loword == MENUID_GAME_LOAD)
  109.          DialogBox_Load (); // fire up the load dialog box
  110.  
  111.       // game menu, save game
  112.       else if (wParam_loword == MENUID_GAME_SAVE)
  113.       {
  114.          if (save_pathname[0] != 0)
  115.             is_dialogbox_save_validated = true; // if the filename is known, save it directly
  116.          else
  117.             DialogBox_Save (); // else fire up the save dialog box
  118.       }
  119.  
  120.       // game menu, save as
  121.       else if (wParam_loword == MENUID_GAME_SAVEAS)
  122.          DialogBox_Save (); // fire up the save dialog box
  123.  
  124.       // game menu, save position as
  125.       else if (wParam_loword == MENUID_GAME_SAVEPOSITIONAS)
  126.          DialogBox_SavePosition (); // fire up the save position dialog box
  127.  
  128.       // game menu, resign
  129.       else if (wParam_loword == MENUID_GAME_RESIGN)
  130.          DialogBox_Resign (); // if a game has started, ask for confirmation
  131.  
  132.       // game menu, statistics (stats are only available in online mode)
  133.       else if (wParam_loword == MENUID_GAME_STATISTICS)
  134.       {
  135.          local_player = Player_FindByType (PLAYER_HUMAN);
  136.          if (local_player != NULL)
  137.             PlayerCard_FindOrCreate (local_player->name); // display player's own player card
  138.       }
  139.  
  140.       // game menu, options
  141.       else if (wParam_loword == MENUID_GAME_OPTIONS)
  142.          DialogBox_Options (); // fire up the options dialog box
  143.  
  144.       // game menu, quit
  145.       else if (wParam_loword == MENUID_GAME_QUIT)
  146.       {
  147.          if (the_board.game_state == STATE_PLAYING)
  148.             DialogBox_Quit (); // if a game has started, ask for confirmation
  149.          else
  150.             is_dialogbox_quit_validated = true; // if game hasn't started yet, quit without question
  151.       }
  152.  
  153.       // chessboard menu, suggest move
  154.       else if (wParam_loword == MENUID_CHESSBOARD_SUGGESTMOVE)
  155.          the_board.players[current_viewer].wants_hint = true; // remember this player wants a hint
  156.  
  157.       // chessboard menu, cancel last move
  158.       else if (wParam_loword == MENUID_CHESSBOARD_CANCELLASTMOVE)
  159.          the_board.players[current_viewer].wants_cancel = true; // remember this player wants to cancel his last move
  160.  
  161.       // chessboard menu, comment move
  162.       else if (wParam_loword == MENUID_CHESSBOARD_COMMENTMOVE)
  163.          DialogBox_Comment (); // fire up the comment dialog box
  164.  
  165.       // chessboard menu, go to move
  166.       else if (wParam_loword == MENUID_CHESSBOARD_GOTOMOVE)
  167.          DialogBox_GoToMove (); // fire up the go to move dialog box
  168.  
  169.       // chessboard menu, swap sides
  170.       else if (wParam_loword == MENUID_CHESSBOARD_SWAPSIDES)
  171.          the_board.want_playerswap = true; // remember board sides are to be swapped
  172.  
  173.       // chessboard menu, beginning of game
  174.       else if (wParam_loword == MENUID_CHESSBOARD_BEGINNINGOFGAME)
  175.       {
  176.          the_board.viewed_move = 0; // enter view mode and go to the beginning of the game
  177.          Audio_PlaySound (SOUNDTYPE_CLICK); // make a click sound
  178.          the_board.reevaluate = true; // evaluate board again
  179.       }
  180.  
  181.       // chessboard menu, previous move (only if arrow is enabled)
  182.       else if ((wParam_loword == MENUID_CHESSBOARD_PREVIOUSMOVE) && (the_scene.gui.larrow.state != 0))
  183.       {
  184.          the_board.viewed_move--; // enter view mode and go back one move
  185.          Audio_PlaySound (SOUNDTYPE_CLICK); // make a click sound
  186.          the_board.reevaluate = true; // evaluate board again
  187.       }
  188.  
  189.       // chessboard menu, next move (only if arrow is enabled)
  190.       else if ((wParam_loword == MENUID_CHESSBOARD_NEXTMOVE) && (the_scene.gui.rarrow.state != 0))
  191.       {
  192.          the_board.viewed_move++; // enter view mode and go forward one move
  193.          Audio_PlaySound (SOUNDTYPE_CLICK); // make a click sound
  194.          the_board.reevaluate = true; // evaluate board again
  195.       }
  196.  
  197.       // chessboard menu, current state of game
  198.       else if (wParam_loword == MENUID_CHESSBOARD_CURRENTSTATEOFGAME)
  199.       {
  200.          the_board.viewed_move = the_board.move_count - 1; // enter view mode and go to the current state of the game
  201.          Audio_PlaySound (SOUNDTYPE_CLICK); // make a click sound
  202.          the_board.reevaluate = true; // evaluate board again
  203.       }
  204.  
  205.       // chessboard menu, top view
  206.       else if (wParam_loword == MENUID_CHESSBOARD_TOPVIEW)
  207.       {
  208.          the_board.players[current_viewer].view_pitch = 89.0f;
  209.          the_board.players[current_viewer].view_yaw = (current_viewer == COLOR_BLACK ? 90.0f : -90.0f);
  210.          the_board.players[current_viewer].view_distance = 58.0f;
  211.       }
  212.  
  213.       // chessboard menu, default view
  214.       else if (wParam_loword == MENUID_CHESSBOARD_DEFAULTVIEW)
  215.       {
  216.          the_board.players[current_viewer].view_pitch = 55.0f;
  217.          the_board.players[current_viewer].view_yaw = (current_viewer == COLOR_BLACK ? 90.0f : -90.0f);
  218.          the_board.players[current_viewer].view_distance = 70.0f;
  219.       }
  220.  
  221.       // chessboard menu, reset view
  222.       else if (wParam_loword == MENUID_CHESSBOARD_RESETVIEW)
  223.       {
  224.          the_board.players[current_viewer].view_pitch = the_board.players[current_viewer].custom_pitch;
  225.          the_board.players[current_viewer].view_yaw = the_board.players[current_viewer].custom_yaw;
  226.          the_board.players[current_viewer].view_distance = the_board.players[current_viewer].custom_distance;
  227.       }
  228.  
  229.       // chessboard menu, zoom in
  230.       else if (wParam_loword == MENUID_CHESSBOARD_ZOOMIN)
  231.       {
  232.          // scroll up & save this as the new custom distance
  233.          the_board.players[current_viewer].view_distance = max (48.0f, the_board.players[current_viewer].view_distance - 2.0f);
  234.          the_board.players[current_viewer].custom_distance = the_board.players[current_viewer].view_distance;
  235.          the_scene.update = true; // update the 3D scene
  236.       }
  237.  
  238.       // chessboard menu, zoom out
  239.       else if (wParam_loword == MENUID_CHESSBOARD_ZOOMOUT)
  240.       {
  241.          // scroll down & save this as the new custom distance
  242.          the_board.players[current_viewer].view_distance = min (100.0f, the_board.players[current_viewer].view_distance + 2.0f);
  243.          the_board.players[current_viewer].custom_distance = the_board.players[current_viewer].view_distance;
  244.          the_scene.update = true; // update the 3D scene
  245.       }
  246.  
  247.       // chessboard menu, change appearance
  248.       else if (wParam_loword == MENUID_CHESSBOARD_CHANGEAPPEARANCE)
  249.          DialogBox_ChangeAppearance ();
  250.  
  251.       // chessboard menu, display windows desktop
  252.       else if (wParam_loword == MENUID_CHESSBOARD_DISPLAYWINDOWSDESKTOP)
  253.          ShowWindow (hWnd, SW_MINIMIZE);
  254.  
  255.       // internet menu, show online players
  256.       else if (wParam_loword == MENUID_INTERNET_SHOWONLINEPLAYERS)
  257.          Window_Opponents ();
  258.  
  259.       // internet menu, show sought games
  260.       else if (wParam_loword == MENUID_INTERNET_SHOWSOUGHTGAMES)
  261.          Window_Sought ();
  262.  
  263.       // internet menu, seek game
  264.       else if (wParam_loword == MENUID_INTERNET_SEEKGAME)
  265.          DialogBox_SendSeek ();
  266.  
  267.       // internet menu, chatter channels
  268.       else if (wParam_loword == MENUID_INTERNET_CHATTERCHANNELS)
  269.          Window_ChatterChannels ();
  270.  
  271.       // internet menu, enter chat text
  272.       else if (wParam_loword == MENUID_INTERNET_ENTERCHATTEXT)
  273.          PostMessage (hWnd, WM_CHAR, L' ', 0); // emulate a space bar hit
  274.  
  275.       // internet menu, display player card
  276.       else if (wParam_loword == MENUID_INTERNET_DISPLAYPLAYERCARD)
  277.          DialogBox_PlayerInfoName ();
  278.  
  279.       // internet menu, display your card
  280.       else if (wParam_loword == MENUID_INTERNET_DISPLAYYOURCARD)
  281.       {
  282.          local_player = Player_FindByType (PLAYER_HUMAN);
  283.          if (local_player != NULL)
  284.             PlayerCard_FindOrCreate (local_player->name); // display player's own player card
  285.       }
  286.  
  287.       // internet menu, MOTD
  288.       else if (wParam_loword == MENUID_INTERNET_MOTD)
  289.          Window_MOTD ();
  290.  
  291.       // help menu, help
  292.       else if (wParam_loword == MENUID_HELP_HELP)
  293.          ShellExecute (NULL, L"open", L"Chess Giants.html", NULL, NULL, SW_MAXIMIZE); // fire up the help file
  294.  
  295.       // help menu, get chess games
  296.       else if (wParam_loword == MENUID_HELP_GETCHESSGAMES)
  297.          ShellExecute (NULL, L"open", L"http://www.chessgames.com", NULL, NULL, SW_MAXIMIZE); // fire up the browser
  298.  
  299.       // help menu, about
  300.       else if (wParam_loword == MENUID_HELP_ABOUT)
  301.          DialogBox_About (); // show the About dialog box
  302.  
  303.       // call the default window message processing function to keep things going
  304.       return (DefWindowProc (hWnd, message, wParam, lParam));
  305.    }
  306.  
  307.    /////////////////////////////////////////////////////
  308.    // ctrl key press or release (while not in animation)
  309.    else if ((message == WM_KEYDOWN) && (wParam == VK_CONTROL) && (animation_endtime + 1.0f < current_time))
  310.    {
  311.       ctrl_pressed = true; // remember the ctrl key is pressed
  312.  
  313.       // call the default window message processing function to keep things going
  314.       return (DefWindowProc (hWnd, message, wParam, lParam));
  315.    }
  316.    else if ((message == WM_KEYUP) && (wParam == VK_CONTROL) && (animation_endtime + 1.0f < current_time))
  317.    {
  318.       ctrl_pressed = false; // remember the ctrl key is released
  319.       rbutton_pushed = false; // remember button is released
  320.       the_scene.update = true; // update the 3D scene
  321.  
  322.       // call the default window message processing function to keep things going
  323.       return (DefWindowProc (hWnd, message, wParam, lParam));
  324.    }
  325.  
  326.    ////////////////////////////////////////////////////////////////////////////////////////////////
  327.    // left mouse button push (while not in animation)
  328.    else if ((message == WM_LBUTTONDOWN) && (animation_endtime + 1.0f < current_time))
  329.    {
  330.       // is the ctrl key pressed (emulates a right click) ?
  331.       if (ctrl_pressed)
  332.       {
  333.          prevgui_x = GET_X_LPARAM (lParam); // remember mouse coordinates
  334.          prevgui_y = GET_Y_LPARAM (lParam);
  335.  
  336.          // if we click something, stop moving the table immediately
  337.          the_board.players[current_viewer].view_pitch = current_pitch;
  338.          the_board.players[current_viewer].view_yaw = current_yaw;
  339.  
  340.          rbutton_pushed = true; // remember button is clicked
  341.       }
  342.  
  343.       // call the default window message processing function to keep things going
  344.       return (DefWindowProc (hWnd, message, wParam, lParam));
  345.    }
  346.  
  347.    ////////////////////////////////////////////////////////////////////////////////////////////////
  348.    // left mouse button release (while not in animation)
  349.    else if ((message == WM_LBUTTONUP) && (animation_endtime + 1.0f < current_time))
  350.    {
  351.       // is the ctrl key pressed (emulates a right click) ?
  352.       if (ctrl_pressed)
  353.       {
  354.          rbutton_pushed = false; // remember button is released
  355.          the_scene.update = true; // update the 3D scene
  356.  
  357.          // call the default window message processing function to keep things going
  358.          return (DefWindowProc (hWnd, message, wParam, lParam));
  359.       }
  360.  
  361.       prevgui_x = GET_X_LPARAM (lParam); // remember mouse coordinates
  362.       prevgui_y = GET_Y_LPARAM (lParam);
  363.  
  364.       // get mouse coordinates
  365.       gui_x = GET_X_LPARAM (lParam);
  366.       gui_y = GET_Y_LPARAM (lParam);
  367.  
  368.       // is the left arrow displayed AND is the mouse hovering it ?
  369.       if ((the_scene.gui.larrow.state != 0) && Button_IsHovered (&the_scene.gui.larrow, gui_x, gui_y))
  370.          SendMessage (hWnd, WM_COMMAND, MENUID_CHESSBOARD_PREVIOUSMOVE, NULL); // send a "previous move" event
  371.  
  372.       // is the right arrow displayed AND is the mouse hovering it ?
  373.       if ((the_scene.gui.rarrow.state != 0) && Button_IsHovered (&the_scene.gui.rarrow, gui_x, gui_y))
  374.          SendMessage (hWnd, WM_COMMAND, MENUID_CHESSBOARD_NEXTMOVE, NULL); // send a "next move" event
  375.  
  376.       // is the chat button displayed AND is the mouse hovering it ?
  377.       if ((the_scene.gui.chatbutton.state != 0) && Button_IsHovered (&the_scene.gui.chatbutton, gui_x, gui_y))
  378.       {
  379.          opposite_player = Player_FindByType (PLAYER_INTERNET); // get a hand on the remote player
  380.  
  381.          // find or create the corresponding interlocutor structure and fire it up
  382.          if (opposite_player != NULL)
  383.             Interlocutor_FindOrCreate (opposite_player->name);
  384.       }
  385.  
  386.       // is the games button displayed AND is the mouse hovering it ?
  387.       if ((the_scene.gui.gamesbutton.state != 0) && Button_IsHovered (&the_scene.gui.gamesbutton, gui_x, gui_y))
  388.          Window_Sought (); // if so, display the sought games window
  389.  
  390.       // is the people button displayed AND is the mouse hovering it ?
  391.       if ((the_scene.gui.peoplebutton.state != 0) && Button_IsHovered (&the_scene.gui.peoplebutton, gui_x, gui_y))
  392.          Window_Opponents (); // if so, display the opponents window
  393.  
  394.       // is the parts selection line displayed AND is the mouse anywhere near it ?
  395.       if (the_scene.gui.is_partspick_displayed && Render_IsMouseInBox (gui_x, gui_y, 0.0f, 0.0f, 100.0f, 11.0f))
  396.       {
  397.          // for each selectable part, if the mouse is on it, mark it as selected
  398.          for (part_index = 0; part_index < 13; part_index++)
  399.             if (Render_IsMouseInBox (gui_x, gui_y, part_index * (100.0f / 13.0f), 0, 100.0f / 13.0f, 11.0f))
  400.             {
  401.                the_scene.gui.partspick_selectedpart = selectable_parts[part_index]; // mark it as selected
  402.                Audio_PlaySound (SOUNDTYPE_CLICK); // make a click sound
  403.                break; // no need to search further if one selection was found
  404.             }
  405.       }
  406.  
  407.       // get current player and see if we're online
  408.       current_player = Player_GetCurrent ();
  409.       remote_player = Player_FindByType (PLAYER_INTERNET);
  410.  
  411.       // if we are not allowed to select anything, don't even try
  412.       if ((current_player->type != PLAYER_HUMAN) || (the_board.viewed_move != the_board.move_count - 1)
  413.           || ((remote_player != NULL) && !remote_player->is_in_game))
  414.       {
  415.          the_scene.update = true; // update the 3D scene
  416.  
  417.          // call the default window message processing function to keep things going
  418.          return (DefWindowProc (hWnd, message, wParam, lParam));
  419.       }
  420.  
  421.       // it's a single click. The user clicked on a square.
  422.  
  423.       // figure out the coordinates on table
  424.       Render_MouseToFloor (gui_x, gui_y, &board_x, &board_y);
  425.  
  426.       // translate them to board coordinates
  427.       the_board.hovered_position[0] = (int) floor ((20.0f - board_y) / 5.0f);
  428.       the_board.hovered_position[1] = 7 - (int) floor ((20.0f - board_x) / 5.0f);
  429.       highlight_endtime = 0; // stop highlighting anything
  430.  
  431.       // if it's outside the table, end the job
  432.       if (!IS_VALID_POSITION (the_board.hovered_position))
  433.       {
  434.          the_scene.update = true; // update the 3D scene
  435.  
  436.          // call the default window message processing function to keep things going
  437.          return (DefWindowProc (hWnd, message, wParam, lParam));
  438.       }
  439.  
  440.       // we clicked a valid slot on the table
  441.  
  442.       current_move = &the_board.moves[the_board.viewed_move]; // quick access to current move
  443.  
  444.       ///////////////////////////////////
  445.       // are we in parts placement mode ?
  446.       if (the_board.game_state == STATE_SETUPPOSITION)
  447.       {
  448.          // quick access to line and column
  449.          line = the_board.hovered_position[0];
  450.          column = the_board.hovered_position[1];
  451.  
  452.          // figure out the color of the part we are placing
  453.          if (the_scene.gui.partspick_selectedpart == tolower (the_scene.gui.partspick_selectedpart))
  454.             part_color = COLOR_BLACK; // black color
  455.          else
  456.             part_color = COLOR_WHITE; // white color
  457.  
  458.          // are we erasing a king ? if so, replace it at its default location
  459.          if ((current_move->slots[line][column].part == PART_KING) && (current_move->slots[line][column].color == COLOR_BLACK)
  460.              && (the_scene.gui.partspick_selectedpart != 'k'))
  461.          {
  462.             // is it ALREADY the king's default location ? if so, give up
  463.             if ((line == 7) && (column == 4))
  464.             {
  465.                Audio_PlaySound (SOUNDTYPE_ILLEGALMOVE); // play the "illegal move" sound
  466.                return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message proc
  467.             }
  468.  
  469.             Move_SetSlot (current_move, 7, 4, COLOR_BLACK, PART_KING); // replace black king
  470.          }
  471.          else if ((current_move->slots[line][column].part == PART_KING) && (current_move->slots[line][column].color == COLOR_WHITE)
  472.                   && (the_scene.gui.partspick_selectedpart != 'K'))
  473.          {
  474.             // is it ALREADY the king's default location ? if so, give up
  475.             if ((line == 0) && (column == 4))
  476.             {
  477.                Audio_PlaySound (SOUNDTYPE_ILLEGALMOVE); // play the "illegal move" sound
  478.                return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message proc
  479.             }
  480.  
  481.             Move_SetSlot (current_move, 0, 4, COLOR_WHITE, PART_KING); // replace white king
  482.          }
  483.  
  484.          // place the selected part on the board
  485.          if (tolower (the_scene.gui.partspick_selectedpart) == 'p')
  486.             Move_SetSlot (current_move, line, column, part_color, PART_PAWN); // pawn
  487.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'r')
  488.             Move_SetSlot (current_move, line, column, part_color, PART_ROOK); // rook
  489.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'n')
  490.             Move_SetSlot (current_move, line, column, part_color, PART_KNIGHT); // knight
  491.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'b')
  492.             Move_SetSlot (current_move, line, column, part_color, PART_BISHOP); // bishop
  493.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'q')
  494.             Move_SetSlot (current_move, line, column, part_color, PART_QUEEN); // queen
  495.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'k')
  496.          {
  497.             // parse the board for other kings of the same color and erase them
  498.             for (index_line = 0; index_line < 8; index_line++)
  499.                for (index_column = 0; index_column < 8; index_column++)
  500.                   if ((current_move->slots[index_line][index_column].color == part_color)
  501.                       && (current_move->slots[index_line][index_column].part == PART_KING))
  502.                      memset (&current_move->slots[index_line][index_column], 0, sizeof (boardslot_t)); // erase this slot
  503.  
  504.             Move_SetSlot (current_move, line, column, part_color, PART_KING); // king
  505.          }
  506.          else
  507.             Move_SetSlot (current_move, line, column, 0, PART_NONE); // no part
  508.  
  509.          // figure out which sound to play
  510.          if (the_scene.gui.partspick_selectedpart != ' ')
  511.             Audio_PlaySound (SOUNDTYPE_MOVE); // play a move sound when placing a part
  512.  
  513.          the_scene.update = true; // update the 3D scene
  514.  
  515.          // call the default window message processing function to keep things going
  516.          return (DefWindowProc (hWnd, message, wParam, lParam));
  517.       }
  518.       // end of parts placement mode
  519.       //////////////////////////////
  520.  
  521.       // does a selection NOT exist yet ?
  522.       if (!IS_VALID_POSITION (the_board.selected_position))
  523.       {
  524.          // is there a selectable part at this location ?
  525.          if ((current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].part != PART_NONE)
  526.             && (current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].color == Board_ColorToMove (&the_board)))
  527.          {
  528.             // mark the selected position as selected (and remember it)
  529.             the_board.selected_position[0] = the_board.hovered_position[0];
  530.             the_board.selected_position[1] = the_board.hovered_position[1];
  531.          }
  532.  
  533.          the_scene.update = true; // update the 3D scene
  534.  
  535.          // call the default window message processing function to keep things going
  536.          return (DefWindowProc (hWnd, message, wParam, lParam));
  537.       }
  538.  
  539.       // a selection exists already
  540.  
  541.       // is it the slot that was previously selected ? (i.e, user wants to "unselect" it)
  542.       if ((the_board.hovered_position[0] == the_board.selected_position[0]) && (the_board.hovered_position[1] == the_board.selected_position[1]))
  543.       {
  544.          // forget the selected position
  545.          the_board.selected_position[0] = -1;
  546.          the_board.selected_position[1] = -1;
  547.  
  548.          the_scene.update = true; // update the 3D scene
  549.  
  550.          // call the default window message processing function to keep things going
  551.          return (DefWindowProc (hWnd, message, wParam, lParam));
  552.       }
  553.  
  554.       // else is it another part of the same color ? (i.e, user wants to change the part he selected)
  555.       else if ((current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].part != PART_NONE)
  556.                && (current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].color == Board_ColorToMove (&the_board)))
  557.       {
  558.          // mark the selected position as selected (and remember it)
  559.          the_board.selected_position[0] = the_board.hovered_position[0];
  560.          the_board.selected_position[1] = the_board.hovered_position[1];
  561.  
  562.          the_scene.update = true; // update the 3D scene
  563.  
  564.          // call the default window message processing function to keep things going
  565.          return (DefWindowProc (hWnd, message, wParam, lParam));
  566.       }
  567.  
  568.       // else is it a possible move ?
  569.       else if ((current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].flags & FLAG_POSSIBLEMOVE)
  570.                || (current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].flags & FLAG_TAKEABLE))
  571.       {
  572.          // are we in check after the move ? (FIXME: call EP version of this func for en passant moves)
  573.          if (Move_IsColorInCheckAfterTestMove (current_move, the_board.selected_position[0], the_board.selected_position[1], the_board.hovered_position[0], the_board.hovered_position[1], Board_ColorToMove (&the_board)))
  574.          {
  575.             Audio_PlaySound (SOUNDTYPE_ILLEGALMOVE); // play the "illegal move" sound
  576.  
  577.             the_scene.update = true; // update the 3D scene
  578.  
  579.             // call the default window message processing function to keep things going
  580.             return (DefWindowProc (hWnd, message, wParam, lParam));
  581.          }
  582.  
  583.          ////////////////////
  584.          // movement is valid
  585.  
  586.          // do it
  587.          Board_AppendMove (&the_board, the_board.selected_position[0], the_board.selected_position[1], the_board.hovered_position[0], the_board.hovered_position[1], PART_NONE, NULL);
  588.          current_move = &the_board.moves[the_board.move_count - 1]; // update current move pointer
  589.  
  590.          // are we in internet mode ?
  591.          if (remote_player != NULL)
  592.             the_board.reevaluate = false; // if so, don't reevaluate the board yet, let the server do it
  593.  
  594.          // was it a pawn being promoted ? if so, display the dialog box and wait for the reply
  595.          if ((current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].part == PART_PAWN)
  596.              && ((the_board.hovered_position[0] == 0) || (the_board.hovered_position[0] == 7)))
  597.             DialogBox_PawnPromotion (); // display the pawn promotion dialog box
  598.  
  599.          // else it was a normal move
  600.          else
  601.          {
  602.             Board_SetSelectedAndHovered (&the_board, -1, -1, -1, -1); // forget the hovered and selected positions
  603.             the_board.has_playerchanged = true; // and switch players
  604.          }
  605.  
  606.          the_scene.update = true; // update the 3D scene
  607.          animation_endtime = current_time + ANIMATION_DURATION; // play animation now
  608.          sound_playtime = current_time + ANIMATION_DURATION - 0.1f; // play sound near the end of animation
  609.  
  610.          // call the default window message processing function to keep things going
  611.          return (DefWindowProc (hWnd, message, wParam, lParam));
  612.       }
  613.  
  614.       // else it's another location
  615.  
  616.       Audio_PlaySound (SOUNDTYPE_ILLEGALMOVE); // play the "illegal move" sound
  617.  
  618.       the_scene.update = true; // update the 3D scene
  619.  
  620.       // call the default window message processing function to keep things going
  621.       return (DefWindowProc (hWnd, message, wParam, lParam));
  622.    }
  623.  
  624.    ////////////////////////////////////////////////////////////////////////////////////////////////
  625.    // right mouse button push (while not in animation)
  626.    else if ((message == WM_RBUTTONDOWN) && (animation_endtime + 1.0f < current_time))
  627.    {
  628.       prevgui_x = GET_X_LPARAM (lParam); // remember mouse coordinates
  629.       prevgui_y = GET_Y_LPARAM (lParam);
  630.  
  631.       // if we click something, stop moving the table immediately
  632.       the_board.players[current_viewer].view_pitch = current_pitch;
  633.       the_board.players[current_viewer].view_yaw = current_yaw;
  634.  
  635.       rbutton_pushed = true; // remember button is clicked
  636.  
  637.       // call the default window message processing function to keep things going
  638.       return (DefWindowProc (hWnd, message, wParam, lParam));
  639.    }
  640.  
  641.    ////////////////////////////////////////////////////////////////////////////////////////////////
  642.    // right mouse button release (while not in animation)
  643.    else if ((message == WM_RBUTTONUP) && (animation_endtime + 1.0f < current_time))
  644.    {
  645.       rbutton_pushed = false; // remember button is released
  646.       the_scene.update = true; // update the 3D scene
  647.  
  648.       // call the default window message processing function to keep things going
  649.       return (DefWindowProc (hWnd, message, wParam, lParam));
  650.    }
  651.  
  652.    /////////////////////////////////
  653.    // left mouse button DOUBLE-click
  654.    else if ((message == WM_LBUTTONDBLCLK) && (animation_endtime + 1.0f < current_time))
  655.    {
  656.       // get mouse coordinates
  657.       gui_x = GET_X_LPARAM (lParam);
  658.       gui_y = GET_Y_LPARAM (lParam);
  659.  
  660.       // are we in game and did we click the move comments area ?
  661.       if ((the_board.game_state >= STATE_PLAYING) && (the_board.viewed_move > 0) && Render_IsMouseInBox (gui_x, gui_y, 10.0f, 0.0f, 80.0f, 10.0f))
  662.       {
  663.          DialogBox_Comment (); // fire up the comment dialog box
  664.          return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message processing function to keep things going
  665.       }
  666.    }
  667.  
  668.    ////////////////////////////////////////////////////////////////////////////////////////////////
  669.    // mouse move (while not in animation)
  670.    else if ((message == WM_MOUSEMOVE) && (animation_endtime + 1.0f < current_time))
  671.    {
  672.       // get mouse coordinates
  673.       gui_x = GET_X_LPARAM (lParam);
  674.       gui_y = GET_Y_LPARAM (lParam);
  675.  
  676.       // handle button update status
  677.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.larrow, gui_x, gui_y);
  678.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.rarrow, gui_x, gui_y);
  679.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.chatbutton, gui_x, gui_y);
  680.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.gamesbutton, gui_x, gui_y);
  681.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.peoplebutton, gui_x, gui_y);
  682.  
  683.       // see if we're online
  684.       remote_player = Player_FindByType (PLAYER_INTERNET);
  685.  
  686.       // are we online AND do we NOT have the right to select anything ?
  687.       if ((remote_player != NULL) && !remote_player->is_in_game)
  688.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default window message processing function to keep things going
  689.  
  690.       // is the parts selection line displayed AND is the mouse anywhere in it ?
  691.       if (the_scene.gui.is_partspick_displayed && Render_IsMouseInBox (gui_x, gui_y, 0.0f, 0.0f, 100.0f, 11.0f))
  692.       {
  693.          // for each selectable part...
  694.          for (part_index = 0; part_index < 13; part_index++)
  695.          {
  696.             // is the mouse hovering it ?
  697.             if (Render_IsMouseInBox (gui_x, gui_y, part_index * (100.0f / 13.0f), 0, 100.0f / 13.0f, 11.0f))
  698.             {
  699.                // was it NOT hovered before ?
  700.                if (the_scene.gui.partspick_hoveredpart != selectable_parts[part_index])
  701.                {
  702.                   the_scene.gui.partspick_hoveredpart = selectable_parts[part_index]; // mark it as hovered
  703.                   the_scene.update = true; // update the scene
  704.                }
  705.             }
  706.  
  707.             // else was it hovered before ?
  708.             else if (the_scene.gui.partspick_hoveredpart == selectable_parts[part_index])
  709.             {
  710.                the_scene.gui.partspick_hoveredpart = 0; // clear the hovered part
  711.                the_scene.update = true; // update the scene
  712.             }
  713.          }
  714.       }
  715.       else
  716.          the_scene.gui.partspick_hoveredpart = 0; // clear the hovered part
  717.  
  718.       // get current and opposite players
  719.       current_player = Player_GetCurrent ();
  720.       opposite_player = Player_GetOpposite ();
  721.  
  722.       // if right button was clicked, compute new pitch and yaw
  723.       if (rbutton_pushed)
  724.       {
  725.          the_board.players[current_viewer].view_pitch += (gui_y - prevgui_y) * 0.3f;
  726.          if (the_board.players[current_viewer].view_pitch < 10.0f)
  727.             the_board.players[current_viewer].view_pitch = 10.0f; // wrap angles around so that they
  728.          if (the_board.players[current_viewer].view_pitch > 89.0f)
  729.             the_board.players[current_viewer].view_pitch = 89.0f; // stay in the [10, 89] bounds
  730.  
  731.          the_board.players[current_viewer].view_yaw += (gui_x - prevgui_x) * 0.3f;
  732.          the_board.players[current_viewer].view_yaw = WrapAngle (the_board.players[current_viewer].view_yaw);
  733.  
  734.          // save these as the new custom angles
  735.          the_board.players[current_viewer].custom_pitch = the_board.players[current_viewer].view_pitch;
  736.          the_board.players[current_viewer].custom_yaw = the_board.players[current_viewer].view_yaw;
  737.  
  738.          // when moving the table around, jump to ideal angles immediately
  739.          current_pitch = the_board.players[current_viewer].view_pitch;
  740.          current_yaw = the_board.players[current_viewer].view_yaw;
  741.  
  742.          the_scene.update = true; // button was clicked, update the 3D scene
  743.       }
  744.  
  745.       // else it's just the mouse wandering around ; have we the right to select something ?
  746.       else if ((the_board.viewed_move == the_board.move_count - 1) && (current_player->type == PLAYER_HUMAN) && (highlight_endtime < current_time))
  747.       {
  748.          // save the old positions
  749.          prev_hovered_position[0] = the_board.hovered_position[0];
  750.          prev_hovered_position[1] = the_board.hovered_position[1];
  751.  
  752.          // figure out the coordinates on table
  753.          Render_MouseToFloor (gui_x, gui_y, &board_x, &board_y);
  754.  
  755.          // translate them to board coordinates
  756.          the_board.hovered_position[0] = (int) floor ((20.0f - board_y) / 5.0f);
  757.          the_board.hovered_position[1] = 7 - (int) floor ((20.0f - board_x) / 5.0f);
  758.  
  759.          // do they differ from last time ?
  760.          if ((the_board.hovered_position[0] != prev_hovered_position[0]) || (the_board.hovered_position[1] != prev_hovered_position[1]))
  761.             the_scene.update = true; // if so, update scene
  762.       }
  763.  
  764.       // has the user the right to leave a command AND is there no comment yet ?
  765.       if ((the_board.game_state >= STATE_PLAYING) && (the_board.viewed_move > 0)
  766.           && ((the_board.moves[the_board.viewed_move].comment == NULL) || (the_board.moves[the_board.viewed_move].comment[0] == 0)))
  767.       {
  768.          // is the mouse above the comments zone ? if so, display a dimmed hint text
  769.          if (Render_IsMouseInBox (gui_x, gui_y, 30.0f, 0.0f, 40.0f, 10.0f))
  770.          {
  771.             if (!the_scene.gui.comment_text.is_displayed)
  772.             {
  773.                Scene_SetText (&the_scene.gui.comment_text, 50.0f, 5.0f, 40.0f, ALIGN_CENTER, ALIGN_CENTER, ALIGN_LEFT, chat_fontindex, RGBA_TO_RGBACOLOR (255, 255, 255, 127), 999999.0f, false, LOCALIZE (L"DoubleClickToEnterComment"));
  774.                the_scene.update = true; // and update the scene
  775.             }
  776.          }
  777.          else
  778.          {
  779.             if (the_scene.gui.comment_text.is_displayed)
  780.             {
  781.                the_scene.gui.comment_text.is_displayed = false; // if not, erase the hint text
  782.                the_scene.update = true; // and update the scene
  783.             }
  784.          }
  785.       }
  786.  
  787.       // remember these coordinates for next time
  788.       prevgui_x = gui_x;
  789.       prevgui_y = gui_y;
  790.  
  791.       // call the default window message processing function to keep things going
  792.       return (DefWindowProc (hWnd, message, wParam, lParam));
  793.    }
  794.  
  795.    ////////////////////////////////////////////////////////////////////////////////////////////////
  796.    // mouse scroll (while not in animation)
  797.    else if ((message == WM_MOUSEWHEEL) && (animation_endtime + 1.0f < current_time))
  798.    {
  799.       // see if we're online
  800.       remote_player = Player_FindByType (PLAYER_INTERNET);
  801.  
  802.       // are we online AND do we NOT have the right to select anything ?
  803.       if ((remote_player != NULL) && !remote_player->is_in_game)
  804.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default window message processing function to keep things going
  805.  
  806.       // scroll up / scroll down ?
  807.       if (GET_WHEEL_DELTA_WPARAM (wParam) > 0)
  808.          the_board.players[current_viewer].view_distance = max (48.0f, the_board.players[current_viewer].view_distance - 2.0f);
  809.       else if (GET_WHEEL_DELTA_WPARAM (wParam) < 0)
  810.          the_board.players[current_viewer].view_distance = min (100.0f, the_board.players[current_viewer].view_distance + 2.0f);
  811.  
  812.       // save this as the new custom distance
  813.       the_board.players[current_viewer].custom_distance = the_board.players[current_viewer].view_distance;
  814.  
  815.       // when moving the table around, jump to ideal angles immediately
  816.       current_distance = the_board.players[current_viewer].view_distance;
  817.  
  818.       the_scene.update = true; // update the 3D scene
  819.  
  820.       // call the default window message processing function to keep things going
  821.       return (DefWindowProc (hWnd, message, wParam, lParam));
  822.    }
  823.  
  824.    ////////////////////////////////////////////////////////////////////////////////////////////////
  825.    // keyboard release
  826.    else if (message == WM_CHAR)
  827.    {
  828.       // are we in position setup mode ?
  829.       if (the_board.game_state == STATE_SETUPPOSITION)
  830.       {
  831.          // is it the enter key ? if so, exit setup mode and start playing
  832.          if (wParam == L'\r')
  833.          {
  834.             current_move = &the_board.moves[the_board.viewed_move]; // quick access to current move
  835.  
  836.             // (in)validate the castling positions
  837.             if ((current_move->slots[0][0].color == COLOR_WHITE) && (current_move->slots[0][0].part == PART_ROOK)
  838.                 && (current_move->slots[0][4].color == COLOR_WHITE) && (current_move->slots[0][4].part == PART_KING))
  839.                current_move->sides[COLOR_WHITE].longcastle_allowed = true; // white castling queenside allowed
  840.             else
  841.                current_move->sides[COLOR_WHITE].longcastle_allowed = false; // white castling queenside no longer possible
  842.             if ((current_move->slots[0][7].color == COLOR_WHITE) && (current_move->slots[0][7].part == PART_ROOK)
  843.                 && (current_move->slots[0][4].color == COLOR_WHITE) && (current_move->slots[0][4].part == PART_KING))
  844.                current_move->sides[COLOR_WHITE].shortcastle_allowed = true; // white castling kingside allowed
  845.             else
  846.                current_move->sides[COLOR_WHITE].shortcastle_allowed = false; // white castling kingside no longer possible
  847.             if ((current_move->slots[7][0].color == COLOR_BLACK) && (current_move->slots[7][0].part == PART_ROOK)
  848.                 && (current_move->slots[7][4].color == COLOR_BLACK) && (current_move->slots[7][4].part == PART_KING))
  849.                current_move->sides[COLOR_BLACK].longcastle_allowed = true; // white castling queenside allowed
  850.             else
  851.                current_move->sides[COLOR_BLACK].longcastle_allowed = false; // white castling queenside no longer possible
  852.             if ((current_move->slots[7][7].color == COLOR_BLACK) && (current_move->slots[7][7].part == PART_ROOK)
  853.                 && (current_move->slots[7][4].color == COLOR_BLACK) && (current_move->slots[7][4].part == PART_KING))
  854.                current_move->sides[COLOR_BLACK].shortcastle_allowed = true; // white castling kingside allowed
  855.             else
  856.                current_move->sides[COLOR_BLACK].shortcastle_allowed = false; // white castling kingside no longer possible
  857.  
  858.             current_move->color = COLOR_BLACK; // so that game starts with white
  859.  
  860.             // validate this board in Forsyth-Edwards Notation and reset it
  861.             Move_DescribeInFEN (current_move);
  862.             wcscpy_s (fen_string, WCHAR_SIZEOF (fen_string), current_move->fen_string); // have a copy of fen string
  863.             Board_Reset (&the_board, fen_string);
  864.  
  865.             the_board.game_state = STATE_PLAYING; // start the game now
  866.             the_board.reevaluate = true; // evaluate board again
  867.  
  868.             the_scene.gui.central_text.disappear_time = current_time + 1.0f; // fade out help text now (FIXME: ugly)
  869.             the_scene.update = true; // update the 3D scene
  870.  
  871.             // call the default window message processing function to keep things going
  872.             return (DefWindowProc (hWnd, message, wParam, lParam));
  873.          }
  874.       }
  875.  
  876.       // else are we in internet mode ?
  877.       else if (Player_FindByType (PLAYER_INTERNET) != NULL)
  878.       {
  879.          entered_ccreply = &the_scene.gui.entered_ccreply; // quick access to entered ccreply
  880.          local_player = Player_FindByType (PLAYER_HUMAN); // quick access to local player
  881.          if (local_player == NULL)
  882.             return (DefWindowProc (hWnd, message, wParam, lParam)); // theoretically impossible condition, but better be sure
  883.          if (selected_chatterchannel == NULL)
  884.             return (DefWindowProc (hWnd, message, wParam, lParam)); // theoretically impossible condition, but better be sure
  885.  
  886.          // are we NOT entering text yet ?
  887.          if (!the_scene.gui.is_entering_text)
  888.          {
  889.             // is it the space bar ?
  890.             if (wParam == L' ')
  891.             {
  892.                the_scene.gui.is_entering_text = true; // remember we are entering text
  893.  
  894.                // reset the entered text buffer
  895.                entered_ccreply->text = (wchar_t *) SAFE_malloc (1, sizeof (wchar_t), false);
  896.                entered_ccreply->text[0] = 0; // only the null terminator will fit
  897.                entered_ccreply->text_length = 0; // and set its length to zero
  898.             }
  899.          }
  900.  
  901.          // else we are currently in text entering mode
  902.          else
  903.          {
  904.             // is the typed character a printable character ?
  905.             if (iswprint (wParam))
  906.             {
  907.                // if so, reallocate space in the typed buffer (include null terminator)
  908.                entered_ccreply->text = (wchar_t *) SAFE_realloc (entered_ccreply->text, entered_ccreply->text_length + 1, entered_ccreply->text_length + 1 + 1, sizeof (wchar_t), false);
  909.                swprintf_s (&entered_ccreply->text[entered_ccreply->text_length], 1 + 1, L"%c", wParam); // append character
  910.                entered_ccreply->text_length++; // buffer holds now one character more
  911.             }
  912.  
  913.             // else is the typed character a backspace AND is there text to erase ?
  914.             else if ((wParam == 0x08) && (entered_ccreply->text_length > 0))
  915.             {
  916.                // if so, reallocate space in the typed buffer (include null terminator)
  917.                entered_ccreply->text = (wchar_t *) SAFE_realloc (entered_ccreply->text, entered_ccreply->text_length + 1, entered_ccreply->text_length + 1 - 1, sizeof (wchar_t), false);
  918.                entered_ccreply->text[entered_ccreply->text_length - 1] = 0; // backspace, delete one character
  919.                entered_ccreply->text_length--; // buffer holds now one character less
  920.             }
  921.  
  922.             // else is the typed character the escape key ?
  923.             else if (wParam == 0x1b)
  924.             {
  925.                SAFE_free ((void **) &entered_ccreply->text); // reset the entered text buffer
  926.                entered_ccreply->text_length = 0; // and set its length to zero
  927.                the_scene.gui.is_entering_text = false; // and exit from the text entering mode
  928.             }
  929.  
  930.             // else is the typed character the enter key ?
  931.             else if (wParam == 0x0d)
  932.                the_scene.gui.is_entering_text = false; // enter, exit from the text entering mode (this will validate our reply)
  933.  
  934.             // else it's an illegal character
  935.             else
  936.                Audio_PlaySound (SOUNDTYPE_ILLEGALMOVE); // illegal character, beep the illegal move sound
  937.          }
  938.  
  939.          the_scene.update = true; // update the 3D scene
  940.  
  941.          // call the default window message processing function to keep things going
  942.          return (DefWindowProc (hWnd, message, wParam, lParam));
  943.       }
  944.    }
  945.  
  946.    ////////////////////////////////////////////////////////////////////////////////////////////////
  947.    // mouse move outside the window
  948.    else if (message == WM_NCMOUSEMOVE)
  949.    {
  950.       rbutton_pushed = false; // remember right button is released
  951.  
  952.       // call the default window message processing function to keep things going
  953.       return (DefWindowProc (hWnd, message, wParam, lParam));
  954.    }
  955.  
  956.    ////////////////////////////////////////////////////////////////////////////////////////////////
  957.    // window repaint
  958.    else if (message == WM_PAINT)
  959.    {
  960.       the_scene.update = true; // update the 3D scene
  961.  
  962.       // call the default window message processing function to keep things going
  963.       return (DefWindowProc (hWnd, message, wParam, lParam));
  964.    }
  965.  
  966.    // call the default window message processing function to keep things going
  967.    return (DefWindowProc (hWnd, message, wParam, lParam));
  968. }
  969.  
  970.  
  971. static bool Button_IsHovered (guibutton_t *button, int gui_x, int gui_y)
  972. {
  973.    // handy wrapper that returns whether a particular GUI button is hovered when the mouse is at the given coordinates
  974.  
  975.    return (Render_IsMouseInBox (gui_x, gui_y, button->left, button->top, button->width, button->height));
  976. }
  977.  
  978.  
  979. static bool Button_UpdateHoverState (guibutton_t *button, int gui_x, int gui_y)
  980. {
  981.    // this function updates the hover state of a GUI button, and returns TRUE if the
  982.    // scene needs to be updated, FALSE otherwise.
  983.  
  984.    // is the button displayed ?
  985.    if (button->state != 0)
  986.    {
  987.       // is the mouse hovering it ?
  988.       if (Render_IsMouseInBox (gui_x, gui_y, button->left, button->top, button->width, button->height))
  989.       {
  990.          // was it NOT hovered before ?
  991.          if (button->state != 2)
  992.          {
  993.             button->state = 2; // mark it as hovered
  994.             return (true); // return TRUE so as to update the scene
  995.          }
  996.       }
  997.  
  998.       // else was it hovered before ?
  999.       else if (button->state == 2)
  1000.       {
  1001.          button->state = 1; // else mark it as not hovered
  1002.          return (true); // return TRUE so as to update the scene
  1003.       }
  1004.    }
  1005.  
  1006.    return (false); // no need to update the scene
  1007. }
  1008.