| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- /*
- ** 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 <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // FILE: GUICommandTranslator.cpp /////////////////////////////////////////////////////////////////
- // Author: Colin Day, March 2002
- // Desc: Translator for commands activated from the selection GUI, such as special unit
- // actions, that require additional clicks in the world like selecting a target
- // object or location
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- // USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
- #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
- #include "Common/ActionManager.h"
- #include "Common/GameCommon.h"
- #include "Common/GameAudio.h"
- #include "Common/NameKeyGenerator.h"
- #include "Common/Player.h"
- #include "Common/PlayerList.h"
- #include "Common/SpecialPower.h"
- #include "Common/ThingTemplate.h"
- #include "GameClient/ControlBar.h"
- #include "GameClient/Drawable.h"
- #include "GameClient/GameText.h"
- #include "Common/Geometry.h"
- #include "GameClient/GUICommandTranslator.h"
- #include "GameClient/CommandXlat.h"
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- // PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
- static enum CommandStatus
- {
- COMMAND_INCOMPLETE = 0,
- COMMAND_COMPLETE
- };
- // PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
- PickAndPlayInfo::PickAndPlayInfo()
- {
- m_air = FALSE;
- m_drawTarget = NULL;
- m_weaponSlot = NULL;
- m_specialPowerType = SPECIAL_INVALID;
- }
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- GUICommandTranslator::GUICommandTranslator()
- {
- }
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- GUICommandTranslator::~GUICommandTranslator()
- {
- }
- //-------------------------------------------------------------------------------------------------
- /** Is the object under the mouse position a valid target for the command */
- //-------------------------------------------------------------------------------------------------
- static Object *validUnderCursor( const ICoord2D *mouse, const CommandButton *command, PickType pickType )
- {
- Object *pickObj = NULL;
- // pick a drawable at the mouse location
- Drawable *pick = TheTacticalView->pickDrawable( mouse, FALSE, pickType );
- // only continue if there is something there
- if( pick && pick->getObject() )
- {
- Player *player = ThePlayerList->getLocalPlayer();
- // get object we picked
- pickObj = pick->getObject();
- if (!command->isValidObjectTarget(player, pickObj))
- pickObj = NULL;
- } // end if
- return pickObj;
- } // end validUnderCursor
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- static CommandStatus doFireWeaponCommand( const CommandButton *command, const ICoord2D *mouse )
- {
-
- // sanity
- if( command == NULL || mouse == NULL )
- return COMMAND_COMPLETE;
- //
- // for single object selections get the source ID and sanity check for illegal object and
- // bail along the way
- //
- ObjectID sourceID = INVALID_ID;
- if( TheInGameUI->getSelectCount() == 1 )
- {
- Drawable *draw = TheInGameUI->getFirstSelectedDrawable();
-
- // sanity
- if( draw == NULL || draw->getObject() == NULL )
- return COMMAND_COMPLETE;
- // get object id
- sourceID = draw->getObject()->getID();
- } // end if
- // create message and send to the logic
- GameMessage *msg;
- if( BitTest( command->getOptions(), NEED_TARGET_POS ) )
- {
- Coord3D world;
- // translate the mouse location into world coords
- TheTacticalView->screenToTerrain( mouse, &world );
-
- // create the message and append arguments
- msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_WEAPON_AT_LOCATION );
- msg->appendIntegerArgument( command->getWeaponSlot() );
- msg->appendLocationArgument( world );
- msg->appendIntegerArgument( command->getMaxShotsToFire() );
- //Also append the object ID (incase weapon doesn't like obstacles on land).
- Object *target = validUnderCursor( mouse, command, PICK_TYPE_SELECTABLE );
- ObjectID targetID = target ? target->getID() : INVALID_ID;
- msg->appendObjectIDArgument( targetID );
- } // end if
- else if( BitTest( command->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) )
- {
- // setup the pick type ... some commands allow us to target shrubbery
- PickType pickType = PICK_TYPE_SELECTABLE;
- if( BitTest( command->getOptions(), ALLOW_SHRUBBERY_TARGET ) == TRUE )
- pickType = (PickType)((Int)pickType | (Int)PICK_TYPE_SHRUBBERY);
- if( BitTest( command->getOptions(), ALLOW_MINE_TARGET ) == TRUE )
- pickType = (PickType)((Int)pickType | (Int)PICK_TYPE_MINES);
- // get the target object under the cursor
- Object *target = validUnderCursor( mouse, command, pickType );
- // only continue if the object meets all the command criteria
- if( target )
- {
- msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_WEAPON_AT_OBJECT );
- msg->appendIntegerArgument( command->getWeaponSlot() );
- msg->appendObjectIDArgument( target->getID() );
- msg->appendIntegerArgument( command->getMaxShotsToFire() );
- } // end if
- } // end else
- else
- {
- msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_WEAPON );
- msg->appendIntegerArgument( command->getWeaponSlot() );
- msg->appendIntegerArgument( command->getMaxShotsToFire() );
- //This could be legit now -- think of firing a self destruct weapon
- //-----------------------------------------------------------------
- //DEBUG_ASSERTCRASH( 0, ("doFireWeaponCommand: Command options say it doesn't need additional user input '%s'\n",
- // command->m_name.str()) );
- //return COMMAND_COMPLETE;
- } // end else
- return COMMAND_COMPLETE;
- } // end fire weapon
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- static CommandStatus doGuardCommand( const CommandButton *command, GuardMode guardMode, const ICoord2D *mouse )
- {
- // sanity
- if( command == NULL || mouse == NULL )
- return COMMAND_COMPLETE;
- if( TheInGameUI->getSelectCount() == 0 )
- return COMMAND_COMPLETE;
- GameMessage *msg = NULL;
- if ( msg == NULL && BitTest( command->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) )
- {
- // get the target object under the cursor
- Object* target = validUnderCursor( mouse, command, PICK_TYPE_SELECTABLE );
- if( target )
- {
- msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_GUARD_OBJECT );
- msg->appendObjectIDArgument( target->getID() );
- msg->appendIntegerArgument(guardMode);
- pickAndPlayUnitVoiceResponse(TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_DO_GUARD_OBJECT);
- }
- }
- if( msg == NULL )
- {
- Coord3D world;
- if (BitTest( command->getOptions(), NEED_TARGET_POS ))
- {
- // translate the mouse location into world coords
- TheTacticalView->screenToTerrain( mouse, &world );
- }
- else
- {
- Drawable *draw = TheInGameUI->getFirstSelectedDrawable();
- if( draw == NULL || draw->getObject() == NULL )
- return COMMAND_COMPLETE;
- world = *draw->getObject()->getPosition();
- }
-
- // create the message and append arguments
- msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_GUARD_POSITION );
- msg->appendLocationArgument(world);
- msg->appendIntegerArgument(guardMode);
- pickAndPlayUnitVoiceResponse(TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_DO_GUARD_POSITION);
- }
- return COMMAND_COMPLETE;
- }
- //-------------------------------------------------------------------------------------------------
- /** Do the set rally point command */
- //-------------------------------------------------------------------------------------------------
- static CommandStatus doAttackMoveCommand( const CommandButton *command, const ICoord2D *mouse )
- {
- // sanity
- if( command == NULL || mouse == NULL )
- return COMMAND_COMPLETE;
- //
- // we can only set rally points for structures ... and we never multiple select structures
- // so we must be sure there is only one thing selected (that thing we will set the point on)
- //
- Drawable *draw = TheInGameUI->getFirstSelectedDrawable();
- DEBUG_ASSERTCRASH( draw, ("doAttackMoveCommand: No selected object(s)\n") );
- // sanity
- if( draw == NULL || draw->getObject() == NULL )
- return COMMAND_COMPLETE;
- // convert mouse point to world coords
- Coord3D world;
- TheTacticalView->screenToTerrain( mouse, &world );
- // send the message to set the rally point
- GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_ATTACKMOVETO );
- msg->appendLocationArgument( world );
- // Play the unit voice response
- pickAndPlayUnitVoiceResponse(TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_DO_ATTACKMOVETO);
- return COMMAND_COMPLETE;
- }
- //-------------------------------------------------------------------------------------------------
- /** Do the set rally point command */
- //-------------------------------------------------------------------------------------------------
- static CommandStatus doSetRallyPointCommand( const CommandButton *command, const ICoord2D *mouse )
- {
- // sanity
- if( command == NULL || mouse == NULL )
- return COMMAND_COMPLETE;
- //
- // we can only set rally points for structures ... and we never multiple select structures
- // so we must be sure there is only one thing selected (that thing we will set the point on)
- //
- DEBUG_ASSERTCRASH( TheInGameUI->getSelectCount() == 1,
- ("doSetRallyPointCommand: The selected count is not 1, we can only set a rally point on a *SINGLE* building\n") );
- Drawable *draw = TheInGameUI->getFirstSelectedDrawable();
- DEBUG_ASSERTCRASH( draw, ("doSetRallyPointCommand: No selected object\n") );
- // sanity
- if( draw == NULL || draw->getObject() == NULL )
- return COMMAND_COMPLETE;
- // convert mouse point to world coords
- Coord3D world;
- TheTacticalView->screenToTerrain( mouse, &world );
- // send the message to set the rally point
- GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_SET_RALLY_POINT );
- msg->appendObjectIDArgument( draw->getObject()->getID() );
- msg->appendLocationArgument( world );
- return COMMAND_COMPLETE;
- } // end doSetRallyPointCommand
- //-------------------------------------------------------------------------------------------------
- /** Do the beacon placement command */
- //-------------------------------------------------------------------------------------------------
- static CommandStatus doPlaceBeacon( const CommandButton *command, const ICoord2D *mouse )
- {
- // sanity
- if( command == NULL || mouse == NULL )
- return COMMAND_COMPLETE;
- // convert mouse point to world coords
- Coord3D world;
- TheTacticalView->screenToTerrain( mouse, &world );
- // send the message to set the rally point
- GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_PLACE_BEACON );
- msg->appendLocationArgument( world );
- return COMMAND_COMPLETE;
- } // end doPlaceBeacon
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- GameMessageDisposition GUICommandTranslator::translateGameMessage(const GameMessage *msg)
- {
- GameMessageDisposition disp = KEEP_MESSAGE;
- // only pay attention to clicks in this translator if there is a pending GUI command
- const CommandButton *command = TheInGameUI->getGUICommand();
- if( command == NULL )
- return disp;
- switch( msg->getType() )
- {
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN:
- {
- //
- //
- // it is necessary to use this input when there is a pending gui command, we don't wan't
- // it to fall through to the rest of the system when we're in pending gui command "mode"
- // because things like selection rectangles will start when we want to stay totally
- // within the gui command "mode" here
- //
- disp = DESTROY_MESSAGE;
- break;
- } // end left mouse down
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK:
- case GameMessage::MSG_MOUSE_LEFT_CLICK:
- {
- CommandStatus commandStatus = COMMAND_COMPLETE;
- ICoord2D mouse = msg->getArgument(0)->pixelRegion.hi;
- // do the command action
- if( command && !command->isContextCommand() )
- {
- switch( command->getCommandType() )
- {
- //---------------------------------------------------------------------------------------
- case GUI_COMMAND_FIRE_WEAPON:
- {
- commandStatus = doFireWeaponCommand( command, &mouse );
-
- PickAndPlayInfo info;
- WeaponSlotType slot = command->getWeaponSlot();
- info.m_weaponSlot = &slot;
-
- pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_DO_WEAPON_AT_LOCATION, &info );
- break;
- } // end fire weapon command
- //---------------------------------------------------------------------------------------
- case GUI_COMMAND_EVACUATE:
- {
- if (BitTest(command->getOptions(), NEED_TARGET_POS)) {
- Coord3D worldPos;
- TheTacticalView->screenToTerrain(&mouse, &worldPos);
- GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_EVACUATE);
- msg->appendLocationArgument(worldPos);
-
- pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_EVACUATE );
- commandStatus = COMMAND_COMPLETE;
- }
- break;
- }
- //---------------------------------------------------------------------------------------
- case GUI_COMMAND_GUARD:
- {
- commandStatus = doGuardCommand( command, GUARDMODE_NORMAL, &mouse );
- break;
- }
- //---------------------------------------------------------------------------------------
- case GUI_COMMAND_GUARD_WITHOUT_PURSUIT:
- {
- commandStatus = doGuardCommand( command, GUARDMODE_GUARD_WITHOUT_PURSUIT, &mouse );
- break;
- }
- //---------------------------------------------------------------------------------------
- case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY:
- {
- commandStatus = doGuardCommand( command, GUARDMODE_GUARD_FLYING_UNITS_ONLY, &mouse );
- break;
- }
- //Special weapons are now always context commands...
- //---------------------------------------------------------------------------------------
- case GUI_COMMAND_SPECIAL_POWER:
- case GUI_COMMAND_SPECIAL_POWER_FROM_SHORTCUT:
- {
- return KEEP_MESSAGE;
- break;
- } // end special power
- case GUI_COMMAND_ATTACK_MOVE:
- {
- commandStatus = doAttackMoveCommand( command, &mouse );
- break;
- }
- //---------------------------------------------------------------------------------------
- case GUI_COMMAND_SET_RALLY_POINT:
- {
- commandStatus = doSetRallyPointCommand( command, &mouse );
- break;
- } // end set rally point
- //---------------------------------------------------------------------------------------
- case GUICOMMANDMODE_PLACE_BEACON:
- {
- commandStatus = doPlaceBeacon( command, &mouse );
- break;
- } // end set rally point
- } // end switch
- // used the input
- disp = DESTROY_MESSAGE;
- // get out of GUI command mode if we completed the command one way or another
- if( commandStatus == COMMAND_COMPLETE )
- {
- TheInGameUI->setPreventLeftClickDeselectionInAlternateMouseModeForOneClick( TRUE );
- TheInGameUI->setGUICommand( NULL );
- }
- } // end if
- break;
- } // end left mouse up
- } // end switch
- // If we're destroying the message, it means we used it. Therefore, destroy the current
- // attack move instruction as well.
- if (disp == DESTROY_MESSAGE)
- TheInGameUI->clearAttackMoveToMode();
- return disp;
- } // end translateMessage
|