/* ** 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: 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