diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt
index 135c9bbda2..bbb20158f8 100644
--- a/GeneralsMD/Code/GameEngine/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt
@@ -425,6 +425,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/SabotageSupplyCenterCrateCollide.h
Include/GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h
Include/GameLogic/Module/SalvageCrateCollide.h
+ Include/GameLogic/Module/StickyBombCrateCollide.h
Include/GameLogic/Module/ScatterShotUpdate.h
Include/GameLogic/Module/ShroudCrateCollide.h
Include/GameLogic/Module/SlavedUpdate.h
@@ -912,6 +913,7 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Collide/CrateCollide/SabotageSupplyCenterCrateCollide.cpp
Source/GameLogic/Object/Collide/CrateCollide/SabotageSupplyDropzoneCrateCollide.cpp
Source/GameLogic/Object/Collide/CrateCollide/SalvageCrateCollide.cpp
+ Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp
Source/GameLogic/Object/Collide/CrateCollide/ShroudCrateCollide.cpp
Source/GameLogic/Object/Collide/CrateCollide/UnitCrateCollide.cpp
Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp
diff --git a/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h b/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h
index 4bd54b6099..82293a34d1 100644
--- a/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h
+++ b/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h
@@ -199,6 +199,7 @@ class ObjectDrawInterface
Note that you must call this AFTER setting the condition codes.
*/
virtual void setAnimationLoopDuration(UnsignedInt numFrames) = 0;
+ virtual bool isIgnoreAnimLoopDuration() const = 0;
/**
similar to the above, but assumes that the current state is a "ONCE",
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CrateCollide.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CrateCollide.h
index e67494da67..526e6ae679 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CrateCollide.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CrateCollide.h
@@ -48,8 +48,10 @@ class CrateCollideModuleData : public CollideModuleData
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
Bool m_isForbidOwnerPlayer; ///< This crate cannot be picked up by the player of the dead thing that made it.
+ Bool m_isAllowNeutralPlayer; ///< This crate can be picked up by the neutral player
Bool m_isBuildingPickup; ///< This crate can be picked up by a Building (bypassing AI requirement)
Bool m_isHumanOnlyPickup; ///< Can this crate only be picked up by a human player? (Mission thing)
+ Bool m_isAllowPickAboveTerrain; ///< Can this crate only be picked when on the ground´?
ScienceType m_pickupScience; ///< Can only be picked up by a unit whose player has this science
FXList *m_executeFX; ///< FXList to play when activated
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h
new file mode 100644
index 0000000000..6f445d9c08
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h
@@ -0,0 +1,108 @@
+/*
+** 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: StickyBombCrateCollide.h
+// Author: Kris Morness, June 2003
+// Desc: A crate (actually a saboteur - mobile crate) that resets ALL command center general powers
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#ifndef STICKY_BOMB_CRATE_COLLIDE_H_
+#define STICKY_BOMB_CRATE_COLLIDE_H_
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "Common/Module.h"
+#include "GameLogic/Module/CrateCollide.h"
+
+// FORWARD REFERENCES /////////////////////////////////////////////////////////////////////////////
+class Thing;
+
+//-------------------------------------------------------------------------------------------------
+class StickyBombCrateCollideModuleData : public CrateCollideModuleData
+{
+public:
+
+ Bool m_needsTarget; ///< Need valid AI target (e.g. ordered to enter a building/unit, or projectile target)
+ 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
+
+ StickyBombCrateCollideModuleData()
+ {
+ m_needsTarget = FALSE;
+ m_allowMultiCollide = FALSE;
+ m_showInfiltrationEvent = FALSE;
+ m_stickyBombObjectName = AsciiString::TheEmptyString;
+ }
+
+ static void buildFieldParse(MultiIniFieldParse& p)
+ {
+ CrateCollideModuleData::buildFieldParse(p);
+
+ static const FieldParse dataFieldParse[] =
+ {
+ { "NeedsTarget", INI::parseBool, NULL, offsetof(StickyBombCrateCollideModuleData, m_needsTarget) },
+ { "AllowMultiCollide", INI::parseBool, NULL, offsetof(StickyBombCrateCollideModuleData, m_allowMultiCollide) },
+ { "ShowInfiltrationEvent", INI::parseBool, NULL, offsetof(StickyBombCrateCollideModuleData, m_showInfiltrationEvent) },
+ { "StickyBombObject", INI::parseAsciiString, NULL, offsetof(StickyBombCrateCollideModuleData, m_stickyBombObjectName) },
+ { 0, 0, 0, 0 }
+ };
+ p.add( dataFieldParse );
+ }
+
+};
+
+//-------------------------------------------------------------------------------------------------
+class StickyBombCrateCollide : public CrateCollide
+{
+
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( StickyBombCrateCollide, "StickyBombCrateCollide" )
+ MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( StickyBombCrateCollide, StickyBombCrateCollideModuleData );
+
+public:
+
+ StickyBombCrateCollide( Thing *thing, const ModuleData* moduleData );
+ // virtual destructor prototype provided by memory pool declaration
+
+protected:
+
+ /// This allows specific vetoes to certain types of crates and their data
+ 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 );
+
+ /// This would allow entering buildings? Lets disable it for now
+ // virtual Bool isSabotageBuildingCrateCollide() const { return TRUE; }
+
+private:
+ Bool m_hasCollided;
+
+};
+
+#endif
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h
index e9bfa21941..e0f1a2b487 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h
@@ -34,6 +34,7 @@
#define __STICK_BOMB_UPDATE_H
#include "GameLogic/Module/UpdateModule.h"
+#include "GameClient/Anim2D.h"
class WeaponTemplate;
class FXList;
@@ -47,11 +48,18 @@ class StickyBombUpdateModuleData : public UpdateModuleData
WeaponTemplate* m_geometryBasedDamageWeaponTemplate;
FXList* m_geometryBasedDamageFX;
+ AsciiString m_animBaseTemplate;
+ AsciiString m_animTimedTemplate;
+ Bool m_showTimer;
+
StickyBombUpdateModuleData()
{
m_offsetZ = 10.0f;
m_geometryBasedDamageWeaponTemplate = NULL;
m_geometryBasedDamageFX = NULL;
+ m_animBaseTemplate = AsciiString::TheEmptyString;
+ m_animTimedTemplate = AsciiString::TheEmptyString;
+ m_showTimer = TRUE;
}
static void buildFieldParse(MultiIniFieldParse& p)
@@ -63,6 +71,9 @@ 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) },
+ { "ShowTimer", INI::parseBool, NULL, offsetof( StickyBombUpdateModuleData, m_showTimer) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
@@ -87,16 +98,26 @@ class StickyBombUpdate : public UpdateModule
void initStickyBomb( Object *object, const Object *bomber, const Coord3D *specificPos = NULL );
void detonate();
- Bool isTimedBomb() const { return m_dieFrame > 0; }
+ Bool isTimedBomb() const { return (m_dieFrame > 0) && getStickyBombUpdateModuleData()->m_showTimer; }
UnsignedInt getDetonationFrame() const { return m_dieFrame; }
Object* getTargetObject() const;
void setTargetObject( Object *obj );
+ //AsciiString getAnimBaseTemplate() { return getStickyBombUpdateModuleData()->m_animBaseTemplate; }
+ //AsciiString getAnimTimedTemplate() { return getStickyBombUpdateModuleData()->m_animTimedTemplate; }
+
+ Anim2DTemplate* getAnimBaseTemplate();
+ Anim2DTemplate* getAnimTimedTemplate();
+
private:
ObjectID m_targetID;
UnsignedInt m_dieFrame;
UnsignedInt m_nextPingFrame;
+
+ Anim2DTemplate* m_animBaseTemplate;
+ Anim2DTemplate* m_animTimedTemplate;
+
};
#endif // __STICK_BOMB_UPDATE_H
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp
index a4387c1a60..87e032a3c7 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp
@@ -294,6 +294,7 @@ static PoolSizeRec sizes[] =
{ "RailedTransportContain", 16, 16 },
{ "RailroadBehavior", 16, 16 },
{ "SalvageCrateCollide", 32, 32 },
+ { "StickyBombCrateCollide", 32, 32 },
{ "ShroudCrateCollide", 32, 32 },
{ "SlavedUpdate", 64, 32 },
{ "SlowDeathBehavior", 1400, 256 },
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
index 5b3850300d..49913b613c 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
@@ -253,6 +253,7 @@
#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h"
#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h"
#include "GameLogic/Module/SalvageCrateCollide.h"
+#include "GameLogic/Module/StickyBombCrateCollide.h"
#include "GameLogic/Module/ShroudCrateCollide.h"
#include "GameLogic/Module/UnitCrateCollide.h"
#include "GameLogic/Module/VeterancyCrateCollide.h"
@@ -545,6 +546,7 @@ void ModuleFactory::init( void )
addModule( SabotageSupplyCenterCrateCollide );
addModule( SabotageSupplyDropzoneCrateCollide );
addModule( SalvageCrateCollide );
+ addModule( StickyBombCrateCollide );
// body modules
addModule( InactiveBody );
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
index 2e7b9c085d..6312386e2a 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
@@ -726,7 +726,7 @@ void Drawable::setAnimationLoopDuration(UnsignedInt numFrames)
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
- if (di)
+ if (di && !di->isIgnoreAnimLoopDuration())
di->setAnimationLoopDuration(numFrames);
}
}
@@ -3679,6 +3679,7 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion)
StickyBombUpdate *update = (StickyBombUpdate*)obj->findUpdateModule( key_StickyBombUpdate );
if( update )
{
+
//This case is tricky. The object that is bombed doesn't know it... but the bomb itself does.
//So what we do is get it's target, then determine if the target has the icon or not.
Object *target = update->getTargetObject();
@@ -3689,8 +3690,20 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion)
//Timed bomb
if( !getIconInfo()->m_icon[ ICON_BOMB_TIMED ] )
{
- getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BOMB_REMOTE ], TheAnim2DCollection );
- getIconInfo()->m_icon[ ICON_BOMB_TIMED ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BOMB_TIMED ], TheAnim2DCollection );
+ Anim2DTemplate* templ = update->getAnimBaseTemplate();
+
+ if (templ == NULL) // Default icon
+ templ = s_animationTemplates[ICON_BOMB_REMOTE];
+
+ getIconInfo()->m_icon[ICON_BOMB_REMOTE] = newInstance(Anim2D)(templ, TheAnim2DCollection);
+
+ templ = update->getAnimTimedTemplate();
+
+ if (templ == NULL) // Default icon
+ templ = s_animationTemplates[ICON_BOMB_TIMED];
+
+ getIconInfo()->m_icon[ICON_BOMB_TIMED] = newInstance(Anim2D)(templ, TheAnim2DCollection);
+
//Because this is a counter icon that ranges from 0-60 seconds, we need to calculate which frame to
//start the animation from. Because timers are second based -- 1000 ms equal 1 frame. So we simply
@@ -3752,7 +3765,13 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion)
//Timed bomb
if( !getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] )
{
- getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BOMB_REMOTE ], TheAnim2DCollection );
+ Anim2DTemplate* templ = update->getAnimBaseTemplate();
+
+ if (templ == NULL) // Default icon
+ templ = s_animationTemplates[ICON_BOMB_REMOTE];
+
+ getIconInfo()->m_icon[ICON_BOMB_REMOTE] = newInstance(Anim2D)(templ, TheAnim2DCollection);
+
}
if( getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] )
{
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp
index 525734ea8c..e77b8ef3c1 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp
@@ -48,11 +48,13 @@
CrateCollideModuleData::CrateCollideModuleData()
{
m_isForbidOwnerPlayer = FALSE;
+ m_isAllowNeutralPlayer = FALSE;
m_executeAnimationDisplayTimeInSeconds = 0.0f;
m_executeAnimationZRisePerSecond = 0.0f;
m_executeAnimationFades = TRUE;
m_isBuildingPickup = FALSE;
m_isHumanOnlyPickup = FALSE;
+ m_isAllowPickAboveTerrain = FALSE;
m_executeFX = NULL;
m_pickupScience = SCIENCE_INVALID;
@@ -75,8 +77,10 @@ void CrateCollideModuleData::buildFieldParse(MultiIniFieldParse& p)
{ "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( CrateCollideModuleData, m_kindof ) },
{ "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( CrateCollideModuleData, m_kindofnot ) },
{ "ForbidOwnerPlayer", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isForbidOwnerPlayer ) },
+ { "AllowNeutralPlayer", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isAllowNeutralPlayer ) },
{ "BuildingPickup", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isBuildingPickup ) },
{ "HumanOnly", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isHumanOnlyPickup ) },
+ { "AllowPickAboveTerrain", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isAllowPickAboveTerrain ) },
{ "PickupScience", INI::parseScience, NULL, offsetof( CrateCollideModuleData, m_pickupScience ) },
{ "ExecuteFX", INI::parseFXList, NULL, offsetof( CrateCollideModuleData, m_executeFX ) },
{ "ExecuteAnimation", INI::parseAsciiString, NULL, offsetof( CrateCollideModuleData, m_executionAnimationTemplate ) },
@@ -149,11 +153,12 @@ Bool CrateCollide::isValidToExecute( const Object *other ) const
if( other == NULL )
return FALSE;
+ const CrateCollideModuleData* md = getCrateCollideModuleData();
+
//Nothing Neutral can pick up any type of crate
- if( other->isNeutralControlled() )
+ if(other->isNeutralControlled() && !md->m_isAllowNeutralPlayer)
return FALSE;
- const CrateCollideModuleData* md = getCrateCollideModuleData();
Bool validBuildingAttempt = md->m_isBuildingPickup && other->isKindOf( KINDOF_STRUCTURE );
// Must be a "Unit" type thing. Real Game Object, not just Object
@@ -168,7 +173,7 @@ Bool CrateCollide::isValidToExecute( const Object *other ) const
return FALSE;
// crates cannot be claimed while in the air, except by buildings
- if( getObject()->isAboveTerrain() && !validBuildingAttempt )
+ if( getObject()->isAboveTerrain() && !(validBuildingAttempt || md->m_isAllowPickAboveTerrain))
return FALSE;
if( md->m_isForbidOwnerPlayer && (getObject()->getControllingPlayer() == other->getControllingPlayer()) )
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp
new file mode 100644
index 0000000000..e377b4aeef
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp
@@ -0,0 +1,206 @@
+/*
+** 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: StickyBombCrateCollide.cpp
+// Author: Kris Morness, June 2003
+// Desc: A crate (actually a saboteur - mobile crate) that resets the timer on the target supply dropzone.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+// 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"
+
+
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+StickyBombCrateCollide::StickyBombCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
+{
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+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::executeCrateBehavior( Object *other )
+{
+
+ DEBUG_LOG(("StickyBombCrateCollide::executeCrateBehavior 0"));
+ const StickyBombCrateCollideModuleData* md = getStickyBombCrateCollideModuleData();
+
+ if (m_hasCollided && !md->m_allowMultiCollide)
+ return FALSE;
+
+ if (md->m_needsTarget) {
+ //Check to make sure that the other object is also the goal object in the AIUpdateInterface
+ //in order to prevent an unintentional conversion simply by having the terrorist walk too close
+ //to it. This should also be valid for Projectiles hitting their intended targets
+ //Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
+
+ Object* obj = getObject();
+ AIUpdateInterface* ai = obj->getAIUpdateInterface();
+ if (ai) {
+ if (ai->getGoalObject() != other)
+ {
+ return false;
+ }
+ }
+ else {
+ ProjectileUpdateInterface* pi = obj->getProjectileUpdateInterface();
+ if (pi && pi->getTargetObject() != other)
+ {
+ return false;
+ }
+ }
+
+ }
+
+ if (md->m_showInfiltrationEvent)
+ TheRadar->tryInfiltrationEvent( other );
+
+ //Get the template of the special object
+ const ThingTemplate* thingTemplate = TheThingFactory->findTemplate(md->m_stickyBombObjectName);
+ if (thingTemplate)
+ {
+ //Create a new special object
+ Object* stickyBombObject = TheThingFactory->newObject(thingTemplate, getObject()->getTeam());
+ if (stickyBombObject)
+ {
+ static NameKeyType key_StickyBombUpdate = NAMEKEY("StickyBombUpdate");
+ StickyBombUpdate* update = (StickyBombUpdate*)stickyBombObject->findUpdateModule(key_StickyBombUpdate);
+ if (!update)
+ {
+ DEBUG_ASSERTCRASH(0,
+ ("Unit '%s' attempted to place %s on %s but the bomb requires a StickyBombUpdate module.",
+ getObject()->getTemplate()->getName().str(),
+ stickyBombObject->getTemplate()->getName().str(),
+ other->getTemplate()->getName().str()));
+ return TRUE;
+ }
+ //Setting the producer ID allows the sticky bomb update module to initialize
+ //and setup timers, etc.
+ update->initStickyBomb(other, getObject());
+ }
+ }
+
+ m_hasCollided = TRUE;
+ return TRUE;
+}
+
+// ------------------------------------------------------------------------------------------------
+/** CRC */
+// ------------------------------------------------------------------------------------------------
+void StickyBombCrateCollide::crc( Xfer *xfer )
+{
+
+ // extend base class
+ CrateCollide::crc( xfer );
+
+} // end crc
+
+// ------------------------------------------------------------------------------------------------
+/** Xfer method
+ * Version Info:
+ * 1: Initial version */
+// ------------------------------------------------------------------------------------------------
+void StickyBombCrateCollide::xfer( Xfer *xfer )
+{
+
+ // version
+ XferVersion currentVersion = 1;
+ XferVersion version = currentVersion;
+ xfer->xferVersion( &version, currentVersion );
+
+ // extend base class
+ CrateCollide::xfer( xfer );
+
+ xfer->xferBool(&m_hasCollided);
+
+} // end xfer
+
+// ------------------------------------------------------------------------------------------------
+/** Load post process */
+// ------------------------------------------------------------------------------------------------
+void StickyBombCrateCollide::loadPostProcess( void )
+{
+
+ // extend base class
+ CrateCollide::loadPostProcess();
+
+} // end loadPostProcess
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp
index e3fe0ab8c7..8f36302999 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp
@@ -285,6 +285,46 @@ void StickyBombUpdate::detonate()
getObject()->kill();// Most things just fire weapons in their death modules
}
+
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+Anim2DTemplate* StickyBombUpdate::getAnimBaseTemplate() {
+
+ if (getStickyBombUpdateModuleData()->m_animBaseTemplate.isNotEmpty()) {
+ //DEBUG_LOG(("SBU::getAnimBaseTemplate - isNotEmpty"));
+ if (m_animBaseTemplate == NULL) {
+ m_animBaseTemplate = TheAnim2DCollection->findTemplate(getStickyBombUpdateModuleData()->m_animBaseTemplate);
+ }
+ //DEBUG_LOG(("SBU::getAnimBaseTemplate - isNull = %d", m_animBaseTemplate == NULL));
+ return m_animBaseTemplate;
+ }
+
+ //DEBUG_LOG(("SBU::getAnimBaseTemplate - is Empty ?!"));
+
+ return NULL;
+}
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+
+Anim2DTemplate* StickyBombUpdate::getAnimTimedTemplate() {
+ if (getStickyBombUpdateModuleData()->m_animTimedTemplate.isNotEmpty()) {
+ //DEBUG_LOG(("SBU::getAnimTimedTemplate - isNotEmpty"));
+ if (m_animTimedTemplate == NULL) {
+ m_animTimedTemplate = TheAnim2DCollection->findTemplate(getStickyBombUpdateModuleData()->m_animTimedTemplate);
+ }
+ //DEBUG_LOG(("SBU::getAnimTimedTemplate - isNull = %d", m_animTimedTemplate == NULL));
+ return m_animTimedTemplate;
+ }
+
+ //DEBUG_LOG(("SBU::getAnimTimedTemplate - is Empty ?!"));
+
+ return NULL;
+
+}
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp
index 9d30ca063e..fa7395ac61 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp
@@ -2866,6 +2866,8 @@ Bool Weapon::privateFireWeapon(
targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y);
+ // Note AW: We have to ignore Ranges when using ScatterTargets, or else the weapon can fail in the next stage
+ ignoreRanges = TRUE;
m_template->fireWeaponTemplate(sourceObj, m_wslot, m_curBarrel, victimObj, &targetPos, bonus, isProjectileDetonation, ignoreRanges, this, projectileID, inflictDamage );
}
else
diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h
index 0e2ff1dc31..2e8d3dbf1a 100644
--- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h
+++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h
@@ -311,6 +311,8 @@ class W3DModelDrawModuleData : public ModuleData
Bool m_receivesDynamicLights; ///< just like it sounds... it sets a property of Drawable, actually
+ Bool m_ignoreAnimScaling; ///< ignore external scaling of animation speed, e.g. for PreAttack.
+
W3DModelDrawModuleData();
~W3DModelDrawModuleData();
@@ -396,6 +398,7 @@ class W3DModelDraw : public DrawModule, public ObjectDrawInterface
Note that you must call this AFTER setting the condition codes.
*/
virtual void setAnimationLoopDuration(UnsignedInt numFrames);
+ virtual bool isIgnoreAnimLoopDuration() const { return getW3DModelDrawModuleData()->m_ignoreAnimScaling; }
/**
similar to the above, but assumes that the current state is a "ONCE",
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 efb6e2f747..8af0024574 100644
--- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp
+++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp
@@ -875,7 +875,8 @@ void ModelConditionInfo::validateTurretInfo() const
{
if (findPristineBone(tur.m_turretAngleNameKey, &tur.m_turretAngleBone) == NULL)
{
- DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretAngleNameKey).str(),m_modelName.str()));
+ //DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretAngleNameKey).str(),m_modelName.str()));
+ DEBUG_LOG(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretAngleNameKey).str(),m_modelName.str()));
tur.m_turretAngleBone = 0;
}
}
@@ -888,7 +889,8 @@ void ModelConditionInfo::validateTurretInfo() const
{
if (findPristineBone(tur.m_turretPitchNameKey, &tur.m_turretPitchBone) == NULL)
{
- DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretPitchNameKey).str(),m_modelName.str()));
+ //DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretPitchNameKey).str(),m_modelName.str()));
+ DEBUG_LOG(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretPitchNameKey).str(),m_modelName.str()));
tur.m_turretPitchBone = 0;
}
}
@@ -898,6 +900,7 @@ void ModelConditionInfo::validateTurretInfo() const
}
}
+
if (isValidTimeToCalcLogicStuff())
{
m_validStuff |= TURRETS_VALID;
@@ -1092,10 +1095,10 @@ void W3DModelDrawModuleData::validateStuffForTimeAndWeather(const Drawable* draw
for (c_it = m_conditionStates.begin(); c_it != m_conditionStates.end(); ++c_it)
{
- if (!a && c_it->m_transitionKey == src && c_it->matchesMode(night, snowy))
+ if (!a && c_it->m_transitionKey == src && (c_it->matchesMode(night, snowy) || c_it->matchesMode(false, false)))
a = true;
- if (!b && c_it->m_transitionKey == dst && c_it->matchesMode(night, snowy))
+ if (!b && c_it->m_transitionKey == dst && (c_it->matchesMode(night, snowy) || c_it->matchesMode(false, false)))
b = true;
}
@@ -1214,6 +1217,7 @@ void W3DModelDrawModuleData::buildFieldParse(MultiIniFieldParse& p)
{ "AttachToBoneInAnotherModule", parseAsciiStringLC, NULL, offsetof(W3DModelDrawModuleData, m_attachToDrawableBone) },
{ "IgnoreConditionStates", ModelConditionFlags::parseFromINI, NULL, offsetof(W3DModelDrawModuleData, m_ignoreConditionStates) },
{ "ReceivesDynamicLights", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_receivesDynamicLights) },
+ { "IgnoreAnimationSpeedScaling", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_ignoreAnimScaling) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
@@ -2046,6 +2050,7 @@ void W3DModelDraw::adjustTransformMtx(Matrix3D& mtx) const
//-------------------------------------------------------------------------------------------------
void W3DModelDraw::doDrawModule(const Matrix3D* transformMtx)
{
+
// update whether or not we should be animating.
setPauseAnimation( !getDrawable()->getShouldAnimate(getW3DModelDrawModuleData()->m_animationsRequirePower) );
@@ -2421,6 +2426,7 @@ void W3DModelDraw::stopClientParticleSystems()
*/
void W3DModelDraw::handleClientTurretPositioning()
{
+
if (!m_curState || !(m_curState->m_validStuff & ModelConditionInfo::TURRETS_VALID))
return;
@@ -2920,6 +2926,7 @@ static Bool turretNamesDiffer(const ModelConditionInfo* a, const ModelConditionI
//-------------------------------------------------------------------------------------------------
void W3DModelDraw::setModelState(const ModelConditionInfo* newState)
{
+
DEBUG_ASSERTCRASH(newState, ("invalid state in W3DModelDraw::setModelState"));
#ifdef DEBUG_OBJECT_ID_EXISTS