/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ControlBar.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Colin Day, March 2002
// Desc: Context sensitive command interface
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_GUI_COMMMAND_NAMES
#define DEFINE_COMMAND_OPTION_NAMES
#define DEFINE_WEAPONSLOTTYPE_NAMES
#define DEFINE_RADIUSCURSOR_NAMES
#include "Common/ActionManager.h"
#include "Common/GameType.h"
#include "Common/MultiplayerSettings.h"
#include "Common/NameKeyGenerator.h"
#include "Common/OVERRIDE.h"
#include "Common/PlayerTemplate.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ProductionPrerequisite.h"
#include "Common/SpecialPower.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "Common/Upgrade.h"
#include "Common/Recorder.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/ProductionUpdate.h"
#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/SpecialPowerModule.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameLogic/Module/RebuildHoleBehavior.h"
#include "GameLogic/ScriptEngine.h"
#include "GameClient/AnimateWindowManager.h"
#include "GameClient/ControlBar.h"
#include "GameClient/ControlBarScheme.h"
#include "GameClient/Drawable.h"
#include "GameClient/Display.h"
#include "GameClient/DisplayStringManager.h"
#include "GameClient/GameClient.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/GameText.h"
#include "GameClient/GadgetPushButton.h"
#include "GameClient/GadgetProgressBar.h"
#include "GameClient/GadgetStaticText.h"
#include "GameClient/GadgetTextEntry.h"
#include "GameClient/InGameUI.h"
#include "GameClient/WindowVideoManager.h"
#include "GameClient/ControlBarResizer.h"
#include "GameClient/GadgetListBox.h"
#include "GameClient/HotKey.h"
#include "GameClient/GameWindowTransitions.h"
#include "GameClient/GUICallbacks.h"
#include "GameNetwork/GameInfo.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
ControlBar *TheControlBar = NULL;
const Image* ControlBar::m_rankVeteranIcon = NULL;
const Image* ControlBar::m_rankEliteIcon = NULL;
const Image* ControlBar::m_rankHeroicIcon = NULL;
///////////////////////////////////////////////////////////////////////////////////////////////////
// CommandButton //////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
const FieldParse CommandButton::s_commandButtonFieldParseTable[] =
{
{ "Command", CommandButton::parseCommand, NULL, offsetof( CommandButton, m_command ) },
{ "Options", INI::parseBitString32, TheCommandOptionNames, offsetof( CommandButton, m_options ) },
{ "Object", INI::parseThingTemplate, NULL, offsetof( CommandButton, m_thingTemplate ) },
{ "Upgrade", INI::parseUpgradeTemplate, NULL, offsetof( CommandButton, m_upgradeTemplate ) },
{ "WeaponSlot", INI::parseLookupList, TheWeaponSlotTypeNamesLookupList, offsetof( CommandButton, m_weaponSlot ) },
{ "MaxShotsToFire", INI::parseInt, NULL, offsetof( CommandButton, m_maxShotsToFire ) },
{ "Science", INI::parseScienceVector, NULL, offsetof( CommandButton, m_science ) },
{ "SpecialPower", INI::parseSpecialPowerTemplate, NULL, offsetof( CommandButton, m_specialPower ) },
{ "TextLabel", INI::parseAsciiString, NULL, offsetof( CommandButton, m_textLabel ) },
{ "DescriptLabel", INI::parseAsciiString, NULL, offsetof( CommandButton, m_descriptionLabel ) },
{ "PurchasedLabel", INI::parseAsciiString, NULL, offsetof( CommandButton, m_purchasedLabel ) },
{ "ConflictingLabel", INI::parseAsciiString, NULL, offsetof( CommandButton, m_conflictingLabel ) },
{ "ButtonImage", INI::parseAsciiString, NULL, offsetof( CommandButton, m_buttonImageName ) },
{ "CursorName", INI::parseAsciiString, NULL, offsetof( CommandButton, m_cursorName ) },
{ "InvalidCursorName", INI::parseAsciiString, NULL, offsetof( CommandButton, m_invalidCursorName ) },
{ "ButtonBorderType", INI::parseLookupList, CommandButtonMappedBorderTypeNames, offsetof( CommandButton, m_commandButtonBorder ) },
{ "RadiusCursorType", INI::parseIndexList, TheRadiusCursorNames, offsetof( CommandButton, m_radiusCursor ) },
{ "UnitSpecificSound", INI::parseAudioEventRTS, NULL, offsetof( CommandButton, m_unitSpecificSound ) },
{ NULL, NULL, NULL, 0 } // keep this last
};
static void commandButtonTooltip(GameWindow *window,
WinInstanceData *instData,
UnsignedInt mouse)
{
TheControlBar->showBuildTooltipLayout(window);
}
/// mark the UI as dirty so the context of everything is re-evaluated
void ControlBar::markUIDirty( void )
{
m_UIDirty = TRUE;
#if defined( _INTERNAL ) || defined( _DEBUG )
UnsignedInt now = TheGameLogic->getFrame();
if( now == m_lastFrameMarkedDirty )
{
//Do nothing.
}
else if( now == m_lastFrameMarkedDirty + 1 )
{
m_consecutiveDirtyFrames++;
}
else
{
m_consecutiveDirtyFrames = 1;
}
m_lastFrameMarkedDirty = now;
if( m_consecutiveDirtyFrames > 20 )
{
DEBUG_CRASH( ("Serious flaw in interface system! Either new code or INI has caused the interface to be marked dirty every frame. This problem actually causes the interface to completely lockup not allowing you to click normal game buttons.") );
}
#endif
}
void ControlBar::populatePurchaseScience( Player* player )
{
// TheInGameUI->deselectAllDrawables();
const CommandSet *commandSet1;
const CommandSet *commandSet3;
const CommandSet *commandSet8;
Int i;
if(TheScriptEngine->isGameEnding())
return;
// get command set
if(!player ||!player->getPlayerTemplate() || player->getPlayerTemplate()->getPurchaseScienceCommandSetRank1().isEmpty() ||
player->getPlayerTemplate()->getPurchaseScienceCommandSetRank3().isEmpty() ||
player->getPlayerTemplate()->getPurchaseScienceCommandSetRank8().isEmpty())
return;
commandSet1 = TheControlBar->findCommandSet(player->getPlayerTemplate()->getPurchaseScienceCommandSetRank1()); // TEMP WILL CHANGE TO PROPER WAY ONCE WORKING
commandSet3 = TheControlBar->findCommandSet(player->getPlayerTemplate()->getPurchaseScienceCommandSetRank3()); // TEMP WILL CHANGE TO PROPER WAY ONCE WORKING
commandSet8 = TheControlBar->findCommandSet(player->getPlayerTemplate()->getPurchaseScienceCommandSetRank8()); // TEMP WILL CHANGE TO PROPER WAY ONCE WORKING
for( i = 0; i < MAX_PURCHASE_SCIENCE_RANK_1; i++ )
m_sciencePurchaseWindowsRank1[i]->winHide(TRUE);
for( i = 0; i < MAX_PURCHASE_SCIENCE_RANK_3; i++ )
m_sciencePurchaseWindowsRank3[i]->winHide(TRUE);
for( i = 0; i < MAX_PURCHASE_SCIENCE_RANK_8; i++ )
m_sciencePurchaseWindowsRank8[i]->winHide(TRUE);
// if no command set match is found hide all the buttons
if( commandSet1 == NULL ||
commandSet3 == NULL ||
commandSet8 == NULL )
return;
// populate the button with commands defined
const CommandButton *commandButton;
for( i = 0; i < MAX_PURCHASE_SCIENCE_RANK_1; i++ )
{
// get command button
commandButton = commandSet1->getCommandButton(i);
// if button is not present, just hide the window
if( commandButton == NULL || BitTest( commandButton->getOptions(), SCRIPT_ONLY ) )
{
// hide window on interface
m_sciencePurchaseWindowsRank1[ i ]->winHide( TRUE );
} // end if
else
{
// make sure the window is not hidden
m_sciencePurchaseWindowsRank1[ i ]->winHide( FALSE );
// Disable by default
m_sciencePurchaseWindowsRank1[ i ]->winEnable( FALSE );
// populate the visible button with data from the command button
setControlCommand( m_sciencePurchaseWindowsRank1[ i ], commandButton );
if (!commandButton->getScienceVec().empty())
{
ScienceType st = commandButton->getScienceVec()[ 0 ];
if( player->isScienceDisabled( st ) )
{
//A script has deemed this science disabled.
m_sciencePurchaseWindowsRank1[ i ]->winEnable( FALSE );
}
else if( player->isScienceHidden( st ) )
{
//A script has deemed this science unavailable, thus hidden
m_sciencePurchaseWindowsRank1[ i ]->winHide( TRUE );
}
else
{
//Handle normal game logic cases!
if(!player->hasScience(st) && TheScienceStore->playerHasPrereqsForScience(player, st) && TheScienceStore->getSciencePurchaseCost(st) <= player->getSciencePurchasePoints())
{
m_sciencePurchaseWindowsRank1[ i ]->winEnable( TRUE );
}
if(player->hasScience(st))
{
m_sciencePurchaseWindowsRank1[ i ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
}
else
{
m_sciencePurchaseWindowsRank1[ i ]->winClearStatus(WIN_STATUS_ALWAYS_COLOR);
}
if(!TheScienceStore->playerHasRootPrereqsForScience(player, st))
m_sciencePurchaseWindowsRank1[ i ]->winHide(TRUE);
}
}
} // end else
} // end for
for( i = 0; i < MAX_PURCHASE_SCIENCE_RANK_3; i++ )
{
// get command button
commandButton = commandSet3->getCommandButton(i);
// if button is not present, just hide the window
if( commandButton == NULL || BitTest( commandButton->getOptions(), SCRIPT_ONLY ) )
{
// hide window on interface
m_sciencePurchaseWindowsRank3[ i ]->winHide( TRUE );
} // end if
else
{
// make sure the window is not hidden
m_sciencePurchaseWindowsRank3[ i ]->winHide( FALSE );
// Disable by default
m_sciencePurchaseWindowsRank3[ i ]->winEnable( FALSE );
// populate the visible button with data from the command button
setControlCommand( m_sciencePurchaseWindowsRank3[ i ], commandButton );
ScienceType st = SCIENCE_INVALID;
ScienceVec sv = commandButton->getScienceVec();
if (! sv.empty())
{
st = sv[ 0 ];
}
if( player->isScienceDisabled( st ) )
{
//A script has deemed this science disabled.
m_sciencePurchaseWindowsRank3[ i ]->winEnable( FALSE );
}
else if( player->isScienceHidden( st ) )
{
//A script has deemed this science unavailable, thus hidden
m_sciencePurchaseWindowsRank3[ i ]->winHide( TRUE );
}
else
{
//Handle normal game logic cases!
if(!player->hasScience(st) && TheScienceStore->playerHasPrereqsForScience(player, st) && TheScienceStore->getSciencePurchaseCost(st) <= player->getSciencePurchasePoints())
{
m_sciencePurchaseWindowsRank3[ i ]->winEnable( TRUE );
}
if(player->hasScience(st))
{
m_sciencePurchaseWindowsRank3[ i ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
}
else
{
m_sciencePurchaseWindowsRank3[ i ]->winClearStatus(WIN_STATUS_ALWAYS_COLOR);
}
if(!TheScienceStore->playerHasRootPrereqsForScience(player, st))
m_sciencePurchaseWindowsRank3[ i ]->winHide(TRUE);
}
} // end else
} // end for
for( i = 0; i < MAX_PURCHASE_SCIENCE_RANK_8; i++ )
{
// get command button
commandButton = commandSet8->getCommandButton(i);
// if button is not present, just hide the window
if( commandButton == NULL || BitTest( commandButton->getOptions(), SCRIPT_ONLY ) )
{
// hide window on interface
m_sciencePurchaseWindowsRank8[ i ]->winHide( TRUE );
} // end if
else
{
// make sure the window is not hidden
m_sciencePurchaseWindowsRank8[ i ]->winHide( FALSE );
// Disable by default
m_sciencePurchaseWindowsRank8[ i ]->winEnable( FALSE );
// populate the visible button with data from the command button
setControlCommand( m_sciencePurchaseWindowsRank8[ i ], commandButton );
ScienceType st = SCIENCE_INVALID;
st = commandButton->getScienceVec()[ 0 ];
if( player->isScienceDisabled( st ) )
{
//A script has deemed this science disabled.
m_sciencePurchaseWindowsRank8[ i ]->winEnable( FALSE );
}
else if( player->isScienceHidden( st ) )
{
//A script has deemed this science unavailable, thus hidden
m_sciencePurchaseWindowsRank8[ i ]->winHide( TRUE );
}
else
{
//Handle normal game logic cases!
if(!player->hasScience(st) && TheScienceStore->playerHasPrereqsForScience(player, st) && TheScienceStore->getSciencePurchaseCost(st) <= player->getSciencePurchasePoints())
{
m_sciencePurchaseWindowsRank8[ i ]->winEnable( TRUE );
}
if(player->hasScience(st))
{
m_sciencePurchaseWindowsRank8[ i ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
}
else
{
m_sciencePurchaseWindowsRank8[ i ]->winClearStatus(WIN_STATUS_ALWAYS_COLOR);
}
if(!TheScienceStore->playerHasRootPrereqsForScience(player, st))
m_sciencePurchaseWindowsRank8[ i ]->winHide(TRUE);
}
} // end else
} // end for
GameWindow *win = NULL;
UnicodeString tempUS;
win = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_PURCHASE_SCIENCE ], TheNameKeyGenerator->nameToKey( "GeneralsExpPoints.wnd:StaticTextRankPointsAvailable" ) );
if(win)
{
tempUS.format(L"%d", player->getSciencePurchasePoints());
GadgetStaticTextSetText(win, tempUS);
}
// redundant to StaticTextTitle in the Zero Hour context
/*
win = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_PURCHASE_SCIENCE ], TheNameKeyGenerator->nameToKey( "GeneralsExpPoints.wnd:StaticTextLevel" ) );
if(win)
{
tempUS.format(TheGameText->fetch("SCIENCE:Rank"), player->getRankLevel());
GadgetStaticTextSetText(win, tempUS);
}
*/
win = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_PURCHASE_SCIENCE ], TheNameKeyGenerator->nameToKey( "GeneralsExpPoints.wnd:ProgressBarExperience" ) );
if(win)
{
Int progress;
progress = ((player->getSkillPoints() - player->getSkillPointsLevelDown()) * 100) /(player->getSkillPointsLevelUp() - player->getSkillPointsLevelDown());
GadgetProgressBarSetProgress(win, progress);
}
win = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_PURCHASE_SCIENCE ], TheNameKeyGenerator->nameToKey( "GeneralsExpPoints.wnd:StaticTextTitle" ) );
if(win)
{
AsciiString tempAs;
tempAs.format("SCIENCE:Rank%d", player->getRankLevel());
GadgetStaticTextSetText(win, TheGameText->fetch(tempAs));
}
//
// to avoid a one frame delay where windows may become enabled/disabled, run the update
// at once to get it all in the correct state immediately
//
updateContextPurchaseScience();
/*
// get the side select buttons
GameWindow* win = m_contextParent[ CP_PURCHASE_SCIENCE ];
Color color = GameMakeColor(255, 255, 255, 255);
/// @todo srj -- evil hack testing code. do not imitate.
ScienceVec purchasable, potential;
TheScienceStore->getPurchasableSciences(player, purchasable, potential);
GadgetListBoxReset(win);
for (ScienceVec::const_iterator it = purchasable.begin(); it != purchasable.end(); ++it)
{
ScienceType st = *it;
UnicodeString u;
u.translate(TheScienceStore->getInternalNameForScience(st));
GadgetListBoxAddEntryText(win, u, color, -1, -1);
}
for (ScienceVec::const_iterator it2 = potential.begin(); it2 != potential.end(); ++it2)
{
ScienceType st = *it2;
AsciiString foo = "(Not Yet)";
foo.concat(TheScienceStore->getInternalNameForScience(st));
UnicodeString u;
u.translate(foo);
GadgetListBoxAddEntryText(win, u, color, -1, -1);
}
GadgetListBoxAddEntryText(win, UnicodeString(L"Cancel"), color, -1, -1);*/
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void ControlBar::updateContextPurchaseScience( void )
{
GameWindow *win =NULL;
Player *player = ThePlayerList->getLocalPlayer();
win = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_PURCHASE_SCIENCE ], TheNameKeyGenerator->nameToKey( "GeneralsExpPoints.wnd:ProgressBarExperience" ) );
if(win)
{
Int progress;
progress = ((player->getSkillPoints() - player->getSkillPointsLevelDown()) * 100) /(player->getSkillPointsLevelUp() - player->getSkillPointsLevelDown());
GadgetProgressBarSetProgress(win, progress);
}
// win = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_PURCHASE_SCIENCE ], TheNameKeyGenerator->nameToKey( "ControlBar.wnd:TextEntryGeneralName" ) );
// if(win)
// {
// UnicodeString temp = GadgetTextEntryGetText(win);
// if(temp.compare(player->getGeneralName()) != 0)
// player->setGeneralName(temp);
// }
/*
/// @todo srj -- evil hack testing code. do not imitate.
Object *obj = m_currentSelectedDrawable->getObject();
if( obj == NULL )
return;
// sanity
if( obj->isKindOf( KINDOF_COMMANDCENTER ) == FALSE )
switchToContext( CB_CONTEXT_NONE, NULL );
GameWindow* win = m_contextParent[ CP_PURCHASE_SCIENCE ];
Int selected;
GadgetListBoxGetSelected( win, &selected );
if( selected != -1 )
{
UnicodeString usci = GadgetListBoxGetText( win, selected, 0 );
AsciiString sci;
sci.translate(usci);
ScienceType st = usci.getCharAt(0) == '(' ? SCIENCE_INVALID : TheScienceStore->getScienceFromInternalName(sci);
if (st != SCIENCE_INVALID)
{
GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_PURCHASE_SCIENCE );
msg->appendIntegerArgument( st );
}
switchToContext( CB_CONTEXT_NONE, NULL );
}
*/
}
//-------------------------------------------------------------------------------------------------
/** parse command definition */
//-------------------------------------------------------------------------------------------------
void CommandButton::parseCommand( INI* ini, void *instance, void *store, const void *userData )
{
const char *token = ini->getNextToken();
Int i;
for( i = 0; TheGuiCommandNames[ i ]; i++ )
{
if( stricmp( TheGuiCommandNames[ i ], token ) == 0 )
{
GUICommandType *command = (GUICommandType *)store;
*command = (GUICommandType)i;
return;
} // end if
} // end for i
// if we're here the command was not found
throw INI_INVALID_DATA;
} // end parseCommand
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CommandButton::CommandButton( void )
{
m_command = GUI_COMMAND_NONE;
m_thingTemplate = NULL;
m_upgradeTemplate = NULL;
m_weaponSlot = PRIMARY_WEAPON;
m_maxShotsToFire = 0x7fffffff; // huge number
m_science.clear();
m_specialPower = NULL;
m_buttonImage = NULL;
//Code renderer handles these states now.
//m_disabledImage = NULL;
//m_hiliteImage = NULL;
//m_pushedImage = NULL;
m_flashCount = 0;
// Added by Sadullah Nader
// The purpose is to initialize these variable to values that are zero or empty
m_conflictingLabel.clear();
m_cursorName.clear();
m_descriptionLabel.clear();
m_invalidCursorName.clear();
m_name.clear();
m_options = 0;
m_purchasedLabel.clear();
m_textLabel.clear();
// End Add
m_window = NULL;
m_commandButtonBorder = COMMAND_BUTTON_BORDER_NONE;
//m_prev = NULL;
m_next = NULL;
m_radiusCursor = RADIUSCURSOR_NONE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CommandButton::~CommandButton( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool CommandButton::isValidRelationshipTarget(Relationship r) const
{
UnsignedInt mask = 0;
if (r == ENEMIES) mask |= NEED_TARGET_ENEMY_OBJECT;
else if (r == ALLIES) mask |= NEED_TARGET_ALLY_OBJECT;
else if (r == NEUTRAL) mask |= NEED_TARGET_NEUTRAL_OBJECT;
return (m_options & mask) != 0;
}
//-------------------------------------------------------------------------------------------------
Bool CommandButton::isValidObjectTarget(const Player* sourcePlayer, const Object* targetObj) const
{
if (!sourcePlayer || !targetObj)
return false;
Relationship r = sourcePlayer->getRelationship(targetObj->getTeam());
return isValidRelationshipTarget(r);
}
//-------------------------------------------------------------------------------------------------
Bool CommandButton::isValidObjectTarget(const Object* sourceObj, const Object* targetObj) const
{
if (!sourceObj || !targetObj)
return false;
Relationship r = sourceObj->getRelationship(targetObj);
return isValidRelationshipTarget(r);
}
//-------------------------------------------------------------------------------------------------
Bool CommandButton::isValidToUseOn(const Object *sourceObj, const Object *targetObj, const Coord3D *targetLocation, CommandSourceType commandSource) const
{
if (m_upgradeTemplate) {
// @todo: Make a const version of pui. We're not altering the production queue, so this const-cast
// is okay.
ProductionUpdateInterface *pui = const_cast