Initial commit of Command & Conquer Generals and Command & Conquer Generals Zero Hour source code.

This commit is contained in:
LFeenanEA 2025-02-27 17:34:39 +00:00
parent 2e338c00cb
commit 3d0ee53a05
No known key found for this signature in database
GPG key ID: C6EBE8C2EA08F7E0
6072 changed files with 2283311 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,514 @@
/*
** Command & Conquer Generals(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_COMMAND_CENTER:
{
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->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

View file

@ -0,0 +1,139 @@
/*
** Command & Conquer Generals(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. //
// //
////////////////////////////////////////////////////////////////////////////////
// HintSpy.cpp
// The HintSpy sits on the message stream and watches for certain messages,
// for which it then generates visual "hints".
// Author: Michael S. Booth, March 2001
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/MessageStream.h"
#include "GameClient/HintSpy.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/GameWindow.h"
#include "GameClient/GameClient.h"
#include "GameClient/Drawable.h"
/**
* This message handler displays UI "hints" (ie: a rectangle for drag selection) based
* upon the messages that pass through it.
*/
GameMessageDisposition HintSpyTranslator::translateGameMessage(const GameMessage *msg)
{
GameMessageDisposition disp = KEEP_MESSAGE;
/// @todo Create an automated way to associate method callbacks with messages
switch( msg->getType() )
{
//-----------------------------------------------------------------------------
case GameMessage::MSG_MOUSEOVER_DRAWABLE_HINT:
{
TheInGameUI->createMouseoverHint( msg );
disp = DESTROY_MESSAGE; //hint no longer needed by anyone. Eat it.
}
break;
case GameMessage::MSG_MOUSEOVER_LOCATION_HINT:
{
TheInGameUI->createMouseoverHint( msg );
disp = DESTROY_MESSAGE; //hint no longer needed by anyone. Eat it.
}
break;
//-----------------------------------------------------------------------------
case GameMessage::MSG_DEFECTOR_HINT:
disp = DESTROY_MESSAGE; //hint no longer needed by anyone. Eat it.
case GameMessage::MSG_DO_MOVETO_HINT:
case GameMessage::MSG_DO_ATTACKMOVETO_HINT:
case GameMessage::MSG_DO_ATTACK_OBJECT_HINT:
case GameMessage::MSG_DO_ATTACK_OBJECT_AFTER_MOVING_HINT:
case GameMessage::MSG_DO_FORCE_ATTACK_OBJECT_HINT:
case GameMessage::MSG_DO_FORCE_ATTACK_GROUND_HINT:
case GameMessage::MSG_ADD_WAYPOINT_HINT:
case GameMessage::MSG_GET_REPAIRED_HINT:
case GameMessage::MSG_DOCK_HINT:
case GameMessage::MSG_GET_HEALED_HINT:
case GameMessage::MSG_DO_REPAIR_HINT:
case GameMessage::MSG_RESUME_CONSTRUCTION_HINT:
case GameMessage::MSG_ENTER_HINT:
case GameMessage::MSG_HIJACK_HINT:
case GameMessage::MSG_CONVERT_TO_CARBOMB_HINT:
#ifdef ALLOW_SURRENDER
case GameMessage::MSG_PICK_UP_PRISONER_HINT:
#endif
case GameMessage::MSG_VALID_GUICOMMAND_HINT:
case GameMessage::MSG_INVALID_GUICOMMAND_HINT:
case GameMessage::MSG_CAPTUREBUILDING_HINT:
case GameMessage::MSG_HACK_HINT:
case GameMessage::MSG_SET_RALLY_POINT_HINT:
case GameMessage::MSG_IMPOSSIBLE_ATTACK_HINT:
case GameMessage::MSG_DO_SPECIAL_POWER_OVERRIDE_DESTINATION_HINT:
case GameMessage::MSG_DO_SALVAGE_HINT:
case GameMessage::MSG_DO_INVALID_HINT:
TheInGameUI->createCommandHint( msg );
disp = DESTROY_MESSAGE; //hint no longer needed by anyone. Eat it.
break;
//-----------------------------------------------------------------------------
case GameMessage::MSG_AREA_SELECTION_HINT:
TheInGameUI->beginAreaSelectHint( msg );
break;
//-----------------------------------------------------------------------------
// An AREA_SELECTION_HINT is always followed by an AREA_SELECTION, so
// watch for it to stop hinting.
case GameMessage::MSG_AREA_SELECTION:
TheInGameUI->endAreaSelectHint( msg );
break;
//-----------------------------------------------------------------------------
case GameMessage::MSG_DO_MOVETO:
case GameMessage::MSG_DO_ATTACKMOVETO:
case GameMessage::MSG_DO_FORCEMOVETO:
case GameMessage::MSG_ADD_WAYPOINT:
TheInGameUI->createMoveHint( msg );
break;
//-----------------------------------------------------------------------------
case GameMessage::MSG_DO_ATTACK_OBJECT:
TheInGameUI->createAttackHint( msg );
break;
//-----------------------------------------------------------------------------
case GameMessage::MSG_DO_FORCE_ATTACK_GROUND:
case GameMessage::MSG_DO_FORCE_ATTACK_OBJECT:
TheInGameUI->createForceAttackHint( msg );
break;
//-----------------------------------------------------------------------------
case GameMessage::MSG_ENTER:
TheInGameUI->createGarrisonHint( msg );
break;
}
return disp;
}

View file

@ -0,0 +1,234 @@
/*
** Command & Conquer Generals(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: HotKey.cpp /////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//
// Electronic Arts Pacific.
//
// Confidential Information
// Copyright (C) 2002 - All Rights Reserved
//
//-----------------------------------------------------------------------------
//
// created: Sep 2002
//
// Filename: HotKey.cpp
//
// author: Chris Huybregts
//
// purpose:
//
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
//-----------------------------------------------------------------------------
// USER INCLUDES //////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
#include "GameClient/HotKey.h"
#include "GameClient/KeyDefs.h"
#include "GameClient/MetaEvent.h"
#include "GameClient/GameWindow.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/keyboard.h"
#include "GameClient/GameText.h"
#include "Common/AudioEventRTS.h"
//-----------------------------------------------------------------------------
// DEFINES ////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
GameMessageDisposition HotKeyTranslator::translateGameMessage(const GameMessage *msg)
{
GameMessageDisposition disp = KEEP_MESSAGE;
GameMessage::Type t = msg->getType();
if ( t == GameMessage::MSG_RAW_KEY_UP)
{
//char key = msg->getArgument(0)->integer;
Int keyState = msg->getArgument(1)->integer;
// for our purposes here, we don't care to distinguish between right and left keys,
// so just fudge a little to simplify things.
Int newModState = 0;
if( keyState & KEY_STATE_CONTROL )
{
newModState |= CTRL;
}
if( keyState & KEY_STATE_SHIFT )
{
newModState |= SHIFT;
}
if( keyState & KEY_STATE_ALT )
{
newModState |= ALT;
}
if(newModState != 0)
return disp;
WideChar key = TheKeyboard->getPrintableKey(msg->getArgument(0)->integer, 0);
UnicodeString uKey;
uKey.set(&key);
AsciiString aKey;
aKey.translate(uKey);
if(TheHotKeyManager && TheHotKeyManager->executeHotKey(aKey))
disp = DESTROY_MESSAGE;
}
return disp;
}
//-----------------------------------------------------------------------------
HotKey::HotKey()
{
m_win = NULL;
//Added By Sadullah Nader
//Initializations missing and needed
m_key.clear();
//
}
//-----------------------------------------------------------------------------
HotKeyManager::HotKeyManager( void )
{
}
//-----------------------------------------------------------------------------
HotKeyManager::~HotKeyManager( void )
{
m_hotKeyMap.clear();
}
//-----------------------------------------------------------------------------
void HotKeyManager::init( void )
{
m_hotKeyMap.clear();
}
//-----------------------------------------------------------------------------
void HotKeyManager::reset( void )
{
m_hotKeyMap.clear();
}
//-----------------------------------------------------------------------------
void HotKeyManager::addHotKey( GameWindow *win, const AsciiString& keyIn)
{
AsciiString key = keyIn;
key.toLower();
HotKeyMap::iterator it = m_hotKeyMap.find(key);
if( it != m_hotKeyMap.end() )
{
DEBUG_ASSERTCRASH(FALSE,("Hotkey %s is already mapped to window %s, current window is %s", key.str(), it->second.m_win->winGetInstanceData()->m_decoratedNameString.str(), win->winGetInstanceData()->m_decoratedNameString.str()));
return;
}
HotKey newHK;
newHK.m_key.set(key);
newHK.m_win = win;
m_hotKeyMap[key] = newHK;
}
//-----------------------------------------------------------------------------
Bool HotKeyManager::executeHotKey( const AsciiString& keyIn )
{
AsciiString key = keyIn;
key.toLower();
HotKeyMap::iterator it = m_hotKeyMap.find(key);
if( it == m_hotKeyMap.end() )
return FALSE;
GameWindow *win = it->second.m_win;
if( !win )
return FALSE;
if( !BitTest( win->winGetStatus(), WIN_STATUS_HIDDEN ) )
{
if( BitTest( win->winGetStatus(), WIN_STATUS_ENABLED ) )
{
TheWindowManager->winSendSystemMsg( win->winGetParent(), GBM_SELECTED, (WindowMsgData)win, win->winGetWindowId() );
// here we make the same click sound that the GUI uses when you click a button
AudioEventRTS buttonClick("GUIClick");
if( TheAudio )
{
TheAudio->addAudioEvent( &buttonClick );
} // end if
return TRUE;
}
AudioEventRTS disabledClick( "GUIClickDisabled" );
if( TheAudio )
{
TheAudio->addAudioEvent( &disabledClick );
}
}
return FALSE;
}
//-----------------------------------------------------------------------------
AsciiString HotKeyManager::searchHotKey( const AsciiString& label)
{
return searchHotKey(TheGameText->fetch(label));
}
//-----------------------------------------------------------------------------
AsciiString HotKeyManager::searchHotKey( const UnicodeString& uStr )
{
if(uStr.isEmpty())
return AsciiString::TheEmptyString;
const WideChar *marker = (const WideChar *)uStr.str();
while (marker && *marker)
{
if (*marker == L'&')
{
// found a '&' - now look for the next char
UnicodeString tmp = UnicodeString::TheEmptyString;
tmp.concat(*(marker+1));
AsciiString retStr;
retStr.translate(tmp);
return retStr;
}
marker++;
}
return AsciiString::TheEmptyString;
}
//-----------------------------------------------------------------------------
HotKeyManager *TheHotKeyManager = NULL;
//-----------------------------------------------------------------------------
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------

View file

@ -0,0 +1,681 @@
/*
** Command & Conquer Generals(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. //
// //
////////////////////////////////////////////////////////////////////////////////
// LookAtXlat.cpp
// Translate raw input events into camera movement commands
// Author: Michael S. Booth, April 2001
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "windows.h"
#include "Common/GameType.h"
#include "Common/MessageStream.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Recorder.h"
#include "Common/StatsCollector.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameClient/Display.h"
#include "GameClient/GameText.h"
#include "GameClient/Mouse.h"
#include "GameClient/Shell.h"
#include "GameClient/GameClient.h"
#include "GameClient/KeyDefs.h"
#include "GameClient/View.h"
#include "GameClient/Drawable.h"
#include "GameClient/LookAtXlat.h"
#include "GameLogic/Module/UpdateModule.h"
#include "GameLogic/GameLogic.h"
#include "Common/GlobalData.h" // for camera pitch angle only
LookAtTranslator *TheLookAtTranslator = NULL;
static enum
{
DIR_UP = 0,
DIR_DOWN,
DIR_LEFT,
DIR_RIGHT
};
static Bool scrollDir[4] = { false, false, false, false };
Int SCROLL_AMT = 100;
static const Int edgeScrollSize = 3;
static Mouse::MouseCursor prevCursor = Mouse::ARROW;
//-----------------------------------------------------------------------------
void LookAtTranslator::setScrolling(Int x)
{
if (!TheInGameUI->getInputEnabled())
return;
prevCursor = TheMouse->getMouseCursor();
m_isScrolling = true;
TheInGameUI->setScrolling( TRUE );
TheTacticalView->setMouseLock( TRUE );
m_scrollType = x;
if(TheStatsCollector)
TheStatsCollector->startScrollTime();
}
//-----------------------------------------------------------------------------
void LookAtTranslator::stopScrolling( void )
{
m_isScrolling = false;
TheInGameUI->setScrolling( FALSE );
TheTacticalView->setMouseLock( FALSE );
TheMouse->setCursor(prevCursor);
m_scrollType = SCROLL_NONE;
// if we have a stats collectore increment the stats
if(TheStatsCollector)
TheStatsCollector->endScrollTime();
}
//-----------------------------------------------------------------------------
LookAtTranslator::LookAtTranslator() :
m_isScrolling(false),
m_isRotating(false),
m_isPitching(false),
m_isChangingFOV(false),
m_timestamp(0),
m_lastPlaneID(INVALID_DRAWABLE_ID),
m_lastMouseMoveFrame(0),
m_scrollType(SCROLL_NONE)
{
//Added By Sadullah Nader
//Initializations misssing and needed
m_anchor.x = m_anchor.y = 0;
m_currentPos.x = m_currentPos.y = 0;
m_originalAnchor.x = m_originalAnchor.y = 0;
//
DEBUG_ASSERTCRASH(!TheLookAtTranslator, ("Already have a LookAtTranslator - why do you need two?"));
TheLookAtTranslator = this;
}
//-----------------------------------------------------------------------------
LookAtTranslator::~LookAtTranslator()
{
if (TheLookAtTranslator == this)
TheLookAtTranslator = NULL;
}
const ICoord2D* LookAtTranslator::getRMBScrollAnchor(void)
{
if (m_isScrolling && m_scrollType == SCROLL_RMB)
{
return &m_anchor;
}
return NULL;
}
Bool LookAtTranslator::hasMouseMovedRecently( void )
{
if (m_lastMouseMoveFrame > TheGameLogic->getFrame())
m_lastMouseMoveFrame = 0; // reset for new game
if (m_lastMouseMoveFrame + LOGICFRAMES_PER_SECOND < TheGameLogic->getFrame())
return false;
return true;
}
void LookAtTranslator::setCurrentPos( const ICoord2D& pos )
{
m_currentPos = pos;
}
//-----------------------------------------------------------------------------
/**
* The LookAt Translator is responsible for camera movements. It is directly responsible for
* right mouse button scrolling, and CTRL-<F key> bookmarking. It also responds to certain
* LOOKAT message on the message stream.
*/
GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage *msg)
{
GameMessageDisposition disp = KEEP_MESSAGE;
GameMessage::Type t = msg->getType();
switch (t)
{
//-----------------------------------------------------------------------------
case GameMessage::MSG_RAW_KEY_DOWN:
case GameMessage::MSG_RAW_KEY_UP:
{
// get key and state from args
UnsignedByte key = msg->getArgument( 0 )->integer;
UnsignedByte state = msg->getArgument( 1 )->integer;
Bool isPressed = !(BitTest( state, KEY_STATE_UP ));
if (TheShell && TheShell->isShellActive())
break;
switch (key)
{
case KEY_UP:
scrollDir[DIR_UP] = isPressed;
break;
case KEY_DOWN:
scrollDir[DIR_DOWN] = isPressed;
break;
case KEY_LEFT:
scrollDir[DIR_LEFT] = isPressed;
break;
case KEY_RIGHT:
scrollDir[DIR_RIGHT] = isPressed;
break;
}
if (TheInGameUI->isSelecting() || (m_isScrolling && m_scrollType != SCROLL_KEY))
break;
// see if we need to start/stop scrolling
Int numDirs = 0;
for (Int i=0; i<4; ++i)
{
if (scrollDir[i])
numDirs++;
}
if (numDirs && !m_isScrolling)
{
setScrolling( SCROLL_KEY );
}
else if (!numDirs && m_isScrolling)
{
stopScrolling();
}
break;
}
//-----------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN:
{
m_lastMouseMoveFrame = TheGameLogic->getFrame();
m_anchor = msg->getArgument( 0 )->pixel;
m_currentPos = msg->getArgument( 0 )->pixel;
// disable mouse scrolling in alternate mouse mode, per Harvard 7/15/03
if (!TheGlobalData->m_useAlternateMouse && !TheInGameUI->isSelecting() && !m_isScrolling)
{
setScrolling(SCROLL_RMB);
}
break;
}
//-----------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP:
{
m_lastMouseMoveFrame = TheGameLogic->getFrame();
if (m_scrollType == SCROLL_RMB)
{
stopScrolling();
}
break;
}
//-----------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN:
{
m_lastMouseMoveFrame = TheGameLogic->getFrame();
m_isRotating = true;
m_anchor = msg->getArgument( 0 )->pixel;
m_originalAnchor = msg->getArgument( 0 )->pixel;
m_currentPos = msg->getArgument( 0 )->pixel;
m_timestamp = TheGameClient->getFrame();
break;
}
//-----------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP:
{
m_lastMouseMoveFrame = TheGameLogic->getFrame();
const UnsignedInt CLICK_DURATION = 5;
const UnsignedInt PIXEL_OFFSET = 5;
m_isRotating = false;
Int dx = m_currentPos.x-m_originalAnchor.x;
if (dx<0) dx = -dx;
Int dy = m_currentPos.y-m_originalAnchor.y;
Bool didMove = dx>PIXEL_OFFSET || dy>PIXEL_OFFSET;
// if middle button is "clicked", reset to "home" orientation
if (!didMove && TheGameClient->getFrame() - m_timestamp < CLICK_DURATION)
{
TheTacticalView->setAngleAndPitchToDefault();
TheTacticalView->setZoomToDefault();
}
break;
}
//-----------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_POSITION:
{
if (m_currentPos.x != msg->getArgument( 0 )->pixel.x || m_currentPos.y != msg->getArgument( 0 )->pixel.y)
m_lastMouseMoveFrame = TheGameLogic->getFrame();
m_currentPos = msg->getArgument( 0 )->pixel;
UnsignedInt height = TheDisplay->getHeight();
UnsignedInt width = TheDisplay->getWidth();
if (TheInGameUI->getInputEnabled() == FALSE) {
// We don't care how we're scrolling, just stop.
if (m_isScrolling)
stopScrolling();
break;
}
if (!TheGlobalData->m_windowed)
{
if (m_isScrolling)
{
if ( m_scrollType == SCROLL_SCREENEDGE && (m_currentPos.x >= edgeScrollSize && m_currentPos.y >= edgeScrollSize && m_currentPos.y < height-edgeScrollSize && m_currentPos.x < width-edgeScrollSize) )
{
stopScrolling();
}
}
else
{
if ( m_currentPos.x < edgeScrollSize || m_currentPos.y < edgeScrollSize || m_currentPos.y >= height-edgeScrollSize || m_currentPos.x >= width-edgeScrollSize )
{
setScrolling(SCROLL_SCREENEDGE);
}
}
}
// rotate the view
if (m_isRotating)
{
const Real FACTOR = 0.01f;
Real angle = FACTOR * (m_currentPos.x - m_anchor.x);
TheTacticalView->setAngle( TheTacticalView->getAngle() + angle );
m_anchor = msg->getArgument( 0 )->pixel;
}
// rotate the view up/down
if (m_isPitching)
{
const Real FACTOR = 0.01f;
Real angle = FACTOR * (m_currentPos.y - m_anchor.y);
TheTacticalView->setPitch( TheTacticalView->getPitch() + angle );
m_anchor = msg->getArgument( 0 )->pixel;
}
#if defined(_DEBUG) || defined(_INTERNAL)
// adjust the field of view
if (m_isChangingFOV)
{
const Real FACTOR = 0.01f;
Real angle = FACTOR * (m_currentPos.y - m_anchor.y);
TheTacticalView->setFieldOfView( TheTacticalView->getFieldOfView() + angle );
m_anchor = msg->getArgument( 0 )->pixel;
}
#endif
break;
}
//-----------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_WHEEL:
{
m_lastMouseMoveFrame = TheGameLogic->getFrame();
Int spin = msg->getArgument( 1 )->integer;
if (spin > 0)
{
for ( ; spin > 0; spin--)
TheTacticalView->zoomIn();
}
else
{
for ( ;spin < 0; spin++ )
TheTacticalView->zoomOut();
}
}
//-----------------------------------------------------------------------------
case GameMessage::MSG_META_OPTIONS:
{
// stop the scrolling
stopScrolling();
// let the message drop through, cause we need to process this message for
// selection as well.
break;
}
//-----------------------------------------------------------------------------
case GameMessage::MSG_FRAME_TICK:
{
Coord2D offset = {0, 0};
// If we've been forced to stop scrolling (script action?) then stop
if (m_isScrolling && !TheInGameUI->isScrolling())
{
TheInGameUI->setScrollAmount(offset);
stopScrolling();
}
else
// scroll the view
if (m_isScrolling)
{
switch (m_scrollType)
{
case SCROLL_RMB:
{
if (TheInGameUI->shouldMoveRMBScrollAnchor())
{
Int maxX = TheDisplay->getWidth()/2;
Int maxY = TheDisplay->getHeight()/2;
if (m_currentPos.x + maxX < m_anchor.x)
m_anchor.x = m_currentPos.x + maxX;
else if (m_currentPos.x - maxX > m_anchor.x)
m_anchor.x = m_currentPos.x - maxX;
if (m_currentPos.y + maxY < m_anchor.y)
m_anchor.y = m_currentPos.y + maxY;
else if (m_currentPos.y - maxY > m_anchor.y)
m_anchor.y = m_currentPos.y - maxY;
}
offset.x = TheGlobalData->m_horizontalScrollSpeedFactor * (m_currentPos.x - m_anchor.x);
offset.y = TheGlobalData->m_verticalScrollSpeedFactor * (m_currentPos.y - m_anchor.y);
Coord2D vec;
vec.x = offset.x;
vec.y = offset.y;
vec.normalize();
// Add in the window scroll amount as the minimum.
offset.x += TheGlobalData->m_horizontalScrollSpeedFactor * vec.x * sqr(TheGlobalData->m_keyboardScrollFactor);
offset.y += TheGlobalData->m_verticalScrollSpeedFactor * vec.y * sqr(TheGlobalData->m_keyboardScrollFactor);
}
break;
case SCROLL_KEY:
{
if (scrollDir[DIR_UP])
{
offset.y -= TheGlobalData->m_verticalScrollSpeedFactor * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor;
}
if (scrollDir[DIR_DOWN])
{
offset.y += TheGlobalData->m_verticalScrollSpeedFactor * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor;
}
if (scrollDir[DIR_LEFT])
{
offset.x -= TheGlobalData->m_horizontalScrollSpeedFactor * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor;
}
if (scrollDir[DIR_RIGHT])
{
offset.x += TheGlobalData->m_horizontalScrollSpeedFactor * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor;
}
}
break;
case SCROLL_SCREENEDGE:
{
UnsignedInt height = TheDisplay->getHeight();
UnsignedInt width = TheDisplay->getWidth();
if (m_currentPos.y < edgeScrollSize)
{
offset.y -= TheGlobalData->m_verticalScrollSpeedFactor * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor;
}
if (m_currentPos.y >= height-edgeScrollSize)
{
offset.y += TheGlobalData->m_verticalScrollSpeedFactor * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor;
}
if (m_currentPos.x < edgeScrollSize)
{
offset.x -= TheGlobalData->m_horizontalScrollSpeedFactor * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor;
}
if (m_currentPos.x >= width-edgeScrollSize)
{
offset.x += TheGlobalData->m_horizontalScrollSpeedFactor * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor;
}
}
break;
}
TheInGameUI->setScrollAmount(offset);
TheTacticalView->scrollBy( &offset );
}
else //not scrolling so reset amount
TheInGameUI->setScrollAmount(offset);
#if !defined(_PLAYTEST)
//if (TheGlobalData->m_saveCameraInReplay /*&& TheRecorder->getMode() != RECORDERMODETYPE_PLAYBACK *//**/&& (TheGameLogic->isInSinglePlayerGame() || TheGameLogic->isInSkirmishGame())/**/)
//if (TheGlobalData->m_saveCameraInReplay && (TheGameLogic->isInMultiplayerGame() || TheGameLogic->isInSinglePlayerGame() || TheGameLogic->isInSkirmishGame()))
if (TheGlobalData->m_saveCameraInReplay && (TheGameLogic->isInSinglePlayerGame() || TheGameLogic->isInSkirmishGame()))
{
ViewLocation currentView;
TheTacticalView->getLocation(&currentView);
GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_SET_REPLAY_CAMERA );
msg->appendLocationArgument( currentView.m_pos );
msg->appendRealArgument( currentView.m_angle );
msg->appendRealArgument( currentView.m_pitch );
msg->appendRealArgument( currentView.m_zoom );
msg->appendIntegerArgument( (Int)TheMouse->getMouseCursor() );
msg->appendPixelArgument( m_currentPos );
}
#endif
break;
}
// ------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
case GameMessage::MSG_META_DEMO_BEGIN_ADJUST_PITCH:
{
DEBUG_ASSERTCRASH(!m_isPitching, ("hmm, mismatched m_isPitching"));
m_isPitching = true;
disp = DESTROY_MESSAGE;
break;
}
#endif // #if defined(_DEBUG) || defined(_INTERNAL)
// ------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
case GameMessage::MSG_META_DEMO_END_ADJUST_PITCH:
{
DEBUG_ASSERTCRASH(m_isPitching, ("hmm, mismatched m_isPitching"));
m_isPitching = false;
disp = DESTROY_MESSAGE;
break;
}
#endif // #if defined(_DEBUG) || defined(_INTERNAL)
// ------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
case GameMessage::MSG_META_DEMO_DESHROUD:
{
ThePartitionManager->revealMapForPlayerPermanently( ThePlayerList->getLocalPlayer()->getPlayerIndex() );
break;
}
#endif // #if defined(_DEBUG) || defined(_INTERNAL)
// ------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
case GameMessage::MSG_META_DEMO_ENSHROUD:
{
// Need to first undo the permanent Look laid down by DEMO_DESHROUD, then blast a shroud dollop.
ThePartitionManager->undoRevealMapForPlayerPermanently( ThePlayerList->getLocalPlayer()->getPlayerIndex() );
ThePartitionManager->shroudMapForPlayer( ThePlayerList->getLocalPlayer()->getPlayerIndex() );
break;
}
#endif // #if defined(_DEBUG) || defined(_INTERNAL)
// ------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
case GameMessage::MSG_META_DEMO_BEGIN_ADJUST_FOV:
{
DEBUG_ASSERTCRASH(!m_isChangingFOV, ("hmm, mismatched m_isChangingFOV"));
m_isChangingFOV = true;
m_anchor = m_currentPos;
break;
}
#endif // #if defined(_DEBUG) || defined(_INTERNAL)
// ------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
case GameMessage::MSG_META_DEMO_END_ADJUST_FOV:
{
DEBUG_ASSERTCRASH(m_isChangingFOV, ("hmm, mismatched m_isChangingFOV"));
m_isChangingFOV = false;
break;
}
#endif // #if defined(_DEBUG) || defined(_INTERNAL)
//-----------------------------------------------------------------------------------------
case GameMessage::MSG_META_SAVE_VIEW1:
case GameMessage::MSG_META_SAVE_VIEW2:
case GameMessage::MSG_META_SAVE_VIEW3:
case GameMessage::MSG_META_SAVE_VIEW4:
case GameMessage::MSG_META_SAVE_VIEW5:
case GameMessage::MSG_META_SAVE_VIEW6:
case GameMessage::MSG_META_SAVE_VIEW7:
case GameMessage::MSG_META_SAVE_VIEW8:
{
Int slot = t - GameMessage::MSG_META_SAVE_VIEW1 + 1;
if ( slot > 0 && slot <= MAX_VIEW_LOCS )
{
TheTacticalView->getLocation( &m_viewLocation[slot-1] );
UnicodeString msg;
msg.format( TheGameText->fetch( "GUI:BookmarkXSet" ), slot );
TheInGameUI->message( msg );
}
disp = DESTROY_MESSAGE;
break;
}
//-----------------------------------------------------------------------------------------
case GameMessage::MSG_META_VIEW_VIEW1:
case GameMessage::MSG_META_VIEW_VIEW2:
case GameMessage::MSG_META_VIEW_VIEW3:
case GameMessage::MSG_META_VIEW_VIEW4:
case GameMessage::MSG_META_VIEW_VIEW5:
case GameMessage::MSG_META_VIEW_VIEW6:
case GameMessage::MSG_META_VIEW_VIEW7:
case GameMessage::MSG_META_VIEW_VIEW8:
{
Int slot = t - GameMessage::MSG_META_VIEW_VIEW1 + 1;
if ( slot > 0 && slot <= MAX_VIEW_LOCS )
{
TheTacticalView->setLocation( &m_viewLocation[slot-1] );
}
disp = DESTROY_MESSAGE;
break;
}
//-----------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
case GameMessage::MSG_META_DEMO_LOCK_CAMERA_TO_PLANES:
{
Drawable *first = NULL;
if (m_lastPlaneID)
first = TheGameClient->findDrawableByID( m_lastPlaneID );
if (first == NULL)
first = TheGameClient->firstDrawable();
if (first)
{
Drawable *d = first;
Bool done = false;
while(!done)
{
// get next Drawable, wrapping around to head of list if necessary
d = d->getNextDrawable();
if (d == NULL)
d = TheGameClient->firstDrawable();
// if we've found an airborne object, lock onto it
// "isAboveTerrain" only indicates that we are currently in the air, but that
// could be the case if we are a buggy jumping a hill, or a unit being paradropped.
// the right thing would be to look at the locomotors.
// so this isn't really right, but will suffice for demo purposes.
if (d->getObject() && d->getObject()->isAboveTerrain() )
{
Bool doLock = true;
// but don't lock onto projectiles
ProjectileUpdateInterface* pui = NULL;
for (BehaviorModule** u = d->getObject()->getBehaviorModules(); *u; ++u)
{
if ((pui = (*u)->getProjectileUpdateInterface()) != NULL)
{
doLock = false;
break;
}
}
if (doLock)
{
TheTacticalView->setCameraLock( d->getObject()->getID() );
m_lastPlaneID = d->getID();
done = true;
break;
}
} // if airborne found
// if we're back to the first, quit
if (d == first)
break;
} // while
} // end plane lock
disp = DESTROY_MESSAGE;
break;
}
#endif // #if defined(_DEBUG) || defined(_INTERNAL)
} // end switch
return disp;
} // end LookAtTranslator
void LookAtTranslator::resetModes()
{
m_isScrolling = FALSE;
m_isRotating = FALSE;
m_isPitching = FALSE;
m_isChangingFOV = FALSE;
}

View file

@ -0,0 +1,641 @@
/*
** Command & Conquer Generals(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: MetaEvent.cpp ////////////////////////////////////////////////////////////////////////////
// Created: Colin Day, September 2001
// Desc: Translating keystrokes into event command messages
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/INI.h"
#include "Common/MessageStream.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Team.h"
#include "Common/ThingTemplate.h"
#include "GameClient/Drawable.h"
#include "GameClient/Mouse.h"
#include "GameClient/GameClient.h"
#include "GameClient/InGameUI.h"
#include "GameClient/KeyDefs.h"
#include "GameClient/ParticleSys.h" // for ParticleSystemDebugDisplay
#include "GameClient/Shell.h"
#include "GameClient/WindowLayout.h"
#include "GameClient/GUICallbacks.h"
#include "GameClient/DebugDisplay.h" // for AudioDebugDisplay
#include "GameClient/MetaEvent.h"
#include "GameLogic/GameLogic.h" // for TheGameLogic->getFrame()
MetaMap *TheMetaMap = NULL;
#ifdef _INTERNAL
// for occasional debugging...
///#pragma optimize("", off)
///#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// DEFINES ////////////////////////////////////////////////////////////////////
// PRIVATE TYPES //////////////////////////////////////////////////////////////////////////////////
// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
static const LookupListRec GameMessageMetaTypeNames[] =
{
{ "SAVE_VIEW1", GameMessage::MSG_META_SAVE_VIEW1 },
{ "SAVE_VIEW2", GameMessage::MSG_META_SAVE_VIEW2 },
{ "SAVE_VIEW3", GameMessage::MSG_META_SAVE_VIEW3 },
{ "SAVE_VIEW4", GameMessage::MSG_META_SAVE_VIEW4 },
{ "SAVE_VIEW5", GameMessage::MSG_META_SAVE_VIEW5 },
{ "SAVE_VIEW6", GameMessage::MSG_META_SAVE_VIEW6 },
{ "SAVE_VIEW7", GameMessage::MSG_META_SAVE_VIEW7 },
{ "SAVE_VIEW8", GameMessage::MSG_META_SAVE_VIEW8 },
{ "VIEW_VIEW1", GameMessage::MSG_META_VIEW_VIEW1 },
{ "VIEW_VIEW2", GameMessage::MSG_META_VIEW_VIEW2 },
{ "VIEW_VIEW3", GameMessage::MSG_META_VIEW_VIEW3 },
{ "VIEW_VIEW4", GameMessage::MSG_META_VIEW_VIEW4 },
{ "VIEW_VIEW5", GameMessage::MSG_META_VIEW_VIEW5 },
{ "VIEW_VIEW6", GameMessage::MSG_META_VIEW_VIEW6 },
{ "VIEW_VIEW7", GameMessage::MSG_META_VIEW_VIEW7 },
{ "VIEW_VIEW8", GameMessage::MSG_META_VIEW_VIEW8 },
{ "CREATE_TEAM0", GameMessage::MSG_META_CREATE_TEAM0 },
{ "CREATE_TEAM1", GameMessage::MSG_META_CREATE_TEAM1 },
{ "CREATE_TEAM2", GameMessage::MSG_META_CREATE_TEAM2 },
{ "CREATE_TEAM3", GameMessage::MSG_META_CREATE_TEAM3 },
{ "CREATE_TEAM4", GameMessage::MSG_META_CREATE_TEAM4 },
{ "CREATE_TEAM5", GameMessage::MSG_META_CREATE_TEAM5 },
{ "CREATE_TEAM6", GameMessage::MSG_META_CREATE_TEAM6 },
{ "CREATE_TEAM7", GameMessage::MSG_META_CREATE_TEAM7 },
{ "CREATE_TEAM8", GameMessage::MSG_META_CREATE_TEAM8 },
{ "CREATE_TEAM9", GameMessage::MSG_META_CREATE_TEAM9 },
{ "SELECT_TEAM0", GameMessage::MSG_META_SELECT_TEAM0 },
{ "SELECT_TEAM1", GameMessage::MSG_META_SELECT_TEAM1 },
{ "SELECT_TEAM2", GameMessage::MSG_META_SELECT_TEAM2 },
{ "SELECT_TEAM3", GameMessage::MSG_META_SELECT_TEAM3 },
{ "SELECT_TEAM4", GameMessage::MSG_META_SELECT_TEAM4 },
{ "SELECT_TEAM5", GameMessage::MSG_META_SELECT_TEAM5 },
{ "SELECT_TEAM6", GameMessage::MSG_META_SELECT_TEAM6 },
{ "SELECT_TEAM7", GameMessage::MSG_META_SELECT_TEAM7 },
{ "SELECT_TEAM8", GameMessage::MSG_META_SELECT_TEAM8 },
{ "SELECT_TEAM9", GameMessage::MSG_META_SELECT_TEAM9 },
{ "ADD_TEAM0", GameMessage::MSG_META_ADD_TEAM0 },
{ "ADD_TEAM1", GameMessage::MSG_META_ADD_TEAM1 },
{ "ADD_TEAM2", GameMessage::MSG_META_ADD_TEAM2 },
{ "ADD_TEAM3", GameMessage::MSG_META_ADD_TEAM3 },
{ "ADD_TEAM4", GameMessage::MSG_META_ADD_TEAM4 },
{ "ADD_TEAM5", GameMessage::MSG_META_ADD_TEAM5 },
{ "ADD_TEAM6", GameMessage::MSG_META_ADD_TEAM6 },
{ "ADD_TEAM7", GameMessage::MSG_META_ADD_TEAM7 },
{ "ADD_TEAM8", GameMessage::MSG_META_ADD_TEAM8 },
{ "ADD_TEAM9", GameMessage::MSG_META_ADD_TEAM9 },
{ "VIEW_TEAM0", GameMessage::MSG_META_VIEW_TEAM0 },
{ "VIEW_TEAM1", GameMessage::MSG_META_VIEW_TEAM1 },
{ "VIEW_TEAM2", GameMessage::MSG_META_VIEW_TEAM2 },
{ "VIEW_TEAM3", GameMessage::MSG_META_VIEW_TEAM3 },
{ "VIEW_TEAM4", GameMessage::MSG_META_VIEW_TEAM4 },
{ "VIEW_TEAM5", GameMessage::MSG_META_VIEW_TEAM5 },
{ "VIEW_TEAM6", GameMessage::MSG_META_VIEW_TEAM6 },
{ "VIEW_TEAM7", GameMessage::MSG_META_VIEW_TEAM7 },
{ "VIEW_TEAM8", GameMessage::MSG_META_VIEW_TEAM8 },
{ "VIEW_TEAM9", GameMessage::MSG_META_VIEW_TEAM9 },
{ "SELECT_MATCHING_UNITS", GameMessage::MSG_META_SELECT_MATCHING_UNITS },
{ "SELECT_NEXT_UNIT", GameMessage::MSG_META_SELECT_NEXT_UNIT },
{ "SELECT_PREV_UNIT", GameMessage::MSG_META_SELECT_PREV_UNIT },
{ "SELECT_NEXT_WORKER", GameMessage::MSG_META_SELECT_NEXT_WORKER },
{ "SELECT_PREV_WORKER", GameMessage::MSG_META_SELECT_PREV_WORKER },
{ "SELECT_HERO", GameMessage::MSG_META_SELECT_HERO },
{ "SELECT_ALL", GameMessage::MSG_META_SELECT_ALL },
{ "VIEW_COMMAND_CENTER", GameMessage::MSG_META_VIEW_COMMAND_CENTER },
{ "VIEW_LAST_RADAR_EVENT", GameMessage::MSG_META_VIEW_LAST_RADAR_EVENT },
{ "SCATTER", GameMessage::MSG_META_SCATTER },
{ "STOP", GameMessage::MSG_META_STOP },
{ "DEPLOY", GameMessage::MSG_META_DEPLOY },
{ "CREATE_FORMATION", GameMessage::MSG_META_CREATE_FORMATION },
{ "FOLLOW", GameMessage::MSG_META_FOLLOW },
{ "CHAT_PLAYERS", GameMessage::MSG_META_CHAT_PLAYERS },
{ "CHAT_ALLIES", GameMessage::MSG_META_CHAT_ALLIES },
{ "CHAT_EVERYONE", GameMessage::MSG_META_CHAT_EVERYONE },
{ "DIPLOMACY", GameMessage::MSG_META_DIPLOMACY },
{ "PLACE_BEACON", GameMessage::MSG_META_PLACE_BEACON },
{ "DELETE_BEACON", GameMessage::MSG_META_REMOVE_BEACON },
{ "OPTIONS", GameMessage::MSG_META_OPTIONS },
{ "TOGGLE_LOWER_DETAILS", GameMessage::MSG_META_TOGGLE_LOWER_DETAILS },
{ "TOGGLE_CONTROL_BAR", GameMessage::MSG_META_TOGGLE_CONTROL_BAR },
{ "BEGIN_PATH_BUILD", GameMessage::MSG_META_BEGIN_PATH_BUILD },
{ "END_PATH_BUILD", GameMessage::MSG_META_END_PATH_BUILD },
{ "BEGIN_FORCEATTACK", GameMessage::MSG_META_BEGIN_FORCEATTACK },
{ "END_FORCEATTACK", GameMessage::MSG_META_END_FORCEATTACK },
{ "BEGIN_FORCEMOVE", GameMessage::MSG_META_BEGIN_FORCEMOVE },
{ "END_FORCEMOVE", GameMessage::MSG_META_END_FORCEMOVE },
{ "BEGIN_WAYPOINTS", GameMessage::MSG_META_BEGIN_WAYPOINTS },
{ "END_WAYPOINTS", GameMessage::MSG_META_END_WAYPOINTS },
{ "BEGIN_PREFER_SELECTION", GameMessage::MSG_META_BEGIN_PREFER_SELECTION },
{ "END_PREFER_SELECTION", GameMessage::MSG_META_END_PREFER_SELECTION },
{ "TAKE_SCREENSHOT", GameMessage::MSG_META_TAKE_SCREENSHOT },
{ "ALL_CHEER", GameMessage::MSG_META_ALL_CHEER },
{ "BEGIN_CAMERA_ROTATE_LEFT", GameMessage::MSG_META_BEGIN_CAMERA_ROTATE_LEFT },
{ "END_CAMERA_ROTATE_LEFT", GameMessage::MSG_META_END_CAMERA_ROTATE_LEFT },
{ "BEGIN_CAMERA_ROTATE_RIGHT", GameMessage::MSG_META_BEGIN_CAMERA_ROTATE_RIGHT },
{ "END_CAMERA_ROTATE_RIGHT", GameMessage::MSG_META_END_CAMERA_ROTATE_RIGHT },
{ "BEGIN_CAMERA_ZOOM_IN", GameMessage::MSG_META_BEGIN_CAMERA_ZOOM_IN },
{ "END_CAMERA_ZOOM_IN", GameMessage::MSG_META_END_CAMERA_ZOOM_IN },
{ "BEGIN_CAMERA_ZOOM_OUT", GameMessage::MSG_META_BEGIN_CAMERA_ZOOM_OUT },
{ "END_CAMERA_ZOOM_OUT", GameMessage::MSG_META_END_CAMERA_ZOOM_OUT },
{ "CAMERA_RESET", GameMessage::MSG_META_CAMERA_RESET },
#if defined(_DEBUG) || defined(_INTERNAL)
{ "HELP", GameMessage::MSG_META_HELP },
{ "DEMO_INSTANT_QUIT", GameMessage::MSG_META_DEMO_INSTANT_QUIT },
{ "DEMO_TOGGLE_BEHIND_BUILDINGS", GameMessage::MSG_META_DEMO_TOGGLE_BEHIND_BUILDINGS },
{ "DEMO_LOD_DECREASE", GameMessage::MSG_META_DEMO_LOD_DECREASE },
{ "DEMO_LOD_INCREASE", GameMessage::MSG_META_DEMO_LOD_INCREASE },
{ "DEMO_TOGGLE_LETTERBOX", GameMessage::MSG_META_DEMO_TOGGLE_LETTERBOX },
{ "DEMO_TOGGLE_MESSAGE_TEXT", GameMessage::MSG_META_DEMO_TOGGLE_MESSAGE_TEXT },
{ "DEMO_GIVE_ALL_SCIENCES", GameMessage::MSG_META_DEMO_GIVE_ALL_SCIENCES },
{ "DEMO_GIVE_RANKLEVEL", GameMessage::MSG_META_DEMO_GIVE_RANKLEVEL },
{ "DEMO_TAKE_RANKLEVEL", GameMessage::MSG_META_DEMO_TAKE_RANKLEVEL },
{ "DEMO_GIVE_SCIENCEPURCHASEPOINTS", GameMessage::MSG_META_DEMO_GIVE_SCIENCEPURCHASEPOINTS },
{ "DEMO_SWITCH_TEAMS", GameMessage::MSG_META_DEMO_SWITCH_TEAMS },
{ "DEMO_SWITCH_TEAMS_CHINA_USA", GameMessage::MSG_META_DEMO_SWITCH_TEAMS_BETWEEN_CHINA_USA },
{ "DEMO_TOGGLE_CASHMAPDEBUG", GameMessage::MSG_META_DEMO_TOGGLE_CASHMAPDEBUG },
{ "DEMO_TOGGLE_GRAPHICALFRAMERATEBAR", GameMessage::MSG_META_DEMO_TOGGLE_GRAPHICALFRAMERATEBAR },
{ "DEMO_TOGGLE_PARTICLEDEBUG", GameMessage::MSG_META_DEMO_TOGGLE_PARTICLEDEBUG },
{ "DEMO_TOGGLE_THREATDEBUG", GameMessage::MSG_META_DEMO_TOGGLE_THREATDEBUG },
{ "DEMO_TOGGLE_VISIONDEBUG", GameMessage::MSG_META_DEMO_TOGGLE_VISIONDEBUG },
{ "DEMO_TOGGLE_PROJECTILEDEBUG", GameMessage::MSG_META_DEMO_TOGGLE_PROJECTILEDEBUG },
{ "DEMO_LOD_DECREASE", GameMessage::MSG_META_DEMO_LOD_DECREASE },
{ "DEMO_LOD_INCREASE", GameMessage::MSG_META_DEMO_LOD_INCREASE },
{ "DEMO_TOGGLE_SHADOW_VOLUMES", GameMessage::MSG_META_DEMO_TOGGLE_SHADOW_VOLUMES },
{ "DEMO_TOGGLE_FOGOFWAR", GameMessage::MSG_META_DEMO_TOGGLE_FOGOFWAR },
{ "DEMO_KILL_ALL_ENEMIES", GameMessage::MSG_META_DEMO_KILL_ALL_ENEMIES },
{ "DEMO_KILL_SELECTION", GameMessage::MSG_META_DEMO_KILL_SELECTION },
{ "DEMO_TOGGLE_HURT_ME_MODE", GameMessage::MSG_META_DEMO_TOGGLE_HURT_ME_MODE },
{ "DEMO_TOGGLE_HAND_OF_GOD_MODE", GameMessage::MSG_META_DEMO_TOGGLE_HAND_OF_GOD_MODE },
{ "DEMO_DEBUG_SELECTION", GameMessage::MSG_META_DEMO_DEBUG_SELECTION },
{ "DEMO_LOCK_CAMERA_TO_SELECTION", GameMessage::MSG_META_DEMO_LOCK_CAMERA_TO_SELECTION },
{ "DEMO_TOGGLE_SOUND", GameMessage::MSG_META_DEMO_TOGGLE_SOUND },
{ "DEMO_TOGGLE_TRACKMARKS", GameMessage::MSG_META_DEMO_TOGGLE_TRACKMARKS },
{ "DEMO_TOGGLE_WATERPLANE", GameMessage::MSG_META_DEMO_TOGGLE_WATERPLANE },
{ "DEMO_TIME_OF_DAY", GameMessage::MSG_META_DEMO_TIME_OF_DAY },
{ "DEMO_TOGGLE_MILITARY_SUBTITLES", GameMessage::MSG_META_DEMO_TOGGLE_MILITARY_SUBTITLES },
{ "DEMO_TOGGLE_MUSIC", GameMessage::MSG_META_DEMO_TOGGLE_MUSIC },
{ "DEMO_MUSIC_NEXT_TRACK", GameMessage::MSG_META_DEMO_MUSIC_NEXT_TRACK },
{ "DEMO_MUSIC_PREV_TRACK", GameMessage::MSG_META_DEMO_MUSIC_PREV_TRACK },
{ "DEMO_NEXT_OBJECTIVE_MOVIE", GameMessage::MSG_META_DEMO_NEXT_OBJECTIVE_MOVIE },
{ "DEMO_PLAY_OBJECTIVE_MOVIE1", GameMessage::MSG_META_DEMO_PLAY_OBJECTIVE_MOVIE1 },
{ "DEMO_PLAY_OBJECTIVE_MOVIE2", GameMessage::MSG_META_DEMO_PLAY_OBJECTIVE_MOVIE2 },
{ "DEMO_PLAY_OBJECTIVE_MOVIE3", GameMessage::MSG_META_DEMO_PLAY_OBJECTIVE_MOVIE3 },
{ "DEMO_PLAY_OBJECTIVE_MOVIE4", GameMessage::MSG_META_DEMO_PLAY_OBJECTIVE_MOVIE4 },
{ "DEMO_PLAY_OBJECTIVE_MOVIE5", GameMessage::MSG_META_DEMO_PLAY_OBJECTIVE_MOVIE5 },
{ "DEMO_PLAY_OBJECTIVE_MOVIE6", GameMessage::MSG_META_DEMO_PLAY_OBJECTIVE_MOVIE6 },
{ "DEMO_BEGIN_ADJUST_PITCH", GameMessage::MSG_META_DEMO_BEGIN_ADJUST_PITCH },
{ "DEMO_END_ADJUST_PITCH", GameMessage::MSG_META_DEMO_END_ADJUST_PITCH },
{ "DEMO_BEGIN_ADJUST_FOV", GameMessage::MSG_META_DEMO_BEGIN_ADJUST_FOV },
{ "DEMO_END_ADJUST_FOV", GameMessage::MSG_META_DEMO_END_ADJUST_FOV },
{ "DEMO_LOCK_CAMERA_TO_PLANES", GameMessage::MSG_META_DEMO_LOCK_CAMERA_TO_PLANES },
{ "DEMO_REMOVE_PREREQ", GameMessage::MSG_META_DEMO_REMOVE_PREREQ },
{ "DEMO_RUNSCRIPT1", GameMessage::MSG_META_DEMO_RUNSCRIPT1 },
{ "DEMO_RUNSCRIPT2", GameMessage::MSG_META_DEMO_RUNSCRIPT2 },
{ "DEMO_RUNSCRIPT3", GameMessage::MSG_META_DEMO_RUNSCRIPT3 },
{ "DEMO_RUNSCRIPT4", GameMessage::MSG_META_DEMO_RUNSCRIPT4 },
{ "DEMO_RUNSCRIPT5", GameMessage::MSG_META_DEMO_RUNSCRIPT5 },
{ "DEMO_RUNSCRIPT6", GameMessage::MSG_META_DEMO_RUNSCRIPT6 },
{ "DEMO_RUNSCRIPT7", GameMessage::MSG_META_DEMO_RUNSCRIPT7 },
{ "DEMO_RUNSCRIPT8", GameMessage::MSG_META_DEMO_RUNSCRIPT8 },
{ "DEMO_RUNSCRIPT9", GameMessage::MSG_META_DEMO_RUNSCRIPT9 },
{ "DEMO_ADDCASH", GameMessage::MSG_META_DEMO_ADD_CASH },
{ "DEMO_TOGGLE_RENDER", GameMessage::MSG_META_DEMO_TOGGLE_RENDER },
{ "DEMO_TOGGLE_BW_VIEW", GameMessage::MSG_META_DEMO_TOGGLE_BW_VIEW },
{ "DEMO_TOGGLE_RED_VIEW", GameMessage::MSG_META_DEMO_TOGGLE_RED_VIEW },
{ "DEMO_TOGGLE_GREEN_VIEW", GameMessage::MSG_META_DEMO_TOGGLE_GREEN_VIEW },
{ "DEMO_TOGGLE_MOTION_BLUR_ZOOM", GameMessage::MSG_META_DEMO_TOGGLE_MOTION_BLUR_ZOOM },
{ "DEMO_SHOW_EXTENTS", GameMessage::MSG_META_DEBUG_SHOW_EXTENTS },
{ "DEMO_SHOW_HEALTH", GameMessage::MSG_META_DEBUG_SHOW_HEALTH },
{ "DEMO_GIVE_VETERANCY", GameMessage::MSG_META_DEBUG_GIVE_VETERANCY },
{ "DEMO_TAKE_VETERANCY", GameMessage::MSG_META_DEBUG_TAKE_VETERANCY },
{ "DEMO_BATTLE_CRY", GameMessage::MSG_META_DEMO_BATTLE_CRY },
#ifdef ALLOW_SURRENDER
{ "DEMO_TEST_SURRENDER", GameMessage::MSG_META_DEMO_TEST_SURRENDER },
#endif
{ "DEMO_TOGGLE_AVI", GameMessage::MSG_META_DEMO_TOGGLE_AVI },
{ "DEMO_PLAY_CAMEO_MOVIE", GameMessage::MSG_META_DEMO_PLAY_CAMEO_MOVIE },
{ "DEMO_TOGGLE_ZOOM_LOCK", GameMessage::MSG_META_DEMO_TOGGLE_ZOOM_LOCK },
{ "DEMO_TOGGLE_SPECIAL_POWER_DELAYS", GameMessage::MSG_META_DEMO_TOGGLE_SPECIAL_POWER_DELAYS },
{ "DEMO_TOGGLE_METRICS", GameMessage::MSG_META_DEMO_TOGGLE_METRICS},
{ "DEMO_DESHROUD", GameMessage::MSG_META_DEMO_DESHROUD },
{ "DEMO_ENSHROUD", GameMessage::MSG_META_DEMO_ENSHROUD },
{ "DEMO_TOGGLE_AI_DEBUG", GameMessage::MSG_META_DEMO_TOGGLE_AI_DEBUG },
{ "DEMO_TOGGLE_NO_DRAW", GameMessage::MSG_NO_DRAW },
{ "DEMO_CYCLE_LOD_LEVEL", GameMessage::MSG_META_DEMO_CYCLE_LOD_LEVEL },
{ "DEMO_DUMP_ASSETS", GameMessage::MSG_META_DEBUG_DUMP_ASSETS},
{ "DEMO_INSTANT_BUILD", GameMessage::MSG_META_DEMO_INSTANT_BUILD },
{ "DEMO_TOGGLE_CAMERA_DEBUG", GameMessage::MSG_META_DEMO_TOGGLE_CAMERA_DEBUG },
/// Begin VTUNE
{ "DEMO_VTUNE_ON", GameMessage::MSG_META_DEBUG_VTUNE_ON },
{ "DEMO_VTUNE_OFF", GameMessage::MSG_META_DEBUG_VTUNE_OFF },
/// End VTUNE
//lorenzen's feather water
{ "DEMO_TOGGLE_FEATHER_WATER", GameMessage::MSG_META_DEBUG_TOGGLE_FEATHER_WATER },
{ "DEMO_INCR_ANIM_SKATE_SPEED", GameMessage::MSG_META_DEBUG_INCR_ANIM_SKATE_SPEED },
{ "DEMO_DECR_ANIM_SKATE_SPEED", GameMessage::MSG_META_DEBUG_DECR_ANIM_SKATE_SPEED },
{ "DEMO_CYCLE_EXTENT_TYPE", GameMessage::MSG_META_DEBUG_CYCLE_EXTENT_TYPE },
{ "DEMO_INCR_EXTENT_MAJOR", GameMessage::MSG_META_DEBUG_INCREASE_EXTENT_MAJOR },
{ "DEMO_DECR_EXTENT_MAJOR", GameMessage::MSG_META_DEBUG_DECREASE_EXTENT_MAJOR },
{ "DEMO_INCR_EXTENT_MAJOR_LARGE", GameMessage::MSG_META_DEBUG_INCREASE_EXTENT_MAJOR_BIG },
{ "DEMO_DECR_EXTENT_MAJOR_LARGE", GameMessage::MSG_META_DEBUG_DECREASE_EXTENT_MAJOR_BIG },
{ "DEMO_INCR_EXTENT_MINOR", GameMessage::MSG_META_DEBUG_INCREASE_EXTENT_MINOR },
{ "DEMO_DECR_EXTENT_MINOR", GameMessage::MSG_META_DEBUG_DECREASE_EXTENT_MINOR },
{ "DEMO_INCR_EXTENT_MINOR_LARGE", GameMessage::MSG_META_DEBUG_INCREASE_EXTENT_MINOR_BIG },
{ "DEMO_DECR_EXTENT_MINOR_LARGE", GameMessage::MSG_META_DEBUG_DECREASE_EXTENT_MINOR_BIG },
{ "DEMO_INCR_EXTENT_HEIGHT", GameMessage::MSG_META_DEBUG_INCREASE_EXTENT_HEIGHT },
{ "DEMO_DECR_EXTENT_HEIGHT", GameMessage::MSG_META_DEBUG_DECREASE_EXTENT_HEIGHT },
{ "DEMO_INCR_EXTENT_HEIGHT_LARGE", GameMessage::MSG_META_DEBUG_INCREASE_EXTENT_HEIGHT_BIG },
{ "DEMO_DECR_EXTENT_HEIGHT_LARGE", GameMessage::MSG_META_DEBUG_DECREASE_EXTENT_HEIGHT_BIG },
{ "DEMO_TOGGLE_NETWORK", GameMessage::MSG_META_DEBUG_TOGGLE_NETWORK },
{ "DEBUG_DUMP_PLAYER_OBJECTS", GameMessage::MSG_META_DEBUG_DUMP_PLAYER_OBJECTS },
{ "DEBUG_DUMP_ALL_PLAYER_OBJECTS", GameMessage::MSG_META_DEBUG_DUMP_ALL_PLAYER_OBJECTS },
{ "DEMO_WIN", GameMessage::MSG_META_DEBUG_WIN },
{ "DEMO_TOGGLE_DEBUG_STATS", GameMessage::MSG_META_DEMO_TOGGLE_DEBUG_STATS },
#endif // defined(_DEBUG) || defined(_INTERNAL)
#if defined(_INTERNAL) || defined(_DEBUG) || defined(_PLAYTEST)
{ "DEMO_TOGGLE_AUDIODEBUG", GameMessage::MSG_META_DEMO_TOGGLE_AUDIODEBUG },
#endif//defined(_INTERNAL) || defined(_DEBUG) || defined(_PLAYTEST)
#ifdef DUMP_PERF_STATS
{ "DEMO_PERFORM_STATISTICAL_DUMP", GameMessage::MSG_META_DEMO_PERFORM_STATISTICAL_DUMP },
#endif//DUMP_PERF_STATS
{ NULL, 0 }// keep this last!
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
static const FieldParse TheMetaMapFieldParseTable[] =
{
{ "Key", INI::parseLookupList, KeyNames, offsetof( MetaMapRec, m_key ) },
{ "Transition", INI::parseLookupList, TransitionNames, offsetof( MetaMapRec, m_transition ) },
{ "Modifiers", INI::parseLookupList, ModifierNames, offsetof( MetaMapRec, m_modState ) },
{ "UseableIn", INI::parseBitString32, TheCommandUsableInNames, offsetof( MetaMapRec, m_usableIn ) },
{ "Category", INI::parseLookupList, CategoryListName, offsetof( MetaMapRec, m_category ) },
{ "Description", INI::parseAndTranslateLabel, 0, offsetof( MetaMapRec, m_description ) },
{ "DisplayName", INI::parseAndTranslateLabel, 0, offsetof( MetaMapRec, m_displayName ) },
{ NULL, NULL, 0, 0 } // keep this last
};
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
MetaEventTranslator::MetaEventTranslator() :
m_lastKeyDown(MK_NONE),
m_lastModState(0)
{
for (Int i = 0; i < NUM_MOUSE_BUTTONS; ++i) {
m_nextUpShouldCreateDoubleClick[i] = FALSE;
}
}
//-------------------------------------------------------------------------------------------------
MetaEventTranslator::~MetaEventTranslator()
{
}
//-------------------------------------------------------------------------------------------------
static const char * findGameMessageNameByType(GameMessage::Type type)
{
for (const LookupListRec* metaNames = GameMessageMetaTypeNames; metaNames->name; metaNames++)
if (metaNames->value == (Int)type)
return metaNames->name;
DEBUG_CRASH(("MetaTypeName %d not found -- did you remember to add it to GameMessageMetaTypeNames[] ?\n"));
return "???";
}
//-------------------------------------------------------------------------------------------------
GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessage *msg)
{
GameMessageDisposition disp = KEEP_MESSAGE;
GameMessage::Type t = msg->getType();
if (t == GameMessage::MSG_RAW_KEY_DOWN || t == GameMessage::MSG_RAW_KEY_UP)
{
MappableKeyType key = (MappableKeyType)msg->getArgument(0)->integer;
Int keyState = msg->getArgument(1)->integer;
// for our purposes here, we don't care to distinguish between right and left keys,
// so just fudge a little to simplify things.
Int newModState = 0;
if( keyState & KEY_STATE_CONTROL )
{
newModState |= CTRL;
}
if( keyState & KEY_STATE_SHIFT )
{
newModState |= SHIFT;
}
if( keyState & KEY_STATE_ALT )
{
newModState |= ALT;
}
for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next)
{
DEBUG_ASSERTCRASH(map->m_meta > GameMessage::MSG_BEGIN_META_MESSAGES &&
map->m_meta < GameMessage::MSG_END_META_MESSAGES, ("hmm, expected only meta-msgs here"));
//
// if this command is *only* usable in the game, we will ignore it if the game client
// has not yet incremented to frame 1 (keeps us from doing in-game commands during
// a map load, which throws the input system into wack because there isn't a
// client frame for the input event, and in the case of a command that pauses the
// game, like the quit menu, the client frame will never get beyond 0 and we
// lose the ability to process any input
//
if( map->m_usableIn == COMMANDUSABLE_GAME && TheGameClient->getFrame() < 1 )
continue;
// if the shell is active, and this command is not usable in shell, continue
if (TheShell && TheShell->isShellActive() && !(map->m_usableIn & COMMANDUSABLE_SHELL) )
continue;
// if the shell is not active and this command is not usable in the game, continue
if (TheShell && !TheShell->isShellActive() && !(map->m_usableIn & COMMANDUSABLE_GAME) )
continue;
// check for the special case of mods-only-changed.
if (
map->m_key == MK_NONE &&
newModState != m_lastModState &&
(
(map->m_transition == UP && map->m_modState == m_lastModState) ||
(map->m_transition == DOWN && map->m_modState == newModState)
)
)
{
//DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s\n", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta)));
/*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta);
disp = DESTROY_MESSAGE;
break;
}
// ok, now check for "normal" key transitions.
if (
map->m_key == key &&
map->m_modState == newModState &&
(
(map->m_transition == UP && (keyState & KEY_STATE_UP)) ||
(map->m_transition == DOWN && (keyState & KEY_STATE_DOWN)) //||
//(map->m_transition == DOUBLEDOWN && (keyState & KEY_STATE_DOWN) && m_lastKeyDown == key)
)
)
{
if( keyState & KEY_STATE_AUTOREPEAT )
{
// if it's an autorepeat of a "known" key, don't generate the meta-event,
// but DO eat the keystroke so no one else can mess with it
//DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s\n", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta)));
}
else
{
/*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta);
//DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() normal: %s\n", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta)));
}
disp = DESTROY_MESSAGE;
break;
}
}
if (t == GameMessage::MSG_RAW_KEY_DOWN)
m_lastKeyDown = key;
m_lastModState = newModState;
}
if (t > GameMessage::MSG_RAW_MOUSE_BEGIN && t < GameMessage::MSG_RAW_MOUSE_END )
{
Int index = 0;
switch (t)
{
case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN:
case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN:
case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN:
{
// Fill out which the current mouse down position
if (t == GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN)
index = 1;
else if (t == GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN)
index = 2;
// else index == 0
m_mouseDownPosition[index] = msg->getArgument(0)->pixel;
m_nextUpShouldCreateDoubleClick[index] = FALSE;
break;
}
case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK:
case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK:
case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK:
{
if (t == GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK)
index = 1;
else if (t == GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK)
index = 2;
// else index == 0
m_nextUpShouldCreateDoubleClick[index] = TRUE;
break;
}
case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP:
case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP:
case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP:
{
ICoord2D location = msg->getArgument(0)->pixel;
// Fill out which the current mouse down position
if (t == GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP)
index = 1;
else if (t == GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP)
index = 2;
// else index == 0
GameMessage *newMessage = NULL;
if (t == GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP)
{
if (m_nextUpShouldCreateDoubleClick[index])
newMessage = TheMessageStream->insertMessage(GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK, const_cast<GameMessage*>(msg));
else
newMessage = TheMessageStream->insertMessage(GameMessage::MSG_MOUSE_LEFT_CLICK, const_cast<GameMessage*>(msg));
m_nextUpShouldCreateDoubleClick[index] = FALSE;
}
else if (t == GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP)
{
if (m_nextUpShouldCreateDoubleClick[index])
newMessage = TheMessageStream->insertMessage(GameMessage::MSG_MOUSE_MIDDLE_DOUBLE_CLICK, const_cast<GameMessage*>(msg));
else
newMessage = TheMessageStream->insertMessage(GameMessage::MSG_MOUSE_MIDDLE_CLICK, const_cast<GameMessage*>(msg));
m_nextUpShouldCreateDoubleClick[index] = FALSE;
}
else if (t == GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP)
{
if (m_nextUpShouldCreateDoubleClick[index])
newMessage = TheMessageStream->insertMessage(GameMessage::MSG_MOUSE_RIGHT_DOUBLE_CLICK, const_cast<GameMessage*>(msg));
else
newMessage = TheMessageStream->insertMessage(GameMessage::MSG_MOUSE_RIGHT_CLICK, const_cast<GameMessage*>(msg));
m_nextUpShouldCreateDoubleClick[index] = FALSE;
}
IRegion2D pixelRegion;
buildRegion( &m_mouseDownPosition[index], &location, &pixelRegion );
if (abs(pixelRegion.hi.x - pixelRegion.lo.x) < TheMouse->m_dragTolerance &&
abs(pixelRegion.hi.y - pixelRegion.lo.y) < TheMouse->m_dragTolerance)
{
pixelRegion.hi.x = pixelRegion.lo.x;
pixelRegion.hi.y = pixelRegion.lo.y;
}
newMessage->appendPixelRegionArgument( pixelRegion );
// append the modifier keys to the message.
newMessage->appendIntegerArgument( msg->getArgument(1)->integer );
break;
}
}
}
return disp;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MetaMap::MetaMap() :
m_metaMaps(NULL)
{
}
//-------------------------------------------------------------------------------------------------
MetaMap::~MetaMap()
{
while (m_metaMaps)
{
MetaMapRec *next = m_metaMaps->m_next;
m_metaMaps->deleteInstance();
m_metaMaps = next;
}
}
//-------------------------------------------------------------------------------------------------
GameMessage::Type MetaMap::findGameMessageMetaType(const char* name)
{
for (const LookupListRec* metaNames = GameMessageMetaTypeNames; metaNames->name; metaNames++)
if (stricmp(metaNames->name, name) == 0)
return (GameMessage::Type)metaNames->value;
DEBUG_CRASH(("MetaTypeName %s not found -- did you remember to add it to GameMessageMetaTypeNames[] ?", name));
return GameMessage::MSG_INVALID;
}
//-------------------------------------------------------------------------------------------------
MetaMapRec *MetaMap::getMetaMapRec(GameMessage::Type t)
{
for (MetaMapRec *map = m_metaMaps; map; map = map->m_next)
{
if (map->m_meta == t)
return map;
}
// not found.. create a new one.
MetaMapRec *m = newInstance(MetaMapRec);
m->m_meta = t;
m->m_key = MK_NONE;
m->m_transition = DOWN;
m->m_modState = NONE;
m->m_usableIn = COMMANDUSABLE_NONE;
m->m_category = CATEGORY_MISC;
m->m_description.clear();
m->m_displayName.clear();
m->m_next = m_metaMaps;
m_metaMaps = m;
return m;
}
//-------------------------------------------------------------------------------------------------
/*static */ void MetaMap::parseMetaMap(INI* ini)
{
// read and ignore the meta-map name
const char *c = ini->getNextToken();
GameMessage::Type t = TheMetaMap->findGameMessageMetaType(c);
if (t == GameMessage::MSG_INVALID)
throw INI_INVALID_DATA;
MetaMapRec *map = TheMetaMap->getMetaMapRec(t);
if (map == NULL)
throw INI_INVALID_DATA;
ini->initFromINI(map, TheMetaMapFieldParseTable);
}
//-------------------------------------------------------------------------------------------------
/*static*/ void INI::parseMetaMapDefinition( INI* ini )
{
MetaMap::parseMetaMap(ini);
}

View file

@ -0,0 +1,320 @@
/*
** Command & Conquer Generals(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: PlaceEventTranslator.cpp ///////////////////////////////////////////////////////////
// Author: Steven Johnson, Dec 2001
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingTemplate.h"
#include "Common/BuildAssistant.h"
#include "GameLogic/Object.h"
#include "GameLogic/GameLogic.h"
#include "GameClient/CommandXlat.h"
#include "GameClient/PlaceEventTranslator.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
//-------------------------------------------------------------------------------------------------
PlaceEventTranslator::PlaceEventTranslator() : m_frameOfUpButton(-1)
{
}
//-------------------------------------------------------------------------------------------------
PlaceEventTranslator::~PlaceEventTranslator()
{
}
//-------------------------------------------------------------------------------------------------
/** Translator to process raw input messages into the "place something" message(s) */
//-------------------------------------------------------------------------------------------------
GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMessage *msg)
{
GameMessageDisposition disp = KEEP_MESSAGE;
switch(msg->getType())
{
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN:
{
// if we're in a building placement mode, do the place and send to all players
const ThingTemplate *build = TheInGameUI->getPendingPlaceType();
if( build && TheInGameUI->isPlacementAnchored() == FALSE )
{
ICoord2D mouse = msg->getArgument(0)->pixel;
Coord3D world;
// translate mouse position to world position
TheTacticalView->screenToTerrain( &mouse, &world );
//
// placing things causes a dozer to go over and build it ... get the dozer in question
// from the in game UI
//
Object *builderObject = TheGameLogic->findObjectByID( TheInGameUI->getPendingPlaceSourceObjectID() );
// if our source object is gone cancel this whole placement process
if( builderObject == NULL )
{
TheInGameUI->placeBuildAvailable( NULL, NULL );
break;
} // end if
// set this location as the placement anchor
TheInGameUI->setPlacementStart( &mouse );
/*
//
// This block of code checks for valid placement on a down mouse click, but since we can
// rotate a building into a valid location, this check prevents us from placing things
// down in some legal locations
//
// get the type of thing we want to build
const ThingTemplate *whatToBuild = TheInGameUI->getPendingPlaceType();
//
// if the spot at which they choose to place this thing is illegal we won't start
// the placement anchor, instead we play a "can't do that" sound
//
LegalBuildCode lbc;
lbc = TheBuildAssistant->isLocationLegalToBuild( &world,
whatToBuild,
TheInGameUI->getPlacementAngle(),
BuildAssistant::USE_QUICK_PATHFIND |
BuildAssistant::TERRAIN_RESTRICTIONS |
BuildAssistant::CLEAR_PATH |
BuildAssistant::NO_OBJECT_OVERLAP,
builderObject );
if( lbc != LBC_OK )
{
static const Sound *noCanDoSound = TheAudio->Sounds->getSound( "NoCanDoSound" );
// play a can't do that sound
TheAudio->Sounds->playSound( noCanDoSound );
// display a message to the user as to why you can't build there
TheInGameUI->displayCantBuildMessage( lbc );
} // end if
else
{
// start placement anchor
TheInGameUI->setPlacementStart(&mouse);
} // end else
*/
// used the input
disp = DESTROY_MESSAGE;
}
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK:
case GameMessage::MSG_MOUSE_LEFT_CLICK:
{
// if we're in a building placement mode, do the place and send to all players
const ThingTemplate *build = TheInGameUI->getPendingPlaceType();
// ... and also remove any radius cursor that is active.
// (srj sez: not sure if this is always necessary... more of a failsafe to make it go away.)
TheInGameUI->setRadiusCursorNone();
if (build && TheInGameUI->isPlacementAnchored())
{
GameMessage *placeMsg;
// Player *player = ThePlayerList->getLocalPlayer();
Coord3D world;
Real angle;
ICoord2D anchorStart, anchorEnd;
Bool isLineBuild = TheBuildAssistant->isLineBuildTemplate( build );
// get the angle of the drawable at the cursor to use as the initial angle
angle = TheInGameUI->getPlacementAngle();
// get start point from the anchor arrow used to place and select angles
TheInGameUI->getPlacementPoints( &anchorStart, &anchorEnd );
// translate the screen position of start to world target location
TheTacticalView->screenToTerrain( &anchorStart, &world );
// get the source object ID of the thing that is "building" the object
ObjectID builderID = INVALID_ID;
Object *builderObj = TheGameLogic->findObjectByID( TheInGameUI->getPendingPlaceSourceObjectID() );
if( builderObj )
builderID = builderObj->getID();
//Kris: September 27, 2002
//Make sure we have enough CASH to build it! It's possible that between the
//time we initiated it and the time we confirm it, a hacker has stolen some of
//our cash!
CanMakeType cmt = TheBuildAssistant->canMakeUnit( builderObj, build );
if( cmt != CANMAKE_OK )
{
if (cmt == CANMAKE_NO_MONEY)
{
TheEva->setShouldPlay(EVA_InsufficientFunds);
TheInGameUI->message( "GUI:NotEnoughMoneyToBuild" );
break;
}
else if (cmt == CANMAKE_QUEUE_FULL)
{
TheInGameUI->message( "GUI:ProductionQueueFull" );
break;
}
else if (cmt == CANMAKE_PARKING_PLACES_FULL)
{
TheInGameUI->message( "GUI:ParkingPlacesFull" );
break;
}
else if( cmt == CANMAKE_MAXED_OUT_FOR_PLAYER )
{
TheInGameUI->message( "GUI:UnitMaxedOut" );
break;
}
// get out of pending placement mode, this will also clear the arrow anchor status
TheInGameUI->placeBuildAvailable( NULL, NULL );
break;
}
// check to see if this is a legal location to build something at
LegalBuildCode lbc;
lbc = TheBuildAssistant->isLocationLegalToBuild( &world,
build,
angle,
BuildAssistant::USE_QUICK_PATHFIND |
BuildAssistant::TERRAIN_RESTRICTIONS |
BuildAssistant::CLEAR_PATH |
BuildAssistant::NO_OBJECT_OVERLAP |
BuildAssistant::SHROUD_REVEALED,
builderObj, NULL );
if( lbc == LBC_OK )
{
/** @todo Do not send local player id as argument once we have player ids
tied into all messages automatically */
// create the right kind of message
if( isLineBuild )
placeMsg = TheMessageStream->appendMessage( GameMessage::MSG_DOZER_CONSTRUCT_LINE );
else
placeMsg = TheMessageStream->appendMessage( GameMessage::MSG_DOZER_CONSTRUCT );
placeMsg->appendIntegerArgument(build->getTemplateID());
placeMsg->appendLocationArgument(world);
placeMsg->appendRealArgument(angle);
if( isLineBuild )
{
Coord3D worldEnd;
TheTacticalView->screenToTerrain( &anchorEnd, &worldEnd );
placeMsg->appendLocationArgument( worldEnd );
} // end if
pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), placeMsg->getType() );
// get out of pending placement mode, this will also clear the arrow anchor status
TheInGameUI->placeBuildAvailable( NULL, NULL );
} // end if, location legal to build at
else
{
// can't place, display why
TheInGameUI->displayCantBuildMessage( lbc );
//Cannot build here -- play the voice sound from the dozer
AudioEventRTS sound = *builderObj->getTemplate()->getPerUnitSound( "VoiceNoBuild" );
sound.setObjectID( builderObj->getID() );
TheAudio->addAudioEvent( &sound );
// play a can't do that sound (UI beep type sound)
static AudioEventRTS noCanDoSound( "NoCanDoSound" );
TheAudio->addAudioEvent( &noCanDoSound );
// unhook the anchor so they can try again
TheInGameUI->setPlacementStart( NULL );
} // end else
// used the input
disp = DESTROY_MESSAGE;
m_frameOfUpButton = TheGameLogic->getFrame();
}
if (disp == DESTROY_MESSAGE)
TheInGameUI->clearAttackMoveToMode();
break;
}
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_POSITION:
{
// if a building placement is in progress update the destination position
if (TheInGameUI->isPlacementAnchored())
{
const Int PLACEMENT_DRAG_THRESHOLD_DIST = 5; // in pixels away from anchor point
ICoord2D mouse = msg->getArgument(0)->pixel;
//
// we will only process placement end point sets (clicking, and dragging to set angles)
// if we have moved far enough away from the start point
//
ICoord2D start;
TheInGameUI->getPlacementPoints( &start, NULL );
Int x, y;
x = mouse.x - start.x;
y = mouse.y - start.y;
if( sqrt( (x * x) + (y * y) ) >= PLACEMENT_DRAG_THRESHOLD_DIST )
{
TheInGameUI->setPlacementEnd(&mouse);
disp = DESTROY_MESSAGE;
} // end if
}
break;
}
}
return disp;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,343 @@
/*
** Command & Conquer Generals(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: WindowXlat.cpp ///////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//
// Westwood Studios Pacific.
//
// Confidential Information
// Copyright (C) 2001 - All Rights Reserved
//
//-----------------------------------------------------------------------------
//
// Project: RTS3
//
// File name: WindowXlat.cpp
//
// Created: Colin Day, September 2001
//
// Desc: Window system translator that monitors raw input messages
// on the stream from the input devices and acts on anything
// relevant to the windowing system.
//
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
// USER INCLUDES //////////////////////////////////////////////////////////////
#include "Common/MessageStream.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/WindowXlat.h"
#include "GameClient/Shell.h"
#include "GameClient/Display.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// DEFINES ////////////////////////////////////////////////////////////////////
// PRIVATE TYPES //////////////////////////////////////////////////////////////
// PRIVATE DATA ///////////////////////////////////////////////////////////////
// PUBLIC DATA ////////////////////////////////////////////////////////////////
// PRIVATE PROTOTYPES /////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#if defined(_DEBUG) || defined(_INTERNAL) //debug hack to view object under mouse stats
extern ICoord2D TheMousePos;
#endif
// rawMouseToWindowMessage ====================================================
/** Translate a raw mouse input event to a game window specific message
* for the window system */
//=============================================================================
static GameWindowMessage rawMouseToWindowMessage( const GameMessage *msg )
{
GameWindowMessage gwm = GWM_NONE;
switch( msg->getType() )
{
// ------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_POSITION:
gwm = GWM_MOUSE_POS;
break;
// ------------------------------------------------------------------------
// Strange, but true. The window stuff really doesn't care about double clicks, so just
// treat it as a down click.. Kinda like a second click.
case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK:
case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN:
gwm = GWM_LEFT_DOWN;
break;
case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP:
gwm = GWM_LEFT_UP;
break;
case GameMessage::MSG_RAW_MOUSE_LEFT_DRAG:
gwm = GWM_LEFT_DRAG;
break;
// ------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK:
case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN:
gwm = GWM_MIDDLE_DOWN;
break;
case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP:
gwm = GWM_MIDDLE_UP;
break;
case GameMessage::MSG_RAW_MOUSE_MIDDLE_DRAG:
gwm = GWM_MIDDLE_DRAG;
break;
// ------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK:
case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN:
gwm = GWM_RIGHT_DOWN;
break;
case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP:
gwm = GWM_RIGHT_UP;
break;
case GameMessage::MSG_RAW_MOUSE_RIGHT_DRAG:
gwm = GWM_RIGHT_DRAG;
break;
// ------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_WHEEL:
if( msg->getArgument( 1 )->integer > 0 )
gwm = GWM_WHEEL_UP;
else
gwm = GWM_WHEEL_DOWN;
break;
} // end switch
return gwm;
} // end rawMouseToWindowMessage
///////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//=============================================================================
WindowTranslator::WindowTranslator()
{
}
//=============================================================================
WindowTranslator::~WindowTranslator()
{
}
// WindowTranslator ===========================================================
/** Window translator that monitors raw input messages on the stream and
* acts on anything relavant to the windowing system */
//=============================================================================
GameMessageDisposition WindowTranslator::translateGameMessage(const GameMessage *msg)
{
GameMessageDisposition disp = KEEP_MESSAGE;
Bool forceKeepMessage = FALSE;
WinInputReturnCode returnCode = WIN_INPUT_NOT_USED;
if (TheTacticalView && TheTacticalView->isMouseLocked())
{
return KEEP_MESSAGE;
}
switch( msg->getType() )
{
// ------------------------------------------------------------------------
case GameMessage::MSG_META_TOGGLE_ATTACKMOVE:
{
// Basically, we're cheating here. The mouse no longer sends us useless spam.
ICoord2D mousePos = TheMouse->getMouseStatus()->pos;
if( TheWindowManager )
TheWindowManager->winProcessMouseEvent( GWM_NONE, &mousePos, NULL );
// Force it to keep the message, regardless of what the window thinks it did with the input.
return KEEP_MESSAGE;
}
// ------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP:
{
if( TheInGameUI && TheInGameUI->isPlacementAnchored() )
{
//If we release the button outside
forceKeepMessage = TRUE;
}
//FALL THROUGH INTENTIONALLY!
}
case GameMessage::MSG_RAW_MOUSE_POSITION:
case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN:
case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK:
case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN:
case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK:
case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP:
case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN:
case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK:
case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP:
{
// all window events have the position of the mouse as arg 0
ICoord2D mousePos = msg->getArgument( 0 )->pixel;
#if defined(_DEBUG) || defined(_INTERNAL) //debug hack to view object under mouse stats
TheMousePos.x = mousePos.x;
TheMousePos.y = mousePos.y;
#endif
// process the mouse event position
GameWindowMessage gwm = rawMouseToWindowMessage( msg );
if( TheWindowManager )
returnCode = TheWindowManager->winProcessMouseEvent( gwm, &mousePos, NULL );
if( TheShell && TheShell->isShellActive() )
returnCode = WIN_INPUT_USED;
if ( TheInGameUI && TheInGameUI->getInputEnabled() == FALSE )
returnCode = WIN_INPUT_USED;
break;
} // end, raw mouse position
// ------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_LEFT_DRAG:
case GameMessage::MSG_RAW_MOUSE_MIDDLE_DRAG:
case GameMessage::MSG_RAW_MOUSE_RIGHT_DRAG:
{
// all window events have the position of the mouse as arg 0
ICoord2D mousePos = msg->getArgument( 0 )->pixel;
// get delta for drag
ICoord2D delta = msg->getArgument( 1 )->pixel;
// process drag event
GameWindowMessage gwm = rawMouseToWindowMessage( msg );
if( TheWindowManager )
returnCode = TheWindowManager->winProcessMouseEvent( gwm, &mousePos, &delta );
if( TheShell && TheShell->isShellActive() )
returnCode = WIN_INPUT_USED;
if ( TheInGameUI && TheInGameUI->getInputEnabled() == FALSE )
returnCode = WIN_INPUT_USED;
break;
} // end drag mouse
// ------------------------------------------------------------------------
case GameMessage::MSG_RAW_MOUSE_WHEEL:
{
// all window events have the position of the mouse as arg 0
ICoord2D mousePos = msg->getArgument( 0 )->pixel;
// get wheel position
Int wheelPos = msg->getArgument( 1 )->integer;
// process wheel event
GameWindowMessage gwm = rawMouseToWindowMessage( msg );
if( TheWindowManager )
returnCode = TheWindowManager->winProcessMouseEvent( gwm, &mousePos,
&wheelPos );
if( TheShell && TheShell->isShellActive() )
returnCode = WIN_INPUT_USED;
if ( TheInGameUI && TheInGameUI->getInputEnabled() == FALSE )
returnCode = WIN_INPUT_USED;
break;
} // end mouse wheel
// ------------------------------------------------------------------------
case GameMessage::MSG_RAW_KEY_DOWN:
case GameMessage::MSG_RAW_KEY_UP:
{
// get key and state from args
UnsignedByte key = msg->getArgument( 0 )->integer;
UnsignedByte state = msg->getArgument( 1 )->integer;
// process event through window system
if( TheWindowManager )
returnCode = TheWindowManager->winProcessKey( key, state );
// If we're in a movie, we want to be able to escape out of it
if(returnCode != WIN_INPUT_USED
&& (key == KEY_ESC)
&& (BitTest( state, KEY_STATE_UP ))
&& TheDisplay->isMoviePlaying()
&& TheGlobalData->m_allowExitOutOfMovies == TRUE )
{
TheDisplay->stopMovie();
returnCode = WIN_INPUT_USED;
}
if(returnCode != WIN_INPUT_USED
&& (key == KEY_ESC)
&& (BitTest( state, KEY_STATE_UP ))
&& (TheInGameUI && (TheInGameUI->getInputEnabled() == FALSE)) )
{
returnCode = WIN_INPUT_USED;
}
break;
} // end key messages
// ------------------------------------------------------------------------
default:
break;
} // end switch( msg->getType() )
// remove event from the stream if the return code specifies to do so
// If TheShell doesn't exist, then well, we're not in RTS, we're in GUIEdit
if( returnCode == WIN_INPUT_USED && !forceKeepMessage )// || (TheShell && TheShell->isShellActive()))
{
disp = DESTROY_MESSAGE;
}
return disp;
}