diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index ed3bbcc1d1..4682573a7f 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -405,6 +405,9 @@ class GlobalData : public SubsystemInterface Bool m_saveCameraInReplay; Bool m_useCameraInReplay; + // TheSuperHackers @feature L3-M 21/08/2025 toggle the money per minute display; 'no' shows only the original current money + Bool m_showMoneyPerMinute; + // TheSuperHackers @feature Mauller 21/06/2025 allow the system time and game time font size to be set, a size of zero disables them Int m_systemTimeFontSize; Int m_gameTimeFontSize; diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Money.h b/GeneralsMD/Code/GameEngine/Include/Common/Money.h index 9b17177669..51e11620a1 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Money.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Money.h @@ -64,13 +64,14 @@ class Money : public Snapshot public: - inline Money() : m_money(0), m_playerIndex(0) + inline Money() : m_playerIndex(0) { + init(); } void init() { - m_money = 0; + setStartingCash(0); } inline UnsignedInt countMoney() const @@ -82,6 +83,10 @@ class Money : public Snapshot UnsignedInt withdraw(UnsignedInt amountToWithdraw, Bool playSound = TRUE); void deposit(UnsignedInt amountToDeposit, Bool playSound = TRUE); + void setStartingCash(UnsignedInt amount); + void updateIncomeBucket(); + UnsignedInt getCashPerMinute() const; + void setPlayerIndex(Int ndx) { m_playerIndex = ndx; } static void parseMoneyAmount( INI *ini, void *instance, void *store, const void* userData ); @@ -105,6 +110,9 @@ class Money : public Snapshot UnsignedInt m_money; ///< amount of money Int m_playerIndex; ///< what is my player index? + UnsignedInt m_incomeBuckets[60]; + UnsignedInt m_currentBucket; + UnsignedInt m_lastBucketFrame; }; #endif // _MONEY_H_ diff --git a/GeneralsMD/Code/GameEngine/Include/Common/UserPreferences.h b/GeneralsMD/Code/GameEngine/Include/Common/UserPreferences.h index 9e4d83a4bf..207a9a2445 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/UserPreferences.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/UserPreferences.h @@ -128,6 +128,7 @@ class OptionPreferences : public UserPreferences Bool getFPSLimitEnabled(void); Bool getNoDynamicLODEnabled(void); Bool getBuildingOcclusionEnabled(void); + Bool getShowMoneyPerMinute(void); Int getParticleCap(void); Int getCampaignDifficulty(void); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index be4b8962e2..3dc057e473 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -934,6 +934,8 @@ GlobalData::GlobalData() m_saveCameraInReplay = FALSE; m_useCameraInReplay = FALSE; + m_showMoneyPerMinute = FALSE; + m_systemTimeFontSize = 8; m_gameTimeFontSize = 8; @@ -1203,6 +1205,7 @@ void GlobalData::parseGameDataDefinition( INI* ini ) TheWritableGlobalData->m_systemTimeFontSize = optionPref.getSystemTimeFontSize(); TheWritableGlobalData->m_gameTimeFontSize = optionPref.getGameTimeFontSize(); + TheWritableGlobalData->m_showMoneyPerMinute = optionPref.getShowMoneyPerMinute(); Int val=optionPref.getGammaValue(); //generate a value between 0.6 and 2.0. diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp index 8a45812ecc..a4897235b1 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp @@ -44,6 +44,8 @@ #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/Money.h" +#include +#include #include "Common/AudioSettings.h" #include "Common/GameAudio.h" @@ -51,6 +53,7 @@ #include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/Xfer.h" +#include "GameLogic/GameLogic.h" // ------------------------------------------------------------------------------------------------ UnsignedInt Money::withdraw(UnsignedInt amountToWithdraw, Bool playSound) @@ -86,6 +89,7 @@ void Money::deposit(UnsignedInt amountToDeposit, Bool playSound) if (playSound) { triggerAudioEvent(TheAudio->getMiscAudio()->m_moneyDepositSound); + m_incomeBuckets[m_currentBucket] += amountToDeposit; } m_money += amountToDeposit; @@ -100,6 +104,45 @@ void Money::deposit(UnsignedInt amountToDeposit, Bool playSound) } } +// ------------------------------------------------------------------------------------------------ +void Money::setStartingCash(UnsignedInt amount) +{ + m_money = amount; + m_currentBucket = 0; + m_lastBucketFrame = 0; + std::fill(m_incomeBuckets, m_incomeBuckets + ARRAY_SIZE(m_incomeBuckets), 0u); +} + +// ------------------------------------------------------------------------------------------------ +void Money::updateIncomeBucket() +{ + UnsignedInt frame = TheGameLogic->getFrame(); + UnsignedInt lastSec = m_lastBucketFrame / LOGICFRAMES_PER_SECOND; + UnsignedInt curSec = frame / LOGICFRAMES_PER_SECOND; + UnsignedInt diff = (curSec > lastSec) ? curSec - lastSec : 0; + if (diff > 0) + { + if (diff > ARRAY_SIZE(m_incomeBuckets)) + diff = ARRAY_SIZE(m_incomeBuckets); + + UnsignedInt next = (m_currentBucket + 1) % ARRAY_SIZE(m_incomeBuckets); + UnsignedInt first = std::min(diff, ARRAY_SIZE(m_incomeBuckets) - next); + std::fill(m_incomeBuckets + next, m_incomeBuckets + next + first, 0u); + + if (diff > first) + std::fill(m_incomeBuckets, m_incomeBuckets + (diff - first), 0u); + + m_currentBucket = (m_currentBucket + diff) % ARRAY_SIZE(m_incomeBuckets); + } + m_lastBucketFrame = frame; +} + +// ------------------------------------------------------------------------------------------------ +UnsignedInt Money::getCashPerMinute() const +{ + return std::accumulate(m_incomeBuckets, m_incomeBuckets + ARRAY_SIZE(m_incomeBuckets), 0u); +} + void Money::triggerAudioEvent(const AudioEventRTS& audioEvent) { Real volume = TheAudio->getAudioSettings()->m_preferredMoneyTransactionVolume; @@ -157,4 +200,5 @@ void Money::parseMoneyAmount( INI *ini, void *instance, void *store, const void* // Someday, maybe, have mulitple fields like Gold:10000 Wood:1000 Tiberian:10 Money * money = (Money *)store; INI::parseUnsignedInt( ini, instance, &money->m_money, userData ); + money->setStartingCash(money->m_money); } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/PlayerTemplate.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/PlayerTemplate.cpp index 3fe64a9088..493af04175 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/PlayerTemplate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/PlayerTemplate.cpp @@ -182,6 +182,7 @@ AsciiString PlayerTemplate::getStartingUnit( Int i ) const Money *theMoney = (Money *)store; theMoney->init(); theMoney->deposit( money ); + theMoney->setStartingCash(money); } // end parseStartMoney diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 2f4a306071..636a2fce1e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -828,6 +828,19 @@ Int OptionPreferences::getGameTimeFontSize(void) return fontSize; } +Bool OptionPreferences::getShowMoneyPerMinute(void) +{ + OptionPreferences::const_iterator it = find("ShowMoneyPerMinute"); + if (it == end()) + return TheGlobalData->m_showMoneyPerMinute; + + if (stricmp(it->second.str(), "yes") == 0) + { + return TRUE; + } + return FALSE; +} + static OptionPreferences *pref = NULL; static void setDefaults( void ) @@ -1354,6 +1367,15 @@ static void saveOptions( void ) TheInGameUI->refreshGameTimeResources(); } + //------------------------------------------------------------------------------------------------- + // Set Money Per Minute + { + Bool showIncome = pref->getShowMoneyPerMinute(); + AsciiString prefString; + prefString = showIncome ? "yes" : "no"; + (*pref)["ShowMoneyPerMinute"] = prefString; + } + //------------------------------------------------------------------------------------------------- // Resolution // diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index 2573860144..62409c4939 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -96,6 +96,40 @@ static const Real placementOpacity = 0.45f; static const RGBColor illegalBuildColor = { 1.0, 0.0, 0.0 }; +// ------------------------------------------------------------------------------------------------ +static UnicodeString formatMoneyValue(UnsignedInt amount) +{ + UnicodeString result; + if (amount >= 100000) + { + result.format(L"%dk", amount / 1000); + } + else + { + result.format(L"%d", amount); + } + return result; +} + +static UnicodeString formatIncomeValue(UnsignedInt cashPerMin) +{ + UnicodeString result; + if (cashPerMin >= 10000) + { + result.format(L"%dk", cashPerMin / 1000); + } + else if (cashPerMin >= 1000) + { + UnsignedInt k = cashPerMin / 100; + result.format(L"%d.%dk", k / 10, k % 10); + } + else + { + result.format(L"%d", cashPerMin); + } + return result; +} + //------------------------------------------------------------------------------------------------- /// The InGameUI singleton instance. InGameUI *TheInGameUI = NULL; @@ -1837,6 +1871,7 @@ void InGameUI::update( void ) // update the player money window if the money amount has changed // this seems like as good a place as any to do the power hide/show static Int lastMoney = -1; + static Int lastIncome = -1; static NameKeyType moneyWindowKey = TheNameKeyGenerator->nameToKey( "ControlBar.wnd:MoneyDisplay" ); static NameKeyType powerWindowKey = TheNameKeyGenerator->nameToKey( "ControlBar.wnd:PowerWindow" ); @@ -1852,16 +1887,39 @@ void InGameUI::update( void ) Player* moneyPlayer = TheControlBar->getCurrentlyViewedPlayer(); if( moneyPlayer) { - Int currentMoney = moneyPlayer->getMoney()->countMoney(); - if( lastMoney != currentMoney ) + Money *money = moneyPlayer->getMoney(); + Bool showIncome = TheGlobalData->m_showMoneyPerMinute; + if (!showIncome) { - UnicodeString buffer; + Int currentMoney = money->countMoney(); + if( lastMoney != currentMoney ) + { + UnicodeString buffer; - buffer.format( TheGameText->fetch( "GUI:ControlBarMoneyDisplay" ), currentMoney ); - GadgetStaticTextSetText( moneyWin, buffer ); - lastMoney = currentMoney; + buffer.format(TheGameText->fetch( "GUI:ControlBarMoneyDisplay" ), currentMoney ); + GadgetStaticTextSetText( moneyWin, buffer ); + lastMoney = currentMoney; - } // end if + } // end if + } + else + { + // TheSuperHackers @feature L3-M 21/08/2025 player money per minute + money->updateIncomeBucket(); + UnsignedInt currentMoney = money->countMoney(); + UnsignedInt cashPerMin = money->getCashPerMinute(); + if (lastMoney != (Int)currentMoney || lastIncome != (Int)cashPerMin) + { + UnicodeString buffer; + UnicodeString moneyStr = formatMoneyValue(currentMoney); + UnicodeString incomeStr = formatIncomeValue(cashPerMin); + + buffer.format(TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:ControlBarMoneyDisplayIncome", L"%ls (%ls)", moneyStr.str(), incomeStr.str())); + GadgetStaticTextSetText(moneyWin, buffer); + lastMoney = currentMoney; + lastIncome = cashPerMin; + } + } moneyWin->winHide(FALSE); powerWin->winHide(FALSE); }