diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt
index 971299c5677..bd7127b2ba4 100644
--- a/Core/GameEngine/CMakeLists.txt
+++ b/Core/GameEngine/CMakeLists.txt
@@ -322,6 +322,7 @@ set(GAMEENGINE_SRC
# Include/GameLogic/Module/FireSpreadUpdate.h
# Include/GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h
# Include/GameLogic/Module/FireWeaponCollide.h
+# Include/GameLogic/Module/AdvancedCollide.h
# Include/GameLogic/Module/FireWeaponPower.h
# Include/GameLogic/Module/FireWeaponUpdate.h
# Include/GameLogic/Module/FireWeaponWhenDamagedBehavior.h
@@ -907,6 +908,7 @@ set(GAMEENGINE_SRC
# Source/GameLogic/Object/Collide/CrateCollide/UnitCrateCollide.cpp
# Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp
# Source/GameLogic/Object/Collide/FireWeaponCollide.cpp
+# Source/GameLogic/Object/Collide/AdvancedCollide.cpp
# Source/GameLogic/Object/Collide/SquishCollide.cpp
# Source/GameLogic/Object/Contain/CaveContain.cpp
# Source/GameLogic/Object/Contain/GarrisonContain.cpp
diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
index 3adc7a00726..d91bedf41d1 100644
--- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
+++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
@@ -143,6 +143,7 @@ static PoolSizeRec PoolSizes[] =
{ "FireSpreadUpdate", 2048, 128 },
{ "FirestormDynamicGeometryInfoUpdate", 16, 16 },
{ "FireWeaponCollide", 2048, 32 },
+ { "AdvancedCollide", 512, 32 },
{ "FireWeaponUpdate", 32, 32 },
{ "FireWeaponAdvancedUpdate", 32, 32 },
{ "FlammableUpdate", 512, 256 },
diff --git a/Generals/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/Generals/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
index aa407c5bd06..7504e206edb 100644
--- a/Generals/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
+++ b/Generals/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
@@ -230,7 +230,6 @@
#include "GameLogic/Module/ImmortalBody.h"
#include "GameLogic/Module/StructureBody.h"
#include "GameLogic/Module/HiveStructureBody.h"
-#include "GameLogic/Module/ShieldBody.h"
// contain includes
// (none)
diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt
index 9c66280369a..fa5f444cabc 100644
--- a/GeneralsMD/Code/GameEngine/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt
@@ -326,6 +326,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/FireSpreadUpdate.h
Include/GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h
Include/GameLogic/Module/FireWeaponCollide.h
+ Include/GameLogic/Module/AdvancedCollide.h
Include/GameLogic/Module/FireWeaponPower.h
Include/GameLogic/Module/FireWeaponUpdate.h
Include/GameLogic/Module/FireWeaponAdvancedUpdate.h
@@ -929,6 +930,7 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Collide/CrateCollide/UnitCrateCollide.cpp
Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp
Source/GameLogic/Object/Collide/FireWeaponCollide.cpp
+ Source/GameLogic/Object/Collide/AdvancedCollide.cpp
Source/GameLogic/Object/Collide/SquishCollide.cpp
Source/GameLogic/Object/Contain/CaveContain.cpp
Source/GameLogic/Object/Contain/GarrisonContain.cpp
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AdvancedCollide.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AdvancedCollide.h
new file mode 100644
index 00000000000..fe0a2db9e93
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AdvancedCollide.h
@@ -0,0 +1,110 @@
+/*
+** 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: AdvancedCollide.h ///////////////////////////////////////////////////////////////////////////
+// Author: Andi W, Oct 25
+// Desc: Do something if we collide with objects or the ground
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#ifndef __AdvancedCollide_H_
+#define __AdvancedCollide_H_
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "GameLogic/Module/CollideModule.h"
+#include "GameLogic/Weapon.h"
+
+// FORWARD REFERENCES /////////////////////////////////////////////////////////////////////////////
+class Object;
+class FXList;
+class ObjectCreationList;
+
+//-------------------------------------------------------------------------------------------------
+class AdvancedCollideModuleData : public CollideModuleData
+{
+public:
+ const WeaponTemplate* m_collideWeaponTemplate; ///< Weapon To Fire
+ const FXList* m_fxlist; ///< FX to play
+ const ObjectCreationList* m_ocl; ///< object creaton list to make
+
+ KindOfMaskType m_kindof; ///< the kind(s) of units that can be collided with
+ KindOfMaskType m_kindofnot; ///< the kind(s) of units that CANNOT be collided with
+
+ ObjectStatusMaskType m_requiredStatus;
+ ObjectStatusMaskType m_forbiddenStatus;
+ ObjectStatusMaskType m_targetRequiredStatus;
+ ObjectStatusMaskType m_targetForbiddenStatus;
+
+ Bool m_fireOnce; ///< trigger effects only once
+ Bool m_collideWithGround; ///< trigger on collide with Ground
+ Bool m_collideWithObjects; ///< trigger on collide with Objects
+
+ Real m_triggerChance; ///< chance to trigger effects on a valid collission
+ Bool m_rollOnce; ///< Only Roll once to trigger effects; Or roll every frame.
+
+
+ AdvancedCollideModuleData()
+ {
+ m_collideWeaponTemplate = NULL;
+ m_fxlist = NULL;
+ m_ocl = NULL;
+ m_fireOnce = FALSE;
+ m_collideWithGround = FALSE;
+ m_collideWithObjects = TRUE;
+ m_triggerChance = 1.0;
+ m_rollOnce = FALSE;
+ }
+
+ static void buildFieldParse(MultiIniFieldParse& p);
+};
+
+//-------------------------------------------------------------------------------------------------
+class AdvancedCollide : public CollideModule
+{
+
+ MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( AdvancedCollide, AdvancedCollideModuleData );
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( AdvancedCollide, "AdvancedCollide" )
+
+public:
+
+ AdvancedCollide( Thing *thing, const ModuleData* moduleData );
+ // virtual destructor prototype provided by memory pool declaration
+
+protected:
+
+ virtual void onCollide( Object *other, const Coord3D *loc, const Coord3D *normal );
+
+ virtual Bool shouldTrigger(Object* other);
+
+private:
+ Weapon* m_collideWeapon;
+ Bool m_everFired;
+ Real m_roll;
+
+};
+
+
+#endif
+
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h
index 6f445d9c087..5be662c7ce7 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h
@@ -51,6 +51,7 @@ class StickyBombCrateCollideModuleData : public CrateCollideModuleData
Bool m_allowMultiCollide; ///< if we are a crate, allow spawning multiple sticky bombs, or just single use
Bool m_showInfiltrationEvent; ///< show an infiltration map event for the target player
AsciiString m_stickyBombObjectName; ///< the object to create
+ Real m_triggerChance; ///< chance to create the bomb when triggering the module
StickyBombCrateCollideModuleData()
{
@@ -58,6 +59,7 @@ class StickyBombCrateCollideModuleData : public CrateCollideModuleData
m_allowMultiCollide = FALSE;
m_showInfiltrationEvent = FALSE;
m_stickyBombObjectName = AsciiString::TheEmptyString;
+ m_triggerChance = 1.0;
}
static void buildFieldParse(MultiIniFieldParse& p)
@@ -70,6 +72,7 @@ class StickyBombCrateCollideModuleData : public CrateCollideModuleData
{ "AllowMultiCollide", INI::parseBool, NULL, offsetof(StickyBombCrateCollideModuleData, m_allowMultiCollide) },
{ "ShowInfiltrationEvent", INI::parseBool, NULL, offsetof(StickyBombCrateCollideModuleData, m_showInfiltrationEvent) },
{ "StickyBombObject", INI::parseAsciiString, NULL, offsetof(StickyBombCrateCollideModuleData, m_stickyBombObjectName) },
+ { "ChanceToTriggerPercent", INI::parsePercentToReal, NULL, offsetof(StickyBombCrateCollideModuleData, m_triggerChance) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
@@ -92,7 +95,7 @@ class StickyBombCrateCollide : public CrateCollide
protected:
/// This allows specific vetoes to certain types of crates and their data
- virtual Bool isValidToExecute( const Object *other ) const;
+ // virtual Bool isValidToExecute( const Object *other ) const;
/// This is the game logic execution function that all real CrateCollides will implement
virtual Bool executeCrateBehavior( Object *other );
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h
index 1dbb45e4f92..e60e32a87fe 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h
@@ -50,7 +50,10 @@ class StickyBombUpdateModuleData : public UpdateModuleData
AsciiString m_animBaseTemplate;
AsciiString m_animTimedTemplate;
- Bool m_showTimer;
+ Bool m_showTimer; ///< if this is disabled, only use animBase for timed bombs
+
+ Bool m_hideAnimBase; ///< will be set automatically if String is Null
+ Bool m_hideAnimTimed; ///< will be set automatically if String is Null
StickyBombUpdateModuleData()
{
@@ -62,6 +65,9 @@ class StickyBombUpdateModuleData : public UpdateModuleData
m_showTimer = TRUE;
}
+ static void parseAnimBaseName(INI* ini, void* instance, void* store, const void* userData);
+ static void parseAnimTimedName(INI* ini, void* instance, void* store, const void* userData);
+
static void buildFieldParse(MultiIniFieldParse& p)
{
UpdateModuleData::buildFieldParse(p);
@@ -71,8 +77,8 @@ class StickyBombUpdateModuleData : public UpdateModuleData
{ "OffsetZ", INI::parseReal, NULL, offsetof( StickyBombUpdateModuleData, m_offsetZ ) },
{ "GeometryBasedDamageWeapon",INI::parseWeaponTemplate, NULL, offsetof( StickyBombUpdateModuleData, m_geometryBasedDamageWeaponTemplate ) },
{ "GeometryBasedDamageFX", INI::parseFXList, NULL, offsetof( StickyBombUpdateModuleData, m_geometryBasedDamageFX ) },
- { "Animation2DBase", INI::parseAsciiString, NULL, offsetof( StickyBombUpdateModuleData, m_animBaseTemplate) },
- { "Animation2DTimed", INI::parseAsciiString, NULL, offsetof( StickyBombUpdateModuleData, m_animTimedTemplate) },
+ { "Animation2DBase", parseAnimBaseName, NULL, 0 },
+ { "Animation2DTimed", parseAnimTimedName, NULL, 0 },
{ "ShowTimer", INI::parseBool, NULL, offsetof( StickyBombUpdateModuleData, m_showTimer) },
{ 0, 0, 0, 0 }
};
@@ -109,6 +115,9 @@ class StickyBombUpdate : public UpdateModule
Anim2DTemplate* getAnimBaseTemplate();
Anim2DTemplate* getAnimTimedTemplate();
+ inline Bool showAnimBaseTemplate() { return !getStickyBombUpdateModuleData()->m_hideAnimBase; }
+ inline Bool showAnimTimedTemplate() { return !getStickyBombUpdateModuleData()->m_hideAnimTimed; }
+
private:
ObjectID m_targetID;
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
index 4d62ac55417..b7d073b3083 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
@@ -245,6 +245,7 @@
// collide includes
#include "GameLogic/Module/FireWeaponCollide.h"
+#include "GameLogic/Module/AdvancedCollide.h"
#include "GameLogic/Module/SquishCollide.h"
#include "GameLogic/Module/ConvertToCarBombCrateCollide.h"
@@ -543,6 +544,7 @@ void ModuleFactory::init( void )
// collide modules
addModule( FireWeaponCollide );
+ addModule( AdvancedCollide );
addModule( SquishCollide );
addModule( HealCrateCollide );
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
index b429e288515..cadbc6a1e82 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
@@ -3744,8 +3744,8 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion)
{
if( update->isTimedBomb() )
{
- //Timed bomb
- if( !getIconInfo()->m_icon[ ICON_BOMB_TIMED ] )
+ //Timed bomb - Base layer
+ if (!getIconInfo()->m_icon[ICON_BOMB_REMOTE] && update->showAnimBaseTemplate())
{
Anim2DTemplate* templ = update->getAnimBaseTemplate();
@@ -3754,7 +3754,11 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion)
getIconInfo()->m_icon[ICON_BOMB_REMOTE] = newInstance(Anim2D)(templ, TheAnim2DCollection);
- templ = update->getAnimTimedTemplate();
+ }
+ //Timed bomb - Timer
+ if( !getIconInfo()->m_icon[ ICON_BOMB_TIMED ] && update->showAnimTimedTemplate())
+ {
+ Anim2DTemplate* templ = update->getAnimTimedTemplate();
if (templ == NULL) // Default icon
templ = s_animationTemplates[ICON_BOMB_TIMED];
@@ -3785,7 +3789,9 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion)
getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->setMinFrame(numFrames - seconds - 1);
getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->reset();
}
- if( getIconInfo()->m_icon[ ICON_BOMB_TIMED ] )
+ Bool showTimedAnim = (getIconInfo()->m_icon[ICON_BOMB_TIMED]) != NULL;
+ Bool showBaseAnim = (getIconInfo()->m_icon[ICON_BOMB_REMOTE]) != NULL;
+ if( showTimedAnim || showBaseAnim)
{
//
// we are going to draw the healing icon relative to the size of the health bar region
@@ -3796,8 +3802,15 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion)
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int barHeight = healthBarRegion->hi.y - healthBarRegion->lo.y;
- Int frameWidth = getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->getCurrentFrameWidth();
- Int frameHeight = getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->getCurrentFrameHeight();
+ Int frameWidth, frameHeight;
+ if (showTimedAnim) {
+ frameWidth = getIconInfo()->m_icon[ICON_BOMB_TIMED]->getCurrentFrameWidth();
+ frameHeight = getIconInfo()->m_icon[ICON_BOMB_TIMED]->getCurrentFrameHeight();
+ }
+ else {
+ frameWidth = getIconInfo()->m_icon[ICON_BOMB_REMOTE]->getCurrentFrameWidth();
+ frameHeight = getIconInfo()->m_icon[ICON_BOMB_REMOTE]->getCurrentFrameHeight();
+ }
// adjust the width to be a % of the health bar region size
Int size = REAL_TO_INT( barWidth * 0.65f );
@@ -3809,18 +3822,21 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion)
screen.x = REAL_TO_INT( healthBarRegion->lo.x + (barWidth * 0.5f) - (frameWidth * 0.5f) );
screen.y = REAL_TO_INT( healthBarRegion->lo.y + barHeight * 0.5f ) + BOMB_ICON_EXTRA_OFFSET;
- getIconInfo()->m_icon[ ICON_BOMB_REMOTE ]->draw( screen.x, screen.y, frameWidth, frameHeight );
- getIconInfo()->m_keepTillFrame[ ICON_BOMB_REMOTE ] = now + 1;
- getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->draw( screen.x, screen.y, frameWidth, frameHeight );
- getIconInfo()->m_keepTillFrame[ ICON_BOMB_TIMED ] = now + 1;
+ if (showBaseAnim) {
+ getIconInfo()->m_icon[ICON_BOMB_REMOTE]->draw(screen.x, screen.y, frameWidth, frameHeight);
+ getIconInfo()->m_keepTillFrame[ICON_BOMB_REMOTE] = now + 1;
+ }
+ if (showTimedAnim) {
+ getIconInfo()->m_icon[ICON_BOMB_TIMED]->draw(screen.x, screen.y, frameWidth, frameHeight);
+ getIconInfo()->m_keepTillFrame[ICON_BOMB_TIMED] = now + 1;
+ }
}
}
}
else
{
//Remote charge
- //Timed bomb
- if( !getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] )
+ if( !getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] && update->showAnimBaseTemplate())
{
Anim2DTemplate* templ = update->getAnimBaseTemplate();
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/AdvancedCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/AdvancedCollide.cpp
new file mode 100644
index 00000000000..c89e11f82ad
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/AdvancedCollide.cpp
@@ -0,0 +1,233 @@
+/*
+** 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: AdvancedCollide.cpp ///////////////////////////////////////////////////////////////////////////
+// Author: Andi W, Oct 25
+// Desc: Do something if we collide with objects or the ground
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
+#define DEFINE_OBJECT_STATUS_NAMES
+#include "Common/Xfer.h"
+#include "GameLogic/Object.h"
+#include "GameClient/FXList.h"
+#include "GameLogic/ObjectCreationList.h"
+#include "GameLogic/Module/AdvancedCollide.h"
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+void AdvancedCollideModuleData::buildFieldParse(MultiIniFieldParse& p)
+{
+ CollideModuleData::buildFieldParse(p);
+
+ static const FieldParse dataFieldParse[] =
+ {
+ { "CollideWeapon", INI::parseWeaponTemplate, NULL, offsetof( AdvancedCollideModuleData, m_collideWeaponTemplate ) },
+ { "OCL", INI::parseObjectCreationList, NULL, offsetof(AdvancedCollideModuleData, m_ocl) },
+ { "FX", INI::parseFXList, NULL, offsetof(AdvancedCollideModuleData, m_fxlist) },
+
+ { "FireOnce", INI::parseBool, NULL, offsetof( AdvancedCollideModuleData, m_fireOnce ) },
+ { "CollideWithGround", INI::parseBool, NULL, offsetof( AdvancedCollideModuleData, m_collideWithGround) },
+ { "CollideWithObjects", INI::parseBool, NULL, offsetof( AdvancedCollideModuleData, m_collideWithObjects) },
+
+ { "RequiredStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( AdvancedCollideModuleData, m_requiredStatus ) },
+ { "ForbiddenStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( AdvancedCollideModuleData, m_forbiddenStatus ) },
+ { "TargetRequiredStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof(AdvancedCollideModuleData, m_targetRequiredStatus) },
+ { "TargetForbiddenStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof(AdvancedCollideModuleData, m_targetForbiddenStatus) },
+ { "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(AdvancedCollideModuleData, m_kindof) },
+ { "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(AdvancedCollideModuleData, m_kindofnot) },
+
+ { "ChanceToTriggerPercent", INI::parsePercentToReal, NULL, offsetof(AdvancedCollideModuleData, m_triggerChance) },
+ { "RollOnceForTrigger", INI::parseBool, NULL, offsetof(AdvancedCollideModuleData, m_rollOnce) },
+
+ { 0, 0, 0, 0 }
+ };
+ p.add(dataFieldParse);
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+AdvancedCollide::AdvancedCollide( Thing *thing, const ModuleData* moduleData ) :
+ CollideModule( thing, moduleData ),
+ m_collideWeapon(NULL)
+{
+ const AdvancedCollideModuleData* d = getAdvancedCollideModuleData();
+ if (d->m_collideWeaponTemplate != NULL) {
+ m_collideWeapon = TheWeaponStore->allocateNewWeapon(getAdvancedCollideModuleData()->m_collideWeaponTemplate, PRIMARY_WEAPON);
+ }
+ m_everFired = FALSE;
+ m_roll = -1;
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+AdvancedCollide::~AdvancedCollide( void )
+{
+ if (m_collideWeapon)
+ deleteInstance(m_collideWeapon);
+}
+
+//-------------------------------------------------------------------------------------------------
+/** The die callback. */
+//-------------------------------------------------------------------------------------------------
+void AdvancedCollide::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
+{
+ const AdvancedCollideModuleData* d = getAdvancedCollideModuleData();
+
+ if( other == NULL && !d->m_collideWithGround)
+ return;
+ if (other != NULL && !d->m_collideWithObjects)
+ return;
+
+ if(shouldTrigger(other))
+ {
+ Object* me = getObject();
+
+ if (m_collideWeapon) {
+ m_collideWeapon->loadAmmoNow(me);
+ m_collideWeapon->fireWeapon(me, other);
+ }
+
+ FXList::doFXObj(d->m_fxlist, me, other);
+
+ ObjectCreationList::create(d->m_ocl, me, other);
+
+ }
+}
+
+//-------------------------------------------------------------------------------------------------
+Bool AdvancedCollide::shouldTrigger(Object* other)
+{
+ const AdvancedCollideModuleData *d = getAdvancedCollideModuleData();
+
+ if (m_everFired && d->m_fireOnce)
+ return FALSE; // can only trigger once ever
+
+ // Check Source Object
+ ObjectStatusMaskType status = getObject()->getStatusBits();
+
+ //We need all required status or else we fail
+ if( !status.testForAll( d->m_requiredStatus ) )
+ return FALSE;
+
+ //If we have any forbidden statii, then fail
+ if( status.testForAny( d->m_forbiddenStatus ) )
+ return FALSE;
+
+ // -----------------------
+ // Check Target Object
+ if (other) {
+ status = other->getStatusBits();
+
+ //We need all required status or else we fail
+ if (!status.testForAll(d->m_targetRequiredStatus))
+ return FALSE;
+
+ //If we have any forbidden statii, then fail
+ if (status.testForAny(d->m_targetForbiddenStatus))
+ return FALSE;
+
+ // must match our kindof flags (if any)
+ if (!other->isKindOfMulti(d->m_kindof, d->m_kindofnot))
+ return FALSE;
+ }
+
+ if (d->m_triggerChance < 1.0) {
+ if (!d->m_rollOnce || m_roll < 0) {
+ m_roll = GameLogicRandomValueReal(0, 1);
+ }
+
+ if (m_roll > d->m_triggerChance)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+// ------------------------------------------------------------------------------------------------
+/** CRC */
+// ------------------------------------------------------------------------------------------------
+void AdvancedCollide::crc( Xfer *xfer )
+{
+
+ // extend base class
+ CollideModule::crc( xfer );
+
+} // end crc
+
+// ------------------------------------------------------------------------------------------------
+/** Xfer method
+ * Version Info:
+ * 1: Initial version */
+// ------------------------------------------------------------------------------------------------
+void AdvancedCollide::xfer( Xfer *xfer )
+{
+
+ // version
+ XferVersion currentVersion = 1;
+ XferVersion version = currentVersion;
+ xfer->xferVersion( &version, currentVersion );
+
+ // extend base class
+ CollideModule::xfer( xfer );
+
+ // weapon
+ Bool collideWeaponPresent = m_collideWeapon ? TRUE : FALSE;
+ xfer->xferBool( &collideWeaponPresent );
+ if( collideWeaponPresent )
+ {
+
+ DEBUG_ASSERTCRASH( m_collideWeapon != NULL,
+ ("AdvancedCollide::xfer - m_collideWeapon present mismatch") );
+ xfer->xferSnapshot( m_collideWeapon );
+
+ } // end else
+ else
+ {
+
+ DEBUG_ASSERTCRASH( m_collideWeapon == NULL,
+ ("AdvancedCollide::Xfer - m_collideWeapon missing mismatch" ));
+
+ } // end else
+
+ // ever fired
+ xfer->xferBool( &m_everFired );
+
+ // trigger chance roll
+ xfer->xferReal(&m_roll);
+
+} // end xfer
+
+// ------------------------------------------------------------------------------------------------
+/** Load post process */
+// ------------------------------------------------------------------------------------------------
+void AdvancedCollide::loadPostProcess( void )
+{
+
+ // extend base class
+ CollideModule::loadPostProcess();
+
+} // end loadPostProcess
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp
index e377b4aeefb..b346a1b7d81 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp
@@ -25,43 +25,28 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: StickyBombCrateCollide.cpp
-// Author: Kris Morness, June 2003
-// Desc: A crate (actually a saboteur - mobile crate) that resets the timer on the target supply dropzone.
+// Author: Andi W - Sept 2025
+// Desc: Creates a Sticks Bomb on the object we collide with
//
///////////////////////////////////////////////////////////////////////////////////////////////////
-
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
-// #include "Common/GameAudio.h"
-// #include "Common/MiscAudio.h"
#include "Common/Player.h"
-// #include "Common/PlayerList.h"
#include "Common/Radar.h"
-// #include "Common/SpecialPower.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
-// #include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
-// #include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
-//#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
-//#include "GameLogic/PartitionManager.h"
-//#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
-//#include "GameLogic/Module/ContainModule.h"
-//#include "GameLogic/Module/DozerAIUpdate.h"
-//#include "GameLogic/Module/HijackerUpdate.h"
-//#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/StickyBombCrateCollide.h"
#include "GameLogic/Module/StickyBombUpdate.h"
-//#include "GameLogic/Module/SpecialPowerModule.h"
@@ -79,29 +64,29 @@ StickyBombCrateCollide::~StickyBombCrateCollide( void )
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
-Bool StickyBombCrateCollide::isValidToExecute( const Object *other ) const
-{
- if( !CrateCollide::isValidToExecute(other) )
- {
- if (other != NULL)
- DEBUG_LOG(("StickyBombCrateCollide::isValidToExecute >>> target '%s' Not Valid.", other->getTemplate()->getName().str()));
- else
- DEBUG_LOG(("StickyBombCrateCollide::isValidToExecute >>> collide with ground!"));
- //Extend functionality.
- return FALSE;
- }
-
- // Do we need anything here?
- DEBUG_LOG(("StickyBombCrateCollide::isValidToExecute >>> target '%s' Valid!", other->getTemplate()->getName().str()));
- return TRUE;
-}
+//Bool StickyBombCrateCollide::isValidToExecute( const Object *other ) const
+//{
+// if( !CrateCollide::isValidToExecute(other) )
+// {
+// if (other != NULL)
+// DEBUG_LOG(("StickyBombCrateCollide::isValidToExecute >>> target '%s' Not Valid.", other->getTemplate()->getName().str()));
+// else
+// DEBUG_LOG(("StickyBombCrateCollide::isValidToExecute >>> collide with ground!"));
+// //Extend functionality.
+// return FALSE;
+// }
+//
+// // Do we need anything here?
+// DEBUG_LOG(("StickyBombCrateCollide::isValidToExecute >>> target '%s' Valid!", other->getTemplate()->getName().str()));
+// return TRUE;
+//}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool StickyBombCrateCollide::executeCrateBehavior( Object *other )
{
- DEBUG_LOG(("StickyBombCrateCollide::executeCrateBehavior 0"));
+ //DEBUG_LOG(("StickyBombCrateCollide::executeCrateBehavior 0"));
const StickyBombCrateCollideModuleData* md = getStickyBombCrateCollideModuleData();
if (m_hasCollided && !md->m_allowMultiCollide)
@@ -131,6 +116,12 @@ Bool StickyBombCrateCollide::executeCrateBehavior( Object *other )
}
+ Real randomNumber = GameLogicRandomValueReal(0, 1);
+ if (randomNumber > md->m_triggerChance) {
+ m_hasCollided = TRUE;
+ return false;
+ }
+
if (md->m_showInfiltrationEvent)
TheRadar->tryInfiltrationEvent( other );
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp
index c461555ee27..848b7f0a9ee 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp
@@ -52,6 +52,25 @@
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
+void StickyBombUpdateModuleData::parseAnimBaseName(INI* ini, void* instance, void* store, const void* /*userData*/)
+{
+ StickyBombUpdateModuleData* self = (StickyBombUpdateModuleData*)instance;
+ self->m_animBaseTemplate = ini->getNextAsciiString();
+ if (stricmp(self->m_animBaseTemplate.str(), "NONE") == 0) {
+ self->m_hideAnimBase = TRUE;
+ }
+}
+//-------------------------------------------------------------------------------------------------
+void StickyBombUpdateModuleData::parseAnimTimedName(INI* ini, void* instance, void* store, const void* /*userData*/)
+{
+ StickyBombUpdateModuleData* self = (StickyBombUpdateModuleData*)instance;
+ self->m_animTimedTemplate = ini->getNextAsciiString();
+ if (stricmp(self->m_animTimedTemplate.str(), "NONE") == 0) {
+ self->m_hideAnimTimed = TRUE;
+ }
+}
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
StickyBombUpdate::StickyBombUpdate( Thing *thing, const ModuleData *moduleData ) : UpdateModule( thing, moduleData )
{
m_targetID = INVALID_ID;