diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt
index bd7127b2ba..5587218f83 100644
--- a/Core/GameEngine/CMakeLists.txt
+++ b/Core/GameEngine/CMakeLists.txt
@@ -460,6 +460,7 @@ set(GAMEENGINE_SRC
# Include/GameLogic/Module/SupplyWarehouseDockUpdate.h
# Include/GameLogic/Module/TechBuildingBehavior.h
# Include/GameLogic/Module/TempWeaponBonusHelper.h
+# Include/GameLogic/Module/BuffEffectHelper.h
# Include/GameLogic/Module/TensileFormationUpdate.h
# Include/GameLogic/Module/ToppleUpdate.h
# Include/GameLogic/Module/TransitionDamageFX.h
@@ -477,11 +478,13 @@ set(GAMEENGINE_SRC
# Include/GameLogic/Module/WanderAIUpdate.h
# Include/GameLogic/Module/WaveGuideUpdate.h
# Include/GameLogic/Module/WeaponBonusUpdate.h
+# Include/GameLogic/Module/BuffUpdate.h
# Include/GameLogic/Module/WeaponBonusUpgrade.h
# Include/GameLogic/Module/WeaponSetUpgrade.h
# Include/GameLogic/Module/WorkerAIUpdate.h
# Include/GameLogic/Object.h
# Include/GameLogic/ObjectCreationList.h
+# Include/GameLogic/BuffSystem.h
# Include/GameLogic/ObjectIter.h
# Include/GameLogic/ObjectScriptStatusBits.h
# Include/GameLogic/ObjectTypes.h
@@ -959,9 +962,11 @@ set(GAMEENGINE_SRC
# Source/GameLogic/Object/Helper/StatusDamageHelper.cpp
# Source/GameLogic/Object/Helper/SubdualDamageHelper.cpp
# Source/GameLogic/Object/Helper/TempWeaponBonusHelper.cpp
+# Source/GameLogic/Object/Helper/BuffEffectHelper.cpp
# Source/GameLogic/Object/Locomotor.cpp
# Source/GameLogic/Object/Object.cpp
# Source/GameLogic/Object/ObjectCreationList.cpp
+# Source/GameLogic/Object/BuffSystem.cpp
# Source/GameLogic/Object/ObjectTypes.cpp
# Source/GameLogic/Object/PartitionManager.cpp
# Source/GameLogic/Object/SimpleObjectIterator.cpp
@@ -1062,6 +1067,7 @@ set(GAMEENGINE_SRC
# Source/GameLogic/Object/Update/UpdateModule.cpp
# Source/GameLogic/Object/Update/WaveGuideUpdate.cpp
# Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp
+# Source/GameLogic/Object/Update/BuffUpdate.cpp
# Source/GameLogic/Object/Upgrade/ActiveShroudUpgrade.cpp
# Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp
# Source/GameLogic/Object/Upgrade/CommandSetUpgrade.cpp
diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
index 322d1a668f..85d384b87a 100644
--- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
+++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
@@ -88,6 +88,7 @@ static PoolSizeRec PoolSizes[] =
{ "SubdualDamageHelper", 1500, 256 },
{ "ChronoDamageHelper", 1500, 256 },
{ "TempWeaponBonusHelper", 4096, 256 },
+ { "BuffEffectHelper", 4096, 256 },
{ "Locomotor", 2048, 32 },
{ "LocomotorTemplate", 192, 32 },
{ "ObjectPool", 1500, 256 },
@@ -105,6 +106,7 @@ static PoolSizeRec PoolSizes[] =
{ "AudioRequest", 256, 8 },
{ "AutoHealBehavior", 1024, 256 },
{ "WeaponBonusUpdate", 16, 16 },
+ { "BuffUpdate", 32, 32 },
{ "GrantStealthBehavior", 4096, 32 },
{ "NeutronBlastBehavior", 4096, 32 },
{ "CountermeasuresBehavior", 256, 32 },
@@ -548,6 +550,11 @@ static PoolSizeRec PoolSizes[] =
{ "MetaMapRec", 256, 32 },
{ "TransportStatus", 32, 32 },
{ "Anim2DTemplate", 32, 32 },
+ { "BuffTemplate", 32, 32 },
+ { "ValueModifierBuffEffectNugget", 64, 32 },
+ { "FlagModifierBuffEffectNugget", 64, 32 },
+ { "ColorTintBuffEffectNugget", 32, 32 },
+ { "ParticleSystemBuffEffectNugget", 32, 32 },
{ "ObjectTypes", 32, 32 },
{ "NetCommandList", 512, 32 },
{ "TurretAIData", 256, 32 },
diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt
index e25aabab8f..615ff699f0 100644
--- a/GeneralsMD/Code/GameEngine/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt
@@ -474,6 +474,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/SupplyWarehouseDockUpdate.h
Include/GameLogic/Module/TechBuildingBehavior.h
Include/GameLogic/Module/TempWeaponBonusHelper.h
+ Include/GameLogic/Module/BuffEffectHelper.h
Include/GameLogic/Module/TensileFormationUpdate.h
Include/GameLogic/Module/ToppleUpdate.h
Include/GameLogic/Module/TransitionDamageFX.h
@@ -492,6 +493,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/TeleporterAIUpdate.h
Include/GameLogic/Module/WaveGuideUpdate.h
Include/GameLogic/Module/WeaponBonusUpdate.h
+ Include/GameLogic/Module/BuffUpdate.h
Include/GameLogic/Module/ArmorDamageScalarUpdate.h
Include/GameLogic/Module/WeaponBonusUpgrade.h
Include/GameLogic/Module/WeaponSetUpgrade.h
@@ -503,6 +505,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/CarrierDroneAIUpdate.h
Include/GameLogic/Object.h
Include/GameLogic/ObjectCreationList.h
+ Include/GameLogic/BuffSystem.h
Include/GameLogic/ObjectIter.h
Include/GameLogic/ObjectScriptStatusBits.h
Include/GameLogic/ObjectTypes.h
@@ -987,9 +990,11 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Helper/SubdualDamageHelper.cpp
Source/GameLogic/Object/Helper/ChronoDamageHelper.cpp
Source/GameLogic/Object/Helper/TempWeaponBonusHelper.cpp
+ Source/GameLogic/Object/Helper/BuffEffectHelper.cpp
Source/GameLogic/Object/Locomotor.cpp
Source/GameLogic/Object/Object.cpp
Source/GameLogic/Object/ObjectCreationList.cpp
+ Source/GameLogic/Object/BuffSystem.cpp
Source/GameLogic/Object/ObjectTypes.cpp
Source/GameLogic/Object/PartitionManager.cpp
Source/GameLogic/Object/SimpleObjectIterator.cpp
@@ -1098,6 +1103,7 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Update/UpdateModule.cpp
Source/GameLogic/Object/Update/WaveGuideUpdate.cpp
Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp
+ Source/GameLogic/Object/Update/BuffUpdate.cpp
Source/GameLogic/Object/Update/ArmorDamageScalarUpdate.cpp
Source/GameLogic/Object/Update/KodiakDeploymentUpdate.cpp
Source/GameLogic/Object/Update/KodiakUpdate.cpp
diff --git a/GeneralsMD/Code/GameEngine/Include/Common/INI.h b/GeneralsMD/Code/GameEngine/Include/Common/INI.h
index 7c21d147a8..c9862f8fd0 100644
--- a/GeneralsMD/Code/GameEngine/Include/Common/INI.h
+++ b/GeneralsMD/Code/GameEngine/Include/Common/INI.h
@@ -176,7 +176,7 @@ class INI
~INI();
void loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType, Xfer *pXfer ); ///< load directory of INI files
- void load( AsciiString filename, INILoadType loadType, Xfer *pXfer ); ///< load INI file
+ void load( AsciiString filename, INILoadType loadType, Xfer *pXfer, Bool optional=FALSE); ///< load INI file
static Bool isDeclarationOfType( AsciiString blockType, AsciiString blockName, char *bufferToCheck );
static Bool isEndOfBlock( char *bufferToCheck );
@@ -214,6 +214,7 @@ class INI
static void parseTerrainBridgeDefinition( INI *ini );
static void parseMetaMapDefinition( INI *ini );
static void parseFXListDefinition( INI *ini );
+ static void parseBuffTemplateDefinition( INI* ini );
static void parseObjectCreationListDefinition( INI* ini );
static void parseMultiplayerSettingsDefinition( INI* ini );
static void parseMultiplayerColorDefinition( INI* ini );
@@ -395,7 +396,7 @@ class INI
static Bool isValidINIFilename( const char *filename ); ///< is this a valid .ini filename
- void prepFile( AsciiString filename, INILoadType loadType );
+ void prepFile( AsciiString filename, INILoadType loadType, Bool optional=FALSE );
void unPrepFile();
void readLine( void );
diff --git a/GeneralsMD/Code/GameEngine/Include/Common/SubsystemInterface.h b/GeneralsMD/Code/GameEngine/Include/Common/SubsystemInterface.h
index e85672d2fe..1ccc400c59 100644
--- a/GeneralsMD/Code/GameEngine/Include/Common/SubsystemInterface.h
+++ b/GeneralsMD/Code/GameEngine/Include/Common/SubsystemInterface.h
@@ -150,7 +150,7 @@ class SubsystemInterfaceList
SubsystemInterfaceList();
~SubsystemInterfaceList();
- void initSubsystem(SubsystemInterface* sys, const char* path1, const char* path2, const char* dirpath, Xfer *pXfer, AsciiString name="");
+ void initSubsystem(SubsystemInterface* sys, const char* path1, const char* path2, const char* dirpath, Xfer *pXfer, AsciiString name="", Bool optional=FALSE);
void addSubsystem(SubsystemInterface* sys);
void removeSubsystem(SubsystemInterface* sys);
void postProcessLoadAll();
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/BuffSystem.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/BuffSystem.h
new file mode 100644
index 0000000000..15e5dae1ae
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/BuffSystem.h
@@ -0,0 +1,185 @@
+/*
+** 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: BuffSystem.h /////////////////////////////////////////////////////////////////////////////
+// Author: Andi W, October 25
+// Desc: Buff/Debuff effects
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#ifndef _BuffSystem_H_
+#define _BuffSystem_H_
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "Common/GameMemory.h"
+#include "GameLogic/Object.h"
+
+// FORWARD REFERENCES /////////////////////////////////////////////////////////////////////////////
+class BuffEffectNugget;
+class BuffTemplate;
+class BuffTemplateStore;
+
+struct BuffEffectTracker;
+
+//class Object;
+
+
+class BuffEffectNugget : public MemoryPoolObject
+{
+ MEMORY_POOL_GLUE_ABC(BuffEffectNugget)
+
+public:
+
+ BuffEffectNugget() { }
+ //virtual ~BuffEffectNugget() { }
+
+ virtual void apply(Object* targetObj, const Object* sourceObj, BuffEffectTracker* buffTracker) const = 0;
+
+ virtual void remove(Object* targetObj, BuffEffectTracker* buffTracker) const = 0;
+
+
+};
+EMPTY_DTOR(BuffEffectNugget)
+
+// -----------------------------------------------
+// -----------------------------------------------
+
+class BuffTemplate : public MemoryPoolObject
+{
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(BuffTemplate, "BuffTemplate")
+
+public:
+ // for lookup
+ AsciiString getName(void) const { return m_name; }
+ void friend_setName(const AsciiString& n) { m_name = n; }
+
+ inline UnsignedInt getMaxStackSize() const { return m_maxStackSize; }
+ inline Bool isStackPerSource() const { return m_stackPerSource; }
+ inline const std::vector& getPriorityTemplates() const { return m_priorityTemplates; }
+
+ //inline WeaponBonusConditionType getWeaponBonusType() const { return m_bonusType; }
+ //inline WeaponBonusConditionType getWeaponBonusTypeAgainst() const { return m_bonusTypeAgainst; }
+ //inline WeaponSetType getWeaponSetFlag() const { return m_weaponSetFlag; }
+ //inline ArmorSetType getArmorSetFlag() const { return m_armorSetFlag; }
+ //inline ObjectStatusMaskType getStatusToSet() const { return m_statusToSet; }
+
+ BuffTemplate();
+
+ /**
+ Toss the contents.
+ */
+ void clear();
+
+ void addBuffEffectNugget(BuffEffectNugget* nugget);
+
+ UnsignedInt getNextTickFrame(UnsignedInt startFrame, UnsignedInt endFrame) const;
+
+ void applyEffects(Object* targetObj, Object* sourceObj, BuffEffectTracker* buffTracker) const;
+ void removeEffects(Object* targetObj, BuffEffectTracker* buffTracker, Bool ignoreFlags = FALSE) const;
+
+ Bool hasPriorityOver(AsciiString templateName) const;
+
+ void reApplyFlags(Object* targetObj, const BuffTemplate* other) const;
+
+ const FieldParse* getFieldParse(void) const { return TheBuffTemplateFieldParse; }
+
+
+protected:
+ AsciiString m_name;
+ UnsignedInt m_maxStackSize;
+ Bool m_stackPerSource; ///< Each sourceObj has its own stack of buffs on the target.
+
+ std::vector m_priorityTemplates; ///< list of buffTemplates this template has priority over.
+
+ // Flags are stored directly in BuffTemplate
+ WeaponBonusConditionType m_bonusType; ///< weapon bonus granted to the object
+ WeaponBonusConditionType m_bonusTypeAgainst; ///< weapon bonus granted when attacking the object
+ WeaponSetType m_weaponSetFlag; ///< the weaponset flag to set
+ ArmorSetType m_armorSetFlag; ///< the armorset flag to set
+ ObjectStatusMaskType m_statusToSet; ///< the status to set (allows multiple)
+
+ // --
+ static const FieldParse TheBuffTemplateFieldParse[];
+ //static const FieldParse flagModifierFieldParse[];
+
+ static void parseFlagModifier(INI* ini, void* instance, void* store, const void* userData);
+
+private:
+
+ // note, this list doesn't own the nuggets; all nuggets are owned by the Store.
+ typedef std::vector BuffEffectNuggetVector;
+ BuffEffectNuggetVector m_nuggets;
+
+};
+EMPTY_DTOR(BuffTemplate)
+
+//-------------------------------------------------------------------------------------------------
+/**
+ The "store" used to hold all the Buffs in existence.
+*/
+class BuffTemplateStore : public SubsystemInterface
+{
+
+public:
+
+ BuffTemplateStore();
+ ~BuffTemplateStore();
+
+ void init() {}
+ void reset() {}
+ void update() {}
+
+ /**
+ return the BuffTemplate with the given namekey.
+ return NULL if no such BuffTemplate exists.
+ */
+ const BuffTemplate* findBuffTemplate(const char* name) const;
+ const BuffTemplate* findBuffTemplate(const AsciiString& name) const { return findBuffTemplate(name.str()); }
+
+ BuffTemplate* findBuffTemplate(const char* name);
+ BuffTemplate* findBuffTemplate(AsciiString& name) { return findBuffTemplate(name.str()); }
+
+ //BuffTemplate* newBuffTemplate(AsciiString& name);
+
+ static void parseBuffTemplateDefinition(INI* ini);
+
+ void addBuffEffectNugget(BuffEffectNugget* nugget);
+
+private:
+
+ typedef std::map< NameKeyType, BuffTemplate*, std::less > BuffTemplateMap;
+ BuffTemplateMap m_buffTemplates;
+
+ // note, this list doesn't own the nuggets; all nuggets are owned by the Store.
+ typedef std::vector BuffEffectNuggetVector;
+ BuffEffectNuggetVector m_nuggets;
+
+};
+
+// EXTERNALS //////////////////////////////////////////////////////////////////////////////////////
+extern BuffTemplateStore* TheBuffTemplateStore;
+
+#endif // _Buff_H_
+
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/FiringTracker.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/FiringTracker.h
index bdfa59d616..864dae7e85 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/FiringTracker.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/FiringTracker.h
@@ -36,6 +36,7 @@
#include "Common/GameMemory.h"
#include "Common/AudioEventRTS.h"
#include "GameLogic/Module/UpdateModule.h"
+#include "GameLogic/WeaponBonusConditionFlags.h"
class Object;
class Weapon;
@@ -90,6 +91,8 @@ class FiringTracker : public UpdateModule
UnsignedInt m_frameToStopLoopingSound; ///< if sound is looping, frame to stop looping it (or zero if not looping)
AudioHandle m_audioHandle;
+
+ WeaponBonusConditionFlags m_prevTargetWeaponBonus; ///< weaponBonus against previous target
};
#endif
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h
index 2b947820da..b358306890 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h
@@ -603,6 +603,8 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface
Bool canAutoAcquireWhileStealthed() const;
+ void applySpeedMultiplier(Real scalar);
+ inline Real getSpeedMultiplier(void) const { return m_speedMultiplier; }
protected:
@@ -812,6 +814,8 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface
Bool m_allowedToChase; ///< Allowed to pursue targets.
Bool m_isInUpdate; ///< If true, we are inside our update method.
Bool m_fixLocoInPostProcess;
+
+ Real m_speedMultiplier; ///< global multiplier to move speed (kept in AIUpdate rather than Locomotor because it's persistent)
};
//------------------------------------------------------------------------------------------------------------
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BuffEffectHelper.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BuffEffectHelper.h
new file mode 100644
index 0000000000..bf349084e8
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BuffEffectHelper.h
@@ -0,0 +1,125 @@
+/*
+** 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: BuffEffectHelper.h ////////////////////////////////////////////////////////////////////////
+// Author: Andi W, Oct 25
+// Desc: Buff Effect Helper - Tracks active buffs
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#ifndef __BuffEffectHelper_H_
+#define __BuffEffectHelper_H_
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "GameLogic/Module/ObjectHelper.h"
+#include "GameLogic/BuffSystem.h"
+#include "GameClient/ParticleSys.h"
+
+
+//enum ParticleSystemID CPP_11(: Int);
+
+
+struct BuffEffectTracker
+{
+ const BuffTemplate* m_template;
+ UnsignedInt m_frameCreated;
+ UnsignedInt m_frameToRemove;
+ UnsignedInt m_numStacks;
+ ObjectID m_sourceID;
+ Bool m_isActive;
+ std::vector m_particleSystemIDs;
+
+ BuffEffectTracker() {
+ m_template = NULL;
+ m_frameCreated = 0;
+ m_frameToRemove = 0;
+ m_numStacks = 0;
+ m_sourceID = INVALID_ID;
+ m_isActive = FALSE;
+ m_particleSystemIDs.clear();
+ }
+
+ void addParticleSystem(ParticleSystemID id) {
+ m_particleSystemIDs.push_back(id);
+ }
+
+ void clearParticleSystems() {
+ DEBUG_LOG(("BuffEffectTracker::clearParticleSystems - count = %d", m_particleSystemIDs.size()));
+ for (ParticleSystemID id : m_particleSystemIDs) {
+ DEBUG_LOG(("BuffEffectTracker::clearParticleSystems - try to remove system %d", id));
+ ParticleSystem* particleSystem = TheParticleSystemManager->findParticleSystem(id);
+ if (particleSystem) {
+ particleSystem->stop();
+ particleSystem->destroy();
+ }
+ else {
+ DEBUG_LOG(("BuffEffectTracker::clearParticleSystems - sys not found!"));
+ }
+ }
+ m_particleSystemIDs.clear();
+ }
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+class BuffEffectHelperModuleData : public ModuleData
+{
+
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+class BuffEffectHelper : public ObjectHelper
+{
+
+ MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(BuffEffectHelper, BuffEffectHelperModuleData)
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(BuffEffectHelper, "BuffEffectHelper")
+
+public:
+
+ BuffEffectHelper(Thing* thing, const ModuleData* modData);
+ // virtual destructor prototype provided by memory pool object
+
+ virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; }
+ virtual UpdateSleepTime update();
+
+ //void doTempWeaponBonus(WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus = TINT_STATUS_INVALID);
+
+ void applyBuff(const BuffTemplate* buffTemplate, Object* sourceObj, UnsignedInt duration);
+
+protected:
+ //WeaponBonusConditionType m_currentBonus;
+ //TintStatus m_currentTint;
+ //UnsignedInt m_frameToRemove;
+ //void clearTempWeaponBonus();
+
+ std::vector m_buffEffects;
+ UnsignedInt m_nextTickFrame;
+
+ void clearAllBuffs();
+};
+
+
+#endif // end __BuffEffectHelper_H_
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BuffUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BuffUpdate.h
new file mode 100644
index 0000000000..e5c2ebf2b4
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BuffUpdate.h
@@ -0,0 +1,113 @@
+/*
+** 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: BuffUpdate.h /////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+//
+// Electronic Arts Pacific.
+//
+// Confidential Information
+// Copyright (C) 2002-2003 - All Rights Reserved
+//
+//-----------------------------------------------------------------------------
+//
+// created: Oct 25
+//
+// Filename: BuffUpdate.h
+//
+// author: Andi W
+//
+// purpose: apply a buff/debuff effect to units in an area
+//
+//-----------------------------------------------------------------------------
+///////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#ifndef __BUFF_UPDATE_H_
+#define __BUFF_UPDATE_H_
+
+//-----------------------------------------------------------------------------
+// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// USER INCLUDES //////////////////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+#include "GameLogic/Module/UpdateModule.h"
+
+//-----------------------------------------------------------------------------
+// FORWARD REFERENCES /////////////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// TYPE DEFINES ///////////////////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+class BuffUpdateModuleData : public UpdateModuleData
+{
+public:
+
+ BuffUpdateModuleData();
+
+ KindOfMaskType m_requiredAffectKindOf; ///< Must be set on target
+ KindOfMaskType m_forbiddenAffectKindOf; ///< Must be clear on target
+ Int m_targetsMask; ///< ALLIES, ENEMIES or NEUTRALS
+ Bool m_isAffectAirborne; ///< Affect Airborne targets
+ UnsignedInt m_buffDuration; ///< How long a hit lasts on target
+ UnsignedInt m_buffDelay; ///< How often to pulse
+ Real m_buffRange; ///< How far to affect
+ AsciiString m_buffTemplateName; ///< Buff type to give
+
+ static void buildFieldParse(MultiIniFieldParse& p);
+};
+
+
+//-------------------------------------------------------------------------------------------------
+class BuffUpdate : public UpdateModule
+{
+
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( BuffUpdate, "BuffUpdate" )
+ MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( BuffUpdate, BuffUpdateModuleData )
+
+public:
+
+ BuffUpdate( Thing *thing, const ModuleData* moduleData );
+ // virtual destructor prototype provided by memory pool declaration
+
+ virtual UpdateSleepTime update( void );
+
+protected:
+
+};
+
+
+//-----------------------------------------------------------------------------
+// INLINING ///////////////////////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// EXTERNALS //////////////////////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+
+#endif
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TempWeaponBonusHelper.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TempWeaponBonusHelper.h
index 48ad2db8e4..62c25dde3e 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TempWeaponBonusHelper.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TempWeaponBonusHelper.h
@@ -1,75 +1,75 @@
-/*
-** 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: TempWeaponBonusHelper.h ////////////////////////////////////////////////////////////////////////
-// Author: Graham Smallwood, June 2003
-// Desc: Object helper - Clears Temporary weapon bonus effects
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#pragma once
-
-#ifndef __TempWeaponBonusHelper_H_
-#define __TempWeaponBonusHelper_H_
-
-// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
-#include "GameLogic/Module/ObjectHelper.h"
-#include "GameClient/TintStatus.h"
-
-enum WeaponBonusConditionType CPP_11(: Int);
-// enum TintStatus CPP_11(: Int);
-
-// ------------------------------------------------------------------------------------------------
-// ------------------------------------------------------------------------------------------------
-class TempWeaponBonusHelperModuleData : public ModuleData
-{
-
-};
-
-// ------------------------------------------------------------------------------------------------
-// ------------------------------------------------------------------------------------------------
-class TempWeaponBonusHelper : public ObjectHelper
-{
-
- MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( TempWeaponBonusHelper, TempWeaponBonusHelperModuleData )
+/*
+** 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: TempWeaponBonusHelper.h ////////////////////////////////////////////////////////////////////////
+// Author: Graham Smallwood, June 2003
+// Desc: Object helper - Clears Temporary weapon bonus effects
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#ifndef __TempWeaponBonusHelper_H_
+#define __TempWeaponBonusHelper_H_
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "GameLogic/Module/ObjectHelper.h"
+#include "GameClient/TintStatus.h"
+
+enum WeaponBonusConditionType CPP_11(: Int);
+// enum TintStatus CPP_11(: Int);
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+class TempWeaponBonusHelperModuleData : public ModuleData
+{
+
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+class TempWeaponBonusHelper : public ObjectHelper
+{
+
+ MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( TempWeaponBonusHelper, TempWeaponBonusHelperModuleData )
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(TempWeaponBonusHelper, "TempWeaponBonusHelper" )
-
-public:
-
- TempWeaponBonusHelper( Thing *thing, const ModuleData *modData );
- // virtual destructor prototype provided by memory pool object
-
- virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; }
- virtual UpdateSleepTime update();
-
- void doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus = TINT_STATUS_INVALID);
-
-protected:
- WeaponBonusConditionType m_currentBonus;
- TintStatus m_currentTint;
- UnsignedInt m_frameToRemove;
- void clearTempWeaponBonus();
-};
-
-
-#endif // end __TempWeaponBonusHelper_H_
+
+public:
+
+ TempWeaponBonusHelper( Thing *thing, const ModuleData *modData );
+ // virtual destructor prototype provided by memory pool object
+
+ virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; }
+ virtual UpdateSleepTime update();
+
+ void doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus = TINT_STATUS_INVALID);
+
+protected:
+ WeaponBonusConditionType m_currentBonus;
+ TintStatus m_currentTint;
+ UnsignedInt m_frameToRemove;
+ void clearTempWeaponBonus();
+};
+
+
+#endif // end __TempWeaponBonusHelper_H_
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h
index 4582a189b6..9ae3b55802 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h
@@ -99,6 +99,7 @@ class UpdateModuleInterface;
class UpgradeModule;
class UpgradeModuleInterface;
class UpgradeTemplate;
+class BuffTemplate;
class ObjectHeldHelper;
class ObjectDisabledHelper;
@@ -108,6 +109,7 @@ class StatusDamageHelper;
class SubdualDamageHelper;
class ChronoDamageHelper;
class TempWeaponBonusHelper;
+class BuffEffectHelper;
class ObjectWeaponStatusHelper;
class ObjectDefectionHelper;
@@ -237,6 +239,7 @@ class Object : public Thing, public Snapshot
void notifyChronoDamage( Real amount );///< At this level, we just pass this on to our helper and do a special tint
void doStatusDamage( ObjectStatusTypes status, Real duration );///< At this level, we just pass this on to our helper
void doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus = TINT_STATUS_INVALID );///< At this level, we just pass this on to our helper
+ void applyBuff(const BuffTemplate* buffTemp, UnsignedInt duration, Object* sourceObj);
void scoreTheKill( const Object *victim ); ///< I just killed this object.
void onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback = TRUE ); ///< I just achieved this level right this moment
@@ -576,6 +579,17 @@ class Object : public Thing, public Snapshot
inline WeaponBonusConditionFlags getWeaponBonusCondition() const { return m_weaponBonusCondition; }
inline void setWeaponBonusConditionFlags(WeaponBonusConditionFlags flags) { m_weaponBonusCondition = flags; }
+ void applyWeaponBonusConditionFlags(WeaponBonusConditionFlags flags);
+ void removeWeaponBonusConditionFlags(WeaponBonusConditionFlags flags);
+
+ // Weapon Bonus Against, i.e. like Target Designator logic
+ inline void setWeaponBonusConditionAgainst(WeaponBonusConditionType wst) { m_weaponBonusConditionAgainst |= (1 << wst); };
+ inline void clearWeaponBonusConditionAgainst(WeaponBonusConditionType wst) { m_weaponBonusConditionAgainst &= ~(1 << wst); };
+ Bool testWeaponBonusConditionAgainst(WeaponBonusConditionType wst) const { return (m_weaponBonusConditionAgainst & (1 << wst)) != 0; }
+ inline WeaponBonusConditionFlags getWeaponBonusConditionAgainst() const { return m_weaponBonusConditionAgainst; }
+ inline void setWeaponBonusConditionFlagsAgainst(WeaponBonusConditionFlags flags) { m_weaponBonusConditionAgainst = flags; }
+
+
Bool getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const;
Bool getSingleLogicalBonePositionOnTurret(WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform) const;
Int getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, Coord3D* positions, Matrix3D* transforms, Bool convertToWorld = TRUE ) const;
@@ -741,7 +755,7 @@ class Object : public Thing, public Snapshot
UnsignedInt m_smcUntil;
- enum { NUM_SLEEP_HELPERS = 9 };
+ enum { NUM_SLEEP_HELPERS = 10 };
ObjectRepulsorHelper* m_repulsorHelper;
ObjectSMCHelper* m_smcHelper;
ObjectWeaponStatusHelper* m_wsHelper;
@@ -750,6 +764,7 @@ class Object : public Thing, public Snapshot
SubdualDamageHelper* m_subdualDamageHelper;
ChronoDamageHelper* m_chronoDamageHelper;
TempWeaponBonusHelper* m_tempWeaponBonusHelper;
+ BuffEffectHelper* m_buffEffectHelper;
FiringTracker* m_firingTracker; ///< Tracker is really a "helper" and is included NUM_SLEEP_HELPERS
// modules
@@ -790,6 +805,8 @@ class Object : public Thing, public Snapshot
WeaponBonusConditionFlags m_weaponBonusCondition;
Byte m_lastWeaponCondition[WEAPONSLOT_COUNT];
+ WeaponBonusConditionFlags m_weaponBonusConditionAgainst; ///< Weapon bonus granted when attacking this target;
+
SpecialPowerMaskType m_specialPowerBits; ///< bits determining what kind of special abilities this object has access to.
//////////////////////////////////////< for the non-stacking healers like ambulance and propaganda
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h
index 314aa3f250..a706f315d5 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h
@@ -205,7 +205,36 @@ enum WeaponBonusConditionType CPP_11(: Int)
WEAPONBONUSCONDITION_FRENZY_ONE,
WEAPONBONUSCONDITION_FRENZY_TWO,
WEAPONBONUSCONDITION_FRENZY_THREE,
- WEAPONBONUSCONDITION_CONTAINED,
+ // New added bonus types
+ WEAPONBONUSCONDITION_CONTAINED, // applied by default when contained
+ WEAPONBONUSCONDITION_FRENZY_FOUR,
+ WEAPONBONUSCONDITION_FRENZY_FIVE,
+
+ WEAPONBONUSCONDITION_BOOST_ONE,
+ WEAPONBONUSCONDITION_BOOST_TWO,
+ WEAPONBONUSCONDITION_BOOST_THREE,
+
+ WEAPONBONUSCONDITION_DEMORALIZED_ONE,
+ WEAPONBONUSCONDITION_DEMORALIZED_TWO,
+ WEAPONBONUSCONDITION_DEMORALIZED_THREE,
+
+ WEAPONBONUSCONDITION_TARGET_PAINT_ONE,
+ WEAPONBONUSCONDITION_TARGET_PAINT_TWO,
+ WEAPONBONUSCONDITION_TARGET_PAINT_THREE,
+
+ WEAPONBONUSCONDITION_CRYO_ONE,
+ WEAPONBONUSCONDITION_CRYO_TWO,
+ WEAPONBONUSCONDITION_CRYO_THREE,
+
+ WEAPONBONUSCONDITION_EXTRA1,
+ WEAPONBONUSCONDITION_EXTRA2,
+ WEAPONBONUSCONDITION_EXTRA3,
+ WEAPONBONUSCONDITION_EXTRA4,
+ WEAPONBONUSCONDITION_EXTRA5,
+ WEAPONBONUSCONDITION_EXTRA6,
+ WEAPONBONUSCONDITION_EXTRA7,
+ WEAPONBONUSCONDITION_EXTRA8,
+
WEAPONBONUSCONDITION_COUNT
};
@@ -245,7 +274,28 @@ static const char *TheWeaponBonusNames[] =
"FRENZY_TWO",
"FRENZY_THREE",
"CONTAINED",
-
+ "FRENZY_FOUR",
+ "FRENZY_FIVE",
+ "BOOST_ONE",
+ "BOOST_TWO",
+ "BOOST_THREE",
+ "DEMORALIZED_ONE",
+ "DEMORALIZED_TWO",
+ "DEMORALIZED_THREE",
+ "TARGET_PAINT_ONE",
+ "TARGET_PAINT_TWO",
+ "TARGET_PAINT_THREE",
+ "CRYO_ONE",
+ "CRYO_TWO",
+ "CRYO_THREE",
+ "EXTRA1",
+ "EXTRA2",
+ "EXTRA3",
+ "EXTRA4",
+ "EXTRA5",
+ "EXTRA6",
+ "EXTRA7",
+ "EXTRA8",
NULL
};
#endif
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp
index f15842701d..21e22e9568 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp
@@ -84,6 +84,7 @@
#include "GameLogic/RankInfo.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/SidesList.h"
+#include "GameLogic/BuffSystem.h"
#include "GameClient/ClientInstance.h"
#include "GameClient/Display.h"
@@ -157,10 +158,10 @@ SubsystemInterfaceList* TheSubsystemList = NULL;
//-------------------------------------------------------------------------------------------------
template
void initSubsystem(SUBSYSTEM*& sysref, AsciiString name, SUBSYSTEM* sys, Xfer *pXfer, const char* path1 = NULL,
- const char* path2 = NULL, const char* dirpath = NULL)
+ const char* path2 = NULL, const char* dirpath = NULL, bool optional = FALSE)
{
sysref = sys;
- TheSubsystemList->initSubsystem(sys, path1, path2, dirpath, pXfer, name);
+ TheSubsystemList->initSubsystem(sys, path1, path2, dirpath, pXfer, name, optional);
}
//-------------------------------------------------------------------------------------------------
@@ -539,7 +540,7 @@ void GameEngine::init()
initSubsystem(TheDamageFXStore,"TheDamageFXStore", MSGNEW("GameEngineSubsystem") DamageFXStore(), &xferCRC, NULL, "Data\\INI\\DamageFX.ini");
initSubsystem(TheArmorStore,"TheArmorStore", MSGNEW("GameEngineSubsystem") ArmorStore(), &xferCRC, NULL, "Data\\INI\\Armor.ini");
initSubsystem(TheBuildAssistant,"TheBuildAssistant", MSGNEW("GameEngineSubsystem") BuildAssistant, NULL);
-
+ initSubsystem(TheBuffTemplateStore, "TheBuffTemplateStore", MSGNEW("GameEngineSubsystem") BuffTemplateStore(), &xferCRC, NULL, "Data\\INI\\BuffTemplate.ini", NULL, TRUE);
#ifdef DUMP_PERF_STATS///////////////////////////////////////////////////////////////////////////
GetPrecisionTimer(&endTime64);//////////////////////////////////////////////////////////////////
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp
index e11ba37c2d..bc6f18a601 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp
@@ -55,6 +55,7 @@
#include "GameClient/Image.h"
#include "GameClient/ParticleSys.h"
#include "GameLogic/Armor.h"
+#include "GameLogic/BuffSystem.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/FPUControl.h"
#include "GameLogic/ObjectCreationList.h"
@@ -87,6 +88,7 @@ static const BlockParse theTypeTable[] =
{ "AudioEvent", INI::parseAudioEventDefinition },
{ "AudioSettings", INI::parseAudioSettingsDefinition },
{ "Bridge", INI::parseTerrainBridgeDefinition },
+ { "BuffTemplate", INI::parseBuffTemplateDefinition },
{ "Campaign", INI::parseCampaignDefinition },
{ "ChallengeGenerals", INI::parseChallengeModeDefinition },
{ "CommandButton", INI::parseCommandButtonDefinition },
@@ -261,7 +263,7 @@ void INI::loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
-void INI::prepFile( AsciiString filename, INILoadType loadType )
+void INI::prepFile( AsciiString filename, INILoadType loadType, Bool optional /*=FALSE*/)
{
// if we have a file open already -- we can't do another one
if( m_file != NULL )
@@ -276,7 +278,10 @@ void INI::prepFile( AsciiString filename, INILoadType loadType )
m_file = TheFileSystem->openFile(filename.str(), File::READ);
if( m_file == NULL )
{
-
+ if (optional) {
+ DEBUG_LOG(("INI::load, cannot open file '%s'", filename.str()));
+ return;
+ }
DEBUG_CRASH(( "INI::load, cannot open file '%s'", filename.str() ));
throw INI_CANT_OPEN_FILE;
@@ -348,12 +353,17 @@ static INIFieldParseProc findFieldParse(const FieldParse* parseTable, const char
//-------------------------------------------------------------------------------------------------
/** Load and parse an INI file */
//-------------------------------------------------------------------------------------------------
-void INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer )
+void INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer, Bool optional /*=FALSE*/)
{
setFPMode(); // so we have consistent Real values for GameLogic -MDC
s_xfer = pXfer;
- prepFile(filename, loadType);
+ prepFile(filename, loadType, optional);
+
+ if (optional && m_file == NULL) {
+ // unPrepFile();
+ return;
+ }
try
{
@@ -405,9 +415,7 @@ void INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer )
catch (...)
{
unPrepFile();
-
- // propagate the exception.
- throw;
+ throw;
}
unPrepFile();
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SubsystemInterface.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SubsystemInterface.cpp
index 23c3bd2bea..b78ef70197 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/System/SubsystemInterface.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SubsystemInterface.cpp
@@ -151,16 +151,16 @@ void SubsystemInterfaceList::removeSubsystem(SubsystemInterface* sys)
#endif
}
//-----------------------------------------------------------------------------
-void SubsystemInterfaceList::initSubsystem(SubsystemInterface* sys, const char* path1, const char* path2, const char* dirpath, Xfer *pXfer, AsciiString name)
+void SubsystemInterfaceList::initSubsystem(SubsystemInterface* sys, const char* path1, const char* path2, const char* dirpath, Xfer *pXfer, AsciiString name, Bool optional /*=FALSE*/)
{
sys->setName(name);
sys->init();
INI ini;
if (path1)
- ini.load(path1, INI_LOAD_OVERWRITE, pXfer );
+ ini.load(path1, INI_LOAD_OVERWRITE, pXfer, optional );
if (path2)
- ini.load(path2, INI_LOAD_OVERWRITE, pXfer );
+ ini.load(path2, INI_LOAD_OVERWRITE, pXfer, optional );
if (dirpath)
ini.loadDirectory(dirpath, TRUE, INI_LOAD_OVERWRITE, pXfer );
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
index bbab7d75a5..aaa0a915ac 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
@@ -198,6 +198,7 @@
#include "GameLogic/Module/TeleporterAIUpdate.h"
#include "GameLogic/Module/WaveGuideUpdate.h"
#include "GameLogic/Module/WeaponBonusUpdate.h"
+#include "GameLogic/Module/BuffUpdate.h"
#include "GameLogic/Module/ArmorDamageScalarUpdate.h"
#include "GameLogic/Module/WorkerAIUpdate.h"
#include "GameLogic/Module/PowerPlantUpdate.h"
@@ -435,6 +436,7 @@ void ModuleFactory::init( void )
addModule( LeafletDropBehavior );
addModule( AutoDepositUpdate );
addModule( WeaponBonusUpdate );
+ addModule( BuffUpdate );
addModule( ArmorDamageScalarUpdate );
addModule( MissileAIUpdate );
addModule( NeutronMissileUpdate );
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/BuffSystem.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/BuffSystem.cpp
new file mode 100644
index 0000000000..bfcd918dd5
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/BuffSystem.cpp
@@ -0,0 +1,741 @@
+/*
+** 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: BuffSystem.cpp ///////////////////////////////////////////////////////////////////////////////
+// Author: Andi W, October 25
+// Desc: Buff/Debuff effects
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
+
+#include "GameLogic/Module/BuffEffectHelper.h"
+
+#define DEFINE_WEAPONBONUSCONDITION_NAMES
+
+#include "Common/GlobalData.h"
+#include "GameLogic/Weapon.h"
+#include "GameLogic/GameLogic.h"
+#include "GameLogic/BuffSystem.h"
+#include "GameLogic/Module/AIUpdate.h"
+#include "GameLogic/Module/BodyModule.h"
+#include "GameClient/TintStatus.h"
+#include "GameClient/Drawable.h"
+#include "GameClient/ParticleSys.h"
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC DATA ////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// BUFF EFFECT NUGGETS AND ALL THEIR PARSE FUNCTIONS GO HERE:
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// GAMEPLAY EFFECTS / MODIFIER NUGGETS GO HERE:
+// ------------------------------------------------------------------------------------------------
+
+
+// Limit multipliers to [0.01, 100.0] to avoid numerical instability
+static void parseMultiplier(INI* ini, void* /*instance*/, void* store, const void* /*userData*/)
+{
+ Real value = INI::scanReal(ini->getNextToken());
+ *(Real*)store = __min(__max(value, 0.01f), 100.0f);
+}
+
+//-------------------------------------------------------------------------------------------------
+class ValueModifierBuffEffectNugget : public BuffEffectNugget
+{
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ValueModifierBuffEffectNugget, "ValueModifierBuffEffectNugget")
+public:
+
+ ValueModifierBuffEffectNugget()
+ {
+ m_armorDamageScalar = 1.0f;
+ m_sightRangeScalar = 1.0f;
+ m_moveSpeedScalar = 1.0f;
+ }
+
+ virtual void apply(Object* targetObj, const Object* sourceObj, BuffEffectTracker* buffTracker) const
+ {
+ DEBUG_LOG(("ValueModifierBuffEffectNugget::apply 0"));
+
+ //Note: We only apply one stack at a time! No need to consider stacks for now.
+
+ if (m_armorDamageScalar != 1.0) {
+ BodyModuleInterface* body = targetObj->getBodyModule();
+ if (body) {
+ body->applyDamageScalar(m_armorDamageScalar);
+ }
+ }
+
+ if (m_sightRangeScalar != 1.0) {
+ targetObj->setVisionRange(targetObj->getVisionRange() * m_sightRangeScalar);
+ targetObj->setShroudClearingRange(targetObj->getShroudClearingRange() * m_sightRangeScalar);
+ }
+
+ if (m_moveSpeedScalar != 1.0) {
+ AIUpdateInterface* ai = targetObj->getAI();
+ if (ai) {
+ ai->applySpeedMultiplier(m_moveSpeedScalar);
+ }
+ }
+ }
+
+ virtual void remove(Object* targetObj, BuffEffectTracker* buffTracker) const
+ {
+ // For now we remove all stacks at once, so we need to include the factor
+ Real exp = INT_TO_REAL(buffTracker->m_numStacks);
+ // TODO: THIS IS WRONG! IT'S NOT ADDITIVE BUT MULTIPLICATIVE
+
+ DEBUG_LOG(("ValueModifierBuffEffectNugget::remove 0"));
+
+ if (m_armorDamageScalar != 1.0) {
+ BodyModuleInterface* body = targetObj->getBodyModule();
+ if (body) {
+ body->applyDamageScalar(1.0f / pow(m_armorDamageScalar, exp));
+ }
+ }
+
+ if (m_sightRangeScalar != 1.0) {
+ Real scalar = 1.0f / pow(m_sightRangeScalar, exp);
+ targetObj->setVisionRange(targetObj->getVisionRange() * scalar);
+ targetObj->setShroudClearingRange(targetObj->getShroudClearingRange() * scalar);
+ }
+
+ if (m_moveSpeedScalar != 1.0) {
+ AIUpdateInterface* ai = targetObj->getAI();
+ if (ai) {
+ ai->applySpeedMultiplier(1.0f / pow(m_moveSpeedScalar, exp));
+ }
+ }
+
+ }
+
+ static void parse(INI* ini, void* instance, void* /*store*/, const void* /*userData*/)
+ {
+ static const FieldParse myFieldParse[] =
+ {
+ { "MovementSpeedScalar", parseMultiplier, NULL, offsetof(ValueModifierBuffEffectNugget, m_moveSpeedScalar) },
+ { "ArmorDamageScalar", parseMultiplier, NULL, offsetof(ValueModifierBuffEffectNugget, m_armorDamageScalar) },
+ { "SightRangeScalar", parseMultiplier, NULL, offsetof(ValueModifierBuffEffectNugget, m_sightRangeScalar) },
+ { 0, 0, 0, 0 }
+ };
+
+ MultiIniFieldParse p;
+ p.add(myFieldParse);
+
+ ValueModifierBuffEffectNugget* nugget = newInstance(ValueModifierBuffEffectNugget);
+
+ ini->initFromINIMulti(nugget, p);
+
+ ((BuffTemplate*)instance)->addBuffEffectNugget(nugget);
+ }
+
+private:
+ Real m_moveSpeedScalar;
+ Real m_armorDamageScalar;
+ Real m_sightRangeScalar;
+
+};
+EMPTY_DTOR(ValueModifierBuffEffectNugget)
+
+
+//-------------------------------------------------------------------------------------------------
+
+//Note: FlagModifierBuffEffectNugget is disabled. Instead we track flags directly in BuffTemplate
+// This is needed to allow overlapping buffs that use the same flags
+
+//class FlagModifierBuffEffectNugget : public BuffEffectNugget
+//{
+// MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(FlagModifierBuffEffectNugget, "FlagModifierBuffEffectNugget")
+//public:
+//
+// FlagModifierBuffEffectNugget()
+// {
+// m_bonusType = WEAPONBONUSCONDITION_INVALID;
+// m_bonusTypeAgainst = WEAPONBONUSCONDITION_INVALID;
+// m_weaponSetFlag = WEAPONSET_NONE;
+// m_armorSetFlag = ARMORSET_NONE;
+//
+// m_statusToSet.clear();
+// m_statusToClear.clear();
+// }
+//
+// virtual void apply(Object* targetObj, const Object* sourceObj, BuffEffectTracker* buffTracker) const
+// {
+// DEBUG_LOG(("FlagModifierBuffEffectNugget::apply 0"));
+//
+// if (m_bonusType != WEAPONBONUSCONDITION_INVALID) {
+// targetObj->setWeaponBonusCondition(m_bonusType);
+// }
+//
+// if (m_bonusTypeAgainst != WEAPONBONUSCONDITION_INVALID) {
+// targetObj->setWeaponBonusConditionAgainst(m_bonusTypeAgainst);
+// }
+//
+// if (m_weaponSetFlag != WEAPONSET_NONE) {
+// targetObj->setWeaponSetFlag(m_weaponSetFlag);
+// }
+//
+// if (m_armorSetFlag != ARMORSET_NONE) {
+// targetObj->setArmorSetFlag(m_armorSetFlag);
+// }
+//
+// // Any check needed, or just run it anyways?
+// targetObj->setStatus(m_statusToSet);
+// targetObj->clearStatus(m_statusToClear);
+//
+// }
+//
+// virtual void remove(Object* targetObj, BuffEffectTracker* buffTracker) const
+// {
+// DEBUG_LOG(("FlagModifierBuffEffectNugget::remove 0"));
+//
+// if (m_bonusType != WEAPONBONUSCONDITION_INVALID) {
+// targetObj->clearWeaponBonusCondition(m_bonusType);
+// }
+//
+// if (m_bonusTypeAgainst != WEAPONBONUSCONDITION_INVALID) {
+// targetObj->clearWeaponBonusConditionAgainst(m_bonusTypeAgainst);
+// }
+//
+// if (m_weaponSetFlag != WEAPONSET_NONE) {
+// targetObj->clearWeaponSetFlag(m_weaponSetFlag);
+// }
+//
+// if (m_armorSetFlag != ARMORSET_NONE) {
+// targetObj->clearArmorSetFlag(m_armorSetFlag);
+// }
+//
+// // Any check needed, or just run it anyways?
+// targetObj->clearStatus(m_statusToSet);
+// targetObj->setStatus(m_statusToClear);
+//
+// }
+//
+// static void parse(INI* ini, void* instance, void* /*store*/, const void* /*userData*/)
+// {
+// static const FieldParse myFieldParse[] =
+// {
+// { "WeaponBonus", INI::parseIndexList, TheWeaponBonusNames, offsetof(FlagModifierBuffEffectNugget, m_bonusType) },
+// { "WeaponBonusAgainst", INI::parseIndexList, TheWeaponBonusNames, offsetof(FlagModifierBuffEffectNugget, m_bonusTypeAgainst) },
+// { "WeaponSetFlag", INI::parseIndexList, WeaponSetFlags::getBitNames(), offsetof(FlagModifierBuffEffectNugget, m_weaponSetFlag) },
+// { "ArmorSetFlag", INI::parseIndexList, ArmorSetFlags::getBitNames(), offsetof(FlagModifierBuffEffectNugget, m_armorSetFlag) },
+// { "StatusToSet", ObjectStatusMaskType::parseFromINI, NULL, offsetof(FlagModifierBuffEffectNugget, m_statusToSet) },
+// { "StatusToClear", ObjectStatusMaskType::parseFromINI, NULL, offsetof(FlagModifierBuffEffectNugget, m_statusToClear) },
+// { 0, 0, 0, 0 }
+// };
+//
+// MultiIniFieldParse p;
+// p.add(myFieldParse);
+//
+// FlagModifierBuffEffectNugget* nugget = newInstance(FlagModifierBuffEffectNugget);
+//
+// ini->initFromINIMulti(nugget, p);
+//
+// ((BuffTemplate*)instance)->addBuffEffectNugget(nugget);
+// }
+//
+//private:
+// WeaponBonusConditionType m_bonusType; ///< weapon bonus granted to the object
+// WeaponBonusConditionType m_bonusTypeAgainst; ///< weapon bonus granted when attacking the object
+// WeaponSetType m_weaponSetFlag; ///< the weaponset flag to set
+// ArmorSetType m_armorSetFlag; ///< the armorset flag to set
+//
+// ObjectStatusMaskType m_statusToSet;
+// ObjectStatusMaskType m_statusToClear;
+//
+//};
+//EMPTY_DTOR(FlagModifierBuffEffectNugget)
+
+
+
+
+//-------------------------------------------------------------------------------------------------
+// VISUAL / AUDIO EFFECT NUGGETS GO HERE:
+//-------------------------------------------------------------------------------------------------
+
+//-------------------------------------------------------------------------------------------------
+class ColorTintBuffEffectNugget : public BuffEffectNugget
+{
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ColorTintBuffEffectNugget, "ColorTintBuffEffectNugget")
+public:
+
+ ColorTintBuffEffectNugget()
+ {
+ m_tintStatus = TINT_STATUS_INVALID;
+ }
+
+ virtual void apply(Object* targetObj, const Object* sourceObj, BuffEffectTracker* buffTracker) const
+ {
+ if (buffTracker->m_numStacks > 1) // can't stack this.
+ return;
+
+ Drawable* draw = targetObj->getDrawable();
+ if (draw)
+ {
+ if (m_tintStatus > TINT_STATUS_INVALID && m_tintStatus < TINT_STATUS_COUNT) {
+ draw->setTintStatus(m_tintStatus);
+ }
+ }
+ }
+
+ virtual void remove(Object* targetObj, BuffEffectTracker* buffTracker) const
+ {
+
+ Drawable* draw = targetObj->getDrawable();
+ if (draw)
+ {
+ if (m_tintStatus > TINT_STATUS_INVALID && m_tintStatus < TINT_STATUS_COUNT) {
+ draw->clearTintStatus(m_tintStatus);
+ }
+ }
+ }
+
+ static void parse(INI* ini, void* instance, void* /*store*/, const void* /*userData*/)
+ {
+ static const FieldParse myFieldParse[] =
+ {
+ { "TintStatusType", TintStatusFlags::parseSingleBitFromINI, NULL, offsetof(ColorTintBuffEffectNugget, m_tintStatus) },
+ { 0, 0, 0, 0 }
+ };
+
+ MultiIniFieldParse p;
+ p.add(myFieldParse);
+
+ ColorTintBuffEffectNugget* nugget = newInstance(ColorTintBuffEffectNugget);
+
+ ini->initFromINIMulti(nugget, p);
+
+ ((BuffTemplate*)instance)->addBuffEffectNugget(nugget);
+ }
+
+private:
+ TintStatus m_tintStatus;
+
+};
+EMPTY_DTOR(ColorTintBuffEffectNugget)
+
+
+//-------------------------------------------------------------------------------------------------
+class ParticleSystemBuffEffectNugget : public BuffEffectNugget
+{
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ParticleSystemBuffEffectNugget, "ParticleSystemBuffEffectNugget")
+public:
+
+ ParticleSystemBuffEffectNugget()
+ {
+ m_particleSysTemplate = NULL;
+ }
+
+ virtual void apply(Object* targetObj, const Object* sourceObj, BuffEffectTracker* buffTracker) const
+ {
+ if (buffTracker->m_numStacks > 1) // don't stack particle systems for now (could be an attribute)
+ return;
+
+ ParticleSystem* sys = TheParticleSystemManager->createParticleSystem(m_particleSysTemplate);
+
+ if (sys) {
+ sys->attachToObject(targetObj);
+ if (buffTracker) {
+ DEBUG_LOG(("ParticleSystemBuffEffectNugget::apply - add system %d to buffTracker", sys->getSystemID()));
+ buffTracker->addParticleSystem(sys->getSystemID());
+ }
+ //sys->setSystemLifetime(data->m_bonusDuration);
+ }
+ }
+
+ virtual void remove(Object* targetObj, BuffEffectTracker* buffTracker) const
+ {
+ // ParticleSystems are cleared for the whole Buff, not per nugget.
+ }
+
+ static void parse(INI* ini, void* instance, void* /*store*/, const void* /*userData*/)
+ {
+ static const FieldParse myFieldParse[] =
+ {
+ { "ParticleSystem", INI::parseParticleSystemTemplate, NULL, offsetof(ParticleSystemBuffEffectNugget, m_particleSysTemplate) },
+ { 0, 0, 0, 0 }
+ };
+
+ MultiIniFieldParse p;
+ p.add(myFieldParse);
+
+ ParticleSystemBuffEffectNugget* nugget = newInstance(ParticleSystemBuffEffectNugget);
+
+ ini->initFromINIMulti(nugget, p);
+
+ ((BuffTemplate*)instance)->addBuffEffectNugget(nugget);
+ }
+
+private:
+ const ParticleSystemTemplate* m_particleSysTemplate;
+
+};
+EMPTY_DTOR(ParticleSystemBuffEffectNugget)
+
+//-------------------------------------------------------------------------------------------------
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+// END BUFF EFFECT NUGGETS
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+const FieldParse BuffTemplate::TheBuffTemplateFieldParse[] =
+{
+ // Effect Nuggets:
+ { "ValueModifier", ValueModifierBuffEffectNugget::parse, 0, 0},
+ //{ "FlagModifier", FlagModifierBuffEffectNugget::parse, 0, 0},
+ { "FlagModifier", BuffTemplate::parseFlagModifier, 0, 0},
+ { "ColorTintEffect", ColorTintBuffEffectNugget::parse, 0, 0},
+ { "ParticleSystemEffect", ParticleSystemBuffEffectNugget::parse, 0, 0},
+ // Generic params:
+ { "MaxStacksSize", INI::parseUnsignedInt, NULL, offsetof(BuffTemplate, m_maxStackSize)},
+ { "HasPriorityOver", INI::parseAsciiStringVectorAppend, NULL, offsetof(BuffTemplate, m_priorityTemplates)},
+
+ { NULL, NULL, 0, 0 } // keep this last
+};
+
+
+// -----------
+// Note: This is a trick to make flags appear like a nugget.
+void BuffTemplate::parseFlagModifier(INI* ini, void* instance, void* /*store*/, const void* /*userData*/)
+ {
+ static const FieldParse myFieldParse[] =
+ {
+ { "WeaponBonus", INI::parseIndexList, TheWeaponBonusNames, offsetof(BuffTemplate, m_bonusType) },
+ { "WeaponBonusAgainst", INI::parseIndexList, TheWeaponBonusNames, offsetof(BuffTemplate, m_bonusTypeAgainst) },
+ { "WeaponSetFlag", INI::parseIndexList, WeaponSetFlags::getBitNames(), offsetof(BuffTemplate, m_weaponSetFlag) },
+ { "ArmorSetFlag", INI::parseIndexList, ArmorSetFlags::getBitNames(), offsetof(BuffTemplate, m_armorSetFlag) },
+ { "StatusToSet", ObjectStatusMaskType::parseFromINI, NULL, offsetof(BuffTemplate, m_statusToSet) },
+ // { "StatusToClear", ObjectStatusMaskType::parseFromINI, NULL, offsetof(BuffTemplate, m_statusToClear) },
+ { 0, 0, 0, 0 }
+ };
+
+ MultiIniFieldParse p;
+ p.add(myFieldParse);
+ ini->initFromINIMulti(instance, p);
+ }
+
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+BuffTemplate::BuffTemplate()
+{
+ m_maxStackSize = 1;
+ m_stackPerSource = FALSE;
+ m_priorityTemplates.clear();
+
+ m_bonusType = WEAPONBONUSCONDITION_INVALID;
+ m_bonusTypeAgainst = WEAPONBONUSCONDITION_INVALID;
+ m_weaponSetFlag = WEAPONSET_NONE;
+ m_armorSetFlag = ARMORSET_NONE;
+
+ m_statusToSet.clear();
+ // m_statusToClear.clear();
+
+ m_nuggets.clear();
+}
+
+//-------------------------------------------------------------------------------------------------
+void BuffTemplate::clear()
+{
+ m_bonusType = WEAPONBONUSCONDITION_INVALID;
+ m_bonusTypeAgainst = WEAPONBONUSCONDITION_INVALID;
+ m_weaponSetFlag = WEAPONSET_NONE;
+ m_armorSetFlag = ARMORSET_NONE;
+
+ m_statusToSet.clear();
+ // m_statusToClear.clear();
+
+ // do NOT delete the nuggets -- they're owned by the Store.
+ m_nuggets.clear();
+}
+
+//-------------------------------------------------------------------------------------------------
+void BuffTemplate::addBuffEffectNugget(BuffEffectNugget* nugget)
+{
+ m_nuggets.push_back(nugget);
+ TheBuffTemplateStore->addBuffEffectNugget(nugget);
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+
+void BuffTemplate::applyEffects(Object* targetObj, Object* sourceObj, BuffEffectTracker* buffTracker) const
+{
+ for (BuffEffectNugget* buffEffectNugget : m_nuggets) {
+ buffEffectNugget->apply(targetObj, sourceObj, buffTracker);
+ }
+
+ // -----------------------
+ // Handle Flag modifiers
+ if (m_bonusType != WEAPONBONUSCONDITION_INVALID) {
+ targetObj->setWeaponBonusCondition(m_bonusType);
+ }
+
+ if (m_bonusTypeAgainst != WEAPONBONUSCONDITION_INVALID) {
+ targetObj->setWeaponBonusConditionAgainst(m_bonusTypeAgainst);
+ }
+
+ if (m_weaponSetFlag != WEAPONSET_NONE) {
+ targetObj->setWeaponSetFlag(m_weaponSetFlag);
+ }
+
+ if (m_armorSetFlag != ARMORSET_NONE) {
+ targetObj->setArmorSetFlag(m_armorSetFlag);
+ }
+
+ // setStatus checks for changes
+ targetObj->setStatus(m_statusToSet);
+}
+//-------------------------------------------------------------------------------------------------
+
+
+void BuffTemplate::removeEffects(Object* targetObj, BuffEffectTracker* buffTracker, Bool ignoreFlags/*=FALSE*/) const
+{
+ for (BuffEffectNugget* buffEffectNugget : m_nuggets) {
+ buffEffectNugget->remove(targetObj, buffTracker);
+ }
+
+ buffTracker->clearParticleSystems();
+
+ // -----------------------
+ // Handle Flag modifiers
+ if (!ignoreFlags) {
+
+ if (m_bonusType != WEAPONBONUSCONDITION_INVALID) {
+ targetObj->clearWeaponBonusCondition(m_bonusType);
+ }
+
+ if (m_bonusTypeAgainst != WEAPONBONUSCONDITION_INVALID) {
+ targetObj->clearWeaponBonusConditionAgainst(m_bonusTypeAgainst);
+ }
+
+ if (m_weaponSetFlag != WEAPONSET_NONE) {
+ targetObj->clearWeaponSetFlag(m_weaponSetFlag);
+ }
+
+ if (m_armorSetFlag != ARMORSET_NONE) {
+ targetObj->clearArmorSetFlag(m_armorSetFlag);
+ }
+
+ // setStatus checks for changes
+ targetObj->clearStatus(m_statusToSet);
+
+ }
+}
+
+//-------------------------------------------------------------------------------------------------
+
+void BuffTemplate::reApplyFlags(Object* targetObj, const BuffTemplate* other) const
+{
+ //Note: other = buff that has been removed.
+ if (other->m_bonusType != WEAPONBONUSCONDITION_INVALID && other->m_bonusType == m_bonusType) {
+ targetObj->setWeaponBonusCondition(m_bonusType);
+ }
+
+ if (other->m_bonusTypeAgainst != WEAPONBONUSCONDITION_INVALID && other->m_bonusTypeAgainst == m_bonusTypeAgainst) {
+ targetObj->setWeaponBonusConditionAgainst(m_bonusTypeAgainst);
+ }
+
+ if (other->m_weaponSetFlag != WEAPONSET_NONE && other->m_weaponSetFlag == m_weaponSetFlag) {
+ targetObj->setWeaponSetFlag(m_weaponSetFlag);
+ }
+
+ if (other->m_armorSetFlag != ARMORSET_NONE && other->m_armorSetFlag == m_armorSetFlag) {
+ targetObj->setArmorSetFlag(m_armorSetFlag);
+ }
+
+ if (other->m_statusToSet != OBJECT_STATUS_MASK_NONE
+ && TEST_OBJECT_STATUS_MASK_ANY(other->m_statusToSet, m_statusToSet)) {
+ targetObj->setStatus(m_statusToSet);
+ }
+
+}
+//-------------------------------------------------------------------------------------------------
+
+UnsignedInt BuffTemplate::getNextTickFrame(UnsignedInt startFrame, UnsignedInt endFrame) const
+{
+ // TODO: If we have continuos buff effects (e.g. DOT), we need to consider this here.
+ return endFrame;
+}
+
+//-------------------------------------------------------------------------------------------------
+Bool BuffTemplate::hasPriorityOver(AsciiString templateName) const
+{
+ for (AsciiString str : m_priorityTemplates) {
+ if (str == templateName)
+ return TRUE;
+ }
+ return FALSE;
+}
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+
+
+BuffTemplateStore* TheBuffTemplateStore = NULL; ///< the TheBuffTemplate store definition
+//-------------------------------------------------------------------------------------------------
+BuffTemplateStore::BuffTemplateStore()
+{
+}
+
+//-------------------------------------------------------------------------------------------------
+BuffTemplateStore::~BuffTemplateStore()
+{
+ // Delete all nuggets
+ for (BuffEffectNuggetVector::iterator i = m_nuggets.begin(); i != m_nuggets.end(); ++i)
+ {
+ if (*i)
+ deleteInstance(*i);
+ }
+ m_nuggets.clear();
+
+ // Delete all templates (?)
+ // delete all the templates, then clear out the table.
+ BuffTemplateMap::iterator it;
+ for (it = m_buffTemplates.begin(); it != m_buffTemplates.end(); ++it) {
+ deleteInstance(it->second);
+ }
+
+ m_buffTemplates.clear();
+}
+
+//-------------------------------------------------------------------------------------------------
+const BuffTemplate* BuffTemplateStore::findBuffTemplate(const char* name) const
+{
+ if (stricmp(name, "None") == 0)
+ return NULL;
+
+ BuffTemplateMap::const_iterator it = m_buffTemplates.find(NAMEKEY(name));
+ if (it == m_buffTemplates.end())
+ {
+ return NULL;
+ }
+ else
+ {
+ return (*it).second;
+ }
+}
+
+//-------------------------------------------------------------------------------------------------
+BuffTemplate* BuffTemplateStore::findBuffTemplate(const char* name)
+{
+ if (stricmp(name, "None") == 0)
+ return NULL;
+
+ BuffTemplateMap::iterator it = m_buffTemplates.find(NAMEKEY(name));
+ if (it == m_buffTemplates.end())
+ {
+ return NULL;
+ }
+ else
+ {
+ return (*it).second;
+ }
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+void BuffTemplateStore::addBuffEffectNugget(BuffEffectNugget* nugget)
+{
+ m_nuggets.push_back(nugget);
+}
+
+//-------------------------------------------------------------------------------------------------
+//BuffTemplate* BuffTemplateStore::newBuffTemplate(AsciiString& name)
+//{
+// BuffTemplate* buffTemplate = const_cast(TheBuffTemplateStore->findBuffTemplate(name));
+// if (buffTemplate == NULL) {
+// buffTemplate = newInstance(BuffTemplate)(name);
+//
+// if (!m_templateMap.insert(std::make_pair(name, sysTemplate)).second) {
+// deleteInstance(sysTemplate);
+// sysTemplate = NULL;
+// }
+// }
+//
+// return buffTemplate;
+//}
+
+//-------------------------------------------------------------------------------------------------
+///*static */ void BuffTemplateStore::parseBuffTemplateDefinition(INI* ini)
+//{
+// AsciiString name;
+//
+// // read the BuffTemplate name
+// const char* c = ini->getNextToken();
+// name.set(c);
+//
+// // Does this make sense?
+// if (!TheBuffTemplateStore) {
+// return;
+// }
+//
+// NameKeyType key = TheNameKeyGenerator->nameToKey(c);
+// BuffTemplate& buffTmp = TheBuffTemplateStore->m_buffTemplates[key];
+// buffTmp.clear();
+// ini->initFromINI(&buffTmp, TheBuffTemplateFieldParse);
+// buffTmp.friend_setName(name);
+//
+// DEBUG_LOG((">>> ADDED BUFF TEMPLATE '%s'", buffTmp.getName().str()));
+//}
+
+//-------------------------------------------------------------------------------------------------
+/*static */ void BuffTemplateStore::parseBuffTemplateDefinition(INI* ini)
+{
+ AsciiString name;
+ BuffTemplate* buffTmp;
+
+ // read the BuffTemplate name
+ const char* c = ini->getNextToken();
+ name.set(c);
+
+ // Does this make sense?
+ if (!TheBuffTemplateStore) {
+ return;
+ }
+
+ NameKeyType key = TheNameKeyGenerator->nameToKey(c);
+
+ //buffTmp = TheBuffTemplateStore->newBuffTemplate(name);
+ buffTmp = newInstance(BuffTemplate);
+
+ buffTmp->clear();
+ buffTmp->friend_setName(name);
+ ini->initFromINI(buffTmp, buffTmp->getFieldParse());
+
+ TheBuffTemplateStore->m_buffTemplates[key] = buffTmp;
+
+ DEBUG_LOG((">>> ADDED BUFF TEMPLATE '%s'", buffTmp->getName().str()));
+}
+
+//-------------------------------------------------------------------------------------------------
+/*static*/ void INI::parseBuffTemplateDefinition(INI* ini)
+{
+ BuffTemplateStore::parseBuffTemplateDefinition(ini);
+}
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp
index e7904cbebf..e32ea4abc3 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp
@@ -82,6 +82,7 @@ void FiringTracker::shotFired(const Weapon* weaponFired, ObjectID victimID)
Object *me = getObject();
const Object *victim = TheGameLogic->findObjectByID(victimID); // May be null for ground shot
+ // Old Target Designator Logic
if( victim && victim->testStatus(OBJECT_STATUS_FAERIE_FIRE) )
{
if( !me->testWeaponBonusCondition(WEAPONBONUSCONDITION_TARGET_FAERIE_FIRE) )
@@ -99,6 +100,26 @@ void FiringTracker::shotFired(const Weapon* weaponFired, ObjectID victimID)
}
}
+ // New Buff based 'WeaponBonusAgainst' Logic
+ {
+ WeaponBonusConditionFlags targetBonusFlags = 0; // if we attack the ground, this stays empty
+ if (victim)
+ targetBonusFlags = victim->getWeaponBonusConditionAgainst();
+
+ // If new bonus is different from previous, remove it.
+ if (targetBonusFlags != m_prevTargetWeaponBonus) {
+ me->removeWeaponBonusConditionFlags(m_prevTargetWeaponBonus);
+ }
+
+ // If we have a new bonus, apply it
+ if (targetBonusFlags != 0) {
+ me->applyWeaponBonusConditionFlags(targetBonusFlags);
+ }
+
+ m_prevTargetWeaponBonus = targetBonusFlags;
+
+ }
+
if( victimID == m_victimID )
{
// Shooting at the same guy
@@ -376,6 +397,9 @@ void FiringTracker::xfer( Xfer *xfer )
// frame to start cooldown
xfer->xferUnsignedInt( &m_frameToStartCooldown );
+ // currenly applied weaponBonus against the prev target
+ xfer->xferUnsignedInt(&m_prevTargetWeaponBonus);
+
} // end xfer
// ------------------------------------------------------------------------------------------------
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Helper/BuffEffectHelper.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Helper/BuffEffectHelper.cpp
new file mode 100644
index 0000000000..d6427f59c9
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Helper/BuffEffectHelper.cpp
@@ -0,0 +1,449 @@
+/*
+** 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: BuffEffectHelper.h ////////////////////////////////////////////////////////////////////////
+// Author: Andi W, Oct 25
+// Desc: Buff Effect Helper - Tracks active buffs
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "PreRTS.h"
+#include "Common/Xfer.h"
+
+#include "GameLogic/Module/BuffEffectHelper.h"
+
+#include "GameClient/Drawable.h"
+#include "GameLogic/GameLogic.h"
+#include "GameLogic/Object.h"
+#include "GameLogic/Weapon.h"
+
+// #include
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+BuffEffectHelper::BuffEffectHelper(Thing* thing, const ModuleData* modData) : ObjectHelper(thing, modData)
+{
+ m_buffEffects.clear();
+ m_nextTickFrame = UnsignedInt(UPDATE_SLEEP_FOREVER);
+
+ setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+BuffEffectHelper::~BuffEffectHelper(void)
+{
+ clearAllBuffs();
+ // Do we need to actually delete items from m_buffEffects?
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+void BuffEffectHelper::clearAllBuffs()
+{
+ for (BuffEffectTracker& buff : m_buffEffects) {
+ if (buff.m_isActive) {
+ buff.m_template->removeEffects(getObject(), &buff);
+ }
+ }
+
+ m_buffEffects.clear();
+ m_nextTickFrame = UnsignedInt(UPDATE_SLEEP_FOREVER);
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+UpdateSleepTime BuffEffectHelper::update()
+{
+ // TODO: Are there any cases were buffs work on dead objects?
+ // Also, should we even get here on a dead object?
+ if (getObject()->isEffectivelyDead()) {
+ clearAllBuffs();
+ return UPDATE_SLEEP_FOREVER;
+ }
+
+ UnsignedInt now = TheGameLogic->getFrame();
+
+ DEBUG_LOG(("BuffEffectHelper::update 0 - (frame = %d)", now));
+
+ // Loop over all active BuffEffectTrackers and check if they expired or need to do something
+
+ Bool removed = FALSE;
+ std::set templatesToRemove;
+ UnsignedInt closestNextTickFrame = UnsignedInt(UPDATE_SLEEP_FOREVER);
+
+ for (auto it = m_buffEffects.begin(); it != m_buffEffects.end(); ) {
+ BuffEffectTracker& bet = *it;
+ if (bet.m_frameToRemove <= now) {
+ // remove buff effect
+ // TODO: Handle dynamic "unstacking"
+
+ // Only remove effects if we were active
+ if (bet.m_isActive) {
+ bet.m_template->removeEffects(getObject(), &bet);
+ templatesToRemove.insert(bet.m_template);
+ removed = TRUE;
+ }
+
+ it = m_buffEffects.erase(it);
+ }
+ else {
+ // TODO: Handle Continuous effects (DOTS)
+
+ UnsignedInt nextTickFrame = bet.m_template->getNextTickFrame(bet.m_frameCreated, bet.m_frameToRemove);
+ if (nextTickFrame < closestNextTickFrame)
+ closestNextTickFrame = nextTickFrame;
+
+ ++it;
+ }
+ }
+
+ // If we removed a buff, we need to re-evaluate all priorities
+ if (removed) {
+ std::set tmplSet; // Template names that any current buff has priority over
+ for (BuffEffectTracker& bet : m_buffEffects) {
+ if (bet.m_template->getPriorityTemplates().size() > 0)
+ tmplSet.insert(bet.m_template->getPriorityTemplates().begin(), bet.m_template->getPriorityTemplates().end());
+ }
+
+ // Now loop again to check active/inactive status
+ for (BuffEffectTracker& bet : m_buffEffects) {
+
+ // Any flag that was removed, but is still set by an active status needs to be reapplied
+ if (bet.m_isActive) {
+ for (const BuffTemplate* tmplToRemove : templatesToRemove) {
+ bet.m_template->reApplyFlags(getObject(), tmplToRemove);
+ }
+ }
+
+ bool isLowerPriority = tmplSet.count(bet.m_template->getName());
+
+ // Note: after removal, we can only activate, never deactivate existing buffs!
+ //if (isLowerPriority && bet.m_isActive) {
+ // bet.m_isActive = FALSE;
+ // bet.m_template->removeEffects(getObject(), &bet);
+ //}
+
+ // Set inactive buffs to active
+ if (!isLowerPriority && !bet.m_isActive) {
+ bet.m_isActive = TRUE;
+ Object* sourceObj = TheGameLogic->findObjectByID(bet.m_sourceID);
+ bet.m_template->applyEffects(getObject(), sourceObj, &bet);
+ }
+
+ }
+
+ }
+
+ return frameToSleepTime(closestNextTickFrame);
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+void BuffEffectHelper::applyBuff(const BuffTemplate* buffTemplate, Object* sourceObj, UnsignedInt duration)
+{
+ DEBUG_LOG(("BuffEffectHelper::applyBuff 0"));
+
+ // Note: Should we check if the buff is allowed to be applied here? Or should that happen
+ // before, wherever this method is called?
+
+ UnsignedInt now = TheGameLogic->getFrame();
+
+ Bool addBuff = TRUE;
+ Bool setActive = TRUE;
+ //TODO: also keep track of next tick frame here
+
+ // Check if we have existing buffs with the same template
+
+ std::set templatesToRemove;
+
+ for (BuffEffectTracker& buff : m_buffEffects) {
+
+ if (buff.m_template == buffTemplate)
+ { // Check if buff with same template exists
+
+ if (!buffTemplate->isStackPerSource() || // And if we stack with the same source only
+ ((sourceObj->getID() == buff.m_sourceID) && (buff.m_sourceID != INVALID_ID)))
+ {
+
+ // buff exists -> refresh duration; don't add a new buff
+ buff.m_frameToRemove = now + duration;
+ if (sourceObj) // always use the last sourceID
+ buff.m_sourceID = sourceObj->getID();
+ addBuff = FALSE;
+
+ // If more stacks are allowed, apply effects again
+ if (buffTemplate->getMaxStackSize() > buff.m_numStacks) {
+ buff.m_numStacks += 1;
+ buffTemplate->applyEffects(getObject(), sourceObj, &buff);
+ }
+ }
+
+ }
+ else // check other buffs
+ {
+ // An existing buff has priority over the one we are adding.
+ // This case should never occur if we are already active and increase the stackSize.
+ if (buff.m_template->hasPriorityOver(buffTemplate->getName())) {
+ setActive = FALSE;
+ }
+
+ // Our new buff has priority over an existing one.
+ // This case should not matter if we are just refreshing.
+ if (buffTemplate->hasPriorityOver(buff.m_template->getName())) {
+ // Only disable it if it was previously active.
+ // At this point we do not know yet if our new buff will be active or not.
+ // But we should never have cyclic priorities so this is ok.
+ if (buff.m_isActive) {
+ buff.m_template->removeEffects(getObject(), &buff);
+ buff.m_isActive = FALSE;
+
+ // When we remove an effect, we need to re-evaluate all flags.
+ templatesToRemove.insert(buff.m_template);
+ }
+ }
+ }
+
+ // We are looping through all buff effects, so we might as well get our next tickFrame
+ UnsignedInt nextTick = buff.m_template->getNextTickFrame(buff.m_frameCreated, buff.m_frameToRemove);
+ if (nextTick < m_nextTickFrame)
+ m_nextTickFrame = nextTick;
+ }
+
+ // Looping again to evaluate flags
+ for (BuffEffectTracker& bet : m_buffEffects) {
+ // Any flag that was removed, but is still set by an active status needs to be reapplied
+ if (bet.m_isActive) {
+ for (const BuffTemplate* tmplToRemove : templatesToRemove) {
+ bet.m_template->reApplyFlags(getObject(), tmplToRemove);
+ }
+ }
+ }
+
+ // We add a new buff to our list
+ if (addBuff) {
+ // Setup BuffEffectTracker entry and add it to the list; TODO: Make this a proper class with memoryPool?
+ BuffEffectTracker bet;
+ bet.m_template = buffTemplate;
+ bet.m_frameCreated = now;
+ bet.m_frameToRemove = now + duration;
+ bet.m_numStacks = 1; //todo
+ bet.m_isActive = setActive; //todo
+ if (sourceObj)
+ bet.m_sourceID = sourceObj->getID();
+
+ m_buffEffects.push_back(bet);
+
+ // Apply the actual buff:
+ if (setActive)
+ buffTemplate->applyEffects(getObject(), sourceObj, &m_buffEffects.back()); //the vector has a copy, use that one.
+
+ // Calculate our next wake frame
+ UnsignedInt nextTick = buffTemplate->getNextTickFrame(now, now + duration);
+ if (nextTick < m_nextTickFrame) {
+ m_nextTickFrame = nextTick;
+ }
+ // -------------------------------
+ }
+
+ DEBUG_LOG(("BuffEffectHelper::applyBuff 1 -- m_nextTickFrame = %d", m_nextTickFrame));
+ setWakeFrame(getObject(), frameToSleepTime(m_nextTickFrame));
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+//void BuffEffectHelper::clearTempWeaponBonus()
+//{
+// if (m_currentBonus != WEAPONBONUSCONDITION_INVALID)
+// {
+// getObject()->clearWeaponBonusCondition(m_currentBonus);
+// m_currentBonus = WEAPONBONUSCONDITION_INVALID;
+// m_frameToRemove = 0;
+//
+// if (getObject()->getDrawable())
+// {
+// if (m_currentTint > TINT_STATUS_INVALID && m_currentTint < TINT_STATUS_COUNT) {
+// getObject()->getDrawable()->clearTintStatus(m_currentTint);
+// m_currentTint = TINT_STATUS_INVALID;
+// }
+//
+// // getObject()->getDrawable()->clearTintStatus(TINT_STATUS_FRENZY);
+// // if (getObject()->isKindOf(KINDOF_INFANTRY))
+// // getObject()->getDrawable()->setSecondMaterialPassOpacity( 0.0f );
+// }
+// }
+//}
+//
+//// ------------------------------------------------------------------------------------------------
+//// ------------------------------------------------------------------------------------------------
+//void BuffEffectHelper::doTempWeaponBonus(WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus)
+//{
+// // Clear any different status we may have. Re-getting the same status will just reset the timer
+// if (m_currentBonus != status)
+// clearTempWeaponBonus();
+//
+// getObject()->setWeaponBonusCondition(status);
+// m_currentBonus = status;
+// m_frameToRemove = TheGameLogic->getFrame() + duration;
+//
+// if (getObject()->getDrawable())
+// {
+// if (tintStatus > TINT_STATUS_INVALID && tintStatus < TINT_STATUS_COUNT) {
+// getObject()->getDrawable()->setTintStatus(tintStatus);
+// m_currentTint = tintStatus;
+// }
+//
+// // getObject()->getDrawable()->setTintStatus(TINT_STATUS_FRENZY);
+//
+// // if (getObject()->isKindOf(KINDOF_INFANTRY))
+// // getObject()->getDrawable()->setSecondMaterialPassOpacity( 1.0f );
+// }
+//
+// setWakeFrame(getObject(), UPDATE_SLEEP(duration));
+//}
+
+// ------------------------------------------------------------------------------------------------
+/** CRC */
+// ------------------------------------------------------------------------------------------------
+void BuffEffectHelper::crc(Xfer* xfer)
+{
+
+ // object helper crc
+ ObjectHelper::crc(xfer);
+
+} // end crc
+
+// ------------------------------------------------------------------------------------------------
+/** Xfer method
+ * Version Info;
+ * 1: Initial version */
+ // ------------------------------------------------------------------------------------------------
+void BuffEffectHelper::xfer(Xfer* xfer)
+{
+
+ // version
+ XferVersion currentVersion = 1;
+ XferVersion version = currentVersion;
+ xfer->xferVersion(&version, currentVersion);
+
+ // object helper base class
+ ObjectHelper::xfer(xfer);
+
+
+ // Next tick frame
+ xfer->xferUnsignedInt(&m_nextTickFrame);
+
+ // Buff Effect Tracker list
+ // ---------
+ // count of vector
+ UnsignedShort count = m_buffEffects.size();
+ xfer->xferUnsignedShort(&count);
+
+ // data
+ if (xfer->getXferMode() == XFER_SAVE)
+ {
+ for (std::vector::iterator it = m_buffEffects.begin(); it != m_buffEffects.end(); ++it)
+ {
+ BuffEffectTracker bet = *it;
+ // m_template
+ AsciiString templateName = bet.m_template->getName();
+ xfer->xferAsciiString(&templateName);
+ // --
+ xfer->xferUnsignedInt(&bet.m_frameCreated);
+ xfer->xferUnsignedInt(&bet.m_frameToRemove);
+ xfer->xferUnsignedInt(&bet.m_numStacks);
+ xfer->xferObjectID(&bet.m_sourceID);
+ xfer->xferBool(&bet.m_isActive);
+
+ // particle system vector count and data
+ UnsignedShort particleSystemCount = bet.m_particleSystemIDs.size();
+ xfer->xferUnsignedShort(&particleSystemCount);
+ ParticleSystemID systemID;
+
+ std::vector::const_iterator it2;
+ for (it2 = bet.m_particleSystemIDs.begin(); it2 != bet.m_particleSystemIDs.end(); ++it2)
+ {
+ systemID = *it2;
+ xfer->xferUser(&systemID, sizeof(ParticleSystemID));
+ }
+ }
+ }
+ else if (xfer->getXferMode() == XFER_LOAD)
+ {
+ // vector should be empty at this point
+ if (m_buffEffects.empty() == FALSE)
+ {
+ DEBUG_CRASH(("BuffEffectHelper::xfer - vector is not empty, but should be"));
+ throw XFER_LIST_NOT_EMPTY;
+ }
+
+ for (UnsignedShort i = 0; i < count; ++i)
+ {
+ AsciiString templateName;
+ xfer->xferAsciiString(&templateName);
+
+ BuffTemplate* bt = TheBuffTemplateStore->findBuffTemplate(templateName);
+ if (bt == NULL)
+ {
+ DEBUG_CRASH(("BuffEffectHelper::xfer - template %s not found", templateName.str()));
+ throw XFER_UNKNOWN_STRING;
+ }
+
+ BuffEffectTracker bet;
+ bet.m_template = bt;
+ xfer->xferUnsignedInt(&bet.m_frameCreated);
+ xfer->xferUnsignedInt(&bet.m_frameToRemove);
+ xfer->xferUnsignedInt(&bet.m_numStacks);
+ xfer->xferObjectID(&bet.m_sourceID);
+ xfer->xferBool(&bet.m_isActive);
+
+ // particle system vector count and data
+ UnsignedShort particleSystemCount;
+ xfer->xferUnsignedShort(&particleSystemCount);
+
+ for (UnsignedShort i = 0; i < particleSystemCount; ++i)
+ {
+ ParticleSystemID systemID;
+ xfer->xferUser(&systemID, sizeof(ParticleSystemID));
+ bet.m_particleSystemIDs.push_back(systemID);
+ }
+
+ m_buffEffects.push_back(bet);
+ }
+ }
+
+
+} // end xfer
+
+// ------------------------------------------------------------------------------------------------
+/** Load post process */
+// ------------------------------------------------------------------------------------------------
+void BuffEffectHelper::loadPostProcess(void)
+{
+
+ // object helper base class
+ ObjectHelper::loadPostProcess();
+
+} // end loadPostProcess
+
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
index 85e884c061..4dd9c24214 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
@@ -96,6 +96,7 @@
#include "GameLogic/Module/SubdualDamageHelper.h"
#include "GameLogic/Module/ChronoDamageHelper.h"
#include "GameLogic/Module/TempWeaponBonusHelper.h"
+#include "GameLogic/Module/BuffEffectHelper.h"
#include "GameLogic/Module/ToppleUpdate.h"
#include "GameLogic/Module/UpdateModule.h"
#include "GameLogic/Module/UpgradeModule.h"
@@ -237,6 +238,7 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu
m_repulsorHelper(NULL),
m_statusDamageHelper(NULL),
m_tempWeaponBonusHelper(NULL),
+ m_buffEffectHelper(NULL),
m_subdualDamageHelper(NULL),
m_chronoDamageHelper(NULL),
m_smcHelper(NULL),
@@ -287,6 +289,7 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu
}
m_weaponBonusCondition = 0;
+ m_weaponBonusConditionAgainst = 0;
m_curWeaponSetFlags.clear();
// sanity
@@ -446,6 +449,16 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu
*curB++ = m_tempWeaponBonusHelper;
}
+ // TODO: Are there any kinds of objects that cannot get buffs at all?
+ {
+ static const NameKeyType buffEffectHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_BuffEffectHelper" );
+ static BuffEffectHelperModuleData tempBuffEffectHelperModuleData;
+ tempBuffEffectHelperModuleData.setModuleTagNameKey( buffEffectHelperModuleDataTagNameKey );
+ m_buffEffectHelper = newInstance(BuffEffectHelper)(this, &tempBuffEffectHelperModuleData);
+ *curB++ = m_buffEffectHelper;
+ }
+
+
// behaviors are always done first, so they get into the publicModule arrays
// before anything else.
for (modIdx = 0; modIdx < mi.getCount(); ++modIdx)
@@ -716,6 +729,7 @@ Object::~Object()
m_tempWeaponBonusHelper = NULL;
m_subdualDamageHelper = NULL;
m_chronoDamageHelper = NULL;
+ m_buffEffectHelper = NULL;
m_smcHelper = NULL;
m_wsHelper = NULL;
m_defectionHelper = NULL;
@@ -4126,6 +4140,15 @@ void Object::crc( Xfer *xfer )
}
#endif // DEBUG_CRC
+ xfer->xferUnsignedInt(&m_weaponBonusConditionAgainst);
+#ifdef DEBUG_CRC
+ if (doLogging)
+ {
+ tmp.format("m_weaponBonusConditionAgainst: %8.8X, ", m_weaponBonusConditionAgainst);
+ logString.concat(tmp);
+ }
+#endif // DEBUG_CRC
+
Real scalar = getBodyModule()->getDamageScalar();
xfer->xferUser(&scalar, sizeof(scalar));
#ifdef DEBUG_CRC
@@ -4570,6 +4593,8 @@ void Object::xfer( Xfer *xfer )
else
m_isReceivingDifficultyBonus = FALSE;
+ xfer->xferUnsignedInt(&m_weaponBonusConditionAgainst);
+
} // end xfer
//-------------------------------------------------------------------------------------------------
@@ -4830,6 +4855,34 @@ void Object::clearWeaponBonusCondition(WeaponBonusConditionType wst)
}
}
+//-------------------------------------------------------------------------------------------------
+// Apply/Remove multiple flags at once
+//-------------------------------------------------------------------------------------------------
+void Object::applyWeaponBonusConditionFlags(WeaponBonusConditionFlags flags)
+{
+ WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition;
+ m_weaponBonusCondition |= flags;
+
+ if (oldCondition != m_weaponBonusCondition)
+ {
+ // Our weapon bonus just changed, so we need to immediately update our weapons
+ m_weaponSet.weaponSetOnWeaponBonusChange(this);
+ }
+}
+
+//-------------------------------------------------------------------------------------------------
+void Object::removeWeaponBonusConditionFlags(WeaponBonusConditionFlags flags)
+ {
+ WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition;
+ m_weaponBonusCondition &= ~flags;
+
+ if (oldCondition != m_weaponBonusCondition)
+ {
+ // Our weapon bonus just changed, so we need to immediately update our weapons
+ m_weaponSet.weaponSetOnWeaponBonusChange(this);
+ }
+ }
+
//-------------------------------------------------------------------------------------------------
/**
A weapon cannot be in charge of maintaining condition flags as it is all event driven.
@@ -5486,6 +5539,14 @@ void Object::notifyChronoDamage(Real amount)
}
}
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+void Object::applyBuff(const BuffTemplate* buffTemp, UnsignedInt duration, Object* sourceObj)
+{
+ DEBUG_LOG(("Object::applyBuff '%s' to obj '%s'", buffTemp->getName().str(), getTemplate()->getName().str()));
+ if (m_buffEffectHelper)
+ m_buffEffectHelper->applyBuff(buffTemp, sourceObj, duration);
+}
//-------------------------------------------------------------------------------------------------
/** Given a special power template, find the module in the object that can implement it.
* There can be at most one */
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp
index f4ce174025..c7e3aee95a 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp
@@ -322,6 +322,7 @@ AIUpdateInterface::AIUpdateInterface( Thing *thing, const ModuleData* moduleData
m_retryPath = FALSE;
m_isInUpdate = FALSE;
m_fixLocoInPostProcess = FALSE;
+ m_speedMultiplier = 1.0;
// ---------------------------------------------
@@ -949,6 +950,10 @@ void AIUpdateInterface::chooseGoodLocomotorFromCurrentSet( void )
m_curLocomotor->setNoSlowDownAsApproachingDest(FALSE);
// ditto for ultra-accuracy.
m_curLocomotor->setUltraAccurate(FALSE);
+
+ // Add speed multiplier to loco
+ if (m_speedMultiplier != 1.0)
+ m_curLocomotor->applySpeedMultiplier(m_speedMultiplier);
}
}
@@ -4568,6 +4573,14 @@ Bool AIUpdateInterface::canAutoAcquireWhileStealthed() const
}
+//----------------------------------------------------------------------------------------------
+void AIUpdateInterface::applySpeedMultiplier(Real scalar) {
+ m_speedMultiplier *= scalar;
+ if (m_curLocomotor)
+ m_curLocomotor->applySpeedMultiplier(scalar); // Use Set instead of Apply?
+}
+
+
//----------------------------------------------------------------------------------------------
/**
* Return the next object that our mood suggests we should attack.
@@ -5315,6 +5328,7 @@ void AIUpdateInterface::xfer( Xfer *xfer )
xfer->xferInt(&repulsorCountdown);
}
+ xfer->xferReal(&m_speedMultiplier);
} // end xfer
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BuffUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BuffUpdate.cpp
new file mode 100644
index 0000000000..a9f2056ba5
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BuffUpdate.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: BuffUpdate.cpp /////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+//
+// Electronic Arts Pacific.
+//
+// Confidential Information
+// Copyright (C) 2002-2003 - All Rights Reserved
+//
+//-----------------------------------------------------------------------------
+//
+// created: Oct 25
+//
+// Filename: BuffUpdate.cpp
+//
+// author: Andi W
+//
+// purpose: apply a buff/debuff effect to units in an area
+//
+//-----------------------------------------------------------------------------
+///////////////////////////////////////////////////////////////////////////////
+
+//-----------------------------------------------------------------------------
+// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// USER INCLUDES //////////////////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
+
+#include "GameLogic/Module/BuffUpdate.h"
+#include "GameLogic/BuffSystem.h"
+#include "GameLogic/Object.h"
+#include "GameLogic/PartitionManager.h"
+#include "GameLogic/Weapon.h"
+#include "GameLogic/Module/ContainModule.h"
+
+//-----------------------------------------------------------------------------
+BuffUpdateModuleData::BuffUpdateModuleData()
+{
+ m_requiredAffectKindOf.clear();
+ m_forbiddenAffectKindOf.clear();
+ m_targetsMask = WEAPON_AFFECTS_ALLIES;
+ m_isAffectAirborne = true;
+ m_buffDuration = 0;
+ m_buffDelay = 0;
+ m_buffRange = 0;
+ m_buffTemplateName.clear();
+}
+
+//-----------------------------------------------------------------------------
+void BuffUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
+{
+ UpdateModuleData::buildFieldParse(p);
+ static const FieldParse dataFieldParse[] =
+ {
+ { "RequiredAffectKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( BuffUpdateModuleData, m_requiredAffectKindOf ) },
+ { "ForbiddenAffectKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( BuffUpdateModuleData, m_forbiddenAffectKindOf ) },
+ { "AffectsTargets", INI::parseBitString32, TheWeaponAffectsMaskNames, offsetof(BuffUpdateModuleData, m_targetsMask) },
+ { "AffectAirborne", INI::parseBool, NULL, offsetof(BuffUpdateModuleData, m_isAffectAirborne) },
+ { "BuffDuration", INI::parseDurationUnsignedInt, NULL, offsetof( BuffUpdateModuleData, m_buffDuration) },
+ { "BuffDelay", INI::parseDurationUnsignedInt, NULL, offsetof( BuffUpdateModuleData, m_buffDelay) },
+ { "BuffRange", INI::parseReal, NULL, offsetof( BuffUpdateModuleData, m_buffRange) },
+ { "BuffTemplateName", INI::parseAsciiString, NULL, offsetof( BuffUpdateModuleData, m_buffTemplateName) },
+ { 0, 0, 0, 0 }
+ };
+ p.add(dataFieldParse);
+}
+
+//-----------------------------------------------------------------------------
+// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
+//-----------------------------------------------------------------------------
+BuffUpdate::BuffUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
+{
+ setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
+}
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+BuffUpdate::~BuffUpdate( void )
+{
+
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+struct tempBuffData // This is used for iterator to apply buff to contained objects
+{
+ const BuffTemplate* m_template;
+ Object* m_sourceObj;
+ UnsignedInt m_duration;
+ KindOfMaskType m_requiredMask;
+ KindOfMaskType m_forbiddenMask;
+ Bool m_isAffectAirborne;
+};
+void containIteratingDoBuff( Object *passenger, void *voidData)
+{
+ tempBuffData *data = (tempBuffData *)voidData;
+
+ if (passenger->isKindOfMulti(data->m_requiredMask, data->m_forbiddenMask)) {
+ if (data->m_isAffectAirborne || !passenger->isAirborneTarget()) {
+ passenger->applyBuff(data->m_template, data->m_duration, data->m_sourceObj); // TODO: create function in object
+ }
+ }
+}
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+UpdateSleepTime BuffUpdate::update( void )
+{
+ DEBUG_LOG(("BuffUpdate::update 0"));
+
+ const BuffUpdateModuleData * data = getBuffUpdateModuleData();
+ const BuffTemplate* buffTemp = TheBuffTemplateStore->findBuffTemplate(data->m_buffTemplateName);
+
+ if (!buffTemp) {
+ DEBUG_LOG(("BuffUpdate::update -- Could not find BuffTemplate '%s'", data->m_buffTemplateName.str()));
+ return UPDATE_SLEEP_FOREVER;
+ }
+
+ Object *me = getObject();
+
+ Int targetFlags = 0;
+ if (data->m_targetsMask & WEAPON_AFFECTS_ALLIES) targetFlags |= PartitionFilterRelationship::ALLOW_ALLIES;
+ if (data->m_targetsMask & WEAPON_AFFECTS_ENEMIES) targetFlags |= PartitionFilterRelationship::ALLOW_ENEMIES;
+ if (data->m_targetsMask & WEAPON_AFFECTS_NEUTRALS) targetFlags |= PartitionFilterRelationship::ALLOW_NEUTRAL;
+
+ PartitionFilterRelationship relationship(me, targetFlags);
+ PartitionFilterSameMapStatus filterMapStatus(me);
+ PartitionFilterAlive filterAlive;
+
+ // Leaving this here commented out to show that I need to reach valid contents of invalid transports.
+ // So these checks are on an individual basis, not in the Partition query
+// PartitionFilterAcceptByKindOf filterKindof(data->m_requiredAffectKindOf,data->m_forbiddenAffectKindOf);
+ PartitionFilter *filters[] = { &relationship, &filterAlive, &filterMapStatus, NULL };
+
+ // scan objects in our region
+ ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( me->getPosition(),
+ data->m_buffRange,
+ FROM_CENTER_2D,
+ filters );
+
+
+ MemoryPoolObjectHolder hold( iter );
+ tempBuffData buffData;
+ buffData.m_template = buffTemp;
+ buffData.m_duration = data->m_buffDuration;
+ buffData.m_requiredMask = data->m_requiredAffectKindOf;
+ buffData.m_forbiddenMask = data->m_forbiddenAffectKindOf;
+ buffData.m_isAffectAirborne = data->m_isAffectAirborne;
+ buffData.m_sourceObj = me; // TODO: Support for projectiles
+
+
+ for( Object *currentObj = iter->first(); currentObj != NULL; currentObj = iter->next() )
+ {
+ if( currentObj->isKindOfMulti(data->m_requiredAffectKindOf, data->m_forbiddenAffectKindOf) )
+ {
+ if (data->m_isAffectAirborne || !currentObj->isAirborneTarget()) {
+ currentObj->applyBuff(buffTemp, data->m_buffDuration, me); // TODO
+ }
+ }
+
+ if( currentObj->getContain() )
+ {
+ currentObj->getContain()->iterateContained(containIteratingDoBuff, &buffData, FALSE);
+ }
+ }
+
+ return UPDATE_SLEEP(data->m_buffDelay); // Only need an internal timer if there are external hooks for wakning us up, or a second thing we can do
+}
+
+// ------------------------------------------------------------------------------------------------
+/** CRC */
+// ------------------------------------------------------------------------------------------------
+void BuffUpdate::crc( Xfer *xfer )
+{
+
+ // extend base class
+ UpdateModule::crc( xfer );
+
+} // end crc
+
+// ------------------------------------------------------------------------------------------------
+/** Xfer method
+ * Version Info:
+ * 1: Initial version */
+// ------------------------------------------------------------------------------------------------
+void BuffUpdate::xfer( Xfer *xfer )
+{
+
+ // version
+ XferVersion currentVersion = 1;
+ XferVersion version = currentVersion;
+ xfer->xferVersion( &version, currentVersion );
+
+ // extend base class
+ UpdateModule::xfer( xfer );
+
+} // end xfer
+
+// ------------------------------------------------------------------------------------------------
+/** Load post process */
+// ------------------------------------------------------------------------------------------------
+void BuffUpdate::loadPostProcess( void )
+{
+
+ // extend base class
+ UpdateModule::loadPostProcess();
+
+} // end loadPostProcess
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp
index cfb49afa74..79af32330a 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp
@@ -68,7 +68,7 @@ WeaponBonusUpdateModuleData::WeaponBonusUpdateModuleData()
{
m_requiredAffectKindOf.clear();
m_forbiddenAffectKindOf.clear();
- m_targetsMask = 0;
+ m_targetsMask = WEAPON_AFFECTS_ALLIES;
m_isAffectAirborne = true;
m_bonusDuration = 0;
m_bonusDelay = 0;
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp
index b0734c207d..d1d3df09d1 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp
@@ -2793,7 +2793,7 @@ Bool Weapon::privateFireWeapon(
WeaponBonus bonus;
computeBonus(sourceObj, extraBonusFlags, bonus);
- //debug_printWeaponBonus(&bonus, m_template->getName());
+ debug_printWeaponBonus(&bonus, m_template->getName());
DEBUG_ASSERTCRASH(getStatus() != OUT_OF_AMMO, ("Hmm, firing weapon that is OUT_OF_AMMO"));
DEBUG_ASSERTCRASH(getStatus() == READY_TO_FIRE, ("Hmm, Weapon is firing more often than should be possible"));