diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index c0c85c3c6e4..971299c5677 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -291,6 +291,7 @@ set(GAMEENGINE_SRC # Include/GameLogic/Module/CostModifierUpgrade.h # Include/GameLogic/Module/CountermeasuresBehavior.h # Include/GameLogic/Module/BattlePlanBonusBehavior.h +# Include/GameLogic/Module/EnergyShieldBehavior.h # Include/GameLogic/Module/CrateCollide.h # Include/GameLogic/Module/CreateCrateDie.h # Include/GameLogic/Module/CreateModule.h @@ -344,6 +345,7 @@ set(GAMEENGINE_SRC # Include/GameLogic/Module/HighlanderBody.h # Include/GameLogic/Module/HijackerUpdate.h # Include/GameLogic/Module/HiveStructureBody.h +# Include/GameLogic/Module/ShieldBody.h # Include/GameLogic/Module/HordeUpdate.h # Include/GameLogic/Module/ImmortalBody.h # Include/GameLogic/Module/InactiveBody.h @@ -854,6 +856,7 @@ set(GAMEENGINE_SRC # Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp # Source/GameLogic/Object/Behavior/CountermeasuresBehavior.cpp # Source/GameLogic/Object/Behavior/BattlePlanBonusBehavior.cpp +# Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp # Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp # Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp # Source/GameLogic/Object/Behavior/FireWeaponWhenDeadBehavior.cpp @@ -880,6 +883,7 @@ set(GAMEENGINE_SRC # Source/GameLogic/Object/Body/BodyModule.cpp # Source/GameLogic/Object/Body/HighlanderBody.cpp # Source/GameLogic/Object/Body/HiveStructureBody.cpp +# Source/GameLogic/Object/Body/ShieldBody.cpp # Source/GameLogic/Object/Body/ImmortalBody.cpp # Source/GameLogic/Object/Body/InactiveBody.cpp # Source/GameLogic/Object/Body/StructureBody.cpp diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl index aed9ab5adea..0856e058579 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl @@ -109,6 +109,7 @@ static PoolSizeRec PoolSizes[] = { "NeutronBlastBehavior", 4096, 32 }, { "CountermeasuresBehavior", 256, 32 }, { "BattlePlanBonusBehavior", 256, 32 }, + { "EnergyShieldBehavior", 256, 32 }, { "BaseRegenerateUpdate", 128, 32 }, { "BoneFXDamage", 64, 32 }, { "BoneFXUpdate", 64, 32 }, @@ -253,6 +254,7 @@ static PoolSizeRec PoolSizes[] = { "SquishCollide", 512, 32 }, { "StructureBody", 512, 64 }, { "HiveStructureBody", 64, 32 }, //Stinger sites + { "ShieldBody", 128, 32 }, { "StructureCollapseUpdate", 32, 32 }, { "StructureToppleUpdate", 32, 32 }, { "SupplyCenterCreate", 32, 32 }, diff --git a/Generals/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/Generals/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 7504e206edb..aa407c5bd06 100644 --- a/Generals/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/Generals/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -230,6 +230,7 @@ #include "GameLogic/Module/ImmortalBody.h" #include "GameLogic/Module/StructureBody.h" #include "GameLogic/Module/HiveStructureBody.h" +#include "GameLogic/Module/ShieldBody.h" // contain includes // (none) diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 2c662daf497..27df30b3cce 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -294,6 +294,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/UnitProductionBonusUpgrade.h Include/GameLogic/Module/CountermeasuresBehavior.h Include/GameLogic/Module/BattlePlanBonusBehavior.h + Include/GameLogic/Module/EnergyShieldBehavior.h Include/GameLogic/Module/CrateCollide.h Include/GameLogic/Module/CreateCrateDie.h Include/GameLogic/Module/CreateModule.h @@ -349,6 +350,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/HighlanderBody.h Include/GameLogic/Module/HijackerUpdate.h Include/GameLogic/Module/HiveStructureBody.h + Include/GameLogic/Module/ShieldBody.h Include/GameLogic/Module/HordeUpdate.h Include/GameLogic/Module/ImmortalBody.h Include/GameLogic/Module/InactiveBody.h @@ -871,6 +873,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp Source/GameLogic/Object/Behavior/CountermeasuresBehavior.cpp Source/GameLogic/Object/Behavior/BattlePlanBonusBehavior.cpp + Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp @@ -900,6 +903,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Body/BodyModule.cpp Source/GameLogic/Object/Body/HighlanderBody.cpp Source/GameLogic/Object/Body/HiveStructureBody.cpp + Source/GameLogic/Object/Body/ShieldBody.cpp Source/GameLogic/Object/Body/ImmortalBody.cpp Source/GameLogic/Object/Body/InactiveBody.cpp Source/GameLogic/Object/Body/StructureBody.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 50e2f0a1acf..17b2e86091b 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -216,6 +216,8 @@ class GlobalData : public SubsystemInterface Real m_ammoPipScaleFactor; Real m_containerPipScaleFactor; + Real m_progressBarYOffset; + UnsignedInt m_historicDamageLimit; //Settings for terrain tracks left by vehicles with treads or wheels diff --git a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h index ef816463902..4a8b48697ad 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h @@ -179,6 +179,8 @@ enum KindOfType CPP_11(: Int) KINDOF_ENABLE_INFANTRY_LIGHTING, ///< Enable infantry-style ambient lighting for this object KINDOF_DISABLE_INFANTRY_LIGHTING, ///< Use regular lighting on this infantry object + KINDOF_SHOW_PROGRESS_BAR, ///< Show progress bar for this unit (Shields, deploy, teleport, etc.) + KINDOF_VTOL, KINDOF_LARGE_AIRCRAFT, KINDOF_MEDIUM_AIRCRAFT, @@ -209,8 +211,6 @@ enum KindOfType CPP_11(: Int) KINDOF_EXTRA14, KINDOF_EXTRA15, KINDOF_EXTRA16, - KINDOF_EXTRA17, - KINDOF_EXTRA18, KINDOF_COUNT // total number of kindofs diff --git a/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h b/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h index 42b49c06801..beb8bec34fb 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h @@ -257,6 +257,28 @@ static const char* AmmoPipsStyleNames[] = }; #endif // end DEFINE_AMMO_PIPS_STYLE_NAMES +// --- +//enum ProgressBarStyle CPP_11(: Int) +//{ +// PROGRESS_BAR_NONE = 0, ///< Default. No progress bar +// PROGRESS_BAR_SHIELD, ///< large white bar +// PROGRESS_BAR_SHIELD, ///< like default, but show a single pip only (full or empty) +// AMMO_PIPS_THIN, ///< like default, but half width +// +// AMMO_PIPS_NUM_TYPES // leave this last +//}; +//#ifdef DEFINE_PROGRESS_BAR_STYLE_NAMES +//static const char* ProgressBarStyleNames[] = +//{ +// "DEFAULT", +// "PERCENTAGE_BAR", +// "SINGLE", +// "THIN", +// +// NULL +//}; +//#endif // end DEFINE_PROGRESS_BAR_STYLE_NAMES + //------------------------------------------------------------------------------------------------- enum ModuleParseMode CPP_11(: Int) { diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index 411308013e4..4ec86d0377a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -762,6 +762,9 @@ class Drawable : public Thing, void drawContained( const IRegion2D *healthBarRegion ); ///< draw icons void drawVeterancy( const IRegion2D *healthBarRegion ); ///< draw veterency information + //new: + void drawProgress(const IRegion2D* healthBarRegion); ///< draw progress bar (shield, deploy, teleport, etc.) + void drawEmoticon( const IRegion2D* healthBarRegion ); void drawHealthBar( const IRegion2D* healthBarRegion ); ///< draw heath bar void drawHealing( const IRegion2D* healthBarRegion ); ///< draw icons diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h index af3bda50c91..4aa2266d000 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h @@ -143,8 +143,15 @@ class ActiveBody : public BodyModule protected: + UnsignedInt m_nextDamageFXTime; + DamageType m_lastDamageFXDone; + DamageInfo m_lastDamageInfo; ///< store the last DamageInfo object that we received + UnsignedInt m_lastDamageTimestamp; ///< frame of last damage dealt + UnsignedInt m_lastHealingTimestamp; ///< frame of last healing dealt + Bool m_lastDamageCleared; + void validateArmorAndDamageFX() const; - void doDamageFX( const DamageInfo *damageInfo ); + virtual void doDamageFX( const DamageInfo *damageInfo ); void createParticleSystems( const AsciiString &boneBaseName, const ParticleSystemTemplate *systemTemplate, @@ -160,6 +167,8 @@ class ActiveBody : public BodyModule virtual void applyChronoParticleSystems(void); + inline const Armor getCurrentArmor() const { return m_curArmor; } + private: Real m_currentHealth; ///< health of the object @@ -170,14 +179,9 @@ class ActiveBody : public BodyModule Real m_currentChronoDamage; ///< Same as Subdual, but for CHRONO_GUN BodyDamageType m_curDamageState; ///< last known damage state - UnsignedInt m_nextDamageFXTime; - DamageType m_lastDamageFXDone; - DamageInfo m_lastDamageInfo; ///< store the last DamageInfo object that we received - UnsignedInt m_lastDamageTimestamp; ///< frame of last damage dealt - UnsignedInt m_lastHealingTimestamp; ///< frame of last healing dealt + Bool m_frontCrushed; Bool m_backCrushed; - Bool m_lastDamageCleared; Bool m_indestructible; ///< is this object indestructible? Bool m_damageFXOverride; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BehaviorModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BehaviorModule.h index f5dacd3dd34..781fb9cb057 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BehaviorModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BehaviorModule.h @@ -83,6 +83,7 @@ class StealthUpdate; class SpyVisionUpdate; // ----------------- class BattlePlanBonusBehaviorInterface; +class EnergyShieldBehaviorInterface; //------------------------------------------------------------------------------------------------- class BehaviorModuleData : public ModuleData @@ -142,6 +143,7 @@ class BehaviorModuleInterface virtual const CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() const = 0; virtual BattlePlanBonusBehaviorInterface* getBattlePlanBonusBehaviorInterface() = 0; + virtual EnergyShieldBehaviorInterface* getEnergyShieldBehaviorInterface() = 0; }; @@ -197,7 +199,8 @@ class BehaviorModule : public ObjectModule, public BehaviorModuleInterface virtual SpawnBehaviorInterface* getSpawnBehaviorInterface() { return NULL; } virtual CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() { return NULL; } virtual const CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() const { return NULL; } - virtual BattlePlanBonusBehaviorInterface* getBattlePlanBonusBehaviorInterface() { return NULL; }; + virtual BattlePlanBonusBehaviorInterface* getBattlePlanBonusBehaviorInterface() { return NULL; } + virtual EnergyShieldBehaviorInterface* getEnergyShieldBehaviorInterface() { return NULL; } protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h new file mode 100644 index 00000000000..e3c1c18a589 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h @@ -0,0 +1,187 @@ +/* +** 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: EnergyShieldBehavior.h ///////////////////////////////////////////////////////////////////////// +// Author: Colin Day, December 2001 +// Desc: Update that heals itself +//------------------------------------------ +// Modified by Kris Morness, September 2002 +// Kris: Added the ability to add effects, radius healing, and restricting the type of objects +// subjected to the heal (or repair). +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __EnergyShieldBehavior_H_ +#define __EnergyShieldBehavior_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameClient/ParticleSys.h" +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/UpgradeModule.h" +#include "GameLogic/Module/UpdateModule.h" +#include "GameLogic/Module/DamageModule.h" +#include "Common/BitFlagsIO.h" + + +class ShieldBody; +class FXList; + +//------------------------------------------------------------------------------------------------- +class EnergyShieldBehaviorModuleData : public UpdateModuleData +{ +public: + UpgradeMuxData m_upgradeMuxData; + Bool m_initiallyActive; + //Real m_shieldMaxHealth; ///< MaxHealth of the shield + //Real m_shieldMaxHealthPercent; ///< MaxHealth as percentage of activeBody MaxHealth (takes priority) + + UnsignedInt m_shieldRechargeDelay; ///< frames of no damage taken until shield recharges + UnsignedInt m_shieldRechargeRate; ///< every this often, we heal the shield ... + Real m_shieldRechargeAmount; ///< by this much. + Real m_shieldRechargeAmountPercent; ///< Same as above but percentage of shieldMaxHealth (takes priority) + + RGBAColorInt m_barColor; + RGBAColorInt m_barBGColor; + Bool m_showBarWhenEmpty; + Bool m_showBarWhenUnselected; + + ModelConditionFlagType m_shieldConditionFlag; + ModelConditionFlagType m_shieldHitConditionFlag; + AsciiString m_shieldSubObjName; + AsciiString m_shieldHitSubObjName; + UnsignedInt m_showShieldWhenHitDuration; + //Bool m_alwaysShowShield; + + FXList* m_fxShieldUp; + FXList* m_fxShieldDown; + + + + //DamageTypeFlags m_damageTypesToPassThrough; + + EnergyShieldBehaviorModuleData(); + + static void buildFieldParse(MultiIniFieldParse& p); + + //static void parseShieldHealthPercent(INI* ini, void* instance, void* store, const void* userData); + //static void parseShieldRechargeAmountPercent(INI* ini, void* instance, void* store, const void* userData); + +}; +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class EnergyShieldBehaviorInterface +{ +public: + virtual void applyDamage(Real amount) = 0; + virtual bool isActive() const = 0; + virtual bool shouldShowHealthBar(bool selected) const = 0; + virtual Real getShieldPercent() const = 0; + virtual RGBAColorInt getHealthBarColor() const = 0; + virtual RGBAColorInt getHealthBarBackgroundColor() const = 0; +}; +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class EnergyShieldBehavior : public UpdateModule, + public UpgradeMux, + public EnergyShieldBehaviorInterface + //public DamageModuleInterface +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( EnergyShieldBehavior, "EnergyShieldBehavior" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( EnergyShieldBehavior, EnergyShieldBehaviorModuleData ) + +public: + + EnergyShieldBehavior( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + // module methods + static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | MODULEINTERFACE_UPGRADE; } // | MODULEINTERFACE_DAMAGE; + + // BehaviorModule + virtual UpgradeModuleInterface* getUpgrade() { return this; } + virtual EnergyShieldBehaviorInterface* getEnergyShieldBehaviorInterface() { return this; } + //virtual DamageModuleInterface* getDamage() { return this; } + + // DamageModuleInterface + //virtual void onDamage( DamageInfo *damageInfo ); + //virtual void onHealing( DamageInfo *damageInfo ) { } + //virtual void onBodyDamageStateChange(const DamageInfo* damageInfo, BodyDamageType oldState, BodyDamageType newState) { } + + // UpdateModuleInterface + virtual UpdateSleepTime update(); + //virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } + + //EnergyShieldBehaviorInterface + virtual void applyDamage(Real amount); + virtual bool isActive() const { return isUpgradeActive(); } + virtual bool shouldShowHealthBar(bool selected) const; + virtual Real getShieldPercent() const; + virtual RGBAColorInt getHealthBarColor() const { return getEnergyShieldBehaviorModuleData()->m_barColor; } + virtual RGBAColorInt getHealthBarBackgroundColor() const { return getEnergyShieldBehaviorModuleData()->m_barBGColor; }; + +protected: + + virtual void upgradeImplementation(); + + virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const + { + getEnergyShieldBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting); + } + + virtual void performUpgradeFX() + { + getEnergyShieldBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject()); + } + + virtual void processUpgradeRemoval() + { + // I can't take it any more. Let the record show that I think the UpgradeMux multiple inheritence is CRAP. + getEnergyShieldBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject()); + } + + virtual Bool requiresAllActivationUpgrades() const + { + return getEnergyShieldBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers; + } + + inline Bool isUpgradeActive() const { return isAlreadyUpgraded(); } + + virtual Bool isSubObjectsUpgrade() { return false; } + + +private: + + ShieldBody* m_body; + //Real m_currentShieldHealth; + UnsignedInt m_healingStepCountdown; + UnsignedInt m_shieldHitCountdown; + + void showShield(bool show, bool isHit = false); + Bool alwaysShowShield() const; +}; + +#endif // __EnergyShieldBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h new file mode 100644 index 00000000000..11fbfc5f572 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h @@ -0,0 +1,123 @@ +/* +** 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: ShieldBody.h ////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: Structure bodies are active bodies specifically for structures that are built +// and/or interactable (is that a world) with the player. +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __ShieldBody_H_ +#define __ShieldBody_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/ActiveBody.h" +#include "GameLogic/Module/UpdateModule.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Object; +class EnergyShieldBehavior; + +//------------------------------------------------------------------------------------------------- +class ShieldBodyModuleData : public ActiveBodyModuleData +{ +public: + + Bool m_startsActive; ///< Initially active without upgrade; + Real m_shieldMaxHealth; ///< MaxHealth of the shield + Real m_shieldMaxHealthPercent; ///< MaxHealth as percentage of activeBody MaxHealth (takes priority) + ArmorSetType m_shieldArmorSetFlag; ///< armorset to use for damage absorbed by the shield + + DamageTypeFlags m_damageTypesToPassThrough; + DamageTypeFlags m_defaultDamageTypesToPassThrough; + + ShieldBodyModuleData(); + + static void buildFieldParse(MultiIniFieldParse& p); + + static void parseShieldHealthPercent(INI* ini, void* instance, void* store, const void* userData); +}; + +//------------------------------------------------------------------------------------------------- +/** Structure body module */ +//------------------------------------------------------------------------------------------------- +class ShieldBody : public ActiveBody //, public UpdateModuleInterface +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ShieldBody, "ShieldBody" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ShieldBody, ShieldBodyModuleData ) + +public: + + ShieldBody( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + //static Int getInterfaceMask() { return UpdateModule::getInterfaceMask(); } + + //// UpdateModuleInterface + //virtual UpdateSleepTime update(void); + + //virtual UpdateModuleInterface* getUpdate() { return this; } + + //virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } + + + //void setConstructorObject( Object *obj ); + //ObjectID getConstructorObjectID( void ) { return m_constructorObjectID; } + + inline Real getShieldMaxHealth() const { return getShieldBodyModuleData()->m_shieldMaxHealth; } + inline Real getShieldCurrentHealth() const { return m_currentShieldHealth; } + Bool rechargeShieldHealth(Real amount); ///< returns True if on full health; + + inline Bool isActive() const { return m_active; } + inline void setActive(Bool value) { m_active = value; } + + Real getShieldPercent(); + +protected: + + virtual void attemptDamage(DamageInfo* damageInfo); ///< try to damage this object + virtual void doDamageFX(const DamageInfo* damageInfo); + + virtual void onDisabledEdge(Bool nowDisabled); + + //ObjectID m_constructorObjectID; ///< object that built this structure + +private: + Real m_currentShieldHealth; + + EnergyShieldBehaviorInterface* m_shieldBehaviorModule; + + Bool m_active; + + void enableShieldEffects(); // when the shield hp is < 0 + void disableShieldEffects(); // when the shield hp is 0 + + void findShieldBehaviorModule(); +}; + +#endif // __ShieldBody_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 72b85b283e2..429a8f1b9f7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -504,6 +504,9 @@ class Object : public Thing, public Snapshot Weapon* findWaypointFollowingCapableWeapon(); Bool getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const; + // Progress bar for various things + Bool getProgressBarShowingInfo(bool selected, Real& progress, Int& type, RGBAColorInt& color, RGBAColorInt& colorBG) const; + void notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) ; /** @@ -737,7 +740,7 @@ class Object : public Thing, public Snapshot UnsignedInt m_smcUntil; - enum { NUM_SLEEP_HELPERS = 8 }; + enum { NUM_SLEEP_HELPERS = 9 }; ObjectRepulsorHelper* m_repulsorHelper; ObjectSMCHelper* m_smcHelper; ObjectWeaponStatusHelper* m_wsHelper; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 46e04975780..0a70e795821 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -219,6 +219,8 @@ GlobalData* GlobalData::m_theOriginal = NULL; { "AmmoPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_ammoPipScreenOffset ) }, { "ContainerPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_containerPipScreenOffset ) }, + { "ProgressBarYOffset", INI::parseReal, NULL, offsetof(GlobalData, m_progressBarYOffset) }, + { "HistoricDamageLimit", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_historicDamageLimit ) }, { "MaxTerrainTracks", INI::parseInt, NULL, offsetof( GlobalData, m_maxTerrainTracks ) }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp index e48e5ce0952..f3c7e99f06e 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp @@ -165,6 +165,8 @@ const char* KindOfMaskType::s_bitNameList[] = "ENABLE_INFANTRY_LIGHTING", "DISABLE_INFANTRY_LIGHTING", + "SHOW_PROGRESS_BAR", + "VTOL", "LARGE_AIRCRAFT", "MEDIUM_AIRCRAFT", @@ -195,8 +197,6 @@ const char* KindOfMaskType::s_bitNameList[] = "EXTRA14", "EXTRA15", "EXTRA16", - "EXTRA17", - "EXTRA18", NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 85445660282..1858b32032c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -51,6 +51,7 @@ #include "GameLogic/Module/BridgeTowerBehavior.h" #include "GameLogic/Module/CountermeasuresBehavior.h" #include "GameLogic/Module/BattlePlanBonusBehavior.h" +#include "GameLogic/Module/EnergyShieldBehavior.h" #include "GameLogic/Module/DumbProjectileBehavior.h" #include "GameLogic/Module/FreeFallProjectileBehavior.h" #include "GameLogic/Module/InstantDeathBehavior.h" @@ -270,6 +271,7 @@ #include "GameLogic/Module/ImmortalBody.h" #include "GameLogic/Module/StructureBody.h" #include "GameLogic/Module/HiveStructureBody.h" +#include "GameLogic/Module/ShieldBody.h" #include "GameLogic/Module/UndeadBody.h" // contain includes @@ -345,6 +347,7 @@ void ModuleFactory::init( void ) addModule( BridgeTowerBehavior ); addModule( CountermeasuresBehavior ); addModule( BattlePlanBonusBehavior ); + addModule( EnergyShieldBehavior ); addModule( DumbProjectileBehavior ); addModule( FreeFallProjectileBehavior ); addModule( PhysicsBehavior ); @@ -565,6 +568,7 @@ void ModuleFactory::init( void ) addModule( ImmortalBody ); addModule( StructureBody ); addModule( HiveStructureBody ); + addModule( ShieldBody ); addModule( UndeadBody ); // contain modules diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 047461e418d..b429e288515 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -2876,6 +2876,8 @@ void Drawable::drawIconUI( void ) //Moved this to last so that it shows up over contained and ammo icons. drawVeterancy( healthBarRegion ); + drawProgress( healthBarRegion ); + #ifdef KRIS_BRUTAL_HACK_FOR_AIRCRAFT_CARRIER_DEBUGGING drawUIText(); #endif @@ -3100,6 +3102,61 @@ void Drawable::drawAmmo( const IRegion2D *healthBarRegion ) } } +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void Drawable::drawProgress( const IRegion2D *healthBarRegion ) +{ + if (!healthBarRegion) + return; + + const Object* obj = getObject(); + + //if (!( + // TheGlobalData->m_showObjectHealth && + // (isSelected() || (TheInGameUI && (TheInGameUI->getMousedOverDrawableID() == getID()))) + // //&& obj->getControllingPlayer() == ThePlayerList->getLocalPlayer() // Shields are visible for all + // )) + // return; + if (!TheGlobalData->m_showObjectHealth) + return; + + Bool selected = isSelected() || (TheInGameUI && (TheInGameUI->getMousedOverDrawableID() == getID())); + + Real progress; + Int type; //not used yet + + RGBAColorInt barColor; + RGBAColorInt barColorBG; + + if (!obj->getProgressBarShowingInfo(selected, progress, type, barColor, barColorBG)) + return; + + Color color, outlineColor; + + color = GameMakeColor(barColor.red, barColor.green, barColor.blue, barColor.alpha); + outlineColor = GameMakeColor(barColorBG.red, barColorBG.green, barColorBG.blue, barColorBG.alpha); + + + Real healthBoxWidth = healthBarRegion->hi.x - healthBarRegion->lo.x; + + Real healthBoxHeight = max(3, healthBarRegion->hi.y - healthBarRegion->lo.y) * 1.5f; + Real healthBoxOutlineSize = 1.0f; + + Real yOffset = -6 + TheGlobalData->m_progressBarYOffset; + + // draw the health box outline + TheDisplay->drawOpenRect(healthBarRegion->lo.x, healthBarRegion->lo.y + yOffset, healthBoxWidth, healthBoxHeight, + healthBoxOutlineSize, outlineColor); + + if (progress > 0) { + + // draw a filled bar for the ammo count + TheDisplay->drawFillRect(healthBarRegion->lo.x + 1, healthBarRegion->lo.y + yOffset + 1, + (healthBoxWidth - 2) * progress, healthBoxHeight - 2, + color); + } + } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp new file mode 100644 index 00000000000..8bc95535e7a --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp @@ -0,0 +1,365 @@ +/* +** 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: EnergyShieldBehavior.cpp /////////////////////////////////////////////////////////////////////// +// Author: +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/Thing.h" +#include "Common/ThingTemplate.h" +#include "Common/INI.h" +#include "Common/Player.h" +#include "Common/Xfer.h" +#include "GameClient/ParticleSys.h" +#include "GameClient/Anim2D.h" +#include "GameClient/InGameUI.h" +#include "GameLogic/Module/EnergyShieldBehavior.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/ShieldBody.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Object.h" +#include "GameLogic/PartitionManager.h" +#include "GameClient/FXList.h" + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +EnergyShieldBehaviorModuleData::EnergyShieldBehaviorModuleData() +{ + m_barColor.red = 255; + m_barColor.green = 255; + m_barColor.blue = 255; + m_barColor.alpha = 255; + + m_barBGColor.red = 255; + m_barBGColor.green = 255; + m_barBGColor.blue = 255; + m_barBGColor.alpha = 255; + + m_shieldSubObjName.clear(); + m_shieldConditionFlag = MODELCONDITION_INVALID; + m_shieldHitConditionFlag = MODELCONDITION_INVALID; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +/*static*/ void EnergyShieldBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + static const FieldParse dataFieldParse[] = + { + { "StartsActive", INI::parseBool, NULL, offsetof(EnergyShieldBehaviorModuleData, m_initiallyActive) }, + + { "ShieldRechargeDelay", INI::parseDurationUnsignedInt, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldRechargeDelay) }, + { "ShieldRechargeRate", INI::parseDurationUnsignedInt, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldRechargeRate) }, + { "ShieldRechargeAmount", INI::parseReal, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldRechargeAmount) }, + { "ShieldRechargeAmountPercent", INI::parsePercentToReal, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldRechargeAmountPercent) }, + + { "ShieldHealthBarColor", INI::parseRGBAColorInt, NULL, offsetof(EnergyShieldBehaviorModuleData, m_barColor) }, + { "ShieldHealthBarBackgroundColor", INI::parseRGBAColorInt, NULL, offsetof(EnergyShieldBehaviorModuleData, m_barBGColor) }, + { "ShowHealthBarBackgroundWhenEmpty", INI::parseBool, NULL, offsetof(EnergyShieldBehaviorModuleData, m_showBarWhenEmpty) }, + { "ShowHealthBarWhenUnselected", INI::parseBool, NULL, offsetof(EnergyShieldBehaviorModuleData, m_showBarWhenUnselected) }, + + { "ShieldSubObjectName", INI::parseAsciiString, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldSubObjName) }, + { "ShieldHitSubObjectName", INI::parseAsciiString, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldHitSubObjName) }, + + { "ShieldModelCondition", INI::parseIndexList, ModelConditionFlags::getBitNames(), offsetof(EnergyShieldBehaviorModuleData, m_shieldConditionFlag) }, + { "ShieldHitModelCondition", INI::parseIndexList, ModelConditionFlags::getBitNames(), offsetof(EnergyShieldBehaviorModuleData, m_shieldHitConditionFlag) }, + { "ShieldHitConditionDuration", INI::parseDurationUnsignedInt, NULL, offsetof(EnergyShieldBehaviorModuleData, m_showShieldWhenHitDuration) }, + + { "ShieldUpFX", INI::parseFXList, NULL, offsetof(EnergyShieldBehaviorModuleData, m_fxShieldUp) }, + { "ShieldDownFX", INI::parseFXList, NULL, offsetof(EnergyShieldBehaviorModuleData, m_fxShieldDown) }, + + //{ "DamageTypesToPassThroughShield", INI::parseDamageTypeFlags, NULL, offsetof(EnergyShieldBehaviorModuleData, m_damageTypesToPassThrough) }, + { 0, 0, 0, 0 } + }; + + UpdateModuleData::buildFieldParse(p); + p.add(dataFieldParse); + p.add(UpgradeMuxData::getFieldParse(), offsetof(EnergyShieldBehaviorModuleData, m_upgradeMuxData)); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +EnergyShieldBehavior::EnergyShieldBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) +{ + + ShieldBody* body = dynamic_cast(getObject()->getBodyModule()); + DEBUG_ASSERTCRASH((body != nullptr), ("EnergyShieldBehavior requires ShieldBody!")); + + m_body = body; + + const EnergyShieldBehaviorModuleData *d = getEnergyShieldBehaviorModuleData(); + + if (d->m_initiallyActive) + { + giveSelfUpgrade(); + } + + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); // We wake when we get attacked +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +EnergyShieldBehavior::~EnergyShieldBehavior( void ) +{ + +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void EnergyShieldBehavior::applyDamage(Real amount) +{ + //DEBUG_LOG((">>> EnergyShieldBehavior::applyDamage - amount = %f", amount)); + + const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); + m_healingStepCountdown = data->m_shieldRechargeDelay; + setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + + // amount = 0 means the shield was already down + + if (amount > 0) + { + // Show shield subobject + if (data->m_shieldSubObjName.isNotEmpty() || data->m_shieldConditionFlag != MODELCONDITION_INVALID) { + + if (getShieldPercent() <= 0) { // Shield is down, hide it + + showShield(false); // shield + showShield(false, true); // shield hit + + FXList::doFXObj(data->m_fxShieldDown, getObject()); + + m_shieldHitCountdown = 0; + } + else if (data->m_showShieldWhenHitDuration > 0) { // Shield took damage, show it + if (m_shieldHitCountdown <= 0) { // If it's not already up, show it. + showShield(true, true); + m_shieldHitCountdown = data->m_showShieldWhenHitDuration; + } + } + } + } +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void EnergyShieldBehavior::showShield(bool show, bool isHit) +{ + Drawable* draw = getObject()->getDrawable(); + if (!draw) + return; + + const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); + + AsciiString subObjName; + ModelConditionFlagType conditionFlag; + + if (isHit) { + subObjName = data->m_shieldHitSubObjName; + conditionFlag = data->m_shieldHitConditionFlag; + } + else { + subObjName = data->m_shieldSubObjName; + conditionFlag = data->m_shieldConditionFlag; + } + + if (show) { + if (conditionFlag != MODELCONDITION_INVALID) { + draw->setModelConditionState(conditionFlag); + } + + if (subObjName.isNotEmpty()) { + draw->showSubObject(subObjName, true); + draw->updateSubObjects(); + } + } + else { + if (conditionFlag != MODELCONDITION_INVALID) { + draw->clearModelConditionState(conditionFlag); + } + + if (subObjName.isNotEmpty()) { + draw->showSubObject(subObjName, false); + draw->updateSubObjects(); + } + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real EnergyShieldBehavior::getShieldPercent() const +{ + if (isActive() && m_body != NULL) + { + return m_body->getShieldPercent(); + } + return 0.0; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +bool EnergyShieldBehavior::shouldShowHealthBar(bool selected) const +{ + if (!isActive() || m_body == NULL) + return FALSE; + + const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); + + if (!selected && !getEnergyShieldBehaviorModuleData()->m_showBarWhenUnselected) + return FALSE; + + if (!data->m_showBarWhenEmpty && m_body->getShieldCurrentHealth() <= 0.0f) + return FALSE; + + return TRUE; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool EnergyShieldBehavior::alwaysShowShield() const +{ + return getEnergyShieldBehaviorModuleData()->m_shieldConditionFlag != MODELCONDITION_INVALID || + getEnergyShieldBehaviorModuleData()->m_shieldSubObjName.isNotEmpty(); +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void EnergyShieldBehavior::upgradeImplementation() +{ + if (m_body) { + m_body->setActive(true); + m_body->rechargeShieldHealth(m_body->getShieldMaxHealth()); + + // if (alwaysShowShield()) { + showShield(true, false); + //} + + } + //setWakeFrame(getObject(), UPDATE_SLEEP_NONE); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//void EnergyShieldBehavior::onDisabledEdge(Bool nowDisabled) +//{ +// +//} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +/** The update callback. */ +//------------------------------------------------------------------------------------------------- +UpdateSleepTime EnergyShieldBehavior::update( void ) +{ + //DEBUG_LOG((">>> EnergyShieldBehavior::update1 - m_healingStepCountdown = %d", m_healingStepCountdown)); + + const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); + + if (m_shieldHitCountdown > 0 && --m_shieldHitCountdown == 0) { + showShield(false, true); + } + + if (m_healingStepCountdown > 0 && --m_healingStepCountdown == 0) { + + if (m_body) { + Real rechargeAmount; + if (data->m_shieldRechargeAmountPercent) { + rechargeAmount = data->m_shieldRechargeAmountPercent * m_body->getShieldMaxHealth(); + } + else { + rechargeAmount = data->m_shieldRechargeAmount; + } + //DEBUG_LOG((">>> EnergyShieldBehavior::update2 - rechargeAmount = %f", rechargeAmount)); + + if (m_body->getShieldCurrentHealth() <= 0) + FXList::doFXObj(data->m_fxShieldUp, getObject()); + + Bool full = m_body->rechargeShieldHealth(rechargeAmount); + showShield(true, false); + + if (!full) + m_healingStepCountdown = data->m_shieldRechargeRate; + } + } + + if (m_healingStepCountdown == 0 && m_shieldHitCountdown == 0) + return UPDATE_SLEEP_FOREVER; + else + return UPDATE_SLEEP_NONE; +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void EnergyShieldBehavior::crc( Xfer *xfer ) +{ + + // extend base class + UpdateModule::crc( xfer ); + + // extend base class + UpgradeMux::upgradeMuxCRC( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void EnergyShieldBehavior::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + UpdateModule::xfer( xfer ); + + // extend base class + UpgradeMux::upgradeMuxXfer( xfer ); + + // current shield health + //xfer->xferReal(&m_currentShieldHealth); + xfer->xferUnsignedInt(&m_healingStepCountdown); + + //hit frames + xfer->xferUnsignedInt(&m_shieldHitCountdown); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void EnergyShieldBehavior::loadPostProcess( void ) +{ + + // extend base class + UpdateModule::loadPostProcess(); + + // extend base class + UpgradeMux::upgradeMuxLoadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp new file mode 100644 index 00000000000..cdd6b3cbbee --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp @@ -0,0 +1,444 @@ +/* +** 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: ShieldBody.cpp //////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: Structure bodies are active bodies specifically for structures that are built +// and/or interactable (is that a world) with the player. +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/Xfer.h" +#include "GameLogic/Object.h" +#include "GameLogic/Damage.h" +#include "GameLogic/Module/ShieldBody.h" +#include "GameLogic/Module/EnergyShieldBehavior.h" +#include "GameLogic/ArmorSet.h" +#include "GameLogic/GameLogic.h" +#include "Common/Player.h" + +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ShieldBodyModuleData::ShieldBodyModuleData() +{ + m_damageTypesToPassThrough = DAMAGE_TYPE_FLAGS_NONE; + m_shieldArmorSetFlag = ARMORSET_NONE; + + // Init default pass through types + m_defaultDamageTypesToPassThrough = DAMAGE_TYPE_FLAGS_NONE; + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_STATUS); + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_DEPLOY); + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_UNRESISTABLE); + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_HEALING); + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_PENALTY); + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_DISARM); + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_HAZARD_CLEANUP); + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_TOPPLING); + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_SUBDUAL_UNRESISTABLE); + m_defaultDamageTypesToPassThrough = setDamageTypeFlag(m_defaultDamageTypesToPassThrough, DAMAGE_CHRONO_UNRESISTABLE); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBodyModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + ActiveBodyModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "StartsActive", INI::parseBool, NULL, offsetof(ShieldBodyModuleData, m_startsActive) }, + + { "ShieldMaxHealth", INI::parseReal, NULL, offsetof(ShieldBodyModuleData, m_shieldMaxHealth) }, + { "ShieldMaxHealthPercent", parseShieldHealthPercent, NULL, 0}, // offsetof(ShieldBodyModuleData, m_shieldMaxHealthPercent) }, + + { "ShieldArmorSetFlag", INI::parseIndexList, ArmorSetFlags::getBitNames(), offsetof(ShieldBodyModuleData, m_shieldArmorSetFlag) }, + + { "ShieldPassThroughDamageTypes", INI::parseDamageTypeFlags, NULL, offsetof(ShieldBodyModuleData, m_damageTypesToPassThrough) }, + { "DefaultShieldPassThroughDamageTypes", INI::parseDamageTypeFlags, NULL, offsetof(ShieldBodyModuleData, m_defaultDamageTypesToPassThrough) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBodyModuleData::parseShieldHealthPercent(INI* ini, void* instance, void* store, const void* /*userData*/) +{ + ShieldBodyModuleData* self = (ShieldBodyModuleData*)instance; + Real healthPercent = INI::scanPercentToReal(ini->getNextToken()); + self->m_shieldMaxHealth = self->m_maxHealth * healthPercent; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ShieldBody::ShieldBody( Thing *thing, const ModuleData* moduleData ) : ActiveBody( thing, moduleData ) +{ + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + + if (data->m_startsActive) { + m_active = true; + m_currentShieldHealth = data->m_shieldMaxHealth; + enableShieldEffects(); + } + + +} // end ShieldBody + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ShieldBody::~ShieldBody( void ) +{ + +} // end ~ShieldBody + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ShieldBody::getShieldPercent() +{ + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + return m_currentShieldHealth / data->m_shieldMaxHealth; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ShieldBody::rechargeShieldHealth(Real amount) +{ + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + + // state before recharge + Bool shieldWasUp = m_currentShieldHealth > 0; + + m_currentShieldHealth = MIN(data->m_shieldMaxHealth, m_currentShieldHealth + amount); + + //DEBUG_LOG((">>> ShieldBody::rechargeShieldHealth - m_currentShieldHealth = %f", m_currentShieldHealth)); + + if (!shieldWasUp && m_currentShieldHealth > 0) { + enableShieldEffects(); + } + + return m_currentShieldHealth >= data->m_shieldMaxHealth; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBody::findShieldBehaviorModule() { + + EnergyShieldBehaviorInterface* esbi = NULL; + + for (BehaviorModule** u = getObject()->getBehaviorModules(); *u; ++u) + { + if ((esbi = (*u)->getEnergyShieldBehaviorInterface()) != NULL) { + m_shieldBehaviorModule = esbi; + return; + } + } + +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBody::enableShieldEffects() { + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + if (data->m_shieldArmorSetFlag != ARMORSET_NONE) { + getObject()->setArmorSetFlag(data->m_shieldArmorSetFlag); + validateArmorAndDamageFX(); + } +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBody::disableShieldEffects() { + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + if (data->m_shieldArmorSetFlag != ARMORSET_NONE) { + getObject()->clearArmorSetFlag(data->m_shieldArmorSetFlag); + validateArmorAndDamageFX(); + } +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBody::doDamageFX(const DamageInfo* damageInfo) +{ + // If our damage is absorbed by the shield, we play the damageFX early to have the proper numbers + // To avoid playing the damageFX again later on, we check if our shield is still up + // and damage is 0, which means we got a full absorb + + if (m_currentShieldHealth > 0 && damageInfo->out.m_actualDamageDealt == 0) + return; + + ActiveBody::doDamageFX(damageInfo); + +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBody::onDisabledEdge(Bool nowDisabled) +{ + if (!isActive()) { // If the shield isn't enabled, no need to do anything + return; + } + + if (nowDisabled) { + m_currentShieldHealth = 0; + disableShieldEffects(); + + if (m_shieldBehaviorModule == NULL) { + findShieldBehaviorModule(); + } + + if (!m_shieldBehaviorModule) { + return; + } + + m_shieldBehaviorModule->applyDamage(getShieldMaxHealth()); + } +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBody::attemptDamage(DamageInfo* damageInfo) +{ + // Shield is not enabled, just pass through + if (!isActive()) { + ActiveBody::attemptDamage(damageInfo); + return; + } + + validateArmorAndDamageFX(); + + // We need to do all the early return checks from ActiveBody here as well + // For anything that should prevent even hitting the shield + + // sanity + if (damageInfo == NULL) + return; + + if (isIndestructible()) + return; + + // initialize these, just in case we bail out early + damageInfo->out.m_actualDamageDealt = 0.0f; + damageInfo->out.m_actualDamageClipped = 0.0f; + + // we cannot damage again objects that are already dead + Object* obj = getObject(); + if (obj->isEffectivelyDead()) + return; + + // Units that get disabled by Chrono damage cannot take damage: + if (obj->isDisabledByType(DISABLED_CHRONO) && + !(damageInfo->in.m_damageType == DAMAGE_CHRONO_GUN || damageInfo->in.m_damageType == DAMAGE_CHRONO_UNRESISTABLE)) + return; + + // -- + // BEGIN SHIELD CALCULATIONS + // --- + + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + + // Pass through full damage + if (getDamageTypeFlag(data->m_damageTypesToPassThrough, damageInfo->in.m_damageType) || + getDamageTypeFlag(data->m_defaultDamageTypesToPassThrough, damageInfo->in.m_damageType)) + { + // We need to switch our armorset for this one + if (data->m_shieldArmorSetFlag != ARMORSET_NONE) { + getObject()->clearArmorSetFlag(data->m_shieldArmorSetFlag); + } + + ActiveBody::attemptDamage(damageInfo); + + // And switch back + if (data->m_shieldArmorSetFlag != ARMORSET_NONE) { + getObject()->setArmorSetFlag(data->m_shieldArmorSetFlag); + //validateArmorAndDamageFX(); + } + return; + } + + // Calculate Shield damage: + // TODO: If we have other ways to use the shield, we could add some conditions + if (m_shieldBehaviorModule == NULL) { + findShieldBehaviorModule(); + } + + if (!m_shieldBehaviorModule) { + DEBUG_LOG(("ShieldBody::attemptDamage - Warning, no EnergyShieldBehaviorModule found.")); + ActiveBody::attemptDamage(damageInfo); + return; + } + + //Shield is already down, just pass through, but stop recovery + if (m_currentShieldHealth <= 0) { + m_shieldBehaviorModule->applyDamage(0.0f); + ActiveBody::attemptDamage(damageInfo); + return; + } + + Real rawDamage = damageInfo->in.m_amount; + Real damageToShield = getCurrentArmor().adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); + + //Note: we apply the damage scalar only to the damage to the actual shield. + // It's later applied again for overkill damage + if (damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE) + { + damageToShield *= m_damageScalar; + } + + Real damageAmountToPass = 0.0; // The damage (done to HP) we pass to the base function + + Bool shieldStillUp = TRUE; + if (damageToShield > 0) { + Real remainingShieldHealth = m_currentShieldHealth - damageToShield; + + shieldStillUp = remainingShieldHealth > 0; + + m_currentShieldHealth = MAX(0, remainingShieldHealth); + + // Apply Damage to shield and Stop shield recharge - Notify shield behavior + m_shieldBehaviorModule->applyDamage(MAX(0, damageToShield)); + //DEBUG_LOG(("ShieldBody::attemptDamage - m_currentShieldHealth = %f.", m_currentShieldHealth)); + + //If the shield is at 0, we still need to disable it + if (!shieldStillUp) { + + // Disable Shield Armor + disableShieldEffects(); + + Real overkillDamage = abs(remainingShieldHealth); + + // the shield overkill damage was already mitigated by shield armor + // so we want it's relative amount of the raw damage (i.e. we invert the armor) + Real relativeRawDamage = (overkillDamage / damageToShield) * rawDamage; + + damageAmountToPass = relativeRawDamage; + } + } + + if (shieldStillUp) { + // Only play the damageFX on a full absorb + damageInfo->out.m_actualDamageDealt = damageToShield; //only used for damageFX + doDamageFX(damageInfo); + damageInfo->out.m_actualDamageDealt = 0; + } + + + damageInfo->in.m_amount = damageAmountToPass; + + // extend + ActiveBody::attemptDamage(damageInfo); + //DEBUG_LOG(("ShieldBody::attemptDamage - getHealth() = %f.", getHealth())); + + if (shieldStillUp) { + // We passed on 0 damage to ActiveBody. + // This means we need to do a couple of things that were skipped + + if (m_lastDamageTimestamp != TheGameLogic->getFrame() && m_lastDamageTimestamp != TheGameLogic->getFrame() - 1) { + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + } + else { + // Multiple damages applied in one/next frame. We prefer the one that tells who the attacker is. + Object* srcObj1 = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); + Object* srcObj2 = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID); + if (srcObj2) { + if (srcObj1) { + if (srcObj2->isKindOf(KINDOF_VEHICLE) || srcObj2->isKindOf(KINDOF_INFANTRY) || + srcObj2->isFactionStructure()) { + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + } + } + else { + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + } + + } + else { + // no change. + } + } + + // Notify the player that they have been attacked by this player + if (m_lastDamageInfo.in.m_sourceID != INVALID_ID) + { + Object* srcObj = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); + if (srcObj) + { + Player* srcPlayer = srcObj->getControllingPlayer(); + obj->getControllingPlayer()->setAttackedBy(srcPlayer->getPlayerIndex()); + } + } + + } + +} +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void ShieldBody::crc( Xfer *xfer ) +{ + + // extend base class + ActiveBody::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void ShieldBody::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // base class + ActiveBody::xfer( xfer ); + + // current shield health + xfer->xferReal(&m_currentShieldHealth); + //xfer->xferUnsignedInt(&m_healingStepCountdown); + + // shield activation + xfer->xferBool(&m_active); + + // constructor object id + //xfer->xferObjectID( &m_constructorObjectID ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void ShieldBody::loadPostProcess( void ) +{ + + // extend base class + ActiveBody::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 075905525c7..9a573a4d4d2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -98,6 +98,7 @@ #include "GameLogic/Module/ToppleUpdate.h" #include "GameLogic/Module/UpdateModule.h" #include "GameLogic/Module/UpgradeModule.h" +#include "GameLogic/Module/EnergyShieldBehavior.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" @@ -1503,6 +1504,40 @@ Bool Object::getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const } } +//============================================================================= +Bool Object::getProgressBarShowingInfo(bool selected, Real& progress, Int& type, RGBAColorInt& color, RGBAColorInt& colorBG) const +{ + if (!isKindOf(KINDOF_SHOW_PROGRESS_BAR)) + return FALSE; + + // We put every case of Progress bars here. + // Maybe we should require a KindOf for performance? + + type = 0; // TODO + color = { 255, 255, 255, 255 }; // Default = white + colorBG = { 255, 255, 255, 255 }; // Default = white + + // Energy Shields + // TODO: This is kinda bad, there should be a better way to do this, if we have multiple shield sources. + // This should come from the Body? + EnergyShieldBehaviorInterface* esbi = NULL; + + for (BehaviorModule** u = m_behaviors; *u; ++u) + { + if ((esbi = (*u)->getEnergyShieldBehaviorInterface()) != NULL) { + if (esbi->shouldShowHealthBar(selected)) { + progress = esbi->getShieldPercent(); + color = esbi->getHealthBarColor(); + colorBG = esbi->getHealthBarBackgroundColor(); + return true; + } + } + } + + return false; + +} + //============================================================================= /* NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp index 9b7cffe0099..c60b7560e11 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp @@ -120,22 +120,16 @@ void ArmorUpgrade::upgradeImplementation( ) // Very simple; just need to flag the Object as having the player upgrade, and the WeaponSet chooser // will do the work of picking the right one from ini. This comment is as long as the code. - DEBUG_LOG(("ArmorUpgrade::upgradeImplementation 0\n")); - const ArmorUpgradeModuleData* data = getArmorUpgradeModuleData(); Object *obj = getObject(); if( !obj ) return; - DEBUG_LOG(("ArmorUpgrade::upgradeImplementation 1\n")); - BodyModuleInterface* body = obj->getBodyModule(); if (body) { body->setArmorSetFlag(data->m_armorSetFlag); - DEBUG_LOG(("ArmorUpgrade::upgradeImplementation 2 - flagsToClear = %d\n", data->m_armorSetFlagsToClear)); - if (data->m_armorSetFlagsToClear.any()) { // We loop over each armorset type and see if we have it set. // Andi: Not sure if this is cleaner solution than storing an array of flags.