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