From 2adc90fc2a83322c49280364072f02d5d6027050 Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 21 Sep 2025 10:06:00 +0200 Subject: [PATCH 1/5] minor improvements for DynamicGeometryClientUpdate --- .../GameClient/Module/DynamicGeometryClientUpdate.h | 2 ++ .../Drawable/Update/DynamicGeometryClientUpdate.cpp | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Module/DynamicGeometryClientUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Module/DynamicGeometryClientUpdate.h index 097430ff856..d096a40e331 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Module/DynamicGeometryClientUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Module/DynamicGeometryClientUpdate.h @@ -83,6 +83,8 @@ class DynamicGeometryClientUpdate : public ClientUpdateModule UnsignedInt m_startFrame; Real m_overrideScale; Real m_overrideAlpha; + + Real m_prevScale; }; #endif // __DynamicGeometryClientUpdate_H_ diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable/Update/DynamicGeometryClientUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable/Update/DynamicGeometryClientUpdate.cpp index 3a951c20d6a..383dd6a12ef 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable/Update/DynamicGeometryClientUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable/Update/DynamicGeometryClientUpdate.cpp @@ -142,6 +142,7 @@ DynamicGeometryClientUpdate::DynamicGeometryClientUpdate( Thing *thing, const Mo m_startFrame = TheGameLogic->getFrame(); m_overrideScale = 1.0; m_overrideAlpha = 1.0; + m_prevScale = 1.0; } //------------------------------------------------------------------------------------------------- @@ -194,8 +195,16 @@ void DynamicGeometryClientUpdate::clientUpdate( void ) Real alpha = (1.0 - progress) * alpha0 + progress * alpha1; Real scale = (1.0 - progress) * scale0 + progress * scale1; + Real newScale = m_overrideScale * scale; - draw->setInstanceScale(m_overrideScale * scale); + Real currentScale = draw->getInstanceScale(); + if (m_prevScale != 1.0) { + currentScale = currentScale / MAX(0.001, m_prevScale); + } + + draw->setInstanceScale(currentScale * newScale); + + m_prevScale = newScale; // This doesn't work for additive! draw->setDrawableOpacity(m_overrideAlpha * alpha); From cf00808d04fa9bcd37f366ffd5e14774a75b9875 Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 21 Sep 2025 19:35:24 +0200 Subject: [PATCH 2/5] IgnoreRotation param in W3DModelDraw --- .../GameClient/Module/W3DModelDraw.h | 5 +++++ .../GameClient/Drawable/Draw/W3DModelDraw.cpp | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h index e284e45bf63..232ab51e081 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h @@ -313,6 +313,11 @@ class W3DModelDrawModuleData : public ModuleData Bool m_ignoreAnimScaling; ///< ignore external scaling of animation speed, e.g. for PreAttack. + Bool m_ignoreRotation; ///< ignore all rotations for this draw module + + Bool m_showForOwnerOnly; ///< show this model only to the owning player + + Bool m_autoSelectObject; ///< show this model only to the owning player W3DModelDrawModuleData(); ~W3DModelDrawModuleData(); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index 76cb7165c82..de619ab9234 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -42,6 +42,8 @@ #include "Common/GlobalData.h" #include "Common/PerfTimer.h" #include "Common/RandomValue.h" +#include "Common/Player.h" +#include "Common/PlayerList.h" #include "Common/ThingTemplate.h" #include "Common/GameLOD.h" #include "Common/Xfer.h" @@ -82,6 +84,8 @@ static inline Bool isValidTimeToCalcLogicStuff() #if defined(DEBUG_CRC) && defined(RTS_DEBUG) #include +#include +#include class LogClass { public: @@ -1218,6 +1222,8 @@ void W3DModelDrawModuleData::buildFieldParse(MultiIniFieldParse& p) { "IgnoreConditionStates", ModelConditionFlags::parseFromINI, NULL, offsetof(W3DModelDrawModuleData, m_ignoreConditionStates) }, { "ReceivesDynamicLights", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_receivesDynamicLights) }, { "IgnoreAnimationSpeedScaling", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_ignoreAnimScaling) }, + { "IgnoreRotation", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_ignoreRotation) }, + { "OnlyVisibleToOwningPlayer", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_showForOwnerOnly) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -2082,6 +2088,21 @@ void W3DModelDraw::adjustTransformMtx(Matrix3D& mtx) const //------------------------------------------------------------------------------------------------- void W3DModelDraw::doDrawModule(const Matrix3D* transformMtx) { + if (getW3DModelDrawModuleData()->m_showForOwnerOnly && + getDrawable() && getDrawable()->getObject() && getDrawable()->getObject()->getControllingPlayer() && + getDrawable()->getObject()->getControllingPlayer() != ThePlayerList->getLocalPlayer()) { + setHidden(TRUE); + return; + } + + Matrix3D transformMtxLocal; // = Matrix3D::Identity; + if (getW3DModelDrawModuleData()->m_ignoreRotation) { + //transformMtxLocal = *transformMtx; + Vector3 trans; + transformMtx->Get_Translation(&trans); + transformMtxLocal.Set(trans); + transformMtx = &transformMtxLocal; + } // update whether or not we should be animating. setPauseAnimation( !getDrawable()->getShouldAnimate(getW3DModelDrawModuleData()->m_animationsRequirePower) ); From 0b25297f9c0f4c60ce5b19df29558a0bddf8fac3 Mon Sep 17 00:00:00 2001 From: andreasw Date: Mon, 22 Sep 2025 20:50:54 +0200 Subject: [PATCH 3/5] Add LifetimeUpdate progress bar Add SpecialPowerOCL Selection --- .../Include/GameLogic/Module/LifetimeUpdate.h | 6 +++ .../GameLogic/Module/OCLSpecialPower.h | 2 + .../Source/GameLogic/Object/Object.cpp | 12 ++++- .../Object/SpecialPower/OCLSpecialPower.cpp | 47 ++++++++++++++++--- .../Object/Update/LifetimeUpdate.cpp | 15 +++++- 5 files changed, 73 insertions(+), 9 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/LifetimeUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/LifetimeUpdate.h index af15b6b25c2..c25d851951a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/LifetimeUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/LifetimeUpdate.h @@ -41,6 +41,7 @@ class LifetimeUpdateModuleData : public UpdateModuleData public: UnsignedInt m_minFrames; UnsignedInt m_maxFrames; + Bool m_showProgressBar; LifetimeUpdateModuleData() { @@ -55,6 +56,7 @@ class LifetimeUpdateModuleData : public UpdateModuleData { { "MinLifetime", INI::parseDurationUnsignedInt, NULL, offsetof( LifetimeUpdateModuleData, m_minFrames ) }, { "MaxLifetime", INI::parseDurationUnsignedInt, NULL, offsetof( LifetimeUpdateModuleData, m_maxFrames ) }, + { "ShowProgressBar", INI::parseBool, NULL, offsetof( LifetimeUpdateModuleData, m_showProgressBar ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -80,11 +82,15 @@ class LifetimeUpdate : public UpdateModule virtual UpdateSleepTime update( void ); + Real getProgress(); + inline Bool showProgressBar(void) const { return getLifetimeUpdateModuleData()->m_showProgressBar; } + private: UnsignedInt calcSleepDelay(UnsignedInt minFrames, UnsignedInt maxFrames); UnsignedInt m_dieFrame; + UnsignedInt m_startDieFrame; ///< for progress bar; When the countdown starts }; #endif // __LIFETIMEUPDATE_H_ diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLSpecialPower.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLSpecialPower.h index 8ac9672ee5a..8676c96fb01 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLSpecialPower.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLSpecialPower.h @@ -71,6 +71,8 @@ class OCLSpecialPowerModuleData : public SpecialPowerModuleData OCLCreateLocType m_createLoc; Bool m_isOCLAdjustPositionToPassable; ///< Adjust target to nearest Passable cell + Bool m_selectObject; ///< Select first created object + //We need to know what the final product is going to be for script placement calculations //for construction sites like the sneak attack. AsciiString m_referenceThingName; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 9a573a4d4d2..7da79cec6bc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -77,6 +77,7 @@ #include "GameLogic/Module/DestroyModule.h" #include "GameLogic/Module/DieModule.h" #include "GameLogic/Module/DozerAIUpdate.h" +#include "GameLogic/Module/LifeTimeUpdate.h" #include "GameLogic/Module/ObjectDefectionHelper.h" #include "GameLogic/Module/ObjectRepulsorHelper.h" #include "GameLogic/Module/ObjectSMCHelper.h" @@ -1515,7 +1516,7 @@ Bool Object::getProgressBarShowingInfo(bool selected, Real& progress, Int& type, type = 0; // TODO color = { 255, 255, 255, 255 }; // Default = white - colorBG = { 255, 255, 255, 255 }; // Default = white + colorBG = { 0, 0, 0, 255 }; // Default = black // Energy Shields // TODO: This is kinda bad, there should be a better way to do this, if we have multiple shield sources. @@ -1532,6 +1533,15 @@ Bool Object::getProgressBarShowingInfo(bool selected, Real& progress, Int& type, return true; } } + else if ((*u)->getModuleNameKey() == NAMEKEY("LifetimeUpdate")) { + LifetimeUpdate* ltu = (LifetimeUpdate*)(*u); + if (ltu->showProgressBar()) { + // always show healthbar or when selected? + progress = 1.0 - ltu->getProgress(); + return true; + } + } + } return false; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp index 0c186b87d13..a3dcdabd6cc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp @@ -39,6 +39,8 @@ #include "GameLogic/PartitionManager.h" #include "GameLogic/TerrainLogic.h" #include "GameLogic/Module/OCLSpecialPower.h" +#include +#include /////////////////////////////////////////////////////////////////////////////////////////////////// // MODULE DATA //////////////////////////////////////////////////////////////////////////////////// @@ -98,6 +100,7 @@ static void parseOCLUpgradePair( INI* ini, void * /*instance*/, void *store, con { "CreateLocation", INI::parseIndexList, TheOCLCreateLocTypeNames, offsetof( OCLSpecialPowerModuleData, m_createLoc ) }, { "ReferenceObject", INI::parseAsciiString, NULL, offsetof( OCLSpecialPowerModuleData, m_referenceThingName ) }, { "OCLAdjustPositionToPassable", INI::parseBool, NULL, offsetof( OCLSpecialPowerModuleData, m_isOCLAdjustPositionToPassable ) }, + { "SelectCreatedObject", INI::parseBool, NULL, offsetof( OCLSpecialPowerModuleData, m_selectObject ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -185,37 +188,53 @@ void OCLSpecialPower::doSpecialPowerAtLocation( const Coord3D *loc, Real angle, // at what point will the "deliverer" come in Coord3D creationCoord; + + Object* createdObject = NULL; + switch (modData->m_createLoc) { case CREATE_AT_EDGE_NEAR_SOURCE: creationCoord = TheTerrainLogic->findClosestEdgePoint( getObject()->getPosition() ); - ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); + createdObject = ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); break; case CREATE_AT_EDGE_NEAR_TARGET: creationCoord = TheTerrainLogic->findClosestEdgePoint(&targetCoord); - ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); + createdObject = ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); break; case CREATE_AT_EDGE_FARTHEST_FROM_TARGET: creationCoord = TheTerrainLogic->findFarthestEdgePoint(&targetCoord); creationCoord.z += CREATE_ABOVE_LOCATION_HEIGHT; - ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); + createdObject = ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); break; case CREATE_AT_LOCATION: // this is the case where the special power stuff originates at the location of the mouse click creationCoord = targetCoord; - ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); + createdObject = ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); break; case USE_OWNER_OBJECT: creationCoord.set( &targetCoord ); - ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle, false ); + createdObject = ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle, false ); break; case CREATE_ABOVE_LOCATION: // this is the case where the special power stuff originates above the location of the mouse click creationCoord = targetCoord; creationCoord.z += CREATE_ABOVE_LOCATION_HEIGHT; - ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); + createdObject = ObjectCreationList::create( ocl, getObject(), &creationCoord, &targetCoord, angle ); break; } + + if (createdObject != NULL && modData->m_selectObject && createdObject->isLocallyControlled()) { + Drawable* draw = createdObject->getDrawable(); + if (draw) + { + //Create the selection message + GameMessage* teamMsg = TheMessageStream->appendMessage(GameMessage::MSG_CREATE_SELECTED_GROUP); + teamMsg->appendBooleanArgument(TRUE);// we are creating a new team so pass true + teamMsg->appendObjectIDArgument(createdObject->getID()); + TheInGameUI->selectDrawable(draw); + TheInGameUI->setDisplayedMaxWarning(FALSE); + } + } } // ------------------------------------------------------------------------------------------------ @@ -244,7 +263,21 @@ void OCLSpecialPower::doSpecialPower( UnsignedInt commandOptions ) SpecialPowerModule::doSpecialPowerAtLocation( &creationCoord, INVALID_ANGLE, commandOptions ); const ObjectCreationList* ocl = findOCL(); - ObjectCreationList::create( ocl, getObject(), &creationCoord, &creationCoord, false ); + Object* createdObject = ObjectCreationList::create( ocl, getObject(), &creationCoord, &creationCoord, false ); + + if (createdObject != NULL && getOCLSpecialPowerModuleData()->m_selectObject + && createdObject->isLocallyControlled()) { + Drawable* draw = createdObject->getDrawable(); + if (draw) + { + //Create the selection message + GameMessage* teamMsg = TheMessageStream->appendMessage(GameMessage::MSG_CREATE_SELECTED_GROUP); + teamMsg->appendBooleanArgument(TRUE);// we are creating a new team so pass true + teamMsg->appendObjectIDArgument(createdObject->getID()); + TheInGameUI->selectDrawable(draw); + TheInGameUI->setDisplayedMaxWarning(FALSE); + } + } } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp index 9e63deff777..2de5cdf89f7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LifetimeUpdate.cpp @@ -45,6 +45,7 @@ LifetimeUpdate::LifetimeUpdate( Thing *thing, const ModuleData* moduleData ) : U // Added By Sadullah Nader // Initializations needed m_dieFrame = 0; + m_startDieFrame = 0; // UnsignedInt delay; if( getObject()->isKindOf( KINDOF_HULK ) && TheGameLogic->getHulkMaxLifetimeOverride() != -1 ) @@ -88,10 +89,19 @@ UnsignedInt LifetimeUpdate::calcSleepDelay(UnsignedInt minFrames, UnsignedInt ma { UnsignedInt delay = GameLogicRandomValue( minFrames, maxFrames ); if (delay < 1) delay = 1; - m_dieFrame = TheGameLogic->getFrame() + delay; + m_startDieFrame = TheGameLogic->getFrame(); + m_dieFrame = m_startDieFrame + delay; return delay; } +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real LifetimeUpdate::getProgress(void) { + if (m_dieFrame > 0 && m_startDieFrame > 0) { + return INT_TO_REAL(TheGameLogic->getFrame() - m_startDieFrame) / INT_TO_REAL(m_dieFrame - m_startDieFrame); + } + return 0.0f; +} //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- UpdateSleepTime LifetimeUpdate::update( void ) @@ -131,6 +141,9 @@ void LifetimeUpdate::xfer( Xfer *xfer ) // die frame xfer->xferUnsignedInt( &m_dieFrame ); + // start die frame + xfer->xferUnsignedInt( &m_startDieFrame); + } // end xfer // ------------------------------------------------------------------------------------------------ From a26292a1d726f0bd484e29cd481dbbd4184d76f9 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 23 Sep 2025 20:29:08 +0200 Subject: [PATCH 4/5] ScatterShot improvements --- .../GameLogic/Module/ScatterShotUpdate.h | 14 +++- .../Behavior/DumbProjectileBehavior.cpp | 2 +- .../Object/Update/ScatterShotUpdate.cpp | 83 +++++++++++++++---- 3 files changed, 82 insertions(+), 17 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ScatterShotUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ScatterShotUpdate.h index 4a93bf29863..0325973406e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ScatterShotUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ScatterShotUpdate.h @@ -35,6 +35,7 @@ #include "GameLogic/Module/UpdateModule.h" #include "GameLogic/Object.h" #include "GameLogic/Weapon.h" +#include "GameLogic/Module/DieModule.h" class FXList; enum DeathType; @@ -51,6 +52,7 @@ class ScatterShotUpdateModuleData : public UpdateModuleData Bool m_preferSimilarTargets; Bool m_preferNearestTargets; Real m_noTargetsScatterRadius; + Real m_noTargetsScatterMinRadius; Bool m_attackGroundWhenNoTargets; Real m_triggerDistanceToTarget; @@ -61,6 +63,8 @@ class ScatterShotUpdateModuleData : public UpdateModuleData Bool m_triggerInstantly; Bool m_stayAliveAfterTrigger; + Bool m_avoidOriginalTarget; + Bool m_avoidPrevTarget; DeathType m_triggerDeathType; const FXList* m_scatterFX; @@ -76,7 +80,7 @@ class ScatterShotUpdateModuleData : public UpdateModuleData //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- -class ScatterShotUpdate : public UpdateModule +class ScatterShotUpdate : public UpdateModule, public DieModuleInterface { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ScatterShotUpdate, "ScatterShotUpdate") @@ -89,6 +93,13 @@ class ScatterShotUpdate : public UpdateModule virtual UpdateSleepTime update(); + static Int getInterfaceMask() { return MODULEINTERFACE_DIE; } + + // BehaviorModule + virtual DieModuleInterface* getDie() { return this; } + + void onDie(const DamageInfo* damageInfo); + protected: Weapon* m_weapon; @@ -98,6 +109,7 @@ class ScatterShotUpdate : public UpdateModule Coord3D m_goalPos; Object* m_goalObj; + ObjectID m_prevTargetID; //< target of previous attack. only used for special case when chaining multiple targets Real m_totalTargetDistance; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp index d7128ed6ecb..f2fdce5eb5a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp @@ -591,7 +591,7 @@ UpdateSleepTime DumbProjectileBehavior::update() return UPDATE_SLEEP_NONE; } - if( m_currentFlightPathStep >= m_flightPath.size() ) + if( m_currentFlightPathStep >= m_flightPath.size() || m_flightPath.size() == 1) { // No more steps to use. Would go out of bounds on vector, so have to do something. // We could allow physics to take over and make us fall, but the point of this whole task diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ScatterShotUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ScatterShotUpdate.cpp index 413818d01c7..f56b88d3d79 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ScatterShotUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ScatterShotUpdate.cpp @@ -88,6 +88,7 @@ ScatterShotUpdateModuleData::ScatterShotUpdateModuleData() { "PreferSimilarTargets", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_preferSimilarTargets) }, { "PreferNearestTargets", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_preferNearestTargets) }, { "NoTargetsScatterRadius", INI::parseReal, NULL, offsetof(ScatterShotUpdateModuleData, m_noTargetsScatterRadius) }, + { "NoTargetsScatterMinRadius", INI::parseReal, NULL, offsetof(ScatterShotUpdateModuleData, m_noTargetsScatterMinRadius) }, { "AttackGroundWhenNoTargets", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_attackGroundWhenNoTargets) }, { "TriggerDistanceToTarget", INI::parseReal, NULL, offsetof(ScatterShotUpdateModuleData, m_triggerDistanceToTarget) }, @@ -96,8 +97,12 @@ ScatterShotUpdateModuleData::ScatterShotUpdateModuleData() { "TriggerLifetime", INI::parseDurationUnsignedInt, NULL, offsetof(ScatterShotUpdateModuleData, m_triggerLifetime) }, { "TriggerOnImpact", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_triggerOnImpact) }, { "TriggerInstantly", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_triggerInstantly) }, + { "StayAliveAfterTrigger", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_stayAliveAfterTrigger) }, + { "AvoidOriginalTargetObject", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_avoidOriginalTarget) }, + { "AvoidPreviousTargetWhenChain", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_avoidPrevTarget) }, + { "TriggeredDeathType", INI::parseIndexList, TheDeathNames, offsetof(ScatterShotUpdateModuleData, m_triggerDeathType) }, { "ScatterFX", INI::parseFXList, NULL, offsetof(ScatterShotUpdateModuleData, m_scatterFX) }, @@ -177,6 +182,23 @@ UpdateSleepTime ScatterShotUpdate::update(void) // Copy weapon bonus flags from the launcher to this projectile WeaponBonusConditionFlags bonusFlags = launcher->getWeaponBonusCondition(); getObject()->setWeaponBonusConditionFlags(bonusFlags); + + // Special Chain Scattershot case: + if (data->m_avoidPrevTarget) { + static NameKeyType ssu_key = NAMEKEY("ScatterShotUpdate"); + + Module* mod = mod = launcher->findUpdateModule(ssu_key); + if (mod) { + ScatterShotUpdate* ssu = (ScatterShotUpdate*)mod; + if (ssu->m_goalObj != NULL) { + m_prevTargetID = ssu->m_goalObj->getID(); + DEBUG_LOG((">>> SSU CHAIN GET PREV TARGET = %d", m_prevTargetID)); + } + else { + DEBUG_LOG((">>> SSU CHAIN GET PREV TARGET IS NULL!")); + } + } + } } if (data->m_triggerDistancePercent > 0) { m_totalTargetDistance = getTargetDistance(); @@ -293,21 +315,26 @@ void ScatterShotUpdate::triggerScatterShot(void) // DEBUG_LOG((">>> SSU - triggerScatterShot 3\n")); - // We always try to fire at the Original weapon's target first bool mainTargetShot = FALSE; - if (shotsLeftPerTarget > 0 && shotsLeft > 0 && data->m_targetMinRadius <= 0) { - if ((m_goalObj) && isValidTarget(m_goalObj)) { - // DEBUG_LOG((">>> SSU - fireWeapon at target Obj\n")); - m_weapon->fireWeapon(getObject(), m_goalObj); - // Bool status = m_weapon->fireWeapon(getObject(), m_goalObj); - // DEBUG_LOG((">>> SSU - fireWeapon success = %s\n", status ? "true" : "false")); - //m_weapon->loadAmmoNow(getObject()); - //weaponSet.reloadAllAmmo(getObject(), true); - shotsLeft--; - mainTargetShot = TRUE; + if (!data->m_avoidOriginalTarget) { + // We always try to fire at the Original weapon's target first + if (shotsLeftPerTarget > 0 && shotsLeft > 0 && data->m_targetMinRadius <= 0) { + if ((m_goalObj) && isValidTarget(m_goalObj)) { + // DEBUG_LOG((">>> SSU - fireWeapon at target Obj\n")); + m_weapon->fireWeapon(getObject(), m_goalObj); + // Bool status = m_weapon->fireWeapon(getObject(), m_goalObj); + // DEBUG_LOG((">>> SSU - fireWeapon success = %s\n", status ? "true" : "false")); + //m_weapon->loadAmmoNow(getObject()); + //weaponSet.reloadAllAmmo(getObject(), true); + shotsLeft--; + mainTargetShot = TRUE; + } } } - + else { + mainTargetShot = TRUE; + } + // If we have shots left (and shots per target), look for targets in range if (shotsLeftPerTarget > 0 && shotsLeft > 0 && data->m_targetSearchRadius > 0) { IterOrderType orderType = ITER_FASTEST; @@ -348,6 +375,8 @@ void ScatterShotUpdate::triggerScatterShot(void) if (obj == m_goalObj && mainTargetShot) continue; + if (data->m_avoidPrevTarget && m_prevTargetID != INVALID_ID && m_prevTargetID == obj->getID()) continue; + if (data->m_targetMinRadius > 0 && sqrt(ThePartitionManager->getDistanceSquared(getObject(), obj->getPosition(), FROM_CENTER_3D)) <= data->m_targetMinRadius) continue; if (isValidTarget(obj)) { @@ -373,7 +402,7 @@ void ScatterShotUpdate::triggerScatterShot(void) targetPos.x = pos->x; targetPos.y = pos->y; - Real scatterRadius = GameLogicRandomValueReal(data->m_targetMinRadius, data->m_noTargetsScatterRadius); + Real scatterRadius = GameLogicRandomValueReal(MAX(data->m_noTargetsScatterMinRadius, data->m_targetMinRadius), data->m_noTargetsScatterRadius); Real scatterAngleRadian = GameLogicRandomValueReal(0, 2 * PI); Coord3D firingOffset; @@ -483,15 +512,26 @@ static Int getVictimAntiMask(const Object* victim) // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ +void ScatterShotUpdate::onDie(const DamageInfo* damageInfo) +{ + const ScatterShotUpdateModuleData* data = getScatterShotUpdateModuleData(); + if (data->m_triggerOnImpact && damageInfo->in.m_deathType == DEATH_DETONATED) { + triggerScatterShot(); + } +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ + Bool ScatterShotUpdate::isValidTarget(const Object* victim) const { const Object* source = getObject(); // basic sanity checks. if (!source || !victim || - source->isEffectivelyDead() || + //source->isEffectivelyDead() || // if we trigger on impact, source is dead victim->isEffectivelyDead() || - source->isDestroyed() || + //source->isDestroyed() || victim->isDestroyed() || victim == source) return FALSE; @@ -607,6 +647,19 @@ void ScatterShotUpdate::xfer(Xfer* xfer) m_goalObj = TheGameLogic->findObjectByID(targetID); } + // prev target obj + xfer->xferObjectID(&m_prevTargetID); + //if (xfer->getXferMode() == XFER_SAVE) + //{ + // ObjectID prevTargetID = m_prevGoalObj->getID(); + // xfer->xferObjectID(&prevTargetID); + //} + //else { + // ObjectID prevTargetID; + // xfer->xferObjectID(&prevTargetID); + // m_prevGoalObj = TheGameLogic->findObjectByID(prevTargetID); + //} + } // end xfer // ------------------------------------------------------------------------------------------------ From ae315b4fba4fc08d43a1d0946b4e07ec7c41a612 Mon Sep 17 00:00:00 2001 From: andreasw Date: Fri, 26 Sep 2025 11:37:13 +0200 Subject: [PATCH 5/5] fix ScatterOnImpact and add angle limitations for ScatterShotUpdate --- .../Include/GameLogic/Module/ScatterShotUpdate.h | 1 + .../GameLogic/Object/Update/ScatterShotUpdate.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ScatterShotUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ScatterShotUpdate.h index 0325973406e..1c7f4ff6b01 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ScatterShotUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ScatterShotUpdate.h @@ -53,6 +53,7 @@ class ScatterShotUpdateModuleData : public UpdateModuleData Bool m_preferNearestTargets; Real m_noTargetsScatterRadius; Real m_noTargetsScatterMinRadius; + Real m_noTargetsScatterMaxAngle; Bool m_attackGroundWhenNoTargets; Real m_triggerDistanceToTarget; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ScatterShotUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ScatterShotUpdate.cpp index f56b88d3d79..9be31488da2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ScatterShotUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ScatterShotUpdate.cpp @@ -89,6 +89,7 @@ ScatterShotUpdateModuleData::ScatterShotUpdateModuleData() { "PreferNearestTargets", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_preferNearestTargets) }, { "NoTargetsScatterRadius", INI::parseReal, NULL, offsetof(ScatterShotUpdateModuleData, m_noTargetsScatterRadius) }, { "NoTargetsScatterMinRadius", INI::parseReal, NULL, offsetof(ScatterShotUpdateModuleData, m_noTargetsScatterMinRadius) }, + { "NoTargetsScatterMaxAngle", INI::parseAngleReal, NULL, offsetof(ScatterShotUpdateModuleData, m_noTargetsScatterMaxAngle) }, { "AttackGroundWhenNoTargets", INI::parseBool, NULL, offsetof(ScatterShotUpdateModuleData, m_attackGroundWhenNoTargets) }, { "TriggerDistanceToTarget", INI::parseReal, NULL, offsetof(ScatterShotUpdateModuleData, m_triggerDistanceToTarget) }, @@ -403,7 +404,16 @@ void ScatterShotUpdate::triggerScatterShot(void) targetPos.y = pos->y; Real scatterRadius = GameLogicRandomValueReal(MAX(data->m_noTargetsScatterMinRadius, data->m_targetMinRadius), data->m_noTargetsScatterRadius); - Real scatterAngleRadian = GameLogicRandomValueReal(0, 2 * PI); + + Real scatterAngleRadian; + if (data->m_noTargetsScatterMaxAngle > 0) { + Real orientation = getObject()->getOrientation(); + scatterAngleRadian = normalizeAngle(GameLogicRandomValueReal(orientation - data->m_noTargetsScatterMaxAngle, orientation + data->m_noTargetsScatterMaxAngle)); + } + else + { + scatterAngleRadian = GameLogicRandomValueReal(0, 2 * PI); + } Coord3D firingOffset; firingOffset.zero();