/* ** 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 . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: ControlBarPopupDescription.cpp ///////////////////////////////////////////////// //----------------------------------------------------------------------------- // // Electronic Arts Pacific. // // Confidential Information // Copyright (C) 2002 - All Rights Reserved // //----------------------------------------------------------------------------- // // created: Sep 2002 // // Filename: ControlBarPopupDescription.cpp // // author: Chris Huybregts // // purpose: // //----------------------------------------------------------------------------- /////////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- // SYSTEM INCLUDES //////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // USER INCLUDES ////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // DEFINES //////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // PRIVATE FUNCTIONS ////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/GlobalData.h" #include "Common/BuildAssistant.h" #include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/ProductionPrerequisite.h" #include "Common/ThingTemplate.h" #include "Common/Upgrade.h" #include "GameClient/AnimateWindowManager.h" #include "GameClient/DisconnectMenu.h" #include "GameClient/GameWindow.h" #include "GameClient/Gadget.h" #include "GameClient/GadgetTextEntry.h" #include "GameClient/GadgetPushButton.h" #include "GameClient/GadgetStaticText.h" #include "GameClient/GameClient.h" #include "GameClient/GameText.h" #include "GameClient/GUICallbacks.h" #include "GameClient/InGameUI.h" #include "GameClient/Controlbar.h" #include "GameClient/DisplayStringManager.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Module/OverchargeBehavior.h" #include "GameLogic/Module/ProductionUpdate.h" #include "GameLogic/ScriptEngine.h" #include "GameNetwork/NetworkInterface.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif static WindowLayout *theLayout = NULL; static GameWindow *theWindow = NULL; static AnimateWindowManager *theAnimateWindowManager = NULL; static GameWindow *prevWindow = NULL; static Bool useAnimation = FALSE; void ControlBarPopupDescriptionUpdateFunc( WindowLayout *layout, void *param ) { if(TheScriptEngine->isGameEnding()) TheControlBar->hideBuildTooltipLayout(); if(theAnimateWindowManager && !TheControlBar->getShowBuildTooltipLayout() && !theAnimateWindowManager->isReversed()) theAnimateWindowManager->reverseAnimateWindow(); else if(!TheControlBar->getShowBuildTooltipLayout() && (!TheGlobalData->m_animateWindows || !useAnimation)) TheControlBar->deleteBuildTooltipLayout(); if ( useAnimation && theAnimateWindowManager && TheGlobalData->m_animateWindows) { Bool wasFinished = theAnimateWindowManager->isFinished(); theAnimateWindowManager->update(); if (theAnimateWindowManager && theAnimateWindowManager->isFinished() && !wasFinished && theAnimateWindowManager->isReversed()) { delete theAnimateWindowManager; theAnimateWindowManager = NULL; TheControlBar->deleteBuildTooltipLayout(); } } } // --------------------------------------------------------------------------------------- void ControlBar::showBuildTooltipLayout( GameWindow *cmdButton ) { if (TheInGameUI->areTooltipsDisabled() || TheScriptEngine->isGameEnding()) { return; } Bool passedWaitTime = FALSE; static Bool isInitialized = FALSE; static UnsignedInt beginWaitTime; if(prevWindow == cmdButton) { m_showBuildToolTipLayout = TRUE; if(!isInitialized && beginWaitTime + cmdButton->getTooltipDelay() < timeGetTime()) { //DEBUG_LOG(("%d beginwaittime, %d tooltipdelay, %dtimegettime\n", beginWaitTime, cmdButton->getTooltipDelay(), timeGetTime())); passedWaitTime = TRUE; } if(!passedWaitTime) return; } else if( !m_buildToolTipLayout->isHidden() ) { if(useAnimation && TheGlobalData->m_animateWindows && !theAnimateWindowManager->isReversed()) theAnimateWindowManager->reverseAnimateWindow(); else if( useAnimation && TheGlobalData->m_animateWindows && theAnimateWindowManager->isReversed()) { return; } else { // m_buildToolTipLayout->destroyWindows(); // m_buildToolTipLayout->deleteInstance(); // m_buildToolTipLayout = NULL; m_buildToolTipLayout->hide(TRUE); prevWindow = NULL; } return; } // will only get here the firsttime through the function through this window if(!passedWaitTime) { prevWindow = cmdButton; beginWaitTime = timeGetTime(); isInitialized = FALSE; return; } isInitialized = TRUE; if(!cmdButton) return; if(BitTest(cmdButton->winGetStyle(), GWS_PUSH_BUTTON)) { const CommandButton *commandButton = (const CommandButton *)GadgetButtonGetData(cmdButton); if(!commandButton) return; // note that, in this branch, ENABLE_SOLO_PLAY is ***NEVER*** defined... // this is so that we have a multiplayer build that cannot possibly be hacked // to work as a solo game! #if !defined(_PLAYTEST) if (TheGameLogic->isInReplayGame()) return; #endif if (TheInGameUI->isQuitMenuVisible()) return; if (TheDisconnectMenu && TheDisconnectMenu->isScreenVisible()) return; // if (m_buildToolTipLayout) // { // m_buildToolTipLayout->destroyWindows(); // m_buildToolTipLayout->deleteInstance(); // // } m_showBuildToolTipLayout = TRUE; // m_buildToolTipLayout = TheWindowManager->winCreateLayout( "ControlBarPopupDescription.wnd" ); // m_buildToolTipLayout->setUpdate(ControlBarPopupDescriptionUpdateFunc); populateBuildTooltipLayout(commandButton); } else { // we're a generic window if(!BitTest(cmdButton->winGetStyle(), GWS_USER_WINDOW) && !BitTest(cmdButton->winGetStyle(), GWS_STATIC_TEXT)) return; populateBuildTooltipLayout(NULL, cmdButton); } m_buildToolTipLayout->hide(FALSE); if (useAnimation && TheGlobalData->m_animateWindows) { theAnimateWindowManager = NEW AnimateWindowManager; theAnimateWindowManager->reset(); theAnimateWindowManager->registerGameWindow( m_buildToolTipLayout->getFirstWindow(), WIN_ANIMATION_SLIDE_RIGHT_FAST, TRUE, 200 ); } } void ControlBar::repopulateBuildTooltipLayout( void ) { if(!prevWindow || !m_buildToolTipLayout) return; if(!BitTest(prevWindow->winGetStyle(), GWS_PUSH_BUTTON)) return; const CommandButton *commandButton = (const CommandButton *)GadgetButtonGetData(prevWindow); populateBuildTooltipLayout(commandButton); } void ControlBar::populateBuildTooltipLayout( const CommandButton *commandButton, GameWindow *tooltipWin) { if(!m_buildToolTipLayout) return; Player *player = ThePlayerList->getLocalPlayer(); UnicodeString name, cost, descrip; UnicodeString requires = UnicodeString::TheEmptyString, requiresList; Bool firstRequirement = true; const ProductionPrerequisite *prereq; Bool fireScienceButton = false; if(commandButton) { const ThingTemplate *thingTemplate = commandButton->getThingTemplate(); const UpgradeTemplate *upgradeTemplate = commandButton->getUpgradeTemplate(); ScienceType st = SCIENCE_INVALID; if(commandButton->getScienceVec().size() > 1) { for(Int j = 0; j < commandButton->getScienceVec().size(); ++j) { st = commandButton->getScienceVec()[ j ]; if( commandButton->getCommandType() != GUI_COMMAND_PURCHASE_SCIENCE ) { if( !player->hasScience( st ) && j > 0 ) { //If we're not looking at a command button that purchases a science, then //it means we are looking at a command button that can USE the science. This //means we want to get the description for the previous science -- the one //we can use, not purchase! st = commandButton->getScienceVec()[ j - 1 ]; } //Now that we got the science for the button that executes the science, we need //to generate a simpler help text! fireScienceButton = true; break; } else if( !player->hasScience( st ) ) { //Purchase science case. The first science we run into that we don't have, that's the //one we'll want to show! break; } } } else if(commandButton->getScienceVec().size() == 1 ) { st = commandButton->getScienceVec()[ 0 ]; } if( commandButton->getDescriptionLabel().isNotEmpty() ) { descrip = TheGameText->fetch(commandButton->getDescriptionLabel()); Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); Object *selectedObject = draw ? draw->getObject() : NULL; if( selectedObject ) { //Special case: Append status of overcharge on China power plant. if( commandButton->getCommandType() == GUI_COMMAND_TOGGLE_OVERCHARGE ) { { OverchargeBehaviorInterface *obi; for( BehaviorModule **bmi = selectedObject->getBehaviorModules(); *bmi; ++bmi ) { obi = (*bmi)->getOverchargeBehaviorInterface(); if( obi ) { descrip.concat( L"\n" ); if( obi->isOverchargeActive() ) descrip.concat( TheGameText->fetch( "TOOLTIP:TooltipNukeReactorOverChargeIsOn" ) ); else descrip.concat( TheGameText->fetch( "TOOLTIP:TooltipNukeReactorOverChargeIsOff" ) ); } } } } //End overcharge special case //Special case: When building units, the CanMakeType determines reasons for not being able to buy stuff. else if( thingTemplate ) { CanMakeType makeType = TheBuildAssistant->canMakeUnit( selectedObject, commandButton->getThingTemplate() ); switch( makeType ) { case CANMAKE_NO_MONEY: descrip.concat( L"\n\n" ); descrip.concat( TheGameText->fetch( "TOOLTIP:TooltipNotEnoughMoneyToBuild" ) ); break; case CANMAKE_QUEUE_FULL: descrip.concat( L"\n\n" ); descrip.concat( TheGameText->fetch( "TOOLTIP:TooltipCannotPurchaseBecauseQueueFull" ) ); break; case CANMAKE_PARKING_PLACES_FULL: descrip.concat( L"\n\n" ); descrip.concat( TheGameText->fetch( "TOOLTIP:TooltipCannotBuildUnitBecauseParkingFull" ) ); break; case CANMAKE_MAXED_OUT_FOR_PLAYER: descrip.concat( L"\n\n" ); descrip.concat( TheGameText->fetch( "TOOLTIP:TooltipCannotBuildUnitBecauseMaximumNumber" ) ); break; //case CANMAKE_NO_PREREQ: // descrip.concat( L"\n\n" ); // descrip.concat( TheGameText->fetch( "TOOLTIP:TooltipCannotBuildDueToPrerequisites" ) ); // break; } } //Special case: When building upgrades else if( upgradeTemplate && !player->hasUpgradeInProduction( upgradeTemplate ) ) { if( commandButton->getCommandType() == GUI_COMMAND_PLAYER_UPGRADE || commandButton->getCommandType() == GUI_COMMAND_OBJECT_UPGRADE ) { ProductionUpdateInterface *pui = selectedObject->getProductionUpdateInterface(); if( pui && pui->getProductionCount() == MAX_BUILD_QUEUE_BUTTONS ) { descrip.concat( L"\n\n" ); descrip.concat( TheGameText->fetch( "TOOLTIP:TooltipCannotPurchaseBecauseQueueFull" ) ); } else if( !TheUpgradeCenter->canAffordUpgrade( ThePlayerList->getLocalPlayer(), upgradeTemplate, FALSE ) ) { descrip.concat( L"\n\n" ); descrip.concat( TheGameText->fetch( "TOOLTIP:TooltipNotEnoughMoneyToBuild" ) ); } } } } } name = TheGameText->fetch(commandButton->getTextLabel().str()); if( thingTemplate && commandButton->getCommandType() != GUI_COMMAND_PURCHASE_SCIENCE ) { //We are either looking at building a unit or a structure that may or may not have any //prerequisites. //Format the cost only when we have to pay for it. cost.format(TheGameText->fetch("TOOLTIP:Cost"), thingTemplate->calcCostToBuild(player)); // ask each prerequisite to give us a list of the non satisfied prerequisites for( Int i=0; igetPrereqCount(); i++ ) { prereq = thingTemplate->getNthPrereq(i); requiresList = prereq->getRequiresList(player); if( requiresList != UnicodeString::TheEmptyString ) { // make sure to put in 'returns' to space things correctly if (firstRequirement) firstRequirement = false; else requires.concat(L", "); } requires.concat(requiresList); } if( !requires.isEmpty() ) { UnicodeString requireFormat = TheGameText->fetch("CONTROLBAR:Requirements"); requires.format(requireFormat.str(), requires.str()); if(!descrip.isEmpty()) descrip.concat(L"\n"); descrip.concat(requires); } } else if( upgradeTemplate ) { //We are looking at an upgrade purchase icon. Maybe we already purchased it? Bool hasUpgradeAlready = false; Bool hasConflictingUpgrade = false; Bool playerUpgradeButton = commandButton->getCommandType() == GUI_COMMAND_PLAYER_UPGRADE; Bool objectUpgradeButton = commandButton->getCommandType() == GUI_COMMAND_OBJECT_UPGRADE; //Check if the local player has the specified upgrade hasUpgradeAlready = player->hasUpgradeComplete( upgradeTemplate ); if( !hasUpgradeAlready ) { //Check if the first selected object has the specified upgrade. Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); if( draw ) { Object *object = draw->getObject(); if( object ) { hasUpgradeAlready = object->hasUpgrade( upgradeTemplate ); if( objectUpgradeButton ) { hasConflictingUpgrade = !object->affectedByUpgrade( upgradeTemplate ); } } } } if( hasConflictingUpgrade && !hasUpgradeAlready ) { if( commandButton->getConflictingLabel().isNotEmpty() ) { descrip = TheGameText->fetch( commandButton->getConflictingLabel() ); } else { descrip = TheGameText->fetch( "TOOLTIP:HasConflictingUpgradeDefault" ); } } else if( hasUpgradeAlready && ( playerUpgradeButton || objectUpgradeButton ) ) { //See if we can fetch the "already upgraded" text for this upgrade. If not.... use the default "fill me in". if( commandButton->getPurchasedLabel().isNotEmpty() ) { descrip = TheGameText->fetch( commandButton->getPurchasedLabel() ); } else { descrip = TheGameText->fetch( "TOOLTIP:AlreadyUpgradedDefault" ); } } else if( !hasUpgradeAlready ) { //Determine the cost of the upgrade. cost.format(TheGameText->fetch("TOOLTIP:Cost"),upgradeTemplate->calcCostToBuild(player)); } } else if( st != SCIENCE_INVALID && !fireScienceButton ) { TheScienceStore->getNameAndDescription(st, name, descrip); cost.format(TheGameText->fetch("TOOLTIP:ScienceCost"),TheScienceStore->getSciencePurchaseCost(st)); // ask each prerequisite to give us a list of the non satisfied prerequisites if( thingTemplate ) { for( Int i=0; igetPrereqCount(); i++ ) { prereq = thingTemplate->getNthPrereq(i); requiresList = prereq->getRequiresList(player); if( requiresList != UnicodeString::TheEmptyString ) { // make sure to put in 'returns' to space things correctly if (firstRequirement) firstRequirement = false; else requires.concat(L", "); } requires.concat(requiresList); } if( !requires.isEmpty() ) { UnicodeString requireFormat = TheGameText->fetch("CONTROLBAR:Requirements"); requires.format(requireFormat.str(), requires.str()); if(!descrip.isEmpty()) descrip.concat(L"\n"); descrip.concat(requires); } } } } else if(tooltipWin) { if( tooltipWin == TheWindowManager->winGetWindowFromId(m_buildToolTipLayout->getFirstWindow(), TheNameKeyGenerator->nameToKey("ControlBar.wnd:MoneyDisplay"))) { name = TheGameText->fetch("CONTROLBAR:Money"); descrip = TheGameText->fetch("CONTROLBAR:MoneyDescription"); } else if(tooltipWin == TheWindowManager->winGetWindowFromId(m_buildToolTipLayout->getFirstWindow(), TheNameKeyGenerator->nameToKey("ControlBar.wnd:PowerWindow")) ) { name = TheGameText->fetch("CONTROLBAR:Power"); descrip = TheGameText->fetch("CONTROLBAR:PowerDescription"); Player *playerToDisplay = NULL; if(TheControlBar->isObserverControlBarOn()) playerToDisplay = TheControlBar->getObserverLookAtPlayer(); else playerToDisplay = ThePlayerList->getLocalPlayer(); if( playerToDisplay && playerToDisplay->getEnergy() ) { Energy *energy = playerToDisplay->getEnergy(); descrip.format(descrip, energy->getProduction(), energy->getConsumption()); } else { descrip.format(descrip, 0, 0); } } else if(tooltipWin == TheWindowManager->winGetWindowFromId(m_buildToolTipLayout->getFirstWindow(), TheNameKeyGenerator->nameToKey("ControlBar.wnd:GeneralsExp")) ) { name = TheGameText->fetch("CONTROLBAR:GeneralsExp"); descrip = TheGameText->fetch("CONTROLBAR:GeneralsExpDescription"); } else { DEBUG_ASSERTCRASH(FALSE, ("ControlBar::populateBuildTooltipLayout We attempted to call the popup tooltip on a game window that has yet to be hand coded in as this fuction was/is designed for only buttons but has been hacked to work with GameWindows.")); return; } } GameWindow *win = TheWindowManager->winGetWindowFromId(m_buildToolTipLayout->getFirstWindow(), TheNameKeyGenerator->nameToKey("ControlBarPopupDescription.wnd:StaticTextName")); if(win) { GadgetStaticTextSetText(win, name); } win = TheWindowManager->winGetWindowFromId(m_buildToolTipLayout->getFirstWindow(), TheNameKeyGenerator->nameToKey("ControlBarPopupDescription.wnd:StaticTextCost")); if(win) { GadgetStaticTextSetText(win, cost); } win = TheWindowManager->winGetWindowFromId(m_buildToolTipLayout->getFirstWindow(), TheNameKeyGenerator->nameToKey("ControlBarPopupDescription.wnd:StaticTextDescription")); if(win) { static NameKeyType winNamekey = TheNameKeyGenerator->nameToKey( AsciiString( "ControlBar.wnd:BackgroundMarker" ) ); static ICoord2D lastOffset = { 0, 0 }; ICoord2D size, newSize, pos; Int diffSize; DisplayString *tempDString = TheDisplayStringManager->newDisplayString(); win->winGetSize(&size.x, &size.y); tempDString->setFont(win->winGetFont()); tempDString->setWordWrap(size.x - 10); tempDString->setText(descrip); tempDString->getSize(&newSize.x, &newSize.y); TheDisplayStringManager->freeDisplayString(tempDString); tempDString = NULL; diffSize = newSize.y - size.y; GameWindow *parent = m_buildToolTipLayout->getFirstWindow(); if(!parent) return; parent->winGetSize(&size.x, &size.y); if(size.y + diffSize < 102) { diffSize = 102 - size.y; } parent->winSetSize(size.x, size.y + diffSize); parent->winGetPosition(&pos.x, &pos.y); // if(size.y + diffSize < 102) // { // // parent->winSetPosition(pos.x, pos.y - (102 - (newSize.y + size.y + diffSize) )); // } // else // heightChange = controlBarPos.y - m_defaultControlBarPosition.y; GameWindow *marker = TheWindowManager->winGetWindowFromId(NULL,winNamekey); static ICoord2D basePos; if(!marker) { return; } TheControlBar->getBackgroundMarkerPos(&basePos.x, &basePos.y); ICoord2D curPos, offset; marker->winGetScreenPosition(&curPos.x,&curPos.y); offset.x = curPos.x - basePos.x; offset.y = curPos.y - basePos.y; parent->winSetPosition(pos.x, (pos.y - diffSize) + (offset.y - lastOffset.y)); lastOffset.x = offset.x; lastOffset.y = offset.y; win->winGetSize(&size.x, &size.y); win->winSetSize(size.x, size.y + diffSize); GadgetStaticTextSetText(win, descrip); } m_buildToolTipLayout->hide(FALSE); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void ControlBar::hideBuildTooltipLayout() { if(theAnimateWindowManager && theAnimateWindowManager->isReversed()) return; if(useAnimation && theAnimateWindowManager && TheGlobalData->m_animateWindows) theAnimateWindowManager->reverseAnimateWindow(); else deleteBuildTooltipLayout(); } void ControlBar::deleteBuildTooltipLayout( void ) { m_showBuildToolTipLayout = FALSE; prevWindow= NULL; m_buildToolTipLayout->hide(TRUE); // if(!m_buildToolTipLayout) // return; // // m_buildToolTipLayout->destroyWindows(); // m_buildToolTipLayout->deleteInstance(); // m_buildToolTipLayout = NULL; if(theAnimateWindowManager) delete theAnimateWindowManager; theAnimateWindowManager = NULL; }