From 9ac4617f792067f7591ec8c2072422364e986b68 Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 14 Sep 2025 19:24:33 +0200 Subject: [PATCH 1/6] First attempt, kinda shit --- Core/GameEngine/CMakeLists.txt | 2 + .../System/GameMemoryInitPools_GeneralsMD.inl | 1 + .../Source/Common/Thing/ModuleFactory.cpp | 1 + GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../Include/GameLogic/Module/ActiveBody.h | 2 + .../Include/GameLogic/Module/ShieldBody.h | 107 ++++++++ .../GameEngine/Include/GameLogic/Object.h | 2 +- .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../GameLogic/Object/Body/ShieldBody.cpp | 232 ++++++++++++++++++ 9 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index c0c85c3c6e4..4d0f77292b6 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -344,6 +344,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 @@ -880,6 +881,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 f7e02d7c8a9..92a657a5134 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl @@ -251,6 +251,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 e0dde9186b8..0f9f86bd2e5 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -349,6 +349,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 @@ -898,6 +899,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/GameLogic/Module/ActiveBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h index af3bda50c91..a0aaa4d02f6 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h @@ -160,6 +160,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 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..ef1e5c216d4 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h @@ -0,0 +1,107 @@ +/* +** 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 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) + + 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) + + DamageTypeFlags m_damageTypesToPassThrough; + + ShieldBodyModuleData(); + + 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); +}; + +//------------------------------------------------------------------------------------------------- +/** 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; } + +protected: + + virtual void attemptDamage(DamageInfo* damageInfo); ///< try to damage this object + + //ObjectID m_constructorObjectID; ///< object that built this structure + +private: + Real m_currentShieldHealth; + UnsignedInt m_healingStepCountdown; + +}; + +#endif // __ShieldBody_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 72b85b283e2..f58be168851 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -737,7 +737,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/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 573b53c2879..5bb1c09e231 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -268,6 +268,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 @@ -561,6 +562,7 @@ void ModuleFactory::init( void ) addModule( ImmortalBody ); addModule( StructureBody ); addModule( HiveStructureBody ); + addModule( ShieldBody ); addModule( UndeadBody ); // contain modules 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..023e3a10b41 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp @@ -0,0 +1,232 @@ +/* +** 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" + +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ShieldBodyModuleData::ShieldBodyModuleData() +{ + m_damageTypesToPassThrough = DAMAGE_TYPE_FLAGS_NONE; + //m_maxHealth = 0; + //m_initialHealth = 0; + //m_subdualDamageCap = 0; + //m_subdualDamageHealRate = 0; + //m_subdualDamageHealAmount = 0; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBodyModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + ActiveBodyModuleData::buildFieldParse(p); + //UpdateModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + //TODO + { "StartsActive", INI::parseBool, NULL, offsetof(ShieldBodyModuleData, m_startsActive) }, + + { "ShieldMaxHealth", INI::parseReal, NULL, offsetof(ShieldBodyModuleData, m_shieldMaxHealth) }, + { "ShieldMaxHealthPercent", parseShieldHealthPercent, NULL, 0}, // offsetof(ShieldBodyModuleData, m_shieldMaxHealthPercent) }, + + { "ShieldRechargeDelay", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeDelay) }, + { "ShieldRechargeRate", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeRate) }, + { "ShieldRechargeAmount", INI::parseReal, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeAmount) }, + { "ShieldRechargeAmountPercent", parseShieldRechargeAmountPercent, NULL, 0}, //offsetof(ShieldBodyModuleData, m_shieldRechargeAmountPercent) }, + + { "DamageTypesToPassThroughShield", INI::parseDamageTypeFlags, NULL, offsetof(ShieldBodyModuleData, m_damageTypesToPassThrough) }, + { 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; +} +//------------------------------------------------------------------------------------------------- +void ShieldBodyModuleData::parseShieldRechargeAmountPercent(INI* ini, void* instance, void* store, const void* /*userData*/) +{ + ShieldBodyModuleData* self = (ShieldBodyModuleData*)instance; + Real amountPercent = INI::scanPercentToReal(ini->getNextToken()); + self->m_shieldMaxHealth = self->m_shieldMaxHealth * amountPercent; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ShieldBody::ShieldBody( Thing *thing, const ModuleData* moduleData ) : ActiveBody( thing, moduleData ) +{ + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + m_currentShieldHealth = data->m_shieldMaxHealth; + //m_constructorObjectID = INVALID_ID; + + //setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); +} // end ShieldBody + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ShieldBody::~ShieldBody( void ) +{ + +} // end ~ShieldBody + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//UpdateSleepTime ShieldBody::update(void) +//{ +// DEBUG_LOG((">>> ShieldBody::update - m_currentShieldHealth = %f, m_healingStepCountdown = %d", m_currentShieldHealth, m_healingStepCountdown)); +// +// //UnsignedInt now = TheGameLogic->getFrame(); +// //UnsignedInt damageFrame = getLastDamageTimestamp(); +// //const ShieldBodyModuleData* data = getShieldBodyModuleData(); +// //if (now < damageFrame + data->m_shieldRechargeDelay) { +// // return UPDATE_SLEEP_NONE; +// //} +// +// m_healingStepCountdown--; +// if (m_healingStepCountdown > 0) +// return UPDATE_SLEEP_NONE; +// +// const ShieldBodyModuleData* data = getShieldBodyModuleData(); +// m_healingStepCountdown = data->m_shieldRechargeRate; +// +// m_currentShieldHealth = MIN(data->m_shieldMaxHealth, m_currentShieldHealth + data->m_shieldRechargeAmount); +// +// //if (m_currentShieldHealth < data->m_shieldMaxHealth) +// // return UPDATE_SLEEP_NONE; +// //else +// // return UPDATE_SLEEP_FOREVER; +// return UPDATE_SLEEP_NONE; +//} // end update +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShieldBody::attemptDamage(DamageInfo* damageInfo) +{ + validateArmorAndDamageFX(); + + // sanity + if (damageInfo == NULL) + return; + + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + //Object* obj = getObject(); + + // Pass through full famage + if (getDamageTypeFlag(data->m_damageTypesToPassThrough, damageInfo->in.m_damageType)) + { + ActiveBody::attemptDamage(damageInfo); + return; + } + + // Calculate Shield damage: + + Real damageToShield = getCurrentArmor().adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); + + Real remainingShieldHealth = m_currentShieldHealth - damageToShield; + m_currentShieldHealth = MAX(0, remainingShieldHealth); + + if (remainingShieldHealth < data->m_shieldMaxHealth) + { + m_healingStepCountdown = data->m_shieldRechargeDelay; + //setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + } + + if (remainingShieldHealth < 0) { + + //TODO: Disable Shield Armor + + Real overkillDamage = abs(remainingShieldHealth); + damageInfo->in.m_amount = overkillDamage; + } + else { + damageInfo->in.m_amount = 0.0001; + } + + // extend + ActiveBody::attemptDamage(damageInfo); +} +// ------------------------------------------------------------------------------------------------ +/** 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); + + // constructor object id + //xfer->xferObjectID( &m_constructorObjectID ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void ShieldBody::loadPostProcess( void ) +{ + + // extend base class + ActiveBody::loadPostProcess(); + +} // end loadPostProcess From 3d00be62315b9f47e8061efa804b05023ecfbe5b Mon Sep 17 00:00:00 2001 From: andreasw Date: Mon, 15 Sep 2025 21:14:50 +0200 Subject: [PATCH 2/6] added EnergyShieldBehavior somehow doesn't build anymore --- Core/GameEngine/CMakeLists.txt | 2 + .../System/GameMemoryInitPools_GeneralsMD.inl | 1 + GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../GameEngine/Include/GameClient/Drawable.h | 3 + .../Include/GameLogic/Module/BehaviorModule.h | 5 +- .../GameLogic/Module/EnergyShieldBehavior.h | 161 ++++++++++++ .../Include/GameLogic/Module/ShieldBody.h | 19 +- .../GameEngine/Include/GameLogic/Object.h | 3 + .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../GameEngine/Source/GameClient/Drawable.cpp | 50 ++++ .../Object/Behavior/EnergyShieldBehavior.cpp | 235 ++++++++++++++++++ .../GameLogic/Object/Body/ShieldBody.cpp | 63 +++-- .../Source/GameLogic/Object/Object.cpp | 25 ++ 13 files changed, 545 insertions(+), 26 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 4d0f77292b6..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 @@ -855,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 diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl index 92a657a5134..be49a3e6e10 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 }, diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 0f9f86bd2e5..e40e955212c 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 @@ -870,6 +871,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 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/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..e6302f76154 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h @@ -0,0 +1,161 @@ +/* +** 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 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) + + //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 getShieldPercent(Real& percentage) 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 getShieldPercent(Real& percentage) const; + +protected: + + virtual void upgradeImplementation() + { + setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + } + + 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; + +}; + +#endif // __EnergyShieldBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h index ef1e5c216d4..87249c53ede 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h @@ -47,11 +47,11 @@ class ShieldBodyModuleData : public ActiveBodyModuleData 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) + 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. + //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) DamageTypeFlags m_damageTypesToPassThrough; @@ -91,6 +91,12 @@ class ShieldBody : public ActiveBody //, public UpdateModuleInterface //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; + + Bool getShieldPercent(Real& percentage); + protected: virtual void attemptDamage(DamageInfo* damageInfo); ///< try to damage this object @@ -99,7 +105,10 @@ class ShieldBody : public ActiveBody //, public UpdateModuleInterface private: Real m_currentShieldHealth; - UnsignedInt m_healingStepCountdown; + + EnergyShieldBehavior* m_shieldBehaviorModule; + + //UnsignedInt m_healingStepCountdown; }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index f58be168851..3b36a923fd2 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(Real& progress, Int& type) const; + void notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) ; /** diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 5bb1c09e231..b408a9b0d8f 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" @@ -344,6 +345,7 @@ void ModuleFactory::init( void ) addModule( BridgeTowerBehavior ); addModule( CountermeasuresBehavior ); addModule( BattlePlanBonusBehavior ); + addModule( EnergyShieldBehavior ); addModule( DumbProjectileBehavior ); addModule( FreeFallProjectileBehavior ); addModule( PhysicsBehavior ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 047461e418d..44e308d1997 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,54 @@ 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; + + Real progress; + Int type; //not used yet + if (!obj->getProgressBarShowingInfo(progress, type)) + return; + + + Color color, outlineColor; + + color = GameMakeColor(255, 255, 255, 255); // white bar + outlineColor = GameMakeColor(255, 255, 255, 255); // white outline + + + 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 = 5; + + // draw the health (actually ammo) 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..b9503bc10d1 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp @@ -0,0 +1,235 @@ +/* +** 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" + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +EnergyShieldBehaviorModuleData::EnergyShieldBehaviorModuleData() +{ + //m_damageTypesToPassThrough = DAMAGE_TYPE_FLAGS_NONE; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +/*static*/ void EnergyShieldBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + static const FieldParse dataFieldParse[] = + { + { "StartsActive", INI::parseBool, NULL, offsetof(EnergyShieldBehaviorModuleData, m_initiallyActive) }, + //{ "ShieldMaxHealth", INI::parseReal, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldMaxHealth) }, + //{ "ShieldMaxHealthPercent", parseShieldHealthPercent, NULL, 0}, // offsetof(EnergyShieldBehaviorModuleData, m_shieldMaxHealthPercent) }, + + { "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::parseReal, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldRechargeAmountPercent) }, + + //{ "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)); +} + +////------------------------------------------------------------------------------------------------- +////------------------------------------------------------------------------------------------------- +//void EnergyShieldBehaviorModuleData::parseShieldHealthPercent(INI* ini, void* instance, void* store, const void* /*userData*/) +//{ +// EnergyShieldBehaviorModuleData* self = (EnergyShieldBehaviorModuleData*)instance; +// Real healthPercent = INI::scanPercentToReal(ini->getNextToken()); +// self->m_shieldMaxHealth = self->m_maxHealth * healthPercent; +//} +////------------------------------------------------------------------------------------------------- +//void EnergyShieldBehaviorModuleData::parseShieldRechargeAmountPercent(INI* ini, void* instance, void* store, const void* /*userData*/) +//{ +// EnergyShieldBehaviorModuleData* self = (EnergyShieldBehaviorModuleData*)instance; +// Real amountPercent = INI::scanPercentToReal(ini->getNextToken()); +// self->m_shieldMaxHealth = self->m_shieldMaxHealth * amountPercent; +//} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +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(); + } + else + { + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +EnergyShieldBehavior::~EnergyShieldBehavior( void ) +{ + +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void EnergyShieldBehavior::applyDamage(Real amount) +{ + if (amount > 0) + { + const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); + m_healingStepCountdown = data->m_shieldRechargeDelay; + setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + } +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +// This should perhaps be returned from the body instead +Bool EnergyShieldBehavior::getShieldPercent(Real &progress) const +{ + if (isActive() && m_body != NULL) + { + //return (m_body->getShieldMaxHealth() / m_body->getShieldCurrentHealth()); + return m_body->getShieldPercent(progress); + } + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +/** The update callback. */ +//------------------------------------------------------------------------------------------------- +UpdateSleepTime EnergyShieldBehavior::update( void ) +{ + DEBUG_LOG((">>> EnergyShieldBehavior::update - m_healingStepCountdown = %d", m_healingStepCountdown)); + + //UnsignedInt now = TheGameLogic->getFrame(); + //UnsignedInt damageFrame = getLastDamageTimestamp(); + //const ShieldBodyModuleData* data = getShieldBodyModuleData(); + //if (now < damageFrame + data->m_shieldRechargeDelay) { + // return UPDATE_SLEEP_NONE; + //} + + m_healingStepCountdown--; + if (m_healingStepCountdown > 0) + return UPDATE_SLEEP_NONE; + + const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); + m_healingStepCountdown = data->m_shieldRechargeRate; + + if (m_body) { + Real rechargeAmount; + if (data->m_shieldRechargeAmountPercent) { + rechargeAmount = data->m_shieldRechargeAmountPercent * m_body->getShieldMaxHealth(); + }else { + rechargeAmount = data->m_shieldRechargeAmount; + } + + Bool full = m_body->rechargeShieldHealth(rechargeAmount); + if (full) + 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); + +} // 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 index 023e3a10b41..b9184ec6193 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp @@ -34,6 +34,7 @@ #include "GameLogic/Object.h" #include "GameLogic/Damage.h" #include "GameLogic/Module/ShieldBody.h" +#include "GameLogic/Module/EnergyShieldBehavior.h" // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// @@ -64,10 +65,10 @@ void ShieldBodyModuleData::buildFieldParse(MultiIniFieldParse& p) { "ShieldMaxHealth", INI::parseReal, NULL, offsetof(ShieldBodyModuleData, m_shieldMaxHealth) }, { "ShieldMaxHealthPercent", parseShieldHealthPercent, NULL, 0}, // offsetof(ShieldBodyModuleData, m_shieldMaxHealthPercent) }, - { "ShieldRechargeDelay", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeDelay) }, - { "ShieldRechargeRate", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeRate) }, - { "ShieldRechargeAmount", INI::parseReal, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeAmount) }, - { "ShieldRechargeAmountPercent", parseShieldRechargeAmountPercent, NULL, 0}, //offsetof(ShieldBodyModuleData, m_shieldRechargeAmountPercent) }, + //{ "ShieldRechargeDelay", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeDelay) }, + //{ "ShieldRechargeRate", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeRate) }, + //{ "ShieldRechargeAmount", INI::parseReal, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeAmount) }, + //{ "ShieldRechargeAmountPercent", parseShieldRechargeAmountPercent, NULL, 0}, //offsetof(ShieldBodyModuleData, m_shieldRechargeAmountPercent) }, { "DamageTypesToPassThroughShield", INI::parseDamageTypeFlags, NULL, offsetof(ShieldBodyModuleData, m_damageTypesToPassThrough) }, { 0, 0, 0, 0 } @@ -84,22 +85,20 @@ void ShieldBodyModuleData::parseShieldHealthPercent(INI* ini, void* instance, vo self->m_shieldMaxHealth = self->m_maxHealth * healthPercent; } //------------------------------------------------------------------------------------------------- -void ShieldBodyModuleData::parseShieldRechargeAmountPercent(INI* ini, void* instance, void* store, const void* /*userData*/) -{ - ShieldBodyModuleData* self = (ShieldBodyModuleData*)instance; - Real amountPercent = INI::scanPercentToReal(ini->getNextToken()); - self->m_shieldMaxHealth = self->m_shieldMaxHealth * amountPercent; -} +//void ShieldBodyModuleData::parseShieldRechargeAmountPercent(INI* ini, void* instance, void* store, const void* /*userData*/) +//{ +// ShieldBodyModuleData* self = (ShieldBodyModuleData*)instance; +// Real amountPercent = INI::scanPercentToReal(ini->getNextToken()); +// self->m_shieldMaxHealth = self->m_shieldMaxHealth * amountPercent; +//} //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- ShieldBody::ShieldBody( Thing *thing, const ModuleData* moduleData ) : ActiveBody( thing, moduleData ) { - const ShieldBodyModuleData* data = getShieldBodyModuleData(); - m_currentShieldHealth = data->m_shieldMaxHealth; - //m_constructorObjectID = INVALID_ID; + /*const ShieldBodyModuleData* data = getShieldBodyModuleData(); + m_currentShieldHealth = data->m_shieldMaxHealth;*/ - //setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); } // end ShieldBody //------------------------------------------------------------------------------------------------- @@ -139,6 +138,26 @@ ShieldBody::~ShieldBody( void ) //} // end update //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- +Bool ShieldBody::getShieldPercent(Real& percentage) +{ + // TODO: + //if (isActive()) { + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + percentage = m_currentShieldHealth / data->m_shieldMaxHealth; + return TRUE; + //} + //return FALSE; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ShieldBody::rechargeShieldHealth(Real amount) +{ + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + m_currentShieldHealth = MIN(data->m_shieldMaxHealth, m_currentShieldHealth + amount); + return m_currentShieldHealth >= data->m_shieldMaxHealth; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- void ShieldBody::attemptDamage(DamageInfo* damageInfo) { validateArmorAndDamageFX(); @@ -158,17 +177,21 @@ void ShieldBody::attemptDamage(DamageInfo* damageInfo) } // Calculate Shield damage: + // TODO: If we have other ways to use the shield, we could add some conditions + EnergyShieldBehaviorInterface* esbi = getEnergyShieldBehaviorInterface(); + if (!esbi) { + DEBUG_LOG(("ShieldBody::attemptDamage - Warning, no EnergyShieldBehaviorModule found.")); + ActiveBody::attemptDamage(damageInfo); + return; + } Real damageToShield = getCurrentArmor().adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); Real remainingShieldHealth = m_currentShieldHealth - damageToShield; m_currentShieldHealth = MAX(0, remainingShieldHealth); - if (remainingShieldHealth < data->m_shieldMaxHealth) - { - m_healingStepCountdown = data->m_shieldRechargeDelay; - //setWakeFrame(getObject(), UPDATE_SLEEP_NONE); - } + // Apply Damage to shield and Stop shield recharge - Notify shield behavior + esbi->applyDamage(MAX(0, damageToShield)); if (remainingShieldHealth < 0) { @@ -213,7 +236,7 @@ void ShieldBody::xfer( Xfer *xfer ) // current shield health xfer->xferReal(&m_currentShieldHealth); - xfer->xferUnsignedInt(&m_healingStepCountdown); + //xfer->xferUnsignedInt(&m_healingStepCountdown); // constructor object id //xfer->xferObjectID( &m_constructorObjectID ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 075905525c7..b5c86d1b073 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,30 @@ Bool Object::getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const } } +//============================================================================= +Bool Object::getProgressBarShowingInfo(Real& progress, Int& type) const +{ + // We put every case of Progress bars here. + // Maybe we should require a KindOf for performance? + + type = 0; // TODO + + // 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) { + return esbi->getShieldPercent(progress); + } + } + + return false; + +} + //============================================================================= /* NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), From 936f13b9993380bda1f2fc7377e3e4218a531c56 Mon Sep 17 00:00:00 2001 From: andreasw Date: Wed, 17 Sep 2025 21:24:09 +0200 Subject: [PATCH 3/6] Basic implementation done --- .../Code/GameEngine/Include/Common/KindOf.h | 4 +- .../GameEngine/Include/Common/ThingTemplate.h | 22 ++ .../Include/GameLogic/Module/ActiveBody.h | 16 +- .../GameLogic/Module/EnergyShieldBehavior.h | 22 +- .../Include/GameLogic/Module/ShieldBody.h | 16 +- .../GameEngine/Include/GameLogic/Object.h | 2 +- .../Source/Common/System/KindOf.cpp | 4 +- .../GameEngine/Source/GameClient/Drawable.cpp | 29 ++- .../Object/Behavior/EnergyShieldBehavior.cpp | 67 ++++- .../GameLogic/Object/Body/ShieldBody.cpp | 246 +++++++++++++++--- .../Source/GameLogic/Object/Object.cpp | 14 +- .../GameLogic/Object/Upgrade/ArmorUpgrade.cpp | 6 - 12 files changed, 366 insertions(+), 82 deletions(-) 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/GameLogic/Module/ActiveBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h index a0aaa4d02f6..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, @@ -172,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/EnergyShieldBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h index e6302f76154..2bd288c3034 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h @@ -45,6 +45,8 @@ #include "Common/BitFlagsIO.h" +class ShieldBody; + //------------------------------------------------------------------------------------------------- class EnergyShieldBehaviorModuleData : public UpdateModuleData { @@ -59,6 +61,11 @@ class EnergyShieldBehaviorModuleData : public UpdateModuleData 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; + //DamageTypeFlags m_damageTypesToPassThrough; EnergyShieldBehaviorModuleData(); @@ -76,7 +83,10 @@ class EnergyShieldBehaviorInterface public: virtual void applyDamage(Real amount) = 0; virtual bool isActive() const = 0; - virtual Bool getShieldPercent(Real& percentage) const = 0; + virtual bool shouldShowHealthBar(bool selected) const = 0; + virtual Real getShieldPercent() const = 0; + virtual RGBAColorInt getHealthBarColor() const = 0; + virtual RGBAColorInt getHealthBarBackgroundColor() const = 0; }; //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -114,14 +124,14 @@ class EnergyShieldBehavior : public UpdateModule, //EnergyShieldBehaviorInterface virtual void applyDamage(Real amount); virtual bool isActive() const { return isUpgradeActive(); } - virtual Bool getShieldPercent(Real& percentage) const; + 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() - { - setWakeFrame(getObject(), UPDATE_SLEEP_NONE); - } + virtual void upgradeImplementation(); virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const { diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h index 87249c53ede..5ef87d1c3ac 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h @@ -39,6 +39,7 @@ // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// class Object; +class EnergyShieldBehavior; //------------------------------------------------------------------------------------------------- class ShieldBodyModuleData : public ActiveBodyModuleData @@ -48,6 +49,7 @@ class ShieldBodyModuleData : public ActiveBodyModuleData 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 //UnsignedInt m_shieldRechargeDelay; ///< frames of no damage taken until shield recharges //UnsignedInt m_shieldRechargeRate; ///< every this often, we heal the shield ... @@ -95,21 +97,29 @@ class ShieldBody : public ActiveBody //, public UpdateModuleInterface inline Real getShieldCurrentHealth() const { return m_currentShieldHealth; } Bool rechargeShieldHealth(Real amount); ///< returns True if on full health; - Bool getShieldPercent(Real& percentage); + 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); //ObjectID m_constructorObjectID; ///< object that built this structure private: Real m_currentShieldHealth; - EnergyShieldBehavior* m_shieldBehaviorModule; + EnergyShieldBehaviorInterface* m_shieldBehaviorModule; + + Bool m_active; - //UnsignedInt m_healingStepCountdown; + 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 3b36a923fd2..429a8f1b9f7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -505,7 +505,7 @@ class Object : public Thing, public Snapshot Bool getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const; // Progress bar for various things - Bool getProgressBarShowingInfo(Real& progress, Int& type) const; + Bool getProgressBarShowingInfo(bool selected, Real& progress, Int& type, RGBAColorInt& color, RGBAColorInt& colorBG) const; void notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) ; 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/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 44e308d1997..b79012ab59d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -3112,23 +3112,30 @@ void Drawable::drawProgress( const IRegion2D *healthBarRegion ) const Object* obj = getObject(); - if (!( - TheGlobalData->m_showObjectHealth && - (isSelected() || (TheInGameUI && (TheInGameUI->getMousedOverDrawableID() == getID()))) && - // obj->getControllingPlayer() == ThePlayerList->getLocalPlayer() // Shields are visible for all - )) + //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 - if (!obj->getProgressBarShowingInfo(progress, type)) - return; + RGBAColorInt barColor; + RGBAColorInt barColorBG; + + if (!obj->getProgressBarShowingInfo(selected, progress, type, barColor, barColorBG)) + return; Color color, outlineColor; - color = GameMakeColor(255, 255, 255, 255); // white bar - outlineColor = GameMakeColor(255, 255, 255, 255); // white outline + 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; @@ -3136,9 +3143,9 @@ void Drawable::drawProgress( const IRegion2D *healthBarRegion ) Real healthBoxHeight = max(3, healthBarRegion->hi.y - healthBarRegion->lo.y) * 1.5f; Real healthBoxOutlineSize = 1.0f; - Real yOffset = 5; + Real yOffset = -5; - // draw the health (actually ammo) box outline + // draw the health box outline TheDisplay->drawOpenRect(healthBarRegion->lo.x, healthBarRegion->lo.y + yOffset, healthBoxWidth, healthBoxHeight, healthBoxOutlineSize, outlineColor); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp index b9503bc10d1..89c1d6ef6b7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp @@ -49,7 +49,15 @@ //------------------------------------------------------------------------------------------------- EnergyShieldBehaviorModuleData::EnergyShieldBehaviorModuleData() { - //m_damageTypesToPassThrough = DAMAGE_TYPE_FLAGS_NONE; + 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; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -65,7 +73,12 @@ EnergyShieldBehaviorModuleData::EnergyShieldBehaviorModuleData() { "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::parseReal, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldRechargeAmountPercent) }, + { "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) }, //{ "DamageTypesToPassThroughShield", INI::parseDamageTypeFlags, NULL, offsetof(EnergyShieldBehaviorModuleData, m_damageTypesToPassThrough) }, { 0, 0, 0, 0 } @@ -98,7 +111,7 @@ EnergyShieldBehavior::EnergyShieldBehavior( Thing *thing, const ModuleData* modu { ShieldBody* body = dynamic_cast(getObject()->getBodyModule()); - DEBUG_ASSERTCRASH((body != nullptr), ("EnergyShieldBehavior requires ShieldBody!")) + DEBUG_ASSERTCRASH((body != nullptr), ("EnergyShieldBehavior requires ShieldBody!")); m_body = body; @@ -108,10 +121,8 @@ EnergyShieldBehavior::EnergyShieldBehavior( Thing *thing, const ModuleData* modu { giveSelfUpgrade(); } - else - { - setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); - } + + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); // We wake when we get attacked } //------------------------------------------------------------------------------------------------- @@ -124,6 +135,8 @@ EnergyShieldBehavior::~EnergyShieldBehavior( void ) //------------------------------------------------------------------------------------------------- void EnergyShieldBehavior::applyDamage(Real amount) { + DEBUG_LOG((">>> EnergyShieldBehavior::applyDamage - amount = %f", amount)); + if (amount > 0) { const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); @@ -133,23 +146,49 @@ void EnergyShieldBehavior::applyDamage(Real amount) } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- -// This should perhaps be returned from the body instead -Bool EnergyShieldBehavior::getShieldPercent(Real &progress) const +Real EnergyShieldBehavior::getShieldPercent() const { if (isActive() && m_body != NULL) { - //return (m_body->getShieldMaxHealth() / m_body->getShieldCurrentHealth()); - return m_body->getShieldPercent(progress); + return m_body->getShieldPercent(); } - return FALSE; + 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; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void EnergyShieldBehavior::upgradeImplementation() +{ + if (m_body) { + m_body->setActive(true); + m_body->rechargeShieldHealth(m_body->getShieldMaxHealth()); + } + //setWakeFrame(getObject(), UPDATE_SLEEP_NONE); +} + //------------------------------------------------------------------------------------------------- /** The update callback. */ //------------------------------------------------------------------------------------------------- UpdateSleepTime EnergyShieldBehavior::update( void ) { - DEBUG_LOG((">>> EnergyShieldBehavior::update - m_healingStepCountdown = %d", m_healingStepCountdown)); + DEBUG_LOG((">>> EnergyShieldBehavior::update1 - m_healingStepCountdown = %d", m_healingStepCountdown)); //UnsignedInt now = TheGameLogic->getFrame(); //UnsignedInt damageFrame = getLastDamageTimestamp(); @@ -172,6 +211,7 @@ UpdateSleepTime EnergyShieldBehavior::update( void ) }else { rechargeAmount = data->m_shieldRechargeAmount; } + DEBUG_LOG((">>> EnergyShieldBehavior::update2 - rechargeAmount = %f", rechargeAmount)); Bool full = m_body->rechargeShieldHealth(rechargeAmount); if (full) @@ -179,6 +219,7 @@ UpdateSleepTime EnergyShieldBehavior::update( void ) else return UPDATE_SLEEP_NONE; } + return UPDATE_SLEEP_FOREVER; } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp index b9184ec6193..f5815b461a8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp @@ -35,6 +35,9 @@ #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 /////////////////////////////////////////////////////////////////////////////// @@ -43,11 +46,7 @@ ShieldBodyModuleData::ShieldBodyModuleData() { m_damageTypesToPassThrough = DAMAGE_TYPE_FLAGS_NONE; - //m_maxHealth = 0; - //m_initialHealth = 0; - //m_subdualDamageCap = 0; - //m_subdualDamageHealRate = 0; - //m_subdualDamageHealAmount = 0; + m_shieldArmorSetFlag = ARMORSET_NONE; } //------------------------------------------------------------------------------------------------- @@ -65,6 +64,9 @@ void ShieldBodyModuleData::buildFieldParse(MultiIniFieldParse& p) { "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) }, + //{ "ShieldUpFX", INI::parseIndexList, ArmorSetFlags::getBitNames(), offsetof(ShieldBodyModuleData, m_shieldArmorSetFlag) }, + //{ "ShieldRechargeDelay", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeDelay) }, //{ "ShieldRechargeRate", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeRate) }, //{ "ShieldRechargeAmount", INI::parseReal, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeAmount) }, @@ -96,8 +98,14 @@ void ShieldBodyModuleData::parseShieldHealthPercent(INI* ini, void* instance, vo //------------------------------------------------------------------------------------------------- ShieldBody::ShieldBody( Thing *thing, const ModuleData* moduleData ) : ActiveBody( thing, moduleData ) { - /*const ShieldBodyModuleData* data = getShieldBodyModuleData(); - m_currentShieldHealth = data->m_shieldMaxHealth;*/ + const ShieldBodyModuleData* data = getShieldBodyModuleData(); + + if (data->m_startsActive) { + m_active = true; + m_currentShieldHealth = data->m_shieldMaxHealth; + enableShieldEffects(); + } + } // end ShieldBody @@ -138,74 +146,251 @@ ShieldBody::~ShieldBody( void ) //} // end update //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- -Bool ShieldBody::getShieldPercent(Real& percentage) +Real ShieldBody::getShieldPercent() { - // TODO: - //if (isActive()) { const ShieldBodyModuleData* data = getShieldBodyModuleData(); - percentage = m_currentShieldHealth / data->m_shieldMaxHealth; - return TRUE; - //} - //return FALSE; + 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::attemptDamage(DamageInfo* damageInfo) { 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(); - //Object* obj = getObject(); - // Pass through full famage + // Pass through full damage if (getDamageTypeFlag(data->m_damageTypesToPassThrough, 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 - EnergyShieldBehaviorInterface* esbi = getEnergyShieldBehaviorInterface(); - if (!esbi) { + 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(damageInfo->in.m_amount); + ActiveBody::attemptDamage(damageInfo); + return; + } + + Real rawDamage = damageInfo->in.m_amount; Real damageToShield = getCurrentArmor().adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); - Real remainingShieldHealth = m_currentShieldHealth - damageToShield; - m_currentShieldHealth = MAX(0, remainingShieldHealth); + //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)); - // Apply Damage to shield and Stop shield recharge - Notify shield behavior - esbi->applyDamage(MAX(0, damageToShield)); + //If the shield is at 0, we still need to disable it + if (!shieldStillUp) { - if (remainingShieldHealth < 0) { + // Disable Shield Armor + disableShieldEffects(); - //TODO: Disable Shield Armor + Real overkillDamage = abs(remainingShieldHealth); - Real overkillDamage = abs(remainingShieldHealth); - damageInfo->in.m_amount = overkillDamage; + // 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; + } } - else { - damageInfo->in.m_amount = 0.0001; + + 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 */ @@ -238,6 +423,9 @@ void ShieldBody::xfer( Xfer *xfer ) xfer->xferReal(&m_currentShieldHealth); //xfer->xferUnsignedInt(&m_healingStepCountdown); + // shield activation + xfer->xferBool(&m_active); + // constructor object id //xfer->xferObjectID( &m_constructorObjectID ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index b5c86d1b073..9a573a4d4d2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -1505,12 +1505,17 @@ Bool Object::getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const } //============================================================================= -Bool Object::getProgressBarShowingInfo(Real& progress, Int& type) 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. @@ -1520,7 +1525,12 @@ Bool Object::getProgressBarShowingInfo(Real& progress, Int& type) const for (BehaviorModule** u = m_behaviors; *u; ++u) { if ((esbi = (*u)->getEnergyShieldBehaviorInterface()) != NULL) { - return esbi->getShieldPercent(progress); + if (esbi->shouldShowHealthBar(selected)) { + progress = esbi->getShieldPercent(); + color = esbi->getHealthBarColor(); + colorBG = esbi->getHealthBarBackgroundColor(); + return true; + } } } 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. From ff8a10e4bd7cececf94da0df42285b28be15bd09 Mon Sep 17 00:00:00 2001 From: Andi Date: Fri, 19 Sep 2025 13:17:20 +0200 Subject: [PATCH 4/6] Added more params. almost done --- .../GameLogic/Module/EnergyShieldBehavior.h | 16 ++ .../Include/GameLogic/Module/ShieldBody.h | 7 +- .../Object/Behavior/EnergyShieldBehavior.cpp | 164 +++++++++++++++--- .../GameLogic/Object/Body/ShieldBody.cpp | 36 ++-- 4 files changed, 175 insertions(+), 48 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h index 2bd288c3034..5478d67ec5c 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h @@ -46,6 +46,7 @@ class ShieldBody; +class FXList; //------------------------------------------------------------------------------------------------- class EnergyShieldBehaviorModuleData : public UpdateModuleData @@ -66,6 +67,18 @@ class EnergyShieldBehaviorModuleData : public UpdateModuleData 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(); @@ -164,7 +177,10 @@ class EnergyShieldBehavior : public UpdateModule, 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 index 5ef87d1c3ac..4198634272f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h @@ -51,19 +51,14 @@ class ShieldBodyModuleData : public ActiveBodyModuleData Real m_shieldMaxHealthPercent; ///< MaxHealth as percentage of activeBody MaxHealth (takes priority) ArmorSetType m_shieldArmorSetFlag; ///< armorset to use for damage absorbed by the shield - //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) - DamageTypeFlags m_damageTypesToPassThrough; + DamageTypeFlags m_defaultDamageTypesToPassThrough; ShieldBodyModuleData(); 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); }; //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp index 89c1d6ef6b7..353a68fd213 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp @@ -44,6 +44,7 @@ #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" +#include "GameClient/FXList.h" //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -58,6 +59,10 @@ EnergyShieldBehaviorModuleData::EnergyShieldBehaviorModuleData() m_barBGColor.green = 255; m_barBGColor.blue = 255; m_barBGColor.alpha = 255; + + m_shieldSubObjName.clear(); + m_shieldConditionFlag = MODELCONDITION_INVALID; + m_shieldHitConditionFlag = MODELCONDITION_INVALID; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -80,6 +85,19 @@ EnergyShieldBehaviorModuleData::EnergyShieldBehaviorModuleData() { "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) }, + //{ "ShowShieldSubObjectWhenHitDuration", INI::parseDurationUnsignedInt, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldSubObjHitDuration) }, + //{ "AlwaysShowShieldSubObject", INI::parseBool, NULL, offsetof(EnergyShieldBehaviorModuleData, m_alwaysShowShieldSubObj) }, + + { "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) }, + //{ "AlwaysShowShield", INI::parseBool, NULL, offsetof(EnergyShieldBehaviorModuleData, m_alwaysShowShield) }, + + { "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 } }; @@ -137,13 +155,79 @@ 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) { - const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); - m_healingStepCountdown = data->m_shieldRechargeDelay; - setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + // 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 @@ -173,11 +257,23 @@ bool EnergyShieldBehavior::shouldShowHealthBar(bool selected) const } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- +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); } @@ -196,30 +292,47 @@ UpdateSleepTime EnergyShieldBehavior::update( void ) //if (now < damageFrame + data->m_shieldRechargeDelay) { // return UPDATE_SLEEP_NONE; //} - - m_healingStepCountdown--; - if (m_healingStepCountdown > 0) - return UPDATE_SLEEP_NONE; - + const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); - m_healingStepCountdown = data->m_shieldRechargeRate; - - 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)); - Bool full = m_body->rechargeShieldHealth(rechargeAmount); - if (full) - return UPDATE_SLEEP_FOREVER; - else - return UPDATE_SLEEP_NONE; + if (m_shieldHitCountdown > 0 && --m_shieldHitCountdown == 0) { + //if (!data->m_alwaysShowShield) + 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; + + //Bool full = m_body->rechargeShieldHealth(rechargeAmount); + //if (full) + // return UPDATE_SLEEP_FOREVER; + //else + // return UPDATE_SLEEP_NONE; + } } - return UPDATE_SLEEP_FOREVER; + + if (m_healingStepCountdown == 0 && m_shieldHitCountdown == 0) + return UPDATE_SLEEP_FOREVER; + else + return UPDATE_SLEEP_NONE; } // ------------------------------------------------------------------------------------------------ @@ -259,6 +372,9 @@ void EnergyShieldBehavior::xfer( Xfer *xfer ) //xfer->xferReal(&m_currentShieldHealth); xfer->xferUnsignedInt(&m_healingStepCountdown); + //hit frames + xfer->xferUnsignedInt(&m_shieldHitCountdown); + } // end xfer // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp index f5815b461a8..9082fb19283 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp @@ -47,6 +47,19 @@ 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); } //------------------------------------------------------------------------------------------------- @@ -54,25 +67,18 @@ ShieldBodyModuleData::ShieldBodyModuleData() void ShieldBodyModuleData::buildFieldParse(MultiIniFieldParse& p) { ActiveBodyModuleData::buildFieldParse(p); - //UpdateModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { - //TODO { "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) }, - //{ "ShieldUpFX", INI::parseIndexList, ArmorSetFlags::getBitNames(), offsetof(ShieldBodyModuleData, m_shieldArmorSetFlag) }, - - //{ "ShieldRechargeDelay", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeDelay) }, - //{ "ShieldRechargeRate", INI::parseDurationUnsignedInt, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeRate) }, - //{ "ShieldRechargeAmount", INI::parseReal, NULL, offsetof(ShieldBodyModuleData, m_shieldRechargeAmount) }, - //{ "ShieldRechargeAmountPercent", parseShieldRechargeAmountPercent, NULL, 0}, //offsetof(ShieldBodyModuleData, m_shieldRechargeAmountPercent) }, - { "DamageTypesToPassThroughShield", INI::parseDamageTypeFlags, NULL, offsetof(ShieldBodyModuleData, m_damageTypesToPassThrough) }, + { "ShieldPassThroughDamageTypes", INI::parseDamageTypeFlags, NULL, offsetof(ShieldBodyModuleData, m_damageTypesToPassThrough) }, + { "DefaultShieldPassThroughDamageTypes", INI::parseDamageTypeFlags, NULL, offsetof(ShieldBodyModuleData, m_defaultDamageTypesToPassThrough) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -86,13 +92,6 @@ void ShieldBodyModuleData::parseShieldHealthPercent(INI* ini, void* instance, vo Real healthPercent = INI::scanPercentToReal(ini->getNextToken()); self->m_shieldMaxHealth = self->m_maxHealth * healthPercent; } -//------------------------------------------------------------------------------------------------- -//void ShieldBodyModuleData::parseShieldRechargeAmountPercent(INI* ini, void* instance, void* store, const void* /*userData*/) -//{ -// ShieldBodyModuleData* self = (ShieldBodyModuleData*)instance; -// Real amountPercent = INI::scanPercentToReal(ini->getNextToken()); -// self->m_shieldMaxHealth = self->m_shieldMaxHealth * amountPercent; -//} //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -254,7 +253,8 @@ void ShieldBody::attemptDamage(DamageInfo* damageInfo) const ShieldBodyModuleData* data = getShieldBodyModuleData(); // Pass through full damage - if (getDamageTypeFlag(data->m_damageTypesToPassThrough, damageInfo->in.m_damageType)) + 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) { @@ -285,7 +285,7 @@ void ShieldBody::attemptDamage(DamageInfo* damageInfo) //Shield is already down, just pass through, but stop recovery if (m_currentShieldHealth <= 0) { - m_shieldBehaviorModule->applyDamage(damageInfo->in.m_amount); + m_shieldBehaviorModule->applyDamage(0.0f); ActiveBody::attemptDamage(damageInfo); return; } From da69b39fec5a36a1721c95ebf473d2bad9c0a1ac Mon Sep 17 00:00:00 2001 From: Andi Date: Fri, 19 Sep 2025 16:03:56 +0200 Subject: [PATCH 5/6] clean up --- .../GameEngine/Include/Common/GlobalData.h | 2 + .../GameEngine/Source/Common/GlobalData.cpp | 2 + .../GameEngine/Source/GameClient/Drawable.cpp | 2 +- .../Object/Behavior/EnergyShieldBehavior.cpp | 41 ++----------------- .../GameLogic/Object/Body/ShieldBody.cpp | 34 ++------------- 5 files changed, 11 insertions(+), 70 deletions(-) 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/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/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index b79012ab59d..b429e288515 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -3143,7 +3143,7 @@ void Drawable::drawProgress( const IRegion2D *healthBarRegion ) Real healthBoxHeight = max(3, healthBarRegion->hi.y - healthBarRegion->lo.y) * 1.5f; Real healthBoxOutlineSize = 1.0f; - Real yOffset = -5; + Real yOffset = -6 + TheGlobalData->m_progressBarYOffset; // draw the health box outline TheDisplay->drawOpenRect(healthBarRegion->lo.x, healthBarRegion->lo.y + yOffset, healthBoxWidth, healthBoxHeight, diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp index 353a68fd213..77f5499f7bc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp @@ -72,8 +72,6 @@ EnergyShieldBehaviorModuleData::EnergyShieldBehaviorModuleData() static const FieldParse dataFieldParse[] = { { "StartsActive", INI::parseBool, NULL, offsetof(EnergyShieldBehaviorModuleData, m_initiallyActive) }, - //{ "ShieldMaxHealth", INI::parseReal, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldMaxHealth) }, - //{ "ShieldMaxHealthPercent", parseShieldHealthPercent, NULL, 0}, // offsetof(EnergyShieldBehaviorModuleData, m_shieldMaxHealthPercent) }, { "ShieldRechargeDelay", INI::parseDurationUnsignedInt, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldRechargeDelay) }, { "ShieldRechargeRate", INI::parseDurationUnsignedInt, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldRechargeRate) }, @@ -87,13 +85,10 @@ EnergyShieldBehaviorModuleData::EnergyShieldBehaviorModuleData() { "ShieldSubObjectName", INI::parseAsciiString, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldSubObjName) }, { "ShieldHitSubObjectName", INI::parseAsciiString, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldHitSubObjName) }, - //{ "ShowShieldSubObjectWhenHitDuration", INI::parseDurationUnsignedInt, NULL, offsetof(EnergyShieldBehaviorModuleData, m_shieldSubObjHitDuration) }, - //{ "AlwaysShowShieldSubObject", INI::parseBool, NULL, offsetof(EnergyShieldBehaviorModuleData, m_alwaysShowShieldSubObj) }, { "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) }, - //{ "AlwaysShowShield", INI::parseBool, NULL, offsetof(EnergyShieldBehaviorModuleData, m_alwaysShowShield) }, { "ShieldUpFX", INI::parseFXList, NULL, offsetof(EnergyShieldBehaviorModuleData, m_fxShieldUp) }, { "ShieldDownFX", INI::parseFXList, NULL, offsetof(EnergyShieldBehaviorModuleData, m_fxShieldDown) }, @@ -107,22 +102,6 @@ EnergyShieldBehaviorModuleData::EnergyShieldBehaviorModuleData() p.add(UpgradeMuxData::getFieldParse(), offsetof(EnergyShieldBehaviorModuleData, m_upgradeMuxData)); } -////------------------------------------------------------------------------------------------------- -////------------------------------------------------------------------------------------------------- -//void EnergyShieldBehaviorModuleData::parseShieldHealthPercent(INI* ini, void* instance, void* store, const void* /*userData*/) -//{ -// EnergyShieldBehaviorModuleData* self = (EnergyShieldBehaviorModuleData*)instance; -// Real healthPercent = INI::scanPercentToReal(ini->getNextToken()); -// self->m_shieldMaxHealth = self->m_maxHealth * healthPercent; -//} -////------------------------------------------------------------------------------------------------- -//void EnergyShieldBehaviorModuleData::parseShieldRechargeAmountPercent(INI* ini, void* instance, void* store, const void* /*userData*/) -//{ -// EnergyShieldBehaviorModuleData* self = (EnergyShieldBehaviorModuleData*)instance; -// Real amountPercent = INI::scanPercentToReal(ini->getNextToken()); -// self->m_shieldMaxHealth = self->m_shieldMaxHealth * amountPercent; -//} - //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- EnergyShieldBehavior::EnergyShieldBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) @@ -153,7 +132,7 @@ EnergyShieldBehavior::~EnergyShieldBehavior( void ) //------------------------------------------------------------------------------------------------- void EnergyShieldBehavior::applyDamage(Real amount) { - DEBUG_LOG((">>> EnergyShieldBehavior::applyDamage - amount = %f", amount)); + //DEBUG_LOG((">>> EnergyShieldBehavior::applyDamage - amount = %f", amount)); const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); m_healingStepCountdown = data->m_shieldRechargeDelay; @@ -284,19 +263,11 @@ void EnergyShieldBehavior::upgradeImplementation() //------------------------------------------------------------------------------------------------- UpdateSleepTime EnergyShieldBehavior::update( void ) { - DEBUG_LOG((">>> EnergyShieldBehavior::update1 - m_healingStepCountdown = %d", m_healingStepCountdown)); - - //UnsignedInt now = TheGameLogic->getFrame(); - //UnsignedInt damageFrame = getLastDamageTimestamp(); - //const ShieldBodyModuleData* data = getShieldBodyModuleData(); - //if (now < damageFrame + data->m_shieldRechargeDelay) { - // return UPDATE_SLEEP_NONE; - //} + //DEBUG_LOG((">>> EnergyShieldBehavior::update1 - m_healingStepCountdown = %d", m_healingStepCountdown)); const EnergyShieldBehaviorModuleData* data = getEnergyShieldBehaviorModuleData(); if (m_shieldHitCountdown > 0 && --m_shieldHitCountdown == 0) { - //if (!data->m_alwaysShowShield) showShield(false, true); } @@ -310,7 +281,7 @@ UpdateSleepTime EnergyShieldBehavior::update( void ) else { rechargeAmount = data->m_shieldRechargeAmount; } - DEBUG_LOG((">>> EnergyShieldBehavior::update2 - rechargeAmount = %f", rechargeAmount)); + //DEBUG_LOG((">>> EnergyShieldBehavior::update2 - rechargeAmount = %f", rechargeAmount)); if (m_body->getShieldCurrentHealth() <= 0) FXList::doFXObj(data->m_fxShieldUp, getObject()); @@ -320,12 +291,6 @@ UpdateSleepTime EnergyShieldBehavior::update( void ) if (!full) m_healingStepCountdown = data->m_shieldRechargeRate; - - //Bool full = m_body->rechargeShieldHealth(rechargeAmount); - //if (full) - // return UPDATE_SLEEP_FOREVER; - //else - // return UPDATE_SLEEP_NONE; } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp index 9082fb19283..8aed0d15f02 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp @@ -115,34 +115,6 @@ ShieldBody::~ShieldBody( void ) } // end ~ShieldBody -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//UpdateSleepTime ShieldBody::update(void) -//{ -// DEBUG_LOG((">>> ShieldBody::update - m_currentShieldHealth = %f, m_healingStepCountdown = %d", m_currentShieldHealth, m_healingStepCountdown)); -// -// //UnsignedInt now = TheGameLogic->getFrame(); -// //UnsignedInt damageFrame = getLastDamageTimestamp(); -// //const ShieldBodyModuleData* data = getShieldBodyModuleData(); -// //if (now < damageFrame + data->m_shieldRechargeDelay) { -// // return UPDATE_SLEEP_NONE; -// //} -// -// m_healingStepCountdown--; -// if (m_healingStepCountdown > 0) -// return UPDATE_SLEEP_NONE; -// -// const ShieldBodyModuleData* data = getShieldBodyModuleData(); -// m_healingStepCountdown = data->m_shieldRechargeRate; -// -// m_currentShieldHealth = MIN(data->m_shieldMaxHealth, m_currentShieldHealth + data->m_shieldRechargeAmount); -// -// //if (m_currentShieldHealth < data->m_shieldMaxHealth) -// // return UPDATE_SLEEP_NONE; -// //else -// // return UPDATE_SLEEP_FOREVER; -// return UPDATE_SLEEP_NONE; -//} // end update //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Real ShieldBody::getShieldPercent() @@ -161,7 +133,7 @@ Bool ShieldBody::rechargeShieldHealth(Real amount) m_currentShieldHealth = MIN(data->m_shieldMaxHealth, m_currentShieldHealth + amount); - DEBUG_LOG((">>> ShieldBody::rechargeShieldHealth - m_currentShieldHealth = %f", m_currentShieldHealth)); + //DEBUG_LOG((">>> ShieldBody::rechargeShieldHealth - m_currentShieldHealth = %f", m_currentShieldHealth)); if (!shieldWasUp && m_currentShieldHealth > 0) { enableShieldEffects(); @@ -312,7 +284,7 @@ void ShieldBody::attemptDamage(DamageInfo* damageInfo) // 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)); + //DEBUG_LOG(("ShieldBody::attemptDamage - m_currentShieldHealth = %f.", m_currentShieldHealth)); //If the shield is at 0, we still need to disable it if (!shieldStillUp) { @@ -342,7 +314,7 @@ void ShieldBody::attemptDamage(DamageInfo* damageInfo) // extend ActiveBody::attemptDamage(damageInfo); - DEBUG_LOG(("ShieldBody::attemptDamage - getHealth() = %f.", getHealth())); + //DEBUG_LOG(("ShieldBody::attemptDamage - getHealth() = %f.", getHealth())); if (shieldStillUp) { // We passed on 0 damage to ActiveBody. From 3a0e169c1504a3d46aa138da38c09dcde4d7a1ca Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 20 Sep 2025 15:49:08 +0200 Subject: [PATCH 6/6] finetuning and fix issues --- .../GameLogic/Module/EnergyShieldBehavior.h | 2 +- .../Include/GameLogic/Module/ShieldBody.h | 2 ++ .../Object/Behavior/EnergyShieldBehavior.cpp | 8 +++++ .../GameLogic/Object/Body/ShieldBody.cpp | 29 +++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h index 5478d67ec5c..e3c1c18a589 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/EnergyShieldBehavior.h @@ -132,7 +132,7 @@ class EnergyShieldBehavior : public UpdateModule, // UpdateModuleInterface virtual UpdateSleepTime update(); - virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } + //virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } //EnergyShieldBehaviorInterface virtual void applyDamage(Real amount); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h index 4198634272f..11fbfc5f572 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShieldBody.h @@ -102,6 +102,8 @@ class ShieldBody : public ActiveBody //, public UpdateModuleInterface 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: diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp index 77f5499f7bc..8bc95535e7a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/EnergyShieldBehavior.cpp @@ -257,6 +257,14 @@ void EnergyShieldBehavior::upgradeImplementation() //setWakeFrame(getObject(), UPDATE_SLEEP_NONE); } +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//void EnergyShieldBehavior::onDisabledEdge(Bool nowDisabled) +//{ +// +//} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- /** The update callback. */ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp index 8aed0d15f02..cdd6b3cbbee 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ShieldBody.cpp @@ -190,8 +190,37 @@ void ShieldBody::doDamageFX(const DamageInfo* 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