Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Core/GameEngine/Include/Common/OptionPreferences.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@
typedef UnsignedInt CursorCaptureMode;
typedef UnsignedInt ScreenEdgeScrollMode;

// TheSuperHackers @feature How remaining time is shown on build queue and cooldown cameos.
// Purely a client side display preference.
enum BuildTimerDisplayMode CPP_11(: Int)
{
BuildTimerDisplayMode_None = 0, ///< no numbers, just the existing clock sweep (retail behavior)
BuildTimerDisplayMode_Seconds, ///< always plain seconds, however large
BuildTimerDisplayMode_Auto, ///< seconds under a minute, M:SS above it

BuildTimerDisplayMode_Count,
BuildTimerDisplayMode_Default = BuildTimerDisplayMode_None
};

//-----------------------------------------------------------------------------
// OptionsPreferences options menu class
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -76,6 +88,7 @@ class OptionPreferences : public UserPreferences
Real getScrollFactor();
Bool getDrawScrollAnchor();
Bool getMoveScrollAnchor();
BuildTimerDisplayMode getBuildTimerDisplayMode() const;
Bool getCursorCaptureEnabledInWindowedGame() const;
Bool getCursorCaptureEnabledInWindowedMenu() const;
Bool getCursorCaptureEnabledInFullscreenGame() const;
Expand Down
4 changes: 4 additions & 0 deletions Core/GameEngine/Include/GameClient/Gadget.h
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ typedef struct _PushButtonData
void *userData; ///< random additional data we can set
const Image *overlayImage; ///< An overlay image (like a veterancy symbol)
AsciiString altSound; ///< use an alternative sound if one is set
// TheSuperHackers @feature Remaining time drawn over the cameo, in seconds. Negative means
// nothing to show. Like drawClock this is one shot -- it is cleared after being drawn, so
// whoever sets it must re-set it every frame.
Int countdownSeconds;
} PushButtonData;

// TabControlData ------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions Core/GameEngine/Include/GameClient/GadgetPushButton.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ void GadgetButtonEnableCheckLike( GameWindow *g, Bool makeCheckLike, Bool initia
void GadgetButtonSetText( GameWindow *g, UnicodeString text );
void GadgetButtonDrawClock( GameWindow *g, Int percent, Color color ); //Darkens the progress
void GadgetButtonDrawInverseClock( GameWindow *g, Int percent, Color color ); //Darkens the remaining portion.
// TheSuperHackers @feature Remaining time in seconds, drawn over the cameo. One shot, like the clocks.
void GadgetButtonDrawCountdown( GameWindow *g, Int seconds );
void GadgetButtonDrawOverlayImage( GameWindow *g, const Image *image );
void GadgetButtonSetBorder( GameWindow *g, Color color, Bool drawBorder = TRUE );
void GadgetButtonSetData(GameWindow *g, void *data);
Expand Down
22 changes: 22 additions & 0 deletions Core/GameEngine/Source/Common/OptionPreferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,28 @@ Bool OptionPreferences::getRightMouseScrollWithAlternateMouseEnabled() const
return FALSE;
}

// TheSuperHackers @feature Countdown numbers on build queue and cooldown cameos, read from
// Options.ini as BuildTimerDisplayMode = None | Seconds | Auto (a plain index also works).
BuildTimerDisplayMode OptionPreferences::getBuildTimerDisplayMode() const
{
OptionPreferences::const_iterator it = find("BuildTimerDisplayMode");
if (it == end())
return BuildTimerDisplayMode_Default;

if (stricmp(it->second.str(), "Auto") == 0)
return BuildTimerDisplayMode_Auto;
if (stricmp(it->second.str(), "Seconds") == 0)
return BuildTimerDisplayMode_Seconds;
if (stricmp(it->second.str(), "None") == 0)
return BuildTimerDisplayMode_None;

Int mode = atoi(it->second.str());
if (mode >= 0 && mode < BuildTimerDisplayMode_Count)
return (BuildTimerDisplayMode)mode;

return BuildTimerDisplayMode_Default;
}

Bool OptionPreferences::getRetaliationModeEnabled()
{
OptionPreferences::const_iterator it = find("Retaliation");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "Common/Upgrade.h"
#include "Common/BuildAssistant.h"
#include "GameLogic/GameLogic.h"
#include "Common/OptionPreferences.h"
#include "GameLogic/Module/BattlePlanUpdate.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameLogic/Module/OverchargeBehavior.h"
Expand Down Expand Up @@ -794,6 +795,30 @@ void ControlBar::updateContextCommand()

GadgetButtonDrawInverseClock(win,produce->getPercentComplete(), m_buildUpClockColor);

// TheSuperHackers @feature Remaining build time on the head queue slot.
if( TheGlobalData->m_buildTimerDisplayMode != BuildTimerDisplayMode_None )
{
Int totalFrames = 0;
if( produce->getProductionType() == PRODUCTION_UNIT )
{
if( produce->getProductionObject() )
totalFrames = produce->getProductionObject()->calcTimeToBuild( obj->getControllingPlayer() );
}
else if( produce->getProductionUpgrade() )
{
totalFrames = produce->getProductionUpgrade()->calcTimeToBuild( obj->getControllingPlayer() );
}

if( totalFrames > 0 )
{
Real remainingReal = totalFrames * (100.0f - produce->getPercentComplete()) / 100.0f;
Int remainingFrames = ( remainingReal > 0.0f ) ? REAL_TO_INT_CEIL( remainingReal ) : 0;
// integer ceiling -- see formatBuildTimeForTooltip for why not the float form
GadgetButtonDrawCountdown( win,
( remainingFrames + LOGICFRAMES_PER_SECOND - 1 ) / LOGICFRAMES_PER_SECOND );
}
}

}

}
Expand Down Expand Up @@ -1418,6 +1443,19 @@ CommandAvailability ControlBar::getCommandAvailability( const CommandButton *com
Int percent = mod->getPercentReady() * 100;

GadgetButtonDrawInverseClock( applyToWin, percent, m_buildUpClockColor );

// TheSuperHackers @feature Remaining recharge time. getReadyFrame is an absolute
// frame, and already accounts for paused and shared/synced powers.
if( TheGlobalData->m_buildTimerDisplayMode != BuildTimerDisplayMode_None )
{
UnsignedInt now = TheGameLogic->getFrame();
UnsignedInt readyFrame = mod->getReadyFrame();
UnsignedInt remainingFrames = ( readyFrame > now ) ? ( readyFrame - now ) : 0;
// integer ceiling -- see formatBuildTimeForTooltip for why not the float form
GadgetButtonDrawCountdown( applyToWin,
( remainingFrames + LOGICFRAMES_PER_SECOND - 1 ) / LOGICFRAMES_PER_SECOND );
}

return COMMAND_NOT_READY;
}
else if( SpecialAbilityUpdate *spUpdate = obj->findSpecialAbilityUpdate( command->getSpecialPowerTemplate()->getSpecialPowerType() ) )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine

#include "Common/GlobalData.h"
#include "Common/OptionPreferences.h"
#include "Common/BuildAssistant.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
Expand Down Expand Up @@ -104,6 +105,20 @@ void ControlBarPopupDescriptionUpdateFunc( WindowLayout *layout, void *param )
if(TheScriptEngine->isGameEnding())
TheControlBar->hideBuildTooltipLayout();

// TheSuperHackers @fix The unit build time follows the player's power state, but nothing
// marks the control bar dirty when power changes - so an open tooltip kept showing the
// old time. Refresh it about once a second while it is up; with the timer display off
// the tooltip carries no time and stays as cheap as retail.
if( TheGlobalData && TheGlobalData->m_buildTimerDisplayMode != BuildTimerDisplayMode_None )
{
static UnsignedInt s_framesSinceRefresh = 0;
if( ++s_framesSinceRefresh >= LOGICFRAMES_PER_SECOND )
{
s_framesSinceRefresh = 0;
TheControlBar->repopulateBuildTooltipLayout();
}
}

if(theAnimateWindowManager && !TheControlBar->getShowBuildTooltipLayout() && !theAnimateWindowManager->isReversed())
theAnimateWindowManager->reverseAnimateWindow();
else if(!TheControlBar->getShowBuildTooltipLayout() && (!TheGlobalData->m_animateWindows || !useAnimation))
Expand Down Expand Up @@ -231,6 +246,39 @@ void ControlBar::showBuildTooltipLayout( GameWindow *cmdButton )
}


// TheSuperHackers @feature Format a build time for the tooltip, using the same Auto/Seconds
// rules as the cameo countdowns (Options.ini: BuildTimerDisplayMode).
//
// Note the value passed in should come from calcTimeToBuild(), which for units already folds
// in the player's current energy penalty -- so a tooltip read while on low power correctly
// shows the slower time, and speeds back up once power is restored. Upgrades have no energy
// penalty in the engine, so their time is the same regardless of power state.
static UnicodeString formatBuildTimeForTooltip( Int buildFrames )
{
UnicodeString result = UnicodeString::TheEmptyString;

if( buildFrames <= 0 )
return result;

if( TheGlobalData == nullptr ||
TheGlobalData->m_buildTimerDisplayMode == BuildTimerDisplayMode_None )
return result;

// Round to nearest, so a template built from a fractional BuildTime reads as the number
// the modder typed rather than always the one above it. Integer math throughout, because
// 1/30 is not exact in float and an exact 8 second build would otherwise come out as
// 8.0000004. The live countdowns deliberately still ceil -- a running timer must not
// show 0 while there is work left.
const Int seconds = ( buildFrames + LOGICFRAMES_PER_SECOND / 2 ) / LOGICFRAMES_PER_SECOND;

if( TheGlobalData->m_buildTimerDisplayMode == BuildTimerDisplayMode_Auto && seconds >= 60 )
result.format( L"%d:%2.2d", seconds / 60, seconds % 60 ); // already reads as time
else
result.format( L"%ds", seconds );

return result;
}

void ControlBar::repopulateBuildTooltipLayout()
{
if(!prevWindow || !m_buildToolTipLayout)
Expand All @@ -248,6 +296,8 @@ void ControlBar::populateBuildTooltipLayout( const CommandButton *commandButton,

Player *player = ThePlayerList->getLocalPlayer();
UnicodeString name, cost, descrip;
// TheSuperHackers @feature Build time shown under the cost, formatted like the cameo timers.
UnicodeString buildTimeText;
UnicodeString requiresFormat = UnicodeString::TheEmptyString, requiresList;
Bool firstRequirement = true;
const ProductionPrerequisite *prereq;
Expand Down Expand Up @@ -415,6 +465,10 @@ void ControlBar::populateBuildTooltipLayout( const CommandButton *commandButton,
cost.format( TheGameText->fetch("TOOLTIP:Cost"), costToBuild );
}

// TheSuperHackers @feature calcTimeToBuild folds in the current energy penalty,
// so this tracks the player's power state as it changes.
buildTimeText = formatBuildTimeForTooltip( thingTemplate->calcTimeToBuild( player ) );
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.

// ask each prerequisite to give us a list of the non satisfied prerequisites
for( Int i=0; i<thingTemplate->getPrereqCount(); i++ )
{
Expand Down Expand Up @@ -512,6 +566,10 @@ void ControlBar::populateBuildTooltipLayout( const CommandButton *commandButton,
cost.format( TheGameText->fetch("TOOLTIP:Cost"), costToBuild );
}

// TheSuperHackers @feature Upgrades carry no energy penalty in the engine, so
// unlike units this time does not change with the player's power state.
buildTimeText = formatBuildTimeForTooltip( upgradeTemplate->calcTimeToBuild( player ) );

if( missingScience )
{
if( !descrip.isEmpty() )
Expand Down Expand Up @@ -607,10 +665,24 @@ void ControlBar::populateBuildTooltipLayout( const CommandButton *commandButton,
win = TheWindowManager->winGetWindowFromId(m_buildToolTipLayout->getFirstWindow(), TheNameKeyGenerator->nameToKey("ControlBarPopupDescription.wnd:StaticTextCost"));
if(win)
{
if( costToBuild > 0 )
// TheSuperHackers @feature Show the build time alongside the cost. Also shown for
// free items, which have no cost line of their own but still take time to build.
// Kept on the cost line, separated by a watch glyph. The description window sits
// directly beneath this one in the .wnd layout, so a second line here overlaps it.
UnicodeString costLine = cost;
if( !buildTimeText.isEmpty() )
{
if( !costLine.isEmpty() )
costLine.concat( L" " );
costLine.concat( (WideChar)0x231A ); // WATCH
costLine.concat( L" " );
costLine.concat( buildTimeText );
}

if( !costLine.isEmpty() )
{
win->winHide( FALSE );
GadgetStaticTextSetText(win, cost);
GadgetStaticTextSetText(win, costLine);
}
else
{
Expand Down
24 changes: 24 additions & 0 deletions Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetPushButton.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -600,10 +600,34 @@ PushButtonData * getNewPushButtonData()
p->userData = nullptr;
p->drawBorder = FALSE;
p->drawClock = NO_CLOCK;
p->countdownSeconds = -1;
p->overlayImage = nullptr;
return p;
}

// GadgetButtonSetBorder ======================================================
/** Set to draw the special borders in the game */
//=============================================================================
// GadgetButtonDrawCountdown ==================================================
/** TheSuperHackers @feature Show remaining time in seconds over the button. Like the clock
* above this is one shot, so it must be re-set every frame while the timer is running. */
//=============================================================================
void GadgetButtonDrawCountdown( GameWindow *g, Int seconds )
{

if( g == nullptr )
return;

PushButtonData *pData = (PushButtonData *)g->winGetUserData();
if(!pData)
{
pData = getNewPushButtonData();
}
pData->countdownSeconds = seconds;
g->winSetUserData(pData);

}

// GadgetButtonSetBorder ======================================================
/** Set to draw the special borders in the game */
//=============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
///////////////////////////////////////////////////////////////////////////////

extern void W3DGadgetPushButtonDraw( GameWindow *window, WinInstanceData *instData );
// TheSuperHackers @feature Free the countdown text's display string; must run before
// TheDisplayStringManager is destroyed.
extern void W3DGadgetPushButtonFreeCountdownString( void );
extern void W3DGadgetPushButtonImageDraw( GameWindow *window, WinInstanceData *instData );
extern void W3DGadgetCheckBoxDraw( GameWindow *window, WinInstanceData *instData );
extern void W3DGadgetCheckBoxImageDraw( GameWindow *window, WinInstanceData *instData );
Expand Down
Loading