diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
index 1ec6dcec40f..f640def4ebf 100644
--- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
+++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
@@ -198,6 +198,7 @@ static PoolSizeRec PoolSizes[] =
{ "BaikonurLaunchPower", 4, 4 },
{ "RadiusDecalUpdate", 16, 16 },
{ "RadiusDecalBehavior", 32, 32 },
+ { "SpecialPowerDesignatorUpdate", 32, 32 },
{ "BattlePlanUpdate", 32, 32 },
{ "LifetimeUpdate", 32, 32 },
{ "LocomotorSetUpgrade", 512, 128 },
diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt
index e6de4f095ae..8cd9d3132ca 100644
--- a/GeneralsMD/Code/GameEngine/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt
@@ -415,6 +415,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/RadarUpgrade.h
Include/GameLogic/Module/RadiusDecalUpdate.h
Include/GameLogic/Module/RadiusDecalBehavior.h
+ Include/GameLogic/Module/SpecialPowerDesignatorUpdate.h
Include/GameLogic/Module/RailedTransportAIUpdate.h
Include/GameLogic/Module/RailedTransportContain.h
Include/GameLogic/Module/RailedTransportDockUpdate.h
@@ -1090,6 +1091,7 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Update/RadarUpdate.cpp
Source/GameLogic/Object/Update/RadiusDecalUpdate.cpp
Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp
+ Source/GameLogic/Object/Update/SpecialPowerDesignatorUpdate.cpp
Source/GameLogic/Object/Update/ResetSpecialPowerTimerWhileAliveUpdate.cpp
Source/GameLogic/Object/Update/ScatterShotUpdate.cpp
Source/GameLogic/Object/Update/SlavedUpdate.cpp
@@ -1220,7 +1222,6 @@ endif()
add_library(z_gameengine STATIC)
target_sources(z_gameengine PRIVATE ${GAMEENGINE_SRC})
-
target_include_directories(z_gameengine PUBLIC
Include
)
diff --git a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h
index 74acacacfaa..9518def0d66 100644
--- a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h
+++ b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h
@@ -213,6 +213,8 @@ enum KindOfType CPP_11(: Int)
KINDOF_EXTRA15,
KINDOF_EXTRA16,
+ KINDOF_TARGET_DESIGNATOR,
+
KINDOF_COUNT, // total number of kindofs
KINDOF_FIRST = 0,
};
diff --git a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPower.h b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPower.h
index 53360161585..279dacd2f68 100644
--- a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPower.h
+++ b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPower.h
@@ -125,6 +125,7 @@ class SpecialPowerTemplate : public Overridable
Real getViewObjectRange( void ) const { return getFO()->m_viewObjectRange; }
Real getRadiusCursorRadius() const { return getFO()->m_radiusCursorRadius; }
Bool isShortcutPower() const { return getFO()->m_shortcutPower; }
+ Bool isNeedsTargetDesignator() const { return getFO()->m_needsTargetDesignator; }
AcademyClassificationType getAcademyClassificationType() const { return m_academyClassificationType; }
EvaMessage getEvaDetectedOwn( void ) const { return getFO()->m_eva_detected_own; }
EvaMessage getEvaDetectedAlly( void ) const { return getFO()->m_eva_detected_ally; }
@@ -156,6 +157,7 @@ class SpecialPowerTemplate : public Overridable
Bool m_publicTimer; ///< display a countdown timer for this special power for all to see
Bool m_sharedNSync; ///< If true, this is a special that is shared between all of a player's command centers
Bool m_shortcutPower; ///< Is this shortcut power capable of being fired by the side panel?
+ Bool m_needsTargetDesignator; ///< Is this special power only allowed to hit designated areas
SpecialPowerType m_type_behavior; //< behave like a default special power, used by new ones only
EvaMessage m_eva_detected_own; //< eva event when constructed by self
EvaMessage m_eva_detected_ally; //< eva event when constructed by ally
diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h
index e0aaff613d0..c6d1f6348ba 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h
@@ -618,6 +618,8 @@ friend class Drawable; // for selection/deselection transactions
virtual void DEBUG_addFloatingText(const AsciiString& text,const Coord3D * pos, Color color);
#endif
+ const SpecialPowerTemplate* getTargetDesignatorPower();
+
protected:
// snapshot methods
virtual void crc( Xfer *xfer );
@@ -687,6 +689,9 @@ friend class Drawable; // for selection/deselection transactions
void handleBuildPlacements( void ); ///< handle updating of placement icons based on mouse pos
void handleRadiusCursor(); ///< handle updating of "radius cursors" that follow the mouse pos
+ //void showDesignatorDecals(const SpecialPowerTemplate* powerTemplate);
+ //void hideDesignatorDecals(void);
+
void incrementSelectCount( void ) { ++m_selectCount; } ///< Increase by one the running total of "selected" drawables
void decrementSelectCount( void ) { --m_selectCount; } ///< Decrease by one the running total of "selected" drawables
virtual View *createView( void ) = 0; ///< Factory for Views
@@ -969,6 +974,11 @@ friend class Drawable; // for selection/deselection transactions
DrawableID m_soloNexusSelectedDrawableID; ///< The drawable of the nexus, if only one angry mob is selected, otherwise, null
+ // UI Decals
+ //Bool m_showDesignatorDecals;
+ const CommandButton* m_designatorCommand;
+
+
// ----------------------------------------------------------------------------------------------
// STATIC Protected Data -------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h
index acaa1984030..dc89a95c8c4 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h
@@ -42,7 +42,7 @@ class RadiusDecalBehaviorModuleData : public UpdateModuleData
public:
UpgradeMuxData m_upgradeMuxData;
Bool m_initiallyActive;
-
+ Bool m_worksWhileContained;
RadiusDecalTemplate m_decalTemplate;
Real m_decalRadius;
@@ -83,6 +83,7 @@ class RadiusDecalBehavior : public UpdateModule, public UpgradeMux
protected:
+ void clearDecal(void);
virtual void upgradeImplementation()
{
@@ -115,11 +116,9 @@ class RadiusDecalBehavior : public UpdateModule, public UpgradeMux
virtual Bool isSubObjectsUpgrade() { return false; }
-private:
RadiusDecal m_radiusDecal;
- void clearDecal( void );
};
#endif // __RadiusDecalBehavior_H_
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialPowerDesignatorUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialPowerDesignatorUpdate.h
new file mode 100644
index 00000000000..59e8759e049
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialPowerDesignatorUpdate.h
@@ -0,0 +1,88 @@
+/*
+** 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: SpecialPowerDesignatorUpdate.h /////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#ifndef __SpecialPowerDesignatorUpdate_H_
+#define __SpecialPowerDesignatorUpdate_H_
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "GameLogic/Module/RadiusDecalBehavior.h"
+
+// FORWARD REFERENCES /////////////////////////////////////////////////////////
+class SpecialPowerTemplate;
+class Thing;
+class FXList;
+
+//-------------------------------------------------------------------------------------------------
+class SpecialPowerDesignatorUpdateModuleData : public RadiusDecalBehaviorModuleData
+{
+public:
+ SpecialPowerTemplate* m_specialPowerTemplate;
+ Real m_designatorRadius;
+ Bool m_alwaysShowDecal;
+ ObjectStatusTypes m_triggerStatusType;
+ UnsignedInt m_triggerStatusTime;
+ const FXList* m_triggerFX;
+
+ SpecialPowerDesignatorUpdateModuleData();
+
+ static void buildFieldParse(MultiIniFieldParse& p);
+};
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+class SpecialPowerDesignatorUpdate : public RadiusDecalBehavior
+{
+
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( SpecialPowerDesignatorUpdate, "SpecialPowerDesignatorUpdate" )
+ MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( SpecialPowerDesignatorUpdate, SpecialPowerDesignatorUpdateModuleData )
+
+public:
+
+ SpecialPowerDesignatorUpdate( Thing *thing, const ModuleData* moduleData );
+ // virtual destructor prototype provided by memory pool declaration
+
+ // UpdateModuleInterface
+ virtual UpdateSleepTime update();
+
+ Real getDesignatorRadius() { return getSpecialPowerDesignatorUpdateModuleData()->m_designatorRadius; }
+
+ Bool isValidDesignatorForSpecialPower(const SpecialPowerTemplate* templ);
+
+ void triggerSpecialPower();
+
+protected:
+
+private:
+
+ UnsignedInt m_statusClearFrame;
+
+};
+
+#endif // __SpecialPowerDesignatorUpdate_H_
+
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialPowerModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialPowerModule.h
index 739b170c080..2742fcb1946 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialPowerModule.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialPowerModule.h
@@ -170,6 +170,8 @@ class SpecialPowerModule : public BehaviorModule,
void resolveSpecialPower( void );
void aboutToDoSpecialPower( const Coord3D *location );
+ void handleTargetDesignator(const Coord3D* location);
+
UnsignedInt m_availableOnFrame; ///< on this frame, this special power is available
Int m_pausedCount; ///< Reference count of sources pausing me
UnsignedInt m_pausedOnFrame;
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp
index 1c07289db41..c5be55ec02c 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp
@@ -60,6 +60,7 @@
#include "GameLogic/Module/SupplyWarehouseDockUpdate.h"
#include "GameLogic/Module/SpecialPowerModule.h"
#include "GameLogic/Module/SpecialAbilityUpdate.h"
+#include "GameLogic/Module/SpecialPowerDesignatorUpdate.h"
#include "GameLogic/Weapon.h"
#include "GameLogic/ExperienceTracker.h"//LORENZEN
@@ -1567,6 +1568,65 @@ Bool ActionManager::canDoSpecialPowerAtLocation( const Object *obj, const Coord3
}
}
+ // Check target designator
+
+ if (spTemplate->isNeedsTargetDesignator()) {
+ bool isDesignatorInRange = false;
+ static NameKeyType key_SpecialPowerDesignatorUpdate = NAMEKEY("SpecialPowerDesignatorUpdate");
+
+ //Iterate over all object and find this module!
+
+ //PartitionFilterRelationship relationship( obj, PartitionFilterRelationship::ALLOW_ALLIES );
+ PartitionFilterSamePlayer filterPlayer(obj->getControllingPlayer());
+ PartitionFilterSameMapStatus filterMapStatus(obj);
+ PartitionFilterAlive filterAlive;
+ PartitionFilterAcceptByKindOf filterKindOf(MAKE_KINDOF_MASK(KINDOF_TARGET_DESIGNATOR), KINDOFMASK_NONE);
+ PartitionFilter* filters[] = { &filterPlayer, &filterAlive, &filterMapStatus, &filterKindOf, NULL };
+ Real MAX_SCAN_RANGE = 5000.0f; //TODO: GlobalData?
+ // scan objects in our region
+ ObjectIterator* iter = ThePartitionManager->iterateObjectsInRange(loc, MAX_SCAN_RANGE, FROM_CENTER_2D, filters);
+ Object* obj2;
+ MemoryPoolObjectHolder hold(iter);
+ for (obj2 = iter->first(); obj2; obj2 = iter->next()) {
+
+ SpecialPowerDesignatorUpdate* update = (SpecialPowerDesignatorUpdate*)obj2->findUpdateModule(key_SpecialPowerDesignatorUpdate);
+ if (update) {
+ if (update->isValidDesignatorForSpecialPower(spTemplate)) {
+
+ Real distSqr = ThePartitionManager->getDistanceSquared(obj2, loc, FROM_CENTER_2D);
+ Real radius = update->getDesignatorRadius();
+ if (distSqr <= (radius*radius)) {
+ isDesignatorInRange = true;
+ break;
+ }
+ }
+ }
+ }
+ if (!isDesignatorInRange)
+ return FALSE;
+ }
+
+ //static NameKeyType key_SpecialPowerDesignatorUpdate = NAMEKEY("SpecialPowerDesignatorUpdate");
+
+ //PartitionFilterSamePlayer filterPlayer(ThePlayerList->getLocalPlayer());
+ //PartitionFilterAlive filterAlive;
+ //PartitionFilterAcceptByKindOf filterKindOf(MAKE_KINDOF_MASK(KINDOF_TARGET_DESIGNATOR), KINDOFMASK_NONE);
+ //PartitionFilter* filters[] = { &filterPlayer, &filterAlive, &filterKindOf, NULL };
+ //// scan objects on entire map
+ //ObjectIterator* iter = ThePartitionManager->iterateAllObjects(filters);
+ //Object* obj;
+ //MemoryPoolObjectHolder hold(iter);
+ //for (obj = iter->first(); obj; obj = iter->next()) {
+
+ // SpecialPowerDesignatorUpdate* update = (SpecialPowerDesignatorUpdate*)obj->findUpdateModule(key_SpecialPowerDesignatorUpdate);
+ // if (update) {
+ // if (update->isValidDesignatorForSpecialPower(powerTemplate)) {
+ // update->setActive(true);
+ // }
+ // }
+ //}
+
+
// First check terrain type, if it is cared about. Don't return a true, since there are more checks.
switch(behaviorType)
{
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp
index 08345baf7fe..8e45ce5c1ff 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp
@@ -304,6 +304,7 @@ void SpecialPowerStore::parseSpecialPowerDefinition( INI *ini )
{ "EvaReadyOwn", INI::parseEvaNameIndexList, TheEvaMessageNames, offsetof(SpecialPowerTemplate, m_eva_ready_own) },
{ "EvaReadyAlly", INI::parseEvaNameIndexList, TheEvaMessageNames, offsetof(SpecialPowerTemplate, m_eva_ready_ally) },
{ "EvaReadyEnemy", INI::parseEvaNameIndexList, TheEvaMessageNames, offsetof(SpecialPowerTemplate, m_eva_ready_enemy) },
+ { "NeedsTargetDesignator", INI::parseBool, nullptr, offsetof(SpecialPowerTemplate, m_needsTargetDesignator) },
{ "Cost", INI::parseInt, NULL, offsetof(SpecialPowerTemplate, m_cost) },
{ nullptr, nullptr, nullptr, 0 }
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp
index 7961fcc2743..8e763d471bd 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp
@@ -202,6 +202,8 @@ const char* const KindOfMaskType::s_bitNameList[] =
"EXTRA15",
"EXTRA16",
+ "TARGET_DESIGNATOR",
+
nullptr
};
static_assert(ARRAY_SIZE(KindOfMaskType::s_bitNameList) == KindOfMaskType::NumBits + 1, "Incorrect array size");
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
index 2a23bca6b99..248a89aec8c 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
@@ -156,6 +156,7 @@
#include "GameLogic/Module/LifetimeUpdate.h"
#include "GameLogic/Module/RadiusDecalUpdate.h"
#include "GameLogic/Module/RadiusDecalBehavior.h"
+#include "GameLogic/Module/SpecialPowerDesignatorUpdate.h"
#include "GameLogic/Module/AutoDepositUpdate.h"
#include "GameLogic/Module/MissileAIUpdate.h"
#include "GameLogic/Module/NeutronMissileUpdate.h"
@@ -436,6 +437,7 @@ void ModuleFactory::init( void )
addModule( LifetimeUpdate );
addModule( RadiusDecalUpdate );
addModule( RadiusDecalBehavior );
+ addModule( SpecialPowerDesignatorUpdate );
addModule( EMPUpdate );
addModule( LeafletDropBehavior );
addModule( AutoDepositUpdate );
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp
index 9a267dd088a..2ab2c519e8a 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp
@@ -84,6 +84,7 @@
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/ProductionUpdate.h"
#include "GameLogic/Module/SpecialPowerModule.h"
+#include "GameLogic/Module/SpecialPowerDesignatorUpdate.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameLogic/Module/SupplyWarehouseDockUpdate.h"
#include "GameLogic/Module/MobMemberSlavedUpdate.h"//ML
@@ -1101,6 +1102,9 @@ InGameUI::InGameUI()
m_tooltipsDisabledUntil = 0;
+ //m_showDesignatorDecals = FALSE;
+ m_designatorCommand = NULL;
+
// init hint lists
for( i = 0; i < MAX_MOVE_HINTS; i++ )
{
@@ -3245,6 +3249,18 @@ void InGameUI::setGUICommand( const CommandButton *command )
// set the command
m_pendingGUICommand = command;
+ // Target designator checks
+ if (m_designatorCommand && m_designatorCommand != m_pendingGUICommand) {
+ m_designatorCommand = NULL;
+ }
+
+ if (command && BitIsSet(command->getOptions(), COMMAND_OPTION_NEED_TARGET)) {
+ const SpecialPowerTemplate* sp = command->getSpecialPowerTemplate();
+ if (sp != nullptr && sp->isNeedsTargetDesignator()) {
+ m_designatorCommand = command;
+ }
+ }
+
// set the mouse cursor for commands that need a targeting or to normal with no command
if( command && BitIsSet( command->getOptions(), COMMAND_OPTION_NEED_TARGET ) && !command->isContextCommand() )
{
@@ -6429,3 +6445,15 @@ void InGameUI::drawPlayerInfoList()
drawY += lineH;
}
}
+
+// -------------
+// -------------
+const SpecialPowerTemplate* InGameUI::getTargetDesignatorPower()
+{
+ if (m_designatorCommand != nullptr)
+ return m_designatorCommand->getSpecialPowerTemplate();
+
+ return nullptr;
+}
+// -------------
+// -------------
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp
index d4ae0fbb575..393982643d3 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp
@@ -48,7 +48,9 @@
#include "GameLogic/Module/UpdateModule.h"
#include "GameLogic/Module/SpecialPowerModule.h"
#include "GameLogic/Module/SpecialPowerUpdateModule.h"
+#include "GameLogic/Module/SpecialPowerDesignatorUpdate.h"
#include "GameLogic/ScriptEngine.h"
+#include "GameLogic/PartitionManager.h"
#include "GameClient/Eva.h"
#include "GameClient/InGameUI.h"
@@ -500,6 +502,8 @@ void SpecialPowerModule::triggerSpecialPower( const Coord3D *location )
aboutToDoSpecialPower( location ); // do BEFORE recharge
+ handleTargetDesignator(location);
+
createViewObject(location);
// we won't be able to use the power for X number of frames now
@@ -553,7 +557,6 @@ void SpecialPowerModule::markSpecialPowerTriggered( const Coord3D *location )
triggerSpecialPower( location );
}
-
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SpecialPowerModule::aboutToDoSpecialPower( const Coord3D *location )
@@ -948,6 +951,58 @@ UnsignedInt SpecialPowerModule::getReadyFrame( void ) const
}
}
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+void SpecialPowerModule::handleTargetDesignator(const Coord3D* loc)
+{
+ const SpecialPowerModuleData* data = getSpecialPowerModuleData();
+ if (!data->m_specialPowerTemplate->isNeedsTargetDesignator())
+ return;
+
+ // Get closest Target designator object
+ static NameKeyType key_SpecialPowerDesignatorUpdate = NAMEKEY("SpecialPowerDesignatorUpdate");
+
+ //Iterate over all object and find this module!
+ Object* obj = getObject();
+
+ //PartitionFilterRelationship relationship( obj, PartitionFilterRelationship::ALLOW_ALLIES );
+ PartitionFilterSamePlayer filterPlayer(obj->getControllingPlayer());
+ PartitionFilterSameMapStatus filterMapStatus(obj);
+ PartitionFilterAlive filterAlive;
+ PartitionFilterAcceptByKindOf filterKindOf(MAKE_KINDOF_MASK(KINDOF_TARGET_DESIGNATOR), KINDOFMASK_NONE);
+ PartitionFilter* filters[] = { &filterPlayer, &filterAlive, &filterMapStatus, &filterKindOf, NULL };
+ Real MAX_SCAN_RANGE = 5000.0f; //TODO: GlobalData?
+ // scan objects in our region
+ ObjectIterator* iter = ThePartitionManager->iterateObjectsInRange(loc, MAX_SCAN_RANGE, FROM_CENTER_2D, filters);
+ Object* obj2;
+ //Object* closestObj = nullptr;
+ SpecialPowerDesignatorUpdate* closestObjUpdate = nullptr;
+ MemoryPoolObjectHolder hold(iter);
+ Real minDistSqr = INFINITY;
+ for (obj2 = iter->first(); obj2; obj2 = iter->next()) {
+
+ SpecialPowerDesignatorUpdate* update = (SpecialPowerDesignatorUpdate*)obj2->findUpdateModule(key_SpecialPowerDesignatorUpdate);
+ if (update) {
+ if (update->isValidDesignatorForSpecialPower(data->m_specialPowerTemplate)) {
+
+ Real distSqr = ThePartitionManager->getDistanceSquared(obj2, loc, FROM_CENTER_2D);
+ Real radius = update->getDesignatorRadius();
+ if (distSqr <= (radius * radius) && minDistSqr) {
+ if (distSqr < minDistSqr) {
+ //closestObj = obj2;
+ closestObjUpdate = update;
+ minDistSqr = distSqr;
+ }
+ }
+ }
+ }
+ }
+ if (closestObjUpdate != nullptr)
+ closestObjUpdate->triggerSpecialPower();
+
+}
+
+
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp
index 7b3fbd13a56..b175a109f68 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp
@@ -42,6 +42,7 @@ RadiusDecalBehaviorModuleData::RadiusDecalBehaviorModuleData()
{
m_initiallyActive = false;
m_decalRadius = 0.0f;
+ m_worksWhileContained = false;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
@@ -53,6 +54,7 @@ RadiusDecalBehaviorModuleData::RadiusDecalBehaviorModuleData()
{ "StartsActive", INI::parseBool, NULL, offsetof(RadiusDecalBehaviorModuleData, m_initiallyActive) },
{ "RadiusDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalTemplate) },
{ "Radius", INI::parseReal, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalRadius) },
+ { "WorksWhileContained", INI::parseBool, NULL, offsetof(RadiusDecalBehaviorModuleData, m_worksWhileContained) },
{ 0, 0, 0, 0 }
};
@@ -120,7 +122,7 @@ void RadiusDecalBehavior::clearDecal()
//-------------------------------------------------------------------------------------------------
UpdateSleepTime RadiusDecalBehavior::update( void )
{
- if (getObject()->isDisabledByType(DISABLED_HELD)) {
+ if (getObject()->isDisabledByType(DISABLED_HELD) && !getRadiusDecalBehaviorModuleData()->m_worksWhileContained) {
if (!m_radiusDecal.isEmpty())
clearDecal();
return UPDATE_SLEEP_NONE; // We wait to be re-enabled
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialPowerDesignatorUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialPowerDesignatorUpdate.cpp
new file mode 100644
index 00000000000..8d2c8e19c0a
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialPowerDesignatorUpdate.cpp
@@ -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: SpecialPowerDesignatorUpdate.cpp ///////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
+
+#include "Common/RandomValue.h"
+#include "Common/Xfer.h"
+#include "GameLogic/GameLogic.h"
+#include "GameLogic/Module/SpecialPowerDesignatorUpdate.h"
+#include "GameLogic/Object.h"
+#include "GameClient/FXList.h"
+#include "GameClient/InGameUI.h"
+
+
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+
+SpecialPowerDesignatorUpdateModuleData::SpecialPowerDesignatorUpdateModuleData()
+{
+ m_specialPowerTemplate = nullptr;
+ m_designatorRadius = 0.0f;
+ m_alwaysShowDecal = false;
+ m_triggerStatusTime = 0;
+ m_triggerStatusType = OBJECT_STATUS_NONE;
+ m_triggerFX = nullptr;
+}
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+/*static*/ void SpecialPowerDesignatorUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
+{
+ RadiusDecalBehaviorModuleData::buildFieldParse(p);
+ static const FieldParse dataFieldParse[] =
+ {
+ { "SpecialPowerTemplate", INI::parseSpecialPowerTemplate, NULL, offsetof(SpecialPowerDesignatorUpdateModuleData, m_specialPowerTemplate) },
+ { "DesignatorRadius", INI::parseReal, NULL, offsetof(SpecialPowerDesignatorUpdateModuleData, m_designatorRadius) },
+ { "AlwaysShowDecal", INI::parseBool, NULL, offsetof(SpecialPowerDesignatorUpdateModuleData, m_alwaysShowDecal) },
+ { "TriggerStatusTime", INI::parseDurationUnsignedInt, NULL, offsetof(SpecialPowerDesignatorUpdateModuleData, m_triggerStatusTime) },
+ { "TriggerStatusType", ObjectStatusMaskType::parseSingleBitFromINI, NULL, offsetof(SpecialPowerDesignatorUpdateModuleData, m_triggerStatusType) },
+ { "DecalRadius", INI::parseReal, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalRadius) },
+ { "TriggerFX", INI::parseFXList, NULL, offsetof(SpecialPowerDesignatorUpdateModuleData, m_triggerFX) },
+ { 0, 0, 0, 0 }
+ };
+
+ p.add(dataFieldParse);
+}
+
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+SpecialPowerDesignatorUpdate::SpecialPowerDesignatorUpdate( Thing *thing, const ModuleData* moduleData ) : RadiusDecalBehavior( thing, moduleData )
+{
+ m_statusClearFrame = 0;
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+SpecialPowerDesignatorUpdate::~SpecialPowerDesignatorUpdate( void )
+{
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+void SpecialPowerDesignatorUpdate::triggerSpecialPower()
+{
+ const SpecialPowerDesignatorUpdateModuleData* data = getSpecialPowerDesignatorUpdateModuleData();
+ if (data->m_triggerStatusTime > 0 && data->m_triggerStatusType != OBJECT_STATUS_NONE) {
+ getObject()->setStatus(MAKE_OBJECT_STATUS_MASK(data->m_triggerStatusType));
+
+ m_statusClearFrame = TheGameLogic->getFrame() + data->m_triggerStatusTime;
+ setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
+ }
+
+ FXList::doFXObj(data->m_triggerFX, getObject());
+}
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+UpdateSleepTime SpecialPowerDesignatorUpdate::update( void )
+{
+ const SpecialPowerDesignatorUpdateModuleData* data = getSpecialPowerDesignatorUpdateModuleData();
+
+ // First handle status
+ if (m_statusClearFrame > 0 && data->m_triggerStatusType != OBJECT_STATUS_NONE) {
+ if (TheGameLogic->getFrame() == m_statusClearFrame) {
+ getObject()->clearStatus(MAKE_OBJECT_STATUS_MASK(data->m_triggerStatusType));
+ }
+ }
+
+ if (data->m_alwaysShowDecal)
+ return RadiusDecalBehavior::update();
+
+ const SpecialPowerTemplate *tmpl = TheInGameUI->getTargetDesignatorPower();
+ if (tmpl != nullptr && tmpl == data->m_specialPowerTemplate) {
+ RadiusDecalBehavior::update();
+ }
+ else if (!m_radiusDecal.isEmpty()) {
+ clearDecal();
+ }
+
+ return UPDATE_SLEEP_NONE;
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+Bool SpecialPowerDesignatorUpdate::isValidDesignatorForSpecialPower(const SpecialPowerTemplate* templ)
+{
+ return isUpgradeActive() && templ == getSpecialPowerDesignatorUpdateModuleData()->m_specialPowerTemplate &&
+ (getSpecialPowerDesignatorUpdateModuleData()->m_worksWhileContained || !getObject()->isDisabledByType(DISABLED_HELD));
+
+}
+
+// ------------------------------------------------------------------------------------------------
+/** CRC */
+// ------------------------------------------------------------------------------------------------
+void SpecialPowerDesignatorUpdate::crc( Xfer *xfer )
+{
+
+ // extend base class
+ RadiusDecalBehavior::crc( xfer );
+
+} // end crc
+
+// ------------------------------------------------------------------------------------------------
+/** Xfer method
+ * Version Info:
+ * 1: Initial version */
+// ------------------------------------------------------------------------------------------------
+void SpecialPowerDesignatorUpdate::xfer( Xfer *xfer )
+{
+
+ // version
+ XferVersion currentVersion = 1;
+ XferVersion version = currentVersion;
+ xfer->xferVersion( &version, currentVersion );
+
+ // extend base class
+ RadiusDecalBehavior::xfer( xfer );
+
+ xfer->xferUnsignedInt(&m_statusClearFrame);
+
+
+} // end xfer
+
+// ------------------------------------------------------------------------------------------------
+/** Load post process */
+// ------------------------------------------------------------------------------------------------
+void SpecialPowerDesignatorUpdate::loadPostProcess( void )
+{
+
+ // extend base class
+ UpdateModule::loadPostProcess();
+
+} // end loadPostProcess