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"));