/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: ControlBarCallback.cpp /////////////////////////////////////////////////////////////////// // Author: Colin Day - October 2001 // Desc: GUI Control bar at the bottom of the screen that houses the // the build buttons, radar etc. /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/NameKeyGenerator.h" #include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/MessageStream.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #include "Common/Radar.h" #include "GameClient/AnimateWindowManager.h" #include "GameClient/Drawable.h" #include "GameClient/GameWindow.h" #include "GameClient/Gadget.h" #include "GameClient/GadgetTextEntry.h" #include "GameClient/GameClient.h" #include "GameClient/GUICallbacks.h" #include "GameClient/InGameUI.h" #include "GameClient/ControlBar.h" #include "GameClient/GameWindowManager.h" #include "GameClient/LanguageFilter.h" #include "GameClient/CommandXlat.h" #include "GameLogic/GameLogic.h" #include "GameLogic/ScriptEngine.h" //external declarations of the Gadgets the callbacks can use WindowLayout *popupCommunicatorLayout = NULL; //------------------------------------------------------------------------------------------------- /** Input procedure for the left HUD */ //------------------------------------------------------------------------------------------------- WindowMsgHandledType LeftHUDInput( GameWindow *window, UnsignedInt msg, WindowMsgData mData1, WindowMsgData mData2 ) { // get player Player *player = ThePlayerList->getLocalPlayer(); // // if the player doesn't have a radar, or the radar is hidden, and the radar is not being // forced to on, we just eat input over the radar window // if( !TheRadar->isRadarForced() && (TheRadar->isRadarHidden() || !player->hasRadar()) ) return MSG_HANDLED; // If the middle mouse button is depressed, then just let the message fall all the // way back to the usual middle mouse button processing. // jkmcd if( TheMouse->getMouseStatus()->middleState == MBS_Down ) return MSG_IGNORED; switch( msg ) { /** @todo This is wrong. The radar should be in the message stream, and eat all messages and propagate them as a new message with the coords converted to world coords and the message flagged as being from the radar. This would let all of the normal processing occur, and allow individual commands to easily reject being used on the radar. */ // ------------------------------------------------------------------------ case GWM_NONE: case GWM_MOUSE_ENTERING: case GWM_MOUSE_LEAVING: { // // consider changing the mouse cursor if we are not in the process of firing // targeted "superweapons" which we can use the radar itself to fire // Bool targeting = FALSE; const CommandButton *command = TheInGameUI->getGUICommand(); if( command && (command->getCommandType() == GUI_COMMAND_SPECIAL_POWER || command->getCommandType() == GUI_COMMAND_SPECIAL_POWER_FROM_SHORTCUT) && BitTest( command->getOptions(), NEED_TARGET_POS ) ) targeting = TRUE; if( targeting == FALSE ) { const DrawableList *drawableList = TheInGameUI->getAllSelectedLocalDrawables(); Mouse::MouseCursor cur = Mouse::ARROW; if (!(drawableList->empty() || msg == GWM_MOUSE_LEAVING)) { if (command && command->getCommandType() == GUI_COMMAND_ATTACK_MOVE) { cur = Mouse::ATTACKMOVETO; } else { cur = Mouse::MOVETO; } } // Groovy TheMouse->setCursor(cur); } // end if return MSG_HANDLED; } // -------------------------------------------------------------------------------------------- case GWM_MOUSE_POS: { // get mouse position ICoord2D mouse; mouse.x = mData1 & 0xFFFF; mouse.y = mData1 >> 16; // get window screen position ICoord2D screenPos; window->winGetScreenPosition( &screenPos.x, &screenPos.y ); // set mouse position to be relative to this window mouse.x -= screenPos.x; mouse.y -= screenPos.y; // is the mouse in the radar window ICoord2D radar; if( (TheRadar->isRadarHidden() == FALSE || TheRadar->isRadarForced()) && TheRadar->localPixelToRadar( &mouse, &radar ) ) { /* // // this is an example piece of code to find the object under the pixel position // of the radar ... should we in the future wish to allow commands to be executed // on objects throught he radar. note tho that this is extremely hard to do because // the pixels on the radar are very small and it's hard to do accurate targeting // Object *obj = TheRadar->objectUnderRadarPixel( &mouse ); UnicodeString msg; if( obj ) msg.format( L"Object under mouse on radar '%S'(%d)", obj->getTemplate()->getName().str(), obj->getID() ); else msg.format( L"Mouse (%d,%d) in Radar window L(%d,%d)", mouse.x, mouse.y, radar.x, radar.y ); TheInGameUI->message( msg ); */ // keep the cursor for any context commands const CommandButton *command = TheInGameUI->getGUICommand(); if( command && (command->getCommandType() == GUI_COMMAND_SPECIAL_POWER || command->getCommandType() == GUI_COMMAND_SPECIAL_POWER_FROM_SHORTCUT) && BitTest( command->getOptions(), NEED_TARGET_POS ) ) { Int index = TheMouse->getCursorIndex( command->getCursorName() ); if( index != Mouse::INVALID_MOUSE_CURSOR ) TheMouse->setCursor( (Mouse::MouseCursor)index ); else TheMouse->setCursor( Mouse::CROSS ); } // end if else { // Else we are not super targeting, so we have to try to refresh the move cursor. // We can't just do this on Enter and Exit, because hotkeys allow state to change // while we are in the radar. const DrawableList *drawableList = TheInGameUI->getAllSelectedLocalDrawables(); Mouse::MouseCursor cur = Mouse::ARROW; if (!(drawableList->empty() || msg == GWM_MOUSE_LEAVING)) { if (command && command->getCommandType() == GUI_COMMAND_ATTACK_MOVE) { cur = Mouse::ATTACKMOVETO; } else { cur = Mouse::MOVETO; } } // Groovy TheMouse->setCursor(cur); } } // end if break; } // end case mouse position // ------------------------------------------------------------------------ case GWM_RIGHT_UP:// Here to eat case GWM_LEFT_UP:// Here to eat break; case GWM_RIGHT_DOWN: case GWM_LEFT_DOWN: { ICoord2D mouse; ICoord2D radar; ICoord2D size; ICoord2D screenPos; Coord3D world; // get window size window->winGetSize( &size.x, &size.y ); // get mouse position mouse.x = mData1 & 0xFFFF; mouse.y = mData1 >> 16; // get window screen position window->winGetScreenPosition( &screenPos.x, &screenPos.y ); // set mouse position to be relative to this window mouse.x -= screenPos.x; mouse.y -= screenPos.y; // // translate mouse position to radar position ... we know that the mouse // location given to us here is relative to the HUD window, which is // completely drawn with the radar ... so it's just a translation from // our window size we're drawing into to the radar cell size // if( (TheRadar->isRadarHidden() == FALSE || TheRadar->isRadarForced()) && TheRadar->localPixelToRadar( &mouse, &radar ) && TheRadar->radarToWorld( &radar, &world ) ) { // No drawables, or a right click automatically means its a look at. // Having drawables and being in attack move mode means that we should attack move. // Having drawables and not being in attack move mode means that we should move. const DrawableList *drawableList = TheInGameUI->getAllSelectedLocalDrawables(); // locally-owned only // see if the user wants to move the tactical view if ( drawableList->empty() || (! TheGlobalData->m_useAlternateMouse && msg == GWM_RIGHT_DOWN) || (TheGlobalData->m_useAlternateMouse && msg == GWM_LEFT_DOWN) ) { TheTacticalView->lookAt( &world ); break; } // evaluate any special powers that can be executed from the radar const CommandButton *command = TheInGameUI->getGUICommand(); if( command && (command->getCommandType() == GUI_COMMAND_SPECIAL_POWER || command->getCommandType() == GUI_COMMAND_SPECIAL_POWER_FROM_SHORTCUT) && BitTest( command->getOptions(), NEED_TARGET_POS ) ) { // do the command TheGameClient->evaluateContextCommand( NULL, &world, CommandTranslator::DO_COMMAND ); } // end if else if( command && command->getCommandType() == GUI_COMMAND_ATTACK_MOVE) { // Attack move has changed from a modifier to a command, so it moves up here. GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_ATTACKMOVETO ); msg->appendLocationArgument( world ); // Play the unit voice response pickAndPlayUnitVoiceResponse(TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_DO_ATTACKMOVETO); } else { GameMessage *newMsg = NULL; // Do the superweapon stuff here, before issuing these other messages // GS Leaving commented out to show that isInAttackMoveToMode is NEVER SET. It's a command now, not a modifier. // if (TheInGameUI->isInAttackMoveToMode()) // { // newMsg = TheMessageStream->appendMessage(GameMessage::MSG_DO_ATTACKMOVETO); // newMsg->appendLocationArgument(world); // // Play the unit voice response // pickAndPlayUnitVoiceResponse(drawableList, GameMessage::MSG_DO_ATTACKMOVETO); // break; // } newMsg = TheMessageStream->appendMessage(GameMessage::MSG_DO_MOVETO); newMsg->appendLocationArgument(world); // Play the unit voice response pickAndPlayUnitVoiceResponse(drawableList, GameMessage::MSG_DO_MOVETO); } // end else } break; } // end left down // ------------------------------------------------------------------------ default: return MSG_IGNORED; } // end switch( msg ) TheInGameUI->clearAttackMoveToMode(); return MSG_HANDLED; } // end LeftHUDInput //------------------------------------------------------------------------------------------------- /** Input procedure for the control bar */ //------------------------------------------------------------------------------------------------- WindowMsgHandledType ControlBarInput( GameWindow *window, UnsignedInt msg, WindowMsgData mData1, WindowMsgData mData2 ) { return MSG_IGNORED; } // end ControlBarInput void ToggleQuitMenu(void); //------------------------------------------------------------------------------------------------- /** System callback for the control bar parent */ //------------------------------------------------------------------------------------------------- WindowMsgHandledType ControlBarSystem( GameWindow *window, UnsignedInt msg, WindowMsgData mData1, WindowMsgData mData2 ) { static NameKeyType buttonCommunicator = NAMEKEY_INVALID; if(TheScriptEngine && TheScriptEngine->isGameEnding()) return MSG_IGNORED; switch( msg ) { // -------------------------------------------------------------------------------------------- case GWM_CREATE: { // get ids for our children controls buttonCommunicator = TheNameKeyGenerator->nameToKey( AsciiString("ControlBar.wnd:PopupCommunicator") ); break; } // end create //--------------------------------------------------------------------------------------------- case GBM_MOUSE_ENTERING: case GBM_MOUSE_LEAVING: { GameWindow *control = (GameWindow *)mData1; TheControlBar->processContextSensitiveButtonTransition( control, (GadgetGameMessage)msg); break; } //--------------------------------------------------------------------------------------------- case GBM_SELECTED: case GBM_SELECTED_RIGHT: { GameWindow *control = (GameWindow *)mData1; static NameKeyType beaconPlacementButtonID = NAMEKEY("ControlBar.wnd:ButtonPlaceBeacon"); static NameKeyType beaconDeleteButtonID = NAMEKEY("ControlBar.wnd:ButtonDeleteBeacon"); static NameKeyType beaconClearTextButtonID = NAMEKEY("ControlBar.wnd:ButtonClearBeaconText"); static NameKeyType beaconGeneralButtonID = NAMEKEY("ControlBar.wnd:ButtonGeneral"); // static NameKeyType buttonSmallID = NAMEKEY("ControlBar.wnd:ButtonSmall"); // static NameKeyType buttonMediumID = NAMEKEY("ControlBar.wnd:ButtonMedium"); static NameKeyType buttonLargeID = NAMEKEY("ControlBar.wnd:ButtonLarge"); static NameKeyType buttonOptions = NAMEKEY("ControlBar.wnd:ButtonOptions"); static NameKeyType buttonIdleWorker = NAMEKEY("ControlBar.wnd:ButtonIdleWorker"); Int controlID = control->winGetWindowId(); if( controlID == buttonCommunicator ) { ToggleDiplomacy(FALSE); } else if( controlID == beaconPlacementButtonID && TheGameLogic->isInMultiplayerGame() && ThePlayerList->getLocalPlayer()->isPlayerActive()) { const CommandButton *commandButton = TheControlBar->findCommandButton( "Command_PlaceBeacon" ); TheInGameUI->setGUICommand( commandButton ); } else if( controlID == beaconDeleteButtonID && TheGameLogic->isInMultiplayerGame() ) { TheMessageStream->appendMessage( GameMessage::MSG_REMOVE_BEACON ); } else if( controlID == beaconClearTextButtonID && TheGameLogic->isInMultiplayerGame() ) { static NameKeyType textID = NAMEKEY("ControlBar.wnd:EditBeaconText"); GameWindow *win = TheWindowManager->winGetWindowFromId(NULL, textID); if (win) { GadgetTextEntrySetText( win, UnicodeString::TheEmptyString ); } } else if( controlID == beaconGeneralButtonID) { HideQuitMenu( ); TheControlBar->togglePurchaseScience(); } //else if( controlID == buttonSmallID) // { // TheControlBar->switchControlBarStage( CONTROL_BAR_STAGE_LOW ); // } // else if( controlID == buttonMediumID) // { // TheControlBar->switchControlBarStage( CONTROL_BAR_STAGE_SQUISHED ); // } else if( controlID == buttonLargeID) { TheControlBar->toggleControlBarStage(); } else if( controlID == buttonOptions) { ToggleQuitMenu(); } else if( controlID == buttonIdleWorker) { HideQuitMenu( ); TheInGameUI->selectNextIdleWorker(); } else { // // all buttons from all the context sensitive user interface windows are part of the // control bar, send the button processing that way // TheControlBar->processContextSensitiveButtonClick( control, (GadgetGameMessage)msg ); } break; } // end button selected //--------------------------------------------------------------------------------------------- case GEM_EDIT_DONE: { GameWindow *control = (GameWindow *)mData1; Int controlID = control->winGetWindowId(); static NameKeyType textID = NAMEKEY("ControlBar.wnd:EditBeaconText"); if (controlID == textID) { // set beacon text if (TheInGameUI->getSelectCount() == 1) { GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_SET_BEACON_TEXT ); UnicodeString newText = GadgetTextEntryGetText( control ); TheLanguageFilter->filterLine(newText); const WideChar * c = newText.str(); while ( c && *c ) { msg->appendWideCharArgument( *c++ ); } msg->appendWideCharArgument( L'\0' ); // trailing NULL } } break; } // end edit done //--------------------------------------------------------------------------------------------- default: return MSG_IGNORED; } // end switch( msg ) return MSG_HANDLED; } // end ControlBarSystem extern void showReplayControls( void ); extern void hideReplayControls( void ); extern void toggleReplayControls( void ); //------------------------------------------------------------------------------------------------- /** Force the control bar to be shown */ //------------------------------------------------------------------------------------------------- void ShowControlBar( Bool immediate ) { showReplayControls(); if(TheControlBar) TheControlBar->showSpecialPowerShortcut(); if (TheWindowManager) { Int id = (Int)TheNameKeyGenerator->nameToKey(AsciiString("ControlBar.wnd:ControlBarParent")); GameWindow *window = TheWindowManager->winGetWindowFromId(NULL, id); if (window) { TheControlBar->switchControlBarStage(CONTROL_BAR_STAGE_DEFAULT); TheTacticalView->setHeight((Int)(TheDisplay->getHeight() * 0.80f)); if (TheControlBar->m_animateWindowManager && !immediate) { TheControlBar->m_animateWindowManager->reset(); //TheControlBar->m_animateWindowManager->registerGameWindow(window, WIN_ANIMATION_SLIDE_BOTTOM_TIMED, TRUE, 1000, 0); TheControlBar->m_animateWindowManager->registerGameWindow(window, WIN_ANIMATION_SLIDE_BOTTOM, TRUE, 500, 0); TheControlBar->animateSpecialPowerShortcut(TRUE); } window->winHide(FALSE); } } // We want to get everything recalced since this is a major state change. if(TheControlBar) TheControlBar->markUIDirty(); }// void ShowControlBar(void) //------------------------------------------------------------------------------------------------- /** Force the control bar to be hidden */ //------------------------------------------------------------------------------------------------- void HideControlBar( Bool immediate ) { hideReplayControls(); if(TheControlBar) TheControlBar->hideSpecialPowerShortcut(); if (TheWindowManager) { Int id = (Int)TheNameKeyGenerator->nameToKey(AsciiString("ControlBar.wnd:ControlBarParent")); GameWindow *window = TheWindowManager->winGetWindowFromId(NULL, id); if (window) { #ifdef SLIDE_LETTERBOX TheTacticalView->setHeight((Int)(TheDisplay->getHeight() * 0.80f)); #else TheTacticalView->setHeight(TheDisplay->getHeight()); #endif } if (immediate) { window->winHide(TRUE); if(TheControlBar) TheControlBar->hideSpecialPowerShortcut(); } else { TheControlBar->m_animateWindowManager->reverseAnimateWindow(); TheControlBar->animateSpecialPowerShortcut(FALSE); } //Always get rid of the purchase science screen! if( TheControlBar ) { TheControlBar->hidePurchaseScience(); } } }//void HideControlBar( void ) //------------------------------------------------------------------------------------------------- /** Toggle the control bar on or off */ //------------------------------------------------------------------------------------------------- void ToggleControlBar( Bool immediate ) { toggleReplayControls(); if (TheWindowManager) { Int id = (Int)TheNameKeyGenerator->nameToKey(AsciiString("ControlBar.wnd:ControlBarParent")); GameWindow *window = TheWindowManager->winGetWindowFromId(NULL, id); if (window) { if (window->winIsHidden()) { if(TheControlBar) TheControlBar->showSpecialPowerShortcut(); //now hidden, we're making it visible again so shrink viewport under the window TheTacticalView->setHeight((Int)(TheDisplay->getHeight() * 0.80f)); window->winHide(!window->winIsHidden()); TheControlBar->switchControlBarStage(CONTROL_BAR_STAGE_DEFAULT); if (TheControlBar->m_animateWindowManager && !immediate) { TheControlBar->m_animateWindowManager->reset(); //TheControlBar->m_animateWindowManager->registerGameWindow(window, WIN_ANIMATION_SLIDE_BOTTOM_TIMED, FALSE, 500, 0); TheControlBar->m_animateWindowManager->registerGameWindow(window, WIN_ANIMATION_SLIDE_BOTTOM, TRUE, 500, 0); TheControlBar->animateSpecialPowerShortcut(TRUE); } } else { if(TheControlBar) TheControlBar->hideSpecialPowerShortcut(); TheTacticalView->setHeight(TheDisplay->getHeight()); window->winHide(!window->winIsHidden()); } } } }// end void ToggleControlBar( void ) //------------------------------------------------------------------------------------------------- /** Resize the control bar */ //-------------------------------------------------------------------------------------------------