diff --git a/Core/GameEngine/Include/Common/UserPreferences.h b/Core/GameEngine/Include/Common/UserPreferences.h index 17f3bbeb515..409daa2c9d5 100644 --- a/Core/GameEngine/Include/Common/UserPreferences.h +++ b/Core/GameEngine/Include/Common/UserPreferences.h @@ -149,6 +149,10 @@ class OptionPreferences : public UserPreferences Int getObserverStatsFontSize(void); Int getObserverNotificationFontSize(void); + Bool getObserverNotificationSpecialPowerUsage(void); + Bool getObserverNotificationSpecialPowerPurchase(void); + Bool getObserverNotificationMilestone(void); + Real getResolutionFontAdjustment(void); Bool getShowMoneyPerMinute(void) const; diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 48947fdd0d7..809eac5a8ab 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -424,6 +424,9 @@ class GlobalData : public SubsystemInterface // Generals Online @feature 16/1/2025 allow the observer notification font size to be set, a size of zero disables it Int m_observerNotificationFontSize; + Bool m_observerNotificationSpecialPowerUsage; + Bool m_observerNotificationSpecialPowerPurchase; + Bool m_observerNotificationMilestone; Real m_shakeSubtleIntensity; ///< Intensity for shaking a camera with SHAKE_SUBTLE Real m_shakeNormalIntensity; ///< Intensity for shaking a camera with SHAKE_NORMAL diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 46d1e2f34cb..92926d75822 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -952,6 +952,10 @@ GlobalData::GlobalData() m_observerStatsFontSize = 7; m_observerNotificationFontSize = 10; + m_observerNotificationSpecialPowerUsage = TRUE; + m_observerNotificationSpecialPowerPurchase = TRUE; + m_observerNotificationMilestone = TRUE; + m_showMoneyPerMinute = FALSE; m_allowMoneyPerMinuteForPlayer = FALSE; @@ -1238,6 +1242,9 @@ void GlobalData::parseGameDataDefinition( INI* ini ) TheWritableGlobalData->m_showMoneyPerMinute = optionPref.getShowMoneyPerMinute(); TheWritableGlobalData->m_observerStatsFontSize = optionPref.getObserverStatsFontSize(); TheWritableGlobalData->m_observerNotificationFontSize = optionPref.getObserverNotificationFontSize(); + TheWritableGlobalData->m_observerNotificationSpecialPowerUsage = optionPref.getObserverNotificationSpecialPowerUsage(); + TheWritableGlobalData->m_observerNotificationSpecialPowerPurchase = optionPref.getObserverNotificationSpecialPowerPurchase(); + TheWritableGlobalData->m_observerNotificationMilestone = optionPref.getObserverNotificationMilestone(); Int val=optionPref.getGammaValue(); //generate a value between 0.6 and 2.0. 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 6fe868a9d8c..0449fde23ec 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -950,6 +950,24 @@ Int OptionPreferences::getObserverNotificationFontSize(void) { return fontSize; } +Bool OptionPreferences::getObserverNotificationSpecialPowerUsage(void) { + OptionPreferences::const_iterator it = find("ObserverNotificationSpecialPowerUsage"); + if (it == end()) return true; + return AsciiString(it->second.str()) != "no"; +} + +Bool OptionPreferences::getObserverNotificationSpecialPowerPurchase(void) { + OptionPreferences::const_iterator it = find("ObserverNotificationSpecialPowerPurchase"); + if (it == end()) return true; + return AsciiString(it->second.str()) != "no"; +} + +Bool OptionPreferences::getObserverNotificationMilestone(void) { + OptionPreferences::const_iterator it = find("ObserverNotificationMilestone"); + if (it == end()) return true; + return AsciiString(it->second.str()) != "no"; +} + Int OptionPreferences::getObserverStatsFontSize(void) { OptionPreferences::const_iterator it = find("ObserverStatsFontSize"); @@ -1555,16 +1573,6 @@ static void saveOptions( void ) TheInGameUI->refreshRenderFpsResources(); } - //------------------------------------------------------------------------------------------------- - // Set Observer notification Font Size - val = pref->getObserverNotificationFontSize(); - if (val >= 0) { - AsciiString prefString; - prefString.format("%d", val); - (*pref)["ObserverNotificationFontSize"] = prefString; - TheInGameUI->refreshObserverNotificationResources(); - } - //------------------------------------------------------------------------------------------------- // Set Observer Stats Font Size val = pref->getObserverStatsFontSize(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index cc14564d242..0cf4a372070 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -1123,7 +1123,7 @@ InGameUI::InGameUI() m_gameTimeDropColor = GameMakeColor(0, 0, 0, 255); // Observer Stats Overlay - m_observerStatsString = NULL; + m_observerStatsString = nullptr; m_observerStatsFont = "Tahoma"; m_observerStatsPointSize = 10; m_observerStatsBold = TRUE; @@ -1131,8 +1131,9 @@ InGameUI::InGameUI() m_observerStatsPosition.y = kHudAnchorY; // Observer notification overlay - m_observerNotificationString = NULL; + m_observerNotificationString = nullptr; m_observerNotificationPointSize = TheGlobalData->m_observerNotificationFontSize; + m_observerNotificationsHidden = false; #if defined(GENERALS_ONLINE) m_colorGood = GameMakeColor(0, 255, 0, 150); @@ -6094,7 +6095,7 @@ void InGameUI::recreateControlBar(void) // Observer Notification // ====================================================================================== namespace { - const Int MAX_NOTIFICATIONS = 8; + const Int MAX_NOTIFICATIONS = 8; // Maximum number of notifs to show at once on screen const UnsignedInt SLIDE_IN_MS = 300; const UnsignedInt VISIBLE_MS = 3000; const UnsignedInt SLIDE_OUT_MS = 300; @@ -6134,63 +6135,64 @@ static UnicodeString formatPowerAction(const AsciiString& powerNameAscii) }; static const Entry table[] = { - {"SuperweaponScudStorm", L"LAUNCHED A SCUD STORM!!!"}, - {"SuperweaponNeutronMissile", L"LAUNCHED A NUKE MISSILE!!!"}, - {"SuperweaponParticleUplinkCannon", L"FIRED A PARTICLE CANNON!!!"}, - {"SuperweaponAnthraxBomb", L"DROPPED AN ANTHRAX BOMB!!!"}, - {"SuperweaponRebelAmbush", L"CALLED IN THE REBEL AMBUSH!!"}, - {"SuperweaponArtilleryBarrage", L"CALLED IN THE ARTILLERY BARRAGE!!"}, - {"SuperweaponEMPPulse", L"CALLED IN AN EMP PULSE!!!"}, - {"SuperweaponCIAIntelligence", L"JUST ACTIVATED THE CIA INTELLIGENCE!"}, - {"SuperweaponSneakAttack", L"OPENED A SNEAK ATTACK!!!"}, + {"SuperweaponScudStorm", L"launched a Scud Storm"}, + {"SuperweaponNeutronMissile", L"launched a Nuke Missile"}, + {"SuperweaponParticleUplinkCannon", L"fired a Particle Cannon"}, + {"SuperweaponAnthraxBomb", L"dropped an Anthrax Bomb"}, + {"SuperweaponRebelAmbush", L"called in a Rebel Ambush"}, + {"SuperweaponArtilleryBarrage", L"called in an Artillery Barrage"}, + {"SuperweaponEMPPulse", L"called in an EMP Bomb"}, + {"SuperweaponCIAIntelligence", L"activated the CIA Intelligence"}, + {"SuperweaponSneakAttack", L"opened a Sneak Attack"}, - {"SuperweaponDaisyCutter", L"CALLED IN THE MOAB!!!"}, - {"AirF_SuperweaponDaisyCutter", L"CALLED IN THE MOAB!!!"}, + {"SuperweaponDaisyCutter", L"called in a MOAB"}, + {"AirF_SuperweaponDaisyCutter", L"called in a MOAB"}, - {"SuperweaponClusterMines", L"CALLED IN A MINE DROP!!"}, - {"Nuke_SuperweaponClusterMines", L"CALLED IN A MINE DROP!!"}, + {"SuperweaponClusterMines", L"called in a Mine Drop"}, + {"Nuke_SuperweaponClusterMines", L"called in a Mine Drop"}, - {"AirF_SuperweaponA10ThunderboltMissileStrike", L"CALLED IN AN A10 STRIKE!!"}, - {"SuperweaponA10ThunderboltMissileStrike", L"CALLED IN AN A10 STRIKE!!"}, + {"AirF_SuperweaponA10ThunderboltMissileStrike", L"called in an A10 Strike"}, + {"SuperweaponA10ThunderboltMissileStrike", L"called in an A10 Strike"}, - {"AirF_SuperweaponSpectreGunship", L"CALLED IN A SPECTRE GUNSHIP!!"}, - {"SuperweaponSpectreGunship", L"CALLED IN A SPECTRE GUNSHIP!!"}, + {"AirF_SuperweaponSpectreGunship", L"called in a Spectre Gunship"}, + {"SuperweaponSpectreGunship", L"called in a Spectre Gunship"}, - {"AirF_SuperweaponCarpetBomb", L"CALLED IN A CARPET BOMB!!"}, - {"Nuke_SuperweaponChinaCarpetBomb", L"CALLED IN A CARPET BOMB!!"}, - {"Early_SuperweaponChinaCarpetBomb", L"CALLED IN A CARPET BOMB!!"}, - {"SuperweaponChinaCarpetBomb", L"CALLED IN A CARPET BOMB!!"}, + {"AirF_SuperweaponCarpetBomb", L"called in a Carpet Bomb"}, + {"Nuke_SuperweaponChinaCarpetBomb", L"called in a Carpet Bomb"}, + {"Early_SuperweaponChinaCarpetBomb", L"called in a Carpet Bomb"}, + {"SuperweaponChinaCarpetBomb", L"called in a Carpet Bomb"}, - {"SuperweaponFrenzy", L"ACTIVATED THE FRENZY!"}, - {"Early_SuperweaponFrenzy", L"ACTIVATED THE FRENZY!"}, + {"SuperweaponCashHack", L"used Cash Hack"}, + {"SuperweaponEmergencyRepair", L"used Emergency Repair"}, - {"Slth_SuperweaponGPSScrambler", L"ACTIVATED A GPS SCRAMBLER!"}, - {"SuperweaponGPSScrambler", L"ACTIVATED A GPS SCRAMBLER!"}, + {"SuperweaponFrenzy", L"activated a Frenzy"}, + {"Early_SuperweaponFrenzy", L"activated a Frenzy"}, - {"Infa_SuperweaponInfantryParadrop", L"DEPLOYED A CHINA INFANTRY PARADROP!"}, - {"Tank_SuperweaponTankParadrop", L"DEPLOYED A TANK PARADROP!"}, - {"SuperweaponParadropAmerica", L"DEPLOYED A USA INFANTRY PARADROP!"}, + {"Slth_SuperweaponGPSScrambler", L"activated a GPS Scrambler"}, + {"SuperweaponGPSScrambler", L"activated a GPS Scrambler"}, - {"SuperweaponLeafletDrop", L"CALLED IN A LEAFLET DROP!!"}, - {"Early_SuperweaponLeafletDrop", L"CALLED IN A LEAFLET DROP!!"}, + {"Infa_SuperweaponInfantryParadrop", L"deployed a China Infantry Paradrop"}, + {"Tank_SuperweaponTankParadrop", L"deployed a China Tank Paradrop"}, + {"SuperweaponParadropAmerica", L"deployed a USA Infantry Paradrop"}, + + {"SuperweaponLeafletDrop", L"called in a Leaflet Drop"}, + {"Early_SuperweaponLeafletDrop", L"called in a Leaflet Drop"}, }; for (const Entry& entry : table) if (powerNameAscii == entry.key) return entry.value; - UnicodeString result = L"USED "; // Fallback for unmapped support powers - UnicodeString temp; - temp.translate(powerNameAscii); - result.concat(temp); + UnicodeString result; + result.format(L"used %hs", powerNameAscii.str()); // Fallback for unmapped support powers return result; } void InGameUI::drawObserverNotifications(Int& x, Int& y) { - if (!TheInGameUI->getInputEnabled() || TheGameLogic->isIntroMoviePlaying() || + if (!TheGameLogic || !TheInGameUI->getInputEnabled() || TheGameLogic->isIntroMoviePlaying() || TheGameLogic->isLoadingMap() || TheInGameUI->isQuitMenuVisible() || - !TheGameLogic || TheGameLogic->getFrame() <= 1 || m_observerNotificationsHidden) + TheGameLogic->getFrame() <= 1 || m_observerNotificationsHidden) return; Player* localPlayer = ThePlayerList->getLocalPlayer(); @@ -6277,51 +6279,41 @@ void InGameUI::drawObserverNotifications(Int& x, Int& y) // Handle milestone initialization and triggers milestone checks once per second. void InGameUI::updateObserverNotifications(UnsignedInt currentFrame) { - if (m_observerMilestones.empty()) { - m_observerMilestones.resize(MAX_SLOTS); - } - - static UnsignedInt lastCheckFrame = 0; - if (currentFrame - lastCheckFrame >= LOGICFRAMES_PER_SECOND) { - lastCheckFrame = currentFrame; + if ((currentFrame % LOGICFRAMES_PER_SECOND) == 0) { checkObserverMilestones(currentFrame); } } void InGameUI::checkObserverMilestones(UnsignedInt currentFrame) { - for (Int slotIndex = 0; slotIndex < MAX_SLOTS; ++slotIndex) { - const GameSlot* slot = TheGameInfo ? TheGameInfo->getConstSlot(slotIndex) : nullptr; - if (!slot || !slot->isOccupied()) - continue; - - AsciiString nameKeyStr; - nameKeyStr.format("player%d", slotIndex); - - if (!ThePlayerList || !TheNameKeyGenerator) - continue; + if (!TheGlobalData->m_observerNotificationMilestone) + return; - Player* p = ThePlayerList->findPlayerWithNameKey(TheNameKeyGenerator->nameToKey(nameKeyStr)); + Int playerCount = ThePlayerList ? ThePlayerList->getPlayerCount() : 0; + if (m_observerMilestones.size() < (size_t)playerCount) + m_observerMilestones.resize(playerCount); - if (!p || !p->isPlayerActive() || p->isPlayerObserver()) - continue; + for (Int i = 0; i < playerCount; ++i) { + Player* p = ThePlayerList->getNthPlayer(i); + if (!p || !p->isPlayerActive() || p->isPlayerObserver()) + continue; - UnicodeString name = p->getPlayerDisplayName(); - if (name.isEmpty()) - continue; + UnicodeString name = p->getPlayerDisplayName(); + if (name.isEmpty()) + continue; - ObserverMilestone& milestone = m_observerMilestones[slotIndex]; - Color playerColor = p->getPlayerColor(); + ObserverMilestone& milestone = m_observerMilestones[i]; + Color playerColor = p->getPlayerColor(); // Check rank milestones Int rank = p->getRankLevel(); if (rank >= 3 && !milestone.reachedLevel3) { milestone.reachedLevel3 = true; - addObserverNotification(name, L" reached Rank 3!", playerColor); + addObserverNotification(name, L" reached Rank 3", playerColor); } if (rank >= 5 && !milestone.reachedLevel5) { milestone.reachedLevel5 = true; - addObserverNotification(name, L" reached Rank 5!", playerColor); + addObserverNotification(name, L" reached Rank 5", playerColor); } // Check economy milestones @@ -6334,16 +6326,16 @@ void InGameUI::checkObserverMilestones(UnsignedInt currentFrame) if (cash >= 100000 && !milestone.warnedFloating100k) { milestone.warnedFloating100k = true; - addObserverNotification(name, L" is floating $100k!", playerColor); + addObserverNotification(name, L" is floating $100k", playerColor); } // Check income milestones in ascending order struct IncomeThreshold { UnsignedInt amount; Bool& reached; const wchar_t* msg; }; IncomeThreshold thresholds[] = { - { 10000, milestone.reached10kCPM, L" reached 10k/min income!" }, - { 20000, milestone.reached20kCPM, L" reached 20k/min income!!" }, - { 50000, milestone.reached50kCPM, L" reached 50k/min income!!!" }, - { 100000, milestone.reached100kCPM, L" reached 100k/min income!!!!" } + { 10000, milestone.reached10kCPM, L" reached 10k/min income" }, + { 20000, milestone.reached20kCPM, L" reached 20k/min income" }, + { 50000, milestone.reached50kCPM, L" reached 50k/min income" }, + { 100000, milestone.reached100kCPM, L" reached 100k/min income" } }; for (auto& threshold : thresholds) { @@ -6395,6 +6387,9 @@ void InGameUI::notifyGeneralPromotion(Player* player, ScienceType science) if (!player || !player->isPlayerActive() || player->isPlayerObserver()) return; + if (!TheGlobalData->m_observerNotificationSpecialPowerPurchase) + return; + UnicodeString scienceName, description; if (!TheScienceStore->getNameAndDescription(science, scienceName, description)) return; @@ -6409,22 +6404,8 @@ void InGameUI::notifySpecialPowerUsed(Player* player, const SpecialPowerTemplate if (!player || !player->isPlayerActive() || !powerTemplate || player->isPlayerObserver()) return; - // Only notify for these support powers - switch (powerTemplate->getSpecialPowerType()) { - case SPECIAL_DAISY_CUTTER: case SPECIAL_CARPET_BOMB: case AIRF_SPECIAL_DAISY_CUTTER: - case SPECIAL_PARTICLE_UPLINK_CANNON: case SPECIAL_SCUD_STORM: case SPECIAL_NEUTRON_MISSILE: - case SPECIAL_AMBUSH: case EARLY_SPECIAL_LEAFLET_DROP: case EARLY_SPECIAL_FRENZY: - case SPECIAL_CLUSTER_MINES: case SPECIAL_EMP_PULSE: case SPECIAL_ANTHRAX_BOMB: - case SPECIAL_A10_THUNDERBOLT_STRIKE: case SPECIAL_ARTILLERY_BARRAGE: case SPECIAL_SPECTRE_GUNSHIP: - case SPECIAL_FRENZY: case SPECIAL_SNEAK_ATTACK: case SPECIAL_CHINA_CARPET_BOMB: case SPECIAL_CIA_INTELLIGENCE: - case SPECIAL_LEAFLET_DROP: case SPECIAL_TANK_PARADROP: case SPECIAL_PARADROP_AMERICA: - case NUKE_SPECIAL_CLUSTER_MINES: case AIRF_SPECIAL_A10_THUNDERBOLT_STRIKE: case AIRF_SPECIAL_SPECTRE_GUNSHIP: - case INFA_SPECIAL_PARADROP_AMERICA: case SLTH_SPECIAL_GPS_SCRAMBLER: case AIRF_SPECIAL_CARPET_BOMB: - case SPECIAL_GPS_SCRAMBLER: case EARLY_SPECIAL_CHINA_CARPET_BOMB: - break; - default: + if (!TheGlobalData->m_observerNotificationSpecialPowerUsage) return; - } UnicodeString msg; msg.format(L"%ls %ls", player->getPlayerDisplayName().str(), formatPowerAction(powerTemplate->getName()).str()); @@ -7160,7 +7141,3 @@ void InGameUI::drawGameTime() m_gameTimeString->draw(horizontalTimerOffset, m_gameTimePosition.y, m_gameTimeColor, m_gameTimeDropColor); m_gameTimeFrameString->draw(horizontalFrameOffset, m_gameTimePosition.y, GameMakeColor(180, 180, 180, 255), m_gameTimeDropColor); } - - - -