diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
index 85d384b87a7..95f714cfcc5 100644
--- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
+++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
@@ -269,6 +269,7 @@ static PoolSizeRec PoolSizes[] =
{ "SupplyWarehouseCreate", 48, 16 },
{ "SupplyWarehouseDockUpdate", 48, 16 },
{ "EnemyNearUpdate", 1024, 32 },
+ { "ProximityCaptureUpdate", 32, 32 },
{ "TechBuildingBehavior", 32, 32 },
{ "ToppleUpdate", 256, 128 },
{ "TransitionDamageFX", 384, 128 },
diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt
index 9fb9bbae71f..b3ee009a069 100644
--- a/GeneralsMD/Code/GameEngine/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt
@@ -65,7 +65,7 @@ set(GAMEENGINE_SRC
Include/Common/List.h
# Include/Common/LocalFile.h
# Include/Common/LocalFileSystem.h
-# Include/Common/MapObject.h
+# Include/Common/MapObject.h
Include/Common/MapReaderWriterInfo.h
Include/Common/MessageStream.h
Include/Common/MiniLog.h
@@ -77,7 +77,7 @@ set(GAMEENGINE_SRC
Include/Common/Money.h
Include/Common/MultiplayerSettings.h
Include/Common/NameKeyGenerator.h
-# Include/Common/ObjectStatusTypes.h
+# Include/Common/ObjectStatusTypes.h
Include/Common/OSDisplay.h
Include/Common/Overridable.h
Include/Common/Override.h
@@ -90,7 +90,7 @@ set(GAMEENGINE_SRC
Include/Common/ProductionPrerequisite.h
Include/Common/QuickmatchPreferences.h
Include/Common/QuotedPrintable.h
-# Include/Common/Radar.h
+# Include/Common/Radar.h
# Include/Common/RAMFile.h
# Include/Common/RandomValue.h
Include/Common/Recorder.h
@@ -200,7 +200,7 @@ set(GAMEENGINE_SRC
Include/GameClient/Line2D.h
Include/GameClient/LoadScreen.h
Include/GameClient/LookAtXlat.h
-# Include/GameClient/MapUtil.h
+# Include/GameClient/MapUtil.h
Include/GameClient/MessageBox.h
Include/GameClient/MetaEvent.h
Include/GameClient/Module/AnimatedParticleSysBoneClientUpdate.h
@@ -208,7 +208,7 @@ set(GAMEENGINE_SRC
Include/GameClient/Module/SwayClientUpdate.h
Include/GameClient/Module/DynamicGeometryClientUpdate.h
Include/GameClient/Mouse.h
-# Include/GameClient/ParabolicEase.h
+# Include/GameClient/ParabolicEase.h
Include/GameClient/ParticleSys.h
Include/GameClient/PlaceEventTranslator.h
Include/GameClient/ProcessAnimateWindow.h
@@ -220,15 +220,15 @@ set(GAMEENGINE_SRC
Include/GameClient/Shell.h
Include/GameClient/ShellHooks.h
Include/GameClient/ShellMenuScheme.h
-# Include/GameClient/Smudge.h
-# Include/GameClient/Snow.h
+# Include/GameClient/Smudge.h
+# Include/GameClient/Snow.h
Include/GameClient/Statistics.h
-# Include/GameClient/TerrainRoads.h
-# Include/GameClient/TerrainVisual.h
+# Include/GameClient/TerrainRoads.h
+# Include/GameClient/TerrainVisual.h
Include/GameClient/TintStatus.h
# Include/GameClient/VideoPlayer.h
-# Include/GameClient/View.h
-# Include/GameClient/Water.h
+# Include/GameClient/View.h
+# Include/GameClient/Water.h
Include/GameClient/WindowLayout.h
# Include/GameClient/WindowVideoManager.h
Include/GameClient/WindowXlat.h
@@ -322,6 +322,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/EjectPilotDie.h
Include/GameLogic/Module/EMPUpdate.h
Include/GameLogic/Module/EnemyNearUpdate.h
+ Include/GameLogic/Module/ProximityCaptureUpdate.h
Include/GameLogic/Module/ExperienceScalarUpgrade.h
Include/GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h
Include/GameLogic/Module/FireSpreadUpdate.h
@@ -529,58 +530,58 @@ set(GAMEENGINE_SRC
Include/GameLogic/WeaponSetFlags.h
Include/GameLogic/WeaponSetType.h
Include/GameLogic/WeaponStatus.h
-# Include/GameNetwork/Connection.h
-# Include/GameNetwork/ConnectionManager.h
-# Include/GameNetwork/DisconnectManager.h
-# Include/GameNetwork/DownloadManager.h
-# Include/GameNetwork/FileTransfer.h
-# Include/GameNetwork/FirewallHelper.h
-# Include/GameNetwork/FrameData.h
-# Include/GameNetwork/FrameDataManager.h
-# Include/GameNetwork/FrameMetrics.h
-# Include/GameNetwork/GameInfo.h
-# Include/GameNetwork/GameMessageParser.h
-# Include/GameNetwork/GameSpy/BuddyDefs.h
-# Include/GameNetwork/GameSpy/BuddyThread.h
-# Include/GameNetwork/GameSpy/GameResultsThread.h
-# Include/GameNetwork/GameSpy/GSConfig.h
-# Include/GameNetwork/GameSpy/LadderDefs.h
-# Include/GameNetwork/GameSpy/LobbyUtils.h
-# Include/GameNetwork/GameSpy/MainMenuUtils.h
-# Include/GameNetwork/GameSpy/PeerDefs.h
-# Include/GameNetwork/GameSpy/PeerDefsImplementation.h
-# Include/GameNetwork/GameSpy/PeerThread.h
-# Include/GameNetwork/GameSpy/PersistentStorageDefs.h
-# Include/GameNetwork/GameSpy/PersistentStorageThread.h
-# Include/GameNetwork/GameSpy/PingThread.h
-# Include/GameNetwork/GameSpy/StagingRoomGameInfo.h
-# Include/GameNetwork/GameSpy/ThreadUtils.h
-# Include/GameNetwork/GameSpyChat.h
-# Include/GameNetwork/GameSpyGameInfo.h
-# Include/GameNetwork/GameSpyGP.h
-# Include/GameNetwork/GameSpyOverlay.h
-# Include/GameNetwork/GameSpyThread.h
+# Include/GameNetwork/Connection.h
+# Include/GameNetwork/ConnectionManager.h
+# Include/GameNetwork/DisconnectManager.h
+# Include/GameNetwork/DownloadManager.h
+# Include/GameNetwork/FileTransfer.h
+# Include/GameNetwork/FirewallHelper.h
+# Include/GameNetwork/FrameData.h
+# Include/GameNetwork/FrameDataManager.h
+# Include/GameNetwork/FrameMetrics.h
+# Include/GameNetwork/GameInfo.h
+# Include/GameNetwork/GameMessageParser.h
+# Include/GameNetwork/GameSpy/BuddyDefs.h
+# Include/GameNetwork/GameSpy/BuddyThread.h
+# Include/GameNetwork/GameSpy/GameResultsThread.h
+# Include/GameNetwork/GameSpy/GSConfig.h
+# Include/GameNetwork/GameSpy/LadderDefs.h
+# Include/GameNetwork/GameSpy/LobbyUtils.h
+# Include/GameNetwork/GameSpy/MainMenuUtils.h
+# Include/GameNetwork/GameSpy/PeerDefs.h
+# Include/GameNetwork/GameSpy/PeerDefsImplementation.h
+# Include/GameNetwork/GameSpy/PeerThread.h
+# Include/GameNetwork/GameSpy/PersistentStorageDefs.h
+# Include/GameNetwork/GameSpy/PersistentStorageThread.h
+# Include/GameNetwork/GameSpy/PingThread.h
+# Include/GameNetwork/GameSpy/StagingRoomGameInfo.h
+# Include/GameNetwork/GameSpy/ThreadUtils.h
+# Include/GameNetwork/GameSpyChat.h
+# Include/GameNetwork/GameSpyGameInfo.h
+# Include/GameNetwork/GameSpyGP.h
+# Include/GameNetwork/GameSpyOverlay.h
+# Include/GameNetwork/GameSpyThread.h
Include/GameNetwork/GUIUtil.h
-# Include/GameNetwork/IPEnumeration.h
-# Include/GameNetwork/LANAPI.h
-# Include/GameNetwork/LANAPICallbacks.h
-# Include/GameNetwork/LANGameInfo.h
-# Include/GameNetwork/LANPlayer.h
-# Include/GameNetwork/NAT.h
-# Include/GameNetwork/NetCommandList.h
-# Include/GameNetwork/NetCommandMsg.h
-# Include/GameNetwork/NetCommandRef.h
-# Include/GameNetwork/NetCommandWrapperList.h
-# Include/GameNetwork/NetPacket.h
-# Include/GameNetwork/NetworkDefs.h
-# Include/GameNetwork/NetworkInterface.h
-# Include/GameNetwork/networkutil.h
-# Include/GameNetwork/RankPointValue.h
-# Include/GameNetwork/Transport.h
-# Include/GameNetwork/udp.h
-# Include/GameNetwork/User.h
-# Include/GameNetwork/WOLBrowser/FEBDispatch.h
-# Include/GameNetwork/WOLBrowser/WebBrowser.h
+# Include/GameNetwork/IPEnumeration.h
+# Include/GameNetwork/LANAPI.h
+# Include/GameNetwork/LANAPICallbacks.h
+# Include/GameNetwork/LANGameInfo.h
+# Include/GameNetwork/LANPlayer.h
+# Include/GameNetwork/NAT.h
+# Include/GameNetwork/NetCommandList.h
+# Include/GameNetwork/NetCommandMsg.h
+# Include/GameNetwork/NetCommandRef.h
+# Include/GameNetwork/NetCommandWrapperList.h
+# Include/GameNetwork/NetPacket.h
+# Include/GameNetwork/NetworkDefs.h
+# Include/GameNetwork/NetworkInterface.h
+# Include/GameNetwork/networkutil.h
+# Include/GameNetwork/RankPointValue.h
+# Include/GameNetwork/Transport.h
+# Include/GameNetwork/udp.h
+# Include/GameNetwork/User.h
+# Include/GameNetwork/WOLBrowser/FEBDispatch.h
+# Include/GameNetwork/WOLBrowser/WebBrowser.h
Include/Precompiled/PreRTS.h
# Source/Common/Audio/AudioEventRTS.cpp
# Source/Common/Audio/AudioRequest.cpp
@@ -684,9 +685,9 @@ set(GAMEENGINE_SRC
# Source/Common/System/LocalFile.cpp
# Source/Common/System/LocalFileSystem.cpp
#Source/Common/System/MemoryInit.cpp
-# Source/Common/System/ObjectStatusTypes.cpp
+# Source/Common/System/ObjectStatusTypes.cpp
Source/Common/System/QuotedPrintable.cpp
-# Source/Common/System/Radar.cpp
+# Source/Common/System/Radar.cpp
# Source/Common/System/RAMFile.cpp
Source/Common/System/registry.cpp
Source/Common/System/SaveGame/GameState.cpp
@@ -830,7 +831,7 @@ set(GAMEENGINE_SRC
Source/GameClient/Input/Mouse.cpp
Source/GameClient/LanguageFilter.cpp
Source/GameClient/Line2D.cpp
-# Source/GameClient/MapUtil.cpp
+# Source/GameClient/MapUtil.cpp
Source/GameClient/MessageStream/CommandXlat.cpp
Source/GameClient/MessageStream/GUICommandTranslator.cpp
Source/GameClient/MessageStream/HintSpy.cpp
@@ -840,10 +841,10 @@ set(GAMEENGINE_SRC
Source/GameClient/MessageStream/PlaceEventTranslator.cpp
Source/GameClient/MessageStream/SelectionXlat.cpp
Source/GameClient/MessageStream/WindowXlat.cpp
-# Source/GameClient/ParabolicEase.cpp
+# Source/GameClient/ParabolicEase.cpp
Source/GameClient/RadiusDecal.cpp
Source/GameClient/SelectionInfo.cpp
-# Source/GameClient/Snow.cpp
+# Source/GameClient/Snow.cpp
Source/GameClient/Statistics.cpp
Source/GameClient/System/Anim2D.cpp
Source/GameClient/System/CampaignManager.cpp
@@ -852,13 +853,13 @@ set(GAMEENGINE_SRC
Source/GameClient/System/Image.cpp
Source/GameClient/System/ParticleSys.cpp
Source/GameClient/System/RayEffect.cpp
-# Source/GameClient/System/Smudge.cpp
-# Source/GameClient/Terrain/TerrainRoads.cpp
-# Source/GameClient/Terrain/TerrainVisual.cpp
+# Source/GameClient/System/Smudge.cpp
+# Source/GameClient/Terrain/TerrainRoads.cpp
+# Source/GameClient/Terrain/TerrainVisual.cpp
# Source/GameClient/VideoPlayer.cpp
# Source/GameClient/VideoStream.cpp
-# Source/GameClient/View.cpp
-# Source/GameClient/Water.cpp
+# Source/GameClient/View.cpp
+# Source/GameClient/Water.cpp
Source/GameLogic/AI/AI.cpp
Source/GameLogic/AI/AIDock.cpp
Source/GameLogic/AI/AIGroup.cpp
@@ -1053,6 +1054,7 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp
Source/GameLogic/Object/Update/EMPUpdate.cpp
Source/GameLogic/Object/Update/EnemyNearUpdate.cpp
+ Source/GameLogic/Object/Update/ProximityCaptureUpdate.cpp
Source/GameLogic/Object/Update/FireOCLAfterWeaponCooldownUpdate.cpp
Source/GameLogic/Object/Update/FireSpreadUpdate.cpp
Source/GameLogic/Object/Update/FirestormDynamicGeometryInfoUpdate.cpp
@@ -1146,53 +1148,53 @@ set(GAMEENGINE_SRC
Source/GameLogic/System/GameLogic.cpp
Source/GameLogic/System/GameLogicDispatch.cpp
Source/GameLogic/System/RankInfo.cpp
-# Source/GameNetwork/Connection.cpp
-# Source/GameNetwork/ConnectionManager.cpp
-# Source/GameNetwork/DisconnectManager.cpp
-# Source/GameNetwork/DownloadManager.cpp
-# Source/GameNetwork/FileTransfer.cpp
-# Source/GameNetwork/FirewallHelper.cpp
-# Source/GameNetwork/FrameData.cpp
-# Source/GameNetwork/FrameDataManager.cpp
-# Source/GameNetwork/FrameMetrics.cpp
-# Source/GameNetwork/GameInfo.cpp
-# Source/GameNetwork/GameMessageParser.cpp
-# #Source/GameNetwork/GameSpyChat.cpp
-# #Source/GameNetwork/GameSpyGameInfo.cpp
-# #Source/GameNetwork/GameSpyGP.cpp
-# Source/GameNetwork/GameSpy/Chat.cpp
-# Source/GameNetwork/GameSpy/GSConfig.cpp
-# Source/GameNetwork/GameSpy/LadderDefs.cpp
-# Source/GameNetwork/GameSpy/LobbyUtils.cpp
-# Source/GameNetwork/GameSpy/MainMenuUtils.cpp
-# Source/GameNetwork/GameSpy/PeerDefs.cpp
-# Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp
-# Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp
-# Source/GameNetwork/GameSpy/Thread/GameResultsThread.cpp
-# Source/GameNetwork/GameSpy/Thread/PeerThread.cpp
-# Source/GameNetwork/GameSpy/Thread/PersistentStorageThread.cpp
-# Source/GameNetwork/GameSpy/Thread/PingThread.cpp
-# Source/GameNetwork/GameSpy/Thread/ThreadUtils.cpp
-# Source/GameNetwork/GameSpyOverlay.cpp
+# Source/GameNetwork/Connection.cpp
+# Source/GameNetwork/ConnectionManager.cpp
+# Source/GameNetwork/DisconnectManager.cpp
+# Source/GameNetwork/DownloadManager.cpp
+# Source/GameNetwork/FileTransfer.cpp
+# Source/GameNetwork/FirewallHelper.cpp
+# Source/GameNetwork/FrameData.cpp
+# Source/GameNetwork/FrameDataManager.cpp
+# Source/GameNetwork/FrameMetrics.cpp
+# Source/GameNetwork/GameInfo.cpp
+# Source/GameNetwork/GameMessageParser.cpp
+# #Source/GameNetwork/GameSpyChat.cpp
+# #Source/GameNetwork/GameSpyGameInfo.cpp
+# #Source/GameNetwork/GameSpyGP.cpp
+# Source/GameNetwork/GameSpy/Chat.cpp
+# Source/GameNetwork/GameSpy/GSConfig.cpp
+# Source/GameNetwork/GameSpy/LadderDefs.cpp
+# Source/GameNetwork/GameSpy/LobbyUtils.cpp
+# Source/GameNetwork/GameSpy/MainMenuUtils.cpp
+# Source/GameNetwork/GameSpy/PeerDefs.cpp
+# Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp
+# Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp
+# Source/GameNetwork/GameSpy/Thread/GameResultsThread.cpp
+# Source/GameNetwork/GameSpy/Thread/PeerThread.cpp
+# Source/GameNetwork/GameSpy/Thread/PersistentStorageThread.cpp
+# Source/GameNetwork/GameSpy/Thread/PingThread.cpp
+# Source/GameNetwork/GameSpy/Thread/ThreadUtils.cpp
+# Source/GameNetwork/GameSpyOverlay.cpp
Source/GameNetwork/GUIUtil.cpp
-# Source/GameNetwork/IPEnumeration.cpp
-# Source/GameNetwork/LANAPI.cpp
-# Source/GameNetwork/LANAPICallbacks.cpp
-# Source/GameNetwork/LANAPIhandlers.cpp
-# Source/GameNetwork/LANGameInfo.cpp
-# Source/GameNetwork/NAT.cpp
-# Source/GameNetwork/NetCommandList.cpp
-# Source/GameNetwork/NetCommandMsg.cpp
-# Source/GameNetwork/NetCommandRef.cpp
-# Source/GameNetwork/NetCommandWrapperList.cpp
-# Source/GameNetwork/NetMessageStream.cpp
-# Source/GameNetwork/NetPacket.cpp
-# Source/GameNetwork/Network.cpp
-# Source/GameNetwork/NetworkUtil.cpp
-# Source/GameNetwork/Transport.cpp
-# Source/GameNetwork/udp.cpp
-# Source/GameNetwork/User.cpp
-# Source/GameNetwork/WOLBrowser/WebBrowser.cpp
+# Source/GameNetwork/IPEnumeration.cpp
+# Source/GameNetwork/LANAPI.cpp
+# Source/GameNetwork/LANAPICallbacks.cpp
+# Source/GameNetwork/LANAPIhandlers.cpp
+# Source/GameNetwork/LANGameInfo.cpp
+# Source/GameNetwork/NAT.cpp
+# Source/GameNetwork/NetCommandList.cpp
+# Source/GameNetwork/NetCommandMsg.cpp
+# Source/GameNetwork/NetCommandRef.cpp
+# Source/GameNetwork/NetCommandWrapperList.cpp
+# Source/GameNetwork/NetMessageStream.cpp
+# Source/GameNetwork/NetPacket.cpp
+# Source/GameNetwork/Network.cpp
+# Source/GameNetwork/NetworkUtil.cpp
+# Source/GameNetwork/Transport.cpp
+# Source/GameNetwork/udp.cpp
+# Source/GameNetwork/User.cpp
+# Source/GameNetwork/WOLBrowser/WebBrowser.cpp
Source/Precompiled/PreRTS.cpp
)
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProximityCaptureUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProximityCaptureUpdate.h
new file mode 100644
index 00000000000..00ebafd4152
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProximityCaptureUpdate.h
@@ -0,0 +1,178 @@
+/*
+** 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: ProximityCaptureUpdate.h /////////////////////////////////////////////////////////////////////////////
+// Author: Matthew D. Campbell, April 2002
+// Desc: Reacts when an enemy is within range
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "GameLogic/Module/UpdateModule.h"
+#include "Common/KindOf.h"
+
+// FORWARD REFERENCES /////////////////////////////////////////////////////////////////////////////
+class Object;
+// class AudioEventRTS;
+class FXList;
+
+//-------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+class ProximityCaptureUpdateModuleData : public UpdateModuleData
+{
+public:
+ UnsignedInt m_captureTickDelay; ///< Time delay between evaluation ticks
+ Real m_captureRate; ///< How much to capture per tick
+ Real m_uncapRate; ///< How much progress to remove
+ Real m_uncapRateNeutral; ///< How much progress to remove when neutral;
+ Real m_recoverRate; ///< How much progress to recover when no units are around;
+ Real m_captureRadius;
+ KindOfMaskType m_requiredKindOf; ///< Must be set on target
+ KindOfMaskType m_forbiddenKindOf; ///< Must be clear on target
+ Bool m_isCountAirborne; ///< Affect Airborne targets
+ Bool m_requiresAllKindOfs; ///< requires ALL requiredKindOfs or just one of them
+
+ Real m_unitValueContentionDelta; ///< unit values between players need to differ by this much to trigger capture
+ Real m_unitValueCountFactor; ///< value factor for the number of units (i.e. equal value for each unit)
+ //Real m_unitValueBuildCostFactor; ///< value factor for sellValue of the unit
+
+ Bool m_showProgressBar;
+
+ UnsignedShort m_capturePingInterval; ///< number of ticks between capture FX
+ Bool m_showCapturePingFlash;
+ Bool m_playDefectorPingSound;
+ // AudioEventRTS m_capturePingSound;
+
+ const FXList* m_startCaptureFX;
+ const FXList* m_startUncapFX;
+ const FXList* m_finishCaptureFX;
+ const FXList* m_capturePingFX;
+ const FXList* m_capturePingContestedFX;
+
+ Int m_skillPointsForCapture; ///< grant XP to the player on capture
+
+ ProximityCaptureUpdateModuleData()
+ {
+ m_captureTickDelay = LOGICFRAMES_PER_SECOND;
+ m_captureRate = 1.0 / 30.0f;
+ m_uncapRate = 1.0 / 30.0f;
+ m_uncapRateNeutral = -1.0f;
+ m_recoverRate = -1.0f;
+
+ m_unitValueContentionDelta = 0.01;
+ m_unitValueCountFactor = 1.0;
+ //m_unitValueBuildCostFactor = 0.0;
+
+ m_startCaptureFX = NULL;
+ m_startUncapFX = NULL;
+ m_finishCaptureFX = NULL;
+ m_capturePingFX = NULL;
+ m_capturePingContestedFX = NULL;
+ m_showCapturePingFlash = true;
+ m_playDefectorPingSound = true;
+ }
+
+ static void buildFieldParse(MultiIniFieldParse& p)
+ {
+ UpdateModuleData::buildFieldParse(p);
+ static const FieldParse dataFieldParse[] =
+ {
+ { "CaptureTickDelay", INI::parseDurationUnsignedInt, NULL, offsetof( ProximityCaptureUpdateModuleData, m_captureTickDelay) },
+ { "CaptureRadius", INI::parseReal, NULL, offsetof( ProximityCaptureUpdateModuleData, m_captureRadius) },
+ { "CaptureProgressPerTick", INI::parsePercentToReal, NULL, offsetof( ProximityCaptureUpdateModuleData, m_captureRate) },
+ { "CaptureProgressRemovePerTick", INI::parsePercentToReal, NULL, offsetof( ProximityCaptureUpdateModuleData, m_uncapRate) },
+ { "CaptureProgressRemovePerTickFromNeutral", INI::parsePercentToReal, NULL, offsetof( ProximityCaptureUpdateModuleData, m_uncapRateNeutral) },
+ { "CaptureProgressRecoverPerTick", INI::parsePercentToReal, NULL, offsetof( ProximityCaptureUpdateModuleData, m_recoverRate) },
+ { "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ProximityCaptureUpdateModuleData, m_requiredKindOf) },
+ { "RequiresAllKindOfs", INI::parseBool, NULL, offsetof(ProximityCaptureUpdateModuleData, m_requiresAllKindOfs) },
+ { "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ProximityCaptureUpdateModuleData, m_forbiddenKindOf) },
+ { "AllowAirborne", INI::parseBool, NULL, offsetof(ProximityCaptureUpdateModuleData, m_isCountAirborne) },
+ { "UnitValueContentionDelta", INI::parseReal, NULL, offsetof(ProximityCaptureUpdateModuleData, m_unitValueContentionDelta) },
+ { "UnitValueCountFactor", INI::parseReal, NULL, offsetof(ProximityCaptureUpdateModuleData, m_unitValueCountFactor) },
+ //{ "UnitValueBuildCostFactor", INI::parseReal, NULL, offsetof(ProximityCaptureUpdateModuleData, m_unitValueBuildCostFactor) },
+ { "ShowProgressBar", INI::parseBool, NULL, offsetof(ProximityCaptureUpdateModuleData, m_showProgressBar) },
+ { "CapturePingInterval", INI::parseUnsignedShort, NULL, offsetof(ProximityCaptureUpdateModuleData, m_capturePingInterval) },
+ { "ShowCaptureFlash", INI::parseBool, NULL, offsetof(ProximityCaptureUpdateModuleData, m_showCapturePingFlash) },
+ { "PlayDefectorPingSound", INI::parseBool, NULL, offsetof(ProximityCaptureUpdateModuleData, m_playDefectorPingSound) },
+ //{ "CapturePingSound", INI::parseAudioEventRTS, NULL, offsetof(ProximityCaptureUpdateModuleData, m_capturePingSound) },
+ { "StartCaptureFX", INI::parseFXList, NULL, offsetof(ProximityCaptureUpdateModuleData, m_startCaptureFX) },
+ { "StartRemoveFX", INI::parseFXList, NULL, offsetof(ProximityCaptureUpdateModuleData, m_startUncapFX) },
+ { "FinishCaptureFX", INI::parseFXList, NULL, offsetof(ProximityCaptureUpdateModuleData, m_finishCaptureFX) },
+ { "CapturePingFX", INI::parseFXList, NULL, offsetof(ProximityCaptureUpdateModuleData, m_capturePingFX) },
+ { "CapturePingContestedFX", INI::parseFXList, NULL, offsetof(ProximityCaptureUpdateModuleData, m_capturePingContestedFX) },
+ { "SkillPointsForCapture", INI::parseInt, NULL, offsetof(ProximityCaptureUpdateModuleData, m_skillPointsForCapture) },
+ { 0, 0, 0, 0 }
+ };
+ p.add(dataFieldParse);
+ }
+};
+
+//-------------------------------------------------------------------------------------------------
+/** EnemyNear update */
+//-------------------------------------------------------------------------------------------------
+class ProximityCaptureUpdate : public UpdateModule
+{
+
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ProximityCaptureUpdate, "ProximityCaptureUpdate" )
+ MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ProximityCaptureUpdate, ProximityCaptureUpdateModuleData )
+
+public:
+
+ ProximityCaptureUpdate( Thing *thing, const ModuleData* moduleData );
+ // virtual destructor prototype provided by memory pool declaration
+
+ virtual UpdateSleepTime update();
+
+ Bool getProgressBarInfo(Real& progress, Int& type, RGBAColorInt& color, RGBAColorInt& colorBG);
+
+protected:
+
+ Int checkDominantPlayer( void );
+
+ void handleCaptureProgress(Int dominantPlayer);
+ void handleFlashEffects(Int dominantPlayer);
+
+ Real getValueForUnit(const Object* obj) const;
+
+private:
+
+ Int m_capturingPlayer;
+ Int m_dominantPlayerPrev;
+ Bool m_isContested;
+ Real m_captureProgress;
+ UnsignedInt m_lastTickFrame;
+
+ UnsignedShort m_capturePingDelay;
+
+ Real m_currentProgressRate; ///< current speed/direction for interpolation
+
+ void startCapture(Int playerId);
+ void finishCapture(Int playerId);
+ void startUncap(Int playerId);
+ void finishUncap(Int playerId);
+
+ Real getCaptureProgressInterp();
+
+};
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
index 6b38fad466e..e2be6907b99 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
@@ -123,6 +123,7 @@
#include "GameLogic/Module/DynamicGeometryInfoUpdate.h"
#include "GameLogic/Module/DynamicShroudClearingRangeUpdate.h"
#include "GameLogic/Module/EnemyNearUpdate.h"
+#include "GameLogic/Module/ProximityCaptureUpdate.h"
#include "GameLogic/Module/FireSpreadUpdate.h"
#include "GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h"
#include "GameLogic/Module/FireWeaponUpdate.h"
@@ -428,6 +429,7 @@ void ModuleFactory::init( void )
addModule( HordeUpdate );
addModule( ToppleUpdate );
addModule( EnemyNearUpdate );
+ addModule( ProximityCaptureUpdate );
addModule( LifetimeUpdate );
addModule( RadiusDecalUpdate );
addModule( RadiusDecalBehavior );
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/FXList.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/FXList.cpp
index 041d2b0570f..1f3982336c9 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/FXList.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/FXList.cpp
@@ -665,10 +665,18 @@ class ParticleSystemFXNugget : public FXNugget
{
//old way:
//newPos.z = TheTerrainLogic->getGrsoundHeight( newPos.x, newPos.y ) + 1;// The plus one prevents scissoring with terrain
-
//new way: now we allow bridges in the GroundHeight.
+ //newer way: include water
+
PathfindLayerEnum layer = TheTerrainLogic->getLayerForDestination(&newPos);
- newPos.z = TheTerrainLogic->getLayerHeight( newPos.x, newPos.y, layer );
+
+ if (Real waterZ = 0; TheGlobalData->m_heightAboveTerrainIncludesWater && layer == LAYER_GROUND &&
+ TheTerrainLogic->isUnderwater(newPos.x, newPos.y, &waterZ)) {
+ newPos.z = waterZ;
+ }
+ else {
+ newPos.z = TheTerrainLogic->getLayerHeight(newPos.x, newPos.y, layer);
+ }
}
else
newPos.z = primary->z + offset.z + m_height.getValue();
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
index 292f15624bf..033b2355b10 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
@@ -102,6 +102,7 @@
#include "GameLogic/Module/UpdateModule.h"
#include "GameLogic/Module/UpgradeModule.h"
#include "GameLogic/Module/EnergyShieldBehavior.h"
+#include "GameLogic/Module/ProximityCaptureUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
@@ -1548,7 +1549,6 @@ Bool Object::getProgressBarShowingInfo(bool selected, Real& progress, Int& type,
return FALSE;
// We put every case of Progress bars here.
- // Maybe we should require a KindOf for performance?
type = 0; // TODO
color = { 255, 255, 255, 255 }; // Default = white
@@ -1577,6 +1577,13 @@ Bool Object::getProgressBarShowingInfo(bool selected, Real& progress, Int& type,
return true;
}
}
+ else if ((*u)->getModuleNameKey() == NAMEKEY("ProximityCaptureUpdate")) {
+ ProximityCaptureUpdate* pcu = (ProximityCaptureUpdate*)(*u);
+ if (pcu->getProgressBarInfo(progress, type, color, colorBG)) {
+ return true;
+ }
+
+ }
}
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AutoDepositUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AutoDepositUpdate.cpp
index 5fe4519b75c..5ea93f121e8 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AutoDepositUpdate.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AutoDepositUpdate.cpp
@@ -133,7 +133,7 @@ void AutoDepositUpdate::awardInitialCaptureBonus( Player *player )
moneyString.format( TheGameText->fetch( "GUI:AddCash" ), getAutoDepositUpdateModuleData()->m_initialCaptureBonus );
Coord3D pos;
pos.set( getObject()->getPosition() );
- pos.z += 10.0f; //add a little z to make it show up above the unit.
+ pos.z += 10.0f + getAutoDepositUpdateModuleData()->m_textZOffset; //add a little z to make it show up above the unit.
Color color = player->getPlayerColor() | GameMakeColor( 0, 0, 0, 230 );
TheInGameUI->addFloatingText( moneyString, &pos, color );
}
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProximityCaptureUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProximityCaptureUpdate.cpp
new file mode 100644
index 00000000000..1f2b596ed73
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProximityCaptureUpdate.cpp
@@ -0,0 +1,517 @@
+/*
+** 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: ProximityCaptureUpdate.cpp ///////////////////////////////////////////////////////////////////////////
+// Author: Matthew D. Campbell, December 2002
+// Desc: Reacts when an enemy is within range
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
+
+#include "Common/PerfTimer.h"
+#include "Common/ThingTemplate.h"
+#include "Common/Xfer.h"
+#include "GameClient/Drawable.h"
+#include "GameClient/Eva.h"
+#include "Common/Radar.h"
+#include "Common/PlayerList.h"
+#include "Common/Player.h"
+#include "GameLogic/PartitionManager.h"
+#include "GameLogic/Module/ProximityCaptureUpdate.h"
+#include "GameLogic/Object.h"
+#include "GameLogic/GameLogic.h"
+#include "Common/GameUtility.h"
+#include "Common/GameAudio.h"
+#include "Common/AudioEventRTS.h"
+#include "GameClient/FXList.h"
+#include "Common/MiscAudio.h"
+//#include "GameLogic/AI.h"
+//#include "GameLogic/Module/AIUpdate.h"
+#include "GameLogic/Module/ContainModule.h"
+#include
+
+//-------------------------------------------------------------------------------------------------
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+ProximityCaptureUpdate::ProximityCaptureUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ),
+ m_capturingPlayer(-1),
+ m_captureProgress(1.0),
+ m_isContested(false)
+{
+ // We always start off as captured by the original owner (including neutral)
+ m_capturingPlayer = getObject()->getControllingPlayer()->getPlayerIndex();
+
+ // bias a random amount so everyone doesn't spike at once
+ UnsignedInt initialDelay = GameLogicRandomValue(0, getProximityCaptureUpdateModuleData()->m_captureTickDelay);
+ setWakeFrame(getObject(), UPDATE_SLEEP(initialDelay));
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+ProximityCaptureUpdate::~ProximityCaptureUpdate( void )
+{
+}
+
+//-----------------
+// Helper functions
+// ----------------
+Bool arePlayersAllied(Int player1, Int player2) {
+ if (player1 == player2 || player1 < 0 || player1 >= MAX_PLAYER_COUNT || player2 < 0 || player2 >= MAX_PLAYER_COUNT)
+ return FALSE;
+
+ return ThePlayerList->getNthPlayer(player1)->getRelationship(ThePlayerList->getNthPlayer(player2)->getDefaultTeam()) == ALLIES;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+struct PlayerResult {
+ Int playerId = -1;
+ Real totalValue = 0.0f;
+
+ bool operator<(const PlayerResult& other) const {
+ return totalValue > other.totalValue; // Descending sort
+ }
+};
+//-------------------------------------------------------------------------------------------------
+// Look around us for units
+//-------------------------------------------------------------------------------------------------
+Int ProximityCaptureUpdate::checkDominantPlayer( void )
+{
+ Object* me = getObject();
+ const ProximityCaptureUpdateModuleData* data = getProximityCaptureUpdateModuleData();
+
+ PartitionFilterSameMapStatus filterMapStatus(me);
+ PartitionFilterAlive filterAlive;
+ //PartitionFilterRejectByKindOf filterRejectKindof(data->m_forbiddenKindOf);
+ //Note: we don't filter for accepted KindOfs her because we want to differentiate between ALL and ANY match cases
+
+ PartitionFilter* filters[] = { &filterAlive, &filterMapStatus, NULL };
+
+ // scan objects in our region
+ ObjectIterator* iter = ThePartitionManager->iterateObjectsInRange(me->getPosition(),
+ data->m_captureRadius,
+ FROM_CENTER_2D,
+ filters);
+
+ MemoryPoolObjectHolder hold(iter);
+
+ // Get total value per player
+ //constexpr size_t SIZE = MAX_PLAYER_COUNT;
+ std::array playerTotals{};
+ playerTotals.fill(0.0f);
+
+ for (Object* currentObj = iter->first(); currentObj != NULL; currentObj = iter->next())
+ {
+ bool match = FALSE;
+ if (!data->m_requiresAllKindOfs)
+ match = currentObj->isAnyKindOf(data->m_requiredKindOf) && !currentObj->isAnyKindOf(data->m_forbiddenKindOf);
+ else
+ match = currentObj->isKindOfMulti(data->m_requiredKindOf, data->m_forbiddenKindOf);
+
+ match &= (data->m_isCountAirborne || !currentObj->isAirborneTarget());
+
+ match &= (currentObj != me);
+ match &= (!currentObj->isNeutralControlled());
+
+ if (!match)
+ continue;
+
+ PlayerIndex pId = currentObj->getControllingPlayer()->getPlayerIndex();
+ if (pId > PLAYER_INDEX_INVALID) {
+ playerTotals[pId] += getValueForUnit(currentObj);
+ }
+ }
+
+ // Find player with highest influence
+ PlayerResult bestResult;
+ PlayerResult secondBestResult;
+ //result.totalValue = -std::numeric_limits::infinity();
+
+ for (int i = 0; i < MAX_PLAYER_COUNT; i++) {
+
+ if (playerTotals[i] > bestResult.totalValue) {
+
+ if (bestResult.playerId != -1) {
+ secondBestResult.totalValue = bestResult.totalValue;
+ secondBestResult.playerId = bestResult.playerId;
+ }
+
+ bestResult.totalValue = playerTotals[i];
+ bestResult.playerId = i;
+ }
+ else if (playerTotals[i] > secondBestResult.totalValue) {
+ secondBestResult.totalValue = playerTotals[i];
+ secondBestResult.playerId = i;
+ }
+ }
+
+ if (bestResult.totalValue <= 0) {
+ // No units at all. No player dominant, but not contested.
+ return -1;
+ }
+
+ if (!arePlayersAllied(bestResult.playerId, secondBestResult.playerId)) {
+ // DEBUG_LOG((">>> ProximityCaptureUpdate:: Is Contested? - best = %f, second = %f", bestResult.totalValue, secondBestResult.totalValue));
+ m_isContested = bestResult.totalValue <= (secondBestResult.totalValue + data->m_unitValueContentionDelta);
+ }
+
+ if (!m_isContested) {
+ // Is the current capturing player an ally?
+ if (arePlayersAllied(bestResult.playerId, m_capturingPlayer)) {
+ if (playerTotals[m_capturingPlayer] > 0) { // Ally still has units in radius
+ return m_capturingPlayer;
+ }
+ }
+ return bestResult.playerId;
+ }
+ else {
+ return -1;
+ }
+
+}
+
+//-------------------------------------------------------------------------------------------------
+
+Real ProximityCaptureUpdate::getValueForUnit(const Object* obj) const
+{
+ const ProximityCaptureUpdateModuleData* data = getProximityCaptureUpdateModuleData();
+ Real value = data->m_unitValueCountFactor;
+
+ //if (data->m_unitValueBuildCostFactor > 0.0f) {
+ // value += obj->getTemplate()->calcCostToBuild();
+ //}
+
+ return value;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+void ProximityCaptureUpdate::handleCaptureProgress(Int dominantPlayer)
+{
+
+ Real captureProgressPrev = m_captureProgress;
+
+ if (m_isContested) {
+ // No update while contested
+ return;
+ }
+
+ const ProximityCaptureUpdateModuleData* data = getProximityCaptureUpdateModuleData();
+ Object* me = getObject();
+ PlayerIndex owningPlayer = me->getControllingPlayer()->getPlayerIndex();
+
+ // If players are allied, we treat it as no dominant player
+ if (arePlayersAllied(dominantPlayer, m_capturingPlayer)) {
+ dominantPlayer = -1;
+ }
+
+ DEBUG_ASSERTCRASH(m_capturingPlayer != -1, ("The capturing player should always have a valid index"));
+
+ // Currently fully captured
+ if (m_captureProgress >= 1.0f) {
+ // No capture process
+ if (dominantPlayer == -1 || dominantPlayer == owningPlayer)
+ return;
+
+ if (dominantPlayer != m_capturingPlayer) {
+ startUncap(dominantPlayer);
+ }
+ }
+
+ if (dominantPlayer == m_capturingPlayer)
+ { // Capture is in progress
+ m_captureProgress += data->m_captureRate;
+ }
+ else if (dominantPlayer == -1 && m_capturingPlayer == owningPlayer)
+ { // Recover capture status
+ if (data->m_recoverRate >= 0.0f)
+ m_captureProgress += data->m_recoverRate;
+ else
+ m_captureProgress += data->m_captureRate;
+ }
+ else
+ { // Uncap is in progress
+ if (me->isNeutralControlled() && data->m_uncapRateNeutral > 0.0f) {
+ m_captureProgress -= data->m_uncapRateNeutral;
+ }
+ else {
+ m_captureProgress -= data->m_uncapRate;
+ }
+ }
+
+ if (m_captureProgress <= 0) {
+ //DEBUG_ASSERTCRASH(dominantPlayer != -1, ("Can't uncap without a dominant player."));
+ m_captureProgress = 0;
+
+ // Finished uncapping, reset capturing player
+ if (dominantPlayer != -1) {
+ finishUncap(dominantPlayer);
+ // Change ownership to neutral
+ m_capturingPlayer = dominantPlayer;
+ startCapture(m_capturingPlayer);
+ }
+ else
+ { // abort capture, back to the owner (neutral)
+ m_capturingPlayer = owningPlayer;
+ }
+ }
+ else if (m_captureProgress >= 1)
+ {
+ m_captureProgress = 1.0;
+ if (m_capturingPlayer == owningPlayer) {
+ // finished recovering, nothing to do here really.
+ }
+ else {
+ // finished capturing, change ownership
+ finishCapture(m_capturingPlayer);
+ }
+ }
+
+ m_currentProgressRate = m_captureProgress - captureProgressPrev;
+}
+// ------------------------------------------------------------------------------------------------
+// A player starts capturing (i.e. filling up his progress)
+void ProximityCaptureUpdate::startCapture(Int playerId)
+{
+ DEBUG_ASSERTLOG(getObject()->isNeutralControlled(), ("ProximityCaptureUpdate::startCapture: Warning, current owner should be neutral!"));
+
+ FXList::doFXObj(getProximityCaptureUpdateModuleData()->m_startCaptureFX, getObject());
+
+ m_capturePingDelay = 0;
+}
+// ------------------------------------------------------------------------------------------------
+// A player has finished capturing (i.e. got ownership)
+void ProximityCaptureUpdate::finishCapture(Int playerId)
+{
+ //TODO: Booby trap support maybe later
+ // if (getObject()->checkAndDetonateBoobyTrap(getObject())) // We need to store at least one unit of the dominant player ?!
+
+ Object* me = getObject();
+ const ProximityCaptureUpdateModuleData* data = getProximityCaptureUpdateModuleData();
+
+ // Just in case we are capturing a building which is already garrisoned by other
+ ContainModuleInterface* contain = me->getContain();
+ if (contain && contain->isGarrisonable())
+ {
+ contain->removeAllContained(TRUE);
+ }
+
+ DEBUG_ASSERTLOG(me->isNeutralControlled(), ("ProximityCaptureUpdate::finishCapture: Warning, current owner should be neutral!"));
+
+ FXList::doFXObj(data->m_finishCaptureFX, getObject());
+
+ Player* newOwner = ThePlayerList->getNthPlayer(playerId);
+ if (newOwner) {
+ me->defect(newOwner->getDefaultTeam(), 1); // one frame of flash!
+
+ newOwner->getAcademyStats()->recordBuildingCapture();
+
+ if (data->m_skillPointsForCapture > 0)
+ {
+ newOwner->addSkillPoints(data->m_skillPointsForCapture);
+ }
+
+ if (newOwner == rts::getObservedOrLocalPlayer())
+ TheRadar->tryEvent(RADAR_EVENT_INFORMATION, me->getPosition());
+ }
+}
+// ------------------------------------------------------------------------------------------------
+// A player starts removing the progress of another
+void ProximityCaptureUpdate::startUncap(Int playerId) {
+ Object* me = getObject();
+ //Warn the victim so he might have a chance to react!
+ if (me && me->isLocallyViewed())
+ {
+ TheEva->setShouldPlay(EVA_BuildingBeingStolen);
+ }
+ TheRadar->tryInfiltrationEvent(me);
+
+ FXList::doFXObj(getProximityCaptureUpdateModuleData()->m_startUncapFX, getObject());
+
+ m_capturePingDelay = 0;
+}
+// ------------------------------------------------------------------------------------------------
+// A player has finished neutralizing
+void ProximityCaptureUpdate::finishUncap(Int playerId) {
+ Object* me = getObject();
+
+ //Play the "building stolen" EVA event if the local player is the victim!
+ if (me && me->isLocallyViewed())
+ {
+ TheEva->setShouldPlay(EVA_BuildingStolen);
+ }
+
+ me->defect(ThePlayerList->getNeutralPlayer()->getDefaultTeam(), 1); // one frame of flash!
+}
+// ------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+UpdateSleepTime ProximityCaptureUpdate::update()
+{
+ const ProximityCaptureUpdateModuleData* data = getProximityCaptureUpdateModuleData();
+
+ Int dominantPlayer = checkDominantPlayer();
+
+ // DEBUG_LOG((">>> ProximityCaptureUpdate::update - dominantPlayer = %d", dominantPlayer));
+
+ handleCaptureProgress(dominantPlayer);
+
+ handleFlashEffects(dominantPlayer);
+
+ m_lastTickFrame = TheGameLogic->getFrame();
+
+ //m_dominantPlayerPrev = dominantPlayer;
+
+ return UPDATE_SLEEP(data->m_captureTickDelay);
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+Bool ProximityCaptureUpdate::getProgressBarInfo(Real& progress, Int& type, RGBAColorInt& color, RGBAColorInt& colorBG)
+{
+ const ProximityCaptureUpdateModuleData* data = getProximityCaptureUpdateModuleData();
+ if (!data->m_showProgressBar)
+ return false;
+
+ if (m_captureProgress >= 1.0)
+ return false;
+
+ progress = getCaptureProgressInterp();
+
+ if (m_isContested)
+ colorBG = { 255, 255, 0, 255 };
+ else
+ colorBG = { 0, 0, 0, 255 };
+
+ RGBColor col;
+ col.setFromInt(ThePlayerList->getNthPlayer(m_capturingPlayer)->getPlayerColor());
+
+ color = { (byte)(col.red * 255.0),(byte)(col.green * 255.0), (byte)(col.blue * 255.0), 255 };
+
+
+ return true;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+void ProximityCaptureUpdate::handleFlashEffects(Int dominantPlayer)
+{
+ if (m_capturePingDelay-- > 0)
+ return;
+
+ Object* me = getObject();
+ Drawable* draw = me->getDrawable();
+
+ if (m_isContested) {
+ FXList::doFXObj(getProximityCaptureUpdateModuleData()->m_capturePingContestedFX, getObject());
+ } else if (m_captureProgress < 1.0 && dominantPlayer != -1 && dominantPlayer != me->getControllingPlayer()->getPlayerIndex() && dominantPlayer != ThePlayerList->getNeutralPlayer()->getPlayerIndex()) {
+ if (draw) {
+ RGBColor myHouseColor;
+ myHouseColor.setFromInt(ThePlayerList->getNthPlayer(dominantPlayer)->getPlayerColor());
+
+ Real saturation = TheGlobalData->m_selectionFlashSaturationFactor;
+ draw->saturateRGB(myHouseColor, saturation);
+ draw->flashAsSelected(&myHouseColor); //In MY house color, not his!
+
+ }
+ if (getProximityCaptureUpdateModuleData()->m_playDefectorPingSound) {
+ AudioEventRTS defectorTimerSound = TheAudio->getMiscAudio()->m_defectorTimerTickSound;
+ defectorTimerSound.setObjectID(me->getID());
+ TheAudio->addAudioEvent(&defectorTimerSound);
+ }
+
+ FXList::doFXObj(getProximityCaptureUpdateModuleData()->m_capturePingFX, getObject());
+
+ }
+
+ m_capturePingDelay = getProximityCaptureUpdateModuleData()->m_capturePingInterval;
+}
+
+// ------------------------------------------------------------------------------------------------
+Real ProximityCaptureUpdate::getCaptureProgressInterp()
+{
+ if (m_captureProgress > 1.0)
+ return 1.0;
+ if (m_captureProgress < 0.0)
+ return 0.0;
+
+ if (m_isContested || getProximityCaptureUpdateModuleData()->m_captureTickDelay == 0)
+ return m_captureProgress;
+
+ UnsignedInt frameDiff = TheGameLogic->getFrame() - m_lastTickFrame;
+ Real progDiff = INT_TO_REAL(frameDiff) / INT_TO_REAL(getProximityCaptureUpdateModuleData()->m_captureTickDelay);
+
+ Real frameShift = (0.5 / INT_TO_REAL(getProximityCaptureUpdateModuleData()->m_captureTickDelay)) * m_currentProgressRate; // half a tick offset
+
+ return MAX(0.0, MIN(m_captureProgress + m_currentProgressRate * progDiff - frameShift, 1.0));
+}
+
+
+// ------------------------------------------------------------------------------------------------
+/** CRC */
+// ------------------------------------------------------------------------------------------------
+void ProximityCaptureUpdate::crc( Xfer *xfer )
+{
+
+ // extend base class
+ UpdateModule::crc( xfer );
+
+}
+
+// ------------------------------------------------------------------------------------------------
+/** Xfer method
+ * Version Info:
+ * 1: Initial version */
+// ------------------------------------------------------------------------------------------------
+void ProximityCaptureUpdate::xfer( Xfer *xfer )
+{
+
+ // version
+ XferVersion currentVersion = 1;
+ XferVersion version = currentVersion;
+ xfer->xferVersion( &version, currentVersion );
+
+ // extend base class
+ UpdateModule::xfer( xfer );
+
+ xfer->xferInt(&m_capturingPlayer);
+ xfer->xferBool(&m_isContested);
+ xfer->xferReal(&m_captureProgress);
+ xfer->xferReal(&m_currentProgressRate);
+ xfer->xferUnsignedInt(&m_lastTickFrame);
+ xfer->xferUnsignedShort(&m_capturePingDelay);
+
+}
+
+// ------------------------------------------------------------------------------------------------
+/** Load post process */
+// ------------------------------------------------------------------------------------------------
+void ProximityCaptureUpdate::loadPostProcess( void )
+{
+
+ // extend base class
+ UpdateModule::loadPostProcess();
+
+}