From 73c29d5a0814724180e2a9789e21a90660e692ab Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 25 Jan 2026 14:12:41 +0100 Subject: [PATCH 1/4] Water physics and debris stuff --- .../Code/GameEngine/Include/Common/Thing.h | 4 +- .../Include/GameLogic/Module/PhysicsUpdate.h | 16 ++++ .../GameEngine/Source/Common/Thing/Thing.cpp | 20 +++++ .../GameLogic/Object/ObjectCreationList.cpp | 16 +++- .../GameLogic/Object/Update/PhysicsUpdate.cpp | 79 ++++++++++++++++--- 5 files changed, 123 insertions(+), 12 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Thing.h b/GeneralsMD/Code/GameEngine/Include/Common/Thing.h index c180ccd0372..68b318f6fda 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Thing.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Thing.h @@ -128,7 +128,9 @@ class Thing : public MemoryPoolObject Real getHeightAboveTerrain() const; Real getHeightAboveTerrainOrWater() const; - Bool isOverWater() const; + Bool isOverWater() const; ///< Checks if the object is above or below a water surface + Bool isAboveWater() const; ///< Checks if the object is above (but not below) a water surface + Bool isBelowWater() const; ///< Checks if the object is below (but not above) a water surface Bool isAboveTerrain() const { return getHeightAboveTerrain() > 0.0f; } Bool isAboveTerrainOrWater() const { return getHeightAboveTerrainOrWater() > 0.0f; } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h index 73a36e9342d..d3dea8c241b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h @@ -36,6 +36,8 @@ enum ObjectID CPP_11(: Int); +class FXList; + enum PhysicsTurningType CPP_11(: Int) { TURN_NEGATIVE = -1, @@ -65,6 +67,11 @@ class PhysicsBehaviorModuleData : public UpdateModuleData Real m_pitchRollYawFactor; Bool m_vehicleCrashAllowAirborne; Real m_bounceFactor; + + Bool m_doWaterPhysics; //< We only do water collission checks if this is set! + Real m_waterExtraFriction; + const FXList* m_waterImpactFX; + const WeaponTemplate* m_vehicleCrashesIntoBuildingWeaponTemplate; const WeaponTemplate* m_vehicleCrashesIntoNonBuildingWeaponTemplate; @@ -193,7 +200,10 @@ class PhysicsBehavior : public UpdateModule, void setExtraFriction(Real b) { m_extraFriction = b; } void setBounceSound(const AudioEventRTS* bounceSound); + void setWaterImpactSound(const AudioEventRTS* waterImpactSound); + void setWaterImpactFX(const FXList* waterImpactFX); const AudioEventRTS* getBounceSound() { return m_bounceSound ? &m_bounceSound->m_event : TheAudio->getValidSilentAudioEvent(); } + const AudioEventRTS* getWaterImpactSound() { return m_waterImpactSound ? &m_waterImpactSound->m_event : TheAudio->getValidSilentAudioEvent(); } /** Reset all values (vel, accel, etc) to starting values. @@ -238,6 +248,8 @@ class PhysicsBehavior : public UpdateModule, void testStunnedUnitForDestruction(void); + Real getExtraFriction() const; + private: enum PhysicsFlagsType @@ -255,6 +267,7 @@ class PhysicsBehavior : public UpdateModule, IS_IN_FREEFALL = 0x0200, IS_IN_UPDATE = 0x0400, IS_STUNNED = 0x0800, + WAS_ABOVE_WATER_LAST_FRAME = 0x1000, }; /* @@ -266,6 +279,7 @@ class PhysicsBehavior : public UpdateModule, Real m_rollRate; ///< rate of rotation around forward vector Real m_pitchRate; ///< rate or rotation around side vector DynamicAudioEventRTS* m_bounceSound; ///< The sound for when this thing bounces, or NULL + DynamicAudioEventRTS* m_waterImpactSound; ///< The sound for when this thing hits the water surface, or NULL Coord3D m_accel; ///< current acceleration Coord3D m_prevAccel; ///< last frame's acceleration Coord3D m_vel; ///< current velocity @@ -284,6 +298,8 @@ class PhysicsBehavior : public UpdateModule, Bool m_originalAllowBounce; ///< orignal state of allow bounce + const FXList* m_waterImpactFX; + void setFlag(PhysicsFlagsType f, Bool set) { if (set) m_flags |= f; else m_flags &= ~f; } Bool getFlag(PhysicsFlagsType f) const { return (m_flags & f) != 0; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/Thing.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/Thing.cpp index 0490ae3fc1f..7d96a949fd3 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/Thing.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/Thing.cpp @@ -343,6 +343,26 @@ Bool Thing::isOverWater() const return m_cachedIsOverWater; } +// ------------------------------------------------------------------------------ +Bool Thing::isBelowWater() const +{ + if (!(m_cacheFlags & VALID_ALTITUDE_SEALEVEL)) + { + getHeightAboveTerrainOrWater(); + } + return m_cachedIsOverWater && m_cachedAltitudeAboveTerrainOrWater <= 0; +} + +// ------------------------------------------------------------------------------ +Bool Thing::isAboveWater() const +{ + if (!(m_cacheFlags & VALID_ALTITUDE_SEALEVEL)) + { + getHeightAboveTerrainOrWater(); + } + return m_cachedIsOverWater && m_cachedAltitudeAboveTerrainOrWater > 0; +} + //============================================================================= /** If we treat this as airborne, then they slide down slopes. This checks whether diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index 28bc686d7b9..6dba65c1edd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -731,6 +731,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget GenericObjectCreationNugget() : m_requiresLivePlayer(FALSE), + m_debrisTemplateName(AsciiString::TheEmptyString), m_debrisToGenerate(1), m_mass(0), m_extraBounciness(0), @@ -887,6 +888,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget { static const FieldParse myFieldParse[] = { + { "DebrisObject", INI::parseAsciiString, NULL, offsetof(GenericObjectCreationNugget, m_debrisTemplateName) }, { "ModelNames", parseDebrisObjectNames, NULL, 0 }, { "Mass", INI::parsePositiveNonZeroReal, NULL, offsetof( GenericObjectCreationNugget, m_mass ) }, { "AnimationSet", parseAnimSet, NULL, offsetof( GenericObjectCreationNugget, m_animSets) }, @@ -895,6 +897,8 @@ class GenericObjectCreationNugget : public ObjectCreationNugget { "MinLODRequired", INI::parseStaticGameLODLevel, NULL, offsetof(GenericObjectCreationNugget, m_minLODRequired) }, { "Shadow", INI::parseBitString32, TheShadowNames, offsetof( GenericObjectCreationNugget, m_shadowType ) }, { "BounceSound", INI::parseAudioEventRTS, NULL, offsetof( GenericObjectCreationNugget, m_bounceSound) }, + { "WaterImpactSound", INI::parseAudioEventRTS, NULL, offsetof( GenericObjectCreationNugget, m_waterImpactSound) }, + { "WaterImpactFX", INI::parseFXList, NULL, offsetof( GenericObjectCreationNugget, m_waterImpactFX) }, { 0, 0, 0, 0 } }; @@ -1148,6 +1152,8 @@ class GenericObjectCreationNugget : public ObjectCreationNugget objUp->setExtraFriction(m_extraFriction); objUp->setAllowBouncing(true); objUp->setBounceSound(&m_bounceSound); + objUp->setWaterImpactSound(&m_waterImpactSound); + objUp->setWaterImpactFX(m_waterImpactFX); DUMPREAL(m_extraBounciness); DUMPREAL(m_extraFriction); @@ -1316,7 +1322,11 @@ class GenericObjectCreationNugget : public ObjectCreationNugget Object* reallyCreate(const Coord3D *pos, const Matrix3D *mtx, Real orientation, const Object *sourceObj, UnsignedInt lifetimeFrames ) const { - static const ThingTemplate* debrisTemplate = TheThingFactory->findTemplate("GenericDebris"); + const ThingTemplate* debrisTemplate; + if (!m_debrisTemplateName.isEmpty()) + debrisTemplate = TheThingFactory->findTemplate(m_debrisTemplateName); + else + debrisTemplate = TheThingFactory->findTemplate("GenericDebris"); if (m_names.empty()) return NULL; @@ -1363,6 +1373,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget continue; tmpl = debrisTemplate; + DEBUG_LOG((">>> CREATE DEBRIS TEMPLATE: %s", debrisTemplate->getName().str())); } DEBUG_ASSERTCRASH(tmpl, ("Object %s not found",m_names[pick].str())); if (!tmpl) @@ -1458,6 +1469,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget AsciiString m_animFinal; }; std::vector m_names; + AsciiString m_debrisTemplateName; AsciiString m_putInContainer; std::vector m_animSets; const FXList* m_fxFinal; @@ -1488,6 +1500,8 @@ class GenericObjectCreationNugget : public ObjectCreationNugget Real m_maxDistanceFormation; Int m_objectCount; // how many objects will there be? AudioEventRTS m_bounceSound; + AudioEventRTS m_waterImpactSound; + const FXList* m_waterImpactFX; Bool m_requiresLivePlayer; Bool m_experienceSink; Bool m_inheritsWeaponBonus; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index c5f836a7737..c9877b56159 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -45,6 +45,7 @@ #include "GameLogic/TerrainLogic.h" #include "GameLogic/Weapon.h" #include "GameLogic/LogicRandomValue.h" +#include "GameClient/FXList.h" const Real DEFAULT_MASS = 1.0f; @@ -137,6 +138,8 @@ PhysicsBehaviorModuleData::PhysicsBehaviorModuleData() m_vehicleCrashesIntoNonBuildingWeaponTemplate = TheWeaponStore->findWeaponTemplate("VehicleCrashesIntoNonBuildingWeapon"); m_vehicleCrashAllowAirborne = FALSE; m_bounceFactor = 1.0f; + + m_waterImpactFX = NULL; } //------------------------------------------------------------------------------------------------- @@ -189,6 +192,10 @@ static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, con { "VehicleCrashesIntoBuildingWeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof(PhysicsBehaviorModuleData, m_vehicleCrashesIntoBuildingWeaponTemplate) }, { "VehicleCrashesIntoNonBuildingWeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof(PhysicsBehaviorModuleData, m_vehicleCrashesIntoNonBuildingWeaponTemplate) }, { "VehicleCrashWeaponAllowAirborne", INI::parseBool, NULL, offsetof(PhysicsBehaviorModuleData, m_vehicleCrashAllowAirborne) }, + + { "DoWaterPhysics", INI::parseBool, NULL, offsetof(PhysicsBehaviorModuleData, m_doWaterPhysics) }, + { "WaterExtraFriction", INI::parseReal, NULL, offsetof(PhysicsBehaviorModuleData, m_waterExtraFriction) }, + { "WaterImpactFX", INI::parseFXList, NULL, offsetof(PhysicsBehaviorModuleData, m_waterImpactFX) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -228,6 +235,9 @@ PhysicsBehavior::PhysicsBehavior( Thing *thing, const ModuleData* moduleData ) : m_pui = NULL; m_bounceSound = NULL; + m_waterImpactSound = NULL; + + m_waterImpactFX = getPhysicsBehaviorModuleData()->m_waterImpactFX; #ifdef SLEEPY_PHYSICS setWakeFrame(getObject(), UPDATE_SLEEP_NONE); @@ -275,9 +285,18 @@ Bool PhysicsBehavior::isIgnoringCollisionsWith(ObjectID id) const } //------------------------------------------------------------------------------------------------- +Real PhysicsBehavior::getExtraFriction() const +{ + if (getPhysicsBehaviorModuleData()->m_doWaterPhysics && getObject()->isBelowWater()) + return m_extraFriction + getPhysicsBehaviorModuleData()->m_waterExtraFriction; + + return m_extraFriction; +} +//------------------------------------------------------------------------------------------------- + Real PhysicsBehavior::getAerodynamicFriction() const { - Real f = getPhysicsBehaviorModuleData()->m_aerodynamicFriction + m_extraFriction; + Real f = getPhysicsBehaviorModuleData()->m_aerodynamicFriction + getExtraFriction(); if (f < MIN_AERO_FRICTION) f = MIN_AERO_FRICTION; if (f > MAX_FRICTION) f = MAX_FRICTION; return f; @@ -286,7 +305,7 @@ Real PhysicsBehavior::getAerodynamicFriction() const //------------------------------------------------------------------------------------------------- Real PhysicsBehavior::getForwardFriction() const { - Real f = getPhysicsBehaviorModuleData()->m_forwardFriction + m_extraFriction; + Real f = getPhysicsBehaviorModuleData()->m_forwardFriction + getExtraFriction(); if (f < MIN_NON_AERO_FRICTION) f = MIN_NON_AERO_FRICTION; if (f > MAX_FRICTION) f = MAX_FRICTION; return f; @@ -295,7 +314,7 @@ Real PhysicsBehavior::getForwardFriction() const //------------------------------------------------------------------------------------------------- Real PhysicsBehavior::getLateralFriction() const { - Real f = getPhysicsBehaviorModuleData()->m_lateralFriction + m_extraFriction; + Real f = getPhysicsBehaviorModuleData()->m_lateralFriction + getExtraFriction(); if (f < MIN_NON_AERO_FRICTION) f = MIN_NON_AERO_FRICTION; if (f > MAX_FRICTION) f = MAX_FRICTION; return f; @@ -304,7 +323,7 @@ Real PhysicsBehavior::getLateralFriction() const //------------------------------------------------------------------------------------------------- Real PhysicsBehavior::getZFriction() const { - Real f = getPhysicsBehaviorModuleData()->m_ZFriction + m_extraFriction; + Real f = getPhysicsBehaviorModuleData()->m_ZFriction + getExtraFriction(); if (f < MIN_NON_AERO_FRICTION) f = MIN_NON_AERO_FRICTION; if (f > MAX_FRICTION) f = MAX_FRICTION; return f; @@ -616,6 +635,29 @@ void PhysicsBehavior::setBounceSound(const AudioEventRTS* bounceSound) } } +//------------------------------------------------------------------------------------------------- +void PhysicsBehavior::setWaterImpactSound(const AudioEventRTS* waterImpactSound) +{ + if (waterImpactSound) + { + if (m_waterImpactSound == NULL) + m_waterImpactSound = newInstance(DynamicAudioEventRTS); + + m_waterImpactSound->m_event = *waterImpactSound; + } + else + { + deleteInstance(m_waterImpactSound); + m_waterImpactSound = NULL; + } +} + +//------------------------------------------------------------------------------------------------- +void PhysicsBehavior::setWaterImpactFX(const FXList* waterImpactFX) +{ + m_waterImpactFX = waterImpactFX; +} + //------------------------------------------------------------------------------------------------- /** * Basic rigid body physics using an Euler integrator. @@ -836,18 +878,21 @@ UpdateSleepTime PhysicsBehavior::update() applyForce(&bounceForce); } - Bool isWaterCollision{ false }; + // Check our height state: + + Bool isUnderWater{ false }; Bool airborneAtEnd{ false }; if (m_pui != nullptr && m_pui->projectileShouldCollideWithWater()) { + // For projectiles that collide with water, we consider water as not airborne airborneAtEnd = obj->isAboveTerrainOrWater(); - - if (!airborneAtEnd) { - const Coord3D* pos = obj->getPosition(); - isWaterCollision = TheTerrainLogic->isUnderwater(pos->x, pos->y); - } + isUnderWater = obj->isBelowWater(); } else { airborneAtEnd = obj->isAboveTerrain(); + if (d->m_doWaterPhysics) { + isUnderWater = obj->isBelowWater(); + DEBUG_LOG((">>> WATER CHECK: %d", isUnderWater)); + } } // it's not good enough to check for airborne being different between @@ -927,8 +972,22 @@ UpdateSleepTime PhysicsBehavior::update() } } + if (d->m_doWaterPhysics && getFlag(WAS_ABOVE_WATER_LAST_FRAME) && isUnderWater) + { + DEBUG_LOG((">>> WATER IMPACT NOW!")); + // do water splash sound + if (m_waterImpactSound) { + AudioEventRTS collisionSound = m_waterImpactSound->m_event; + collisionSound.setObjectID(getObject()->getID()); + TheAudio->addAudioEvent(&collisionSound); + } + + FXList::doFXObj(m_waterImpactFX, getObject()); + } + setFlag(UPDATE_EVER_RUN, true); setFlag(WAS_AIRBORNE_LAST_FRAME, airborneAtEnd); + setFlag(WAS_ABOVE_WATER_LAST_FRAME, !isUnderWater); setFlag(IS_IN_UPDATE, false); return calcSleepTime(); From 83872d9f9c05e644cb7962219427ccbd3880307e Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 25 Jan 2026 19:56:08 +0100 Subject: [PATCH 2/4] ShipSlowDeathBehavior.cpp WIP --- .../System/GameMemoryInitPools_GeneralsMD.inl | 1 + GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../GameLogic/Module/ShipSlowDeathBehavior.h | 182 +++++++++ .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../Object/Update/ShipSlowDeathBehavior.cpp | 345 ++++++++++++++++++ 5 files changed, 532 insertions(+) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl index 95f714cfcc5..94187718a63 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl @@ -160,6 +160,7 @@ static PoolSizeRec PoolSizes[] = { "DelayedUpgradeBehavior", 128, 64 }, { "GenerateMinefieldBehavior", 32, 32 }, { "HelicopterSlowDeathBehavior", 64, 32 }, + { "ShipSlowDeathBehavior", 64, 32 }, { "ParkingPlaceBehavior", 32, 32 }, { "FlightDeckBehavior", 8, 8 }, #ifdef ALLOW_SURRENDER diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index b3ee009a069..80b193315a2 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -350,6 +350,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/HealCrateCollide.h Include/GameLogic/Module/HeightDieUpdate.h Include/GameLogic/Module/HelicopterSlowDeathUpdate.h + Include/GameLogic/Module/ShipSlowDeathBehavior.h Include/GameLogic/Module/HelixContain.h Include/GameLogic/Module/MultiAddOnContain.h Include/GameLogic/Module/HighlanderBody.h @@ -1064,6 +1065,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Update/FloatUpdate.cpp Source/GameLogic/Object/Update/HeightDieUpdate.cpp Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp + Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp Source/GameLogic/Object/Update/HijackerUpdate.cpp Source/GameLogic/Object/Update/HordeUpdate.cpp Source/GameLogic/Object/Update/LaserUpdate.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h new file mode 100644 index 00000000000..3d93d526d86 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h @@ -0,0 +1,182 @@ +/* +** 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: ShipSlowDeathBehavior.h ////////////////////////////////////////////////////////////// +// Author: Andi W, 01 2026 +// Desc: ship slow deaths +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// +#include "Common/AudioEventRTS.h" +#include "GameLogic/Module/SlowDeathBehavior.h" + +// FORWARD DECLARATIONS /////////////////////////////////////////////////////////////////////////// +class ParticleSystemTemplate; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +enum ShipToppleType CPP_11(: Int) +{ + TOPPLE_NONE = 0, + TOPPLE_FRONT, + TOPPLE_BACK, + TOPPLE_SIDE_LEFT, + TOPPLE_SIDE_RIGHT, + TOPPLE_SIDE, + TOPPLE_RANDOM, + + TOPPLE_COUNT +}; + +#ifdef DEFINE_SHIP_TOPPLE_NAMES +static const char* const TheShipToppleNames[] = +{ + "NONE", + "FRONT", + "BACK", + "LEFT", + "RIGHT", + "SIDE", + "RANDOM", + NULL +}; +static_assert(ARRAY_SIZE(TheShipToppleNames) == TOPPLE_COUNT + 1, "Incorrect array size"); +#endif + +//------------------------------------------------------------------------------------------------- +class ShipSlowDeathBehaviorModuleData : public SlowDeathBehaviorModuleData +{ + +public: + + ShipSlowDeathBehaviorModuleData( void ); + + static void buildFieldParse(MultiIniFieldParse &p ); + + UnsignedInt m_initialDelay; + UnsignedInt m_initialDelayVariance; + ShipToppleType m_toppleType; + + Real m_initWobbleMaxPitch; + Real m_initWobbleMaxYaw; + Real m_initWobbleMaxRoll; + UnsignedInt m_initWobbleInterval; + + Real m_toppleFrontMaxPitch; + Real m_toppleBackMaxPitch; + Real m_toppleSideMaxRoll; + UnsignedInt m_toppleDuration; + + Real m_sinkWobbleMaxPitch; + Real m_sinkWobbleMaxYaw; + Real m_sinkWobbleMaxRoll; + UnsignedInt m_sinkWobbleInterval; + + Real m_sinkHowFast; + + AudioEventRTS m_deathSound; ///< Sound played during death sequence. + const ObjectCreationList* m_oclEjectPilot; ///< OCL for ejecting pilot + const FXList* m_fxHitGround; ///< fx for hitting the ground + const ObjectCreationList* m_oclHitGround; ///< OCL at hit ground event + + const ParticleSystemTemplate* m_attachParticleSystem; ///< particle system to attach + AsciiString m_attachParticleBone; ///< bone to attach particle system to + Coord3D m_attachParticleLoc; ///< loc attach particle system to if bone not present + + //Real m_spiralOrbitTurnRate; ///< (rads per frame) rate at which we do big circles down toward the ground + //Real m_spiralOrbitForwardSpeed; ///< (dist per frame) speed at which we move "forward" in the downward spiral + //Real m_spiralOrbitForwardSpeedDamping;///< every frame our forward speed in the orbit is adjusted by this amount + //Real m_minSelfSpin; ///< (rads per frame) min turning rate at which we spin around our center of gravity + //Real m_maxSelfSpin; ///< (rads per frame) max turning rate at which we spin around our center of gravity + //Real m_selfSpinUpdateDelay; ///< (frames) every this many frames we will update the self spin angle, but we'll keep it inbetween the min and max self spin + //Real m_selfSpinUpdateAmount; ///< (radian) when we update the self spin every SelfSpinUpdateDelay frames, we change it this much, but keep it between min and max self spin + //Real m_fallHowFast; ///< a fraction of gravity we use to modify the helicopert locmotor lift + //Real m_minBladeFlyOffDelay; ///< (frames) min frame that the blade will fly off at + //Real m_maxBladeFlyOffDelay; ///< (frames) max frame that the blade will fly off at + + //AsciiString m_bladeObjectName; ///< object name of the blade piece + //AsciiString m_bladeBone; ///< bone name of main propeller blade + + //const FXList *m_fxBlade; ///< blade fly off event fx list + //const ObjectCreationList *m_oclBlade; ///< OCL at blade fly off event + //const FXList *m_fxHitGround; ///< fx for hitting the ground + //const ObjectCreationList *m_oclHitGround; ///< OCL at hit ground event + //const FXList *m_fxFinalBlowUp; ///< the final blow up + //const ObjectCreationList *m_oclFinalBlowUp;///< OCL at final blow up event + //Real m_delayFromGroundToFinalDeath; ///< (frames) delay from when we hit the ground to final BOOM! + //AsciiString m_finalRubbleObject; ///< final rubble object to create after it's ALL over + //Real m_maxBraking; ///< max braking we may use during death spiral + + //// @todo propagate this up to SlowDeathBehaviorModuleData. I don't wanna do it today, cause its 4/3. jkmcd + //AudioEventRTS m_deathSound; ///< Sound played during death sequence. + +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class ShipSlowDeathBehavior : public SlowDeathBehavior +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ShipSlowDeathBehavior, "ShipSlowDeathBehavior" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ShipSlowDeathBehavior, ShipSlowDeathBehaviorModuleData ) + +public: + + ShipSlowDeathBehavior( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + virtual void beginSlowDeath( const DamageInfo *damageInfo ); ///< begin the slow death cycle + virtual UpdateSleepTime update(); + +protected: + + void doInitPhase(); + void doTopplePhase(); + void doSinkPhase(); + + void beginInitPhase(); + void beginTopplePhase(); + void beginSinkPhase(); + + //Int m_orbitDirection; ///< -1 or +1 ... use ORBIT_DIRECTION_LEFT/RIGHT + //Real m_forwardAngle; /**< angle of the direction "forward" we want to move in our downspin + // which is independent of the actual direction angle of the object */ + //Real m_forwardSpeed; ///< the speed we're travelling forward in our spiral orbit + //Real m_selfSpin; ///< rads per frame that we change our facing direction + //Bool m_selfSpinTowardsMax; ///< TRUE when our self spin rate is increasing towards the MaxSelfSpin + //UnsignedInt m_lastSelfSpinUpdateFrame; ///< frame we last updated the self spin on + //UnsignedInt m_bladeFlyOffFrame; ///< frame we throw the blade off at + //UnsignedInt m_hitGroundFrame; ///< frame we hit the ground on + + // @todo propagate this up to SlowDeathBehavior. I don't wanna do it today, cause its 4/3. jkmcd + AudioEventRTS m_deathSound; ///< Sound played during death sequence. + + UnsignedInt m_initStartFrame; + UnsignedInt m_toppleStartFrame; + UnsignedInt m_sinkStartFrame; + Bool m_shipSinkStarted; + Int m_chosenToppleType; +}; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index e2be6907b99..a5f0f724220 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -58,6 +58,7 @@ #include "GameLogic/Module/ChronoDeathBehavior.h" #include "GameLogic/Module/SlowDeathBehavior.h" #include "GameLogic/Module/HelicopterSlowDeathUpdate.h" +#include "GameLogic/Module/ShipSlowDeathBehavior.h" #include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" #include "GameLogic/Module/CaveContain.h" #include "GameLogic/Module/OpenContain.h" @@ -362,6 +363,7 @@ void ModuleFactory::init( void ) addModule( ChronoDeathBehavior ); addModule( SlowDeathBehavior ); addModule( HelicopterSlowDeathBehavior ); + addModule( ShipSlowDeathBehavior ); addModule( NeutronMissileSlowDeathBehavior ); addModule( CaveContain ); addModule( OpenContain ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp new file mode 100644 index 00000000000..18e3729c999 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp @@ -0,0 +1,345 @@ +/* +** 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: ShipSlowDeathBehavior.cpp //////////////////////////////////////////////////////////// +// Author: Andi W, 01 2026 +// Desc: ship slow deaths +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#include "Common/GameAudio.h" +#include "Common/GlobalData.h" +#include "Common/RandomValue.h" +#include "Common/ThingFactory.h" +#include "Common/ThingTemplate.h" +#include "Common/Xfer.h" +#include "GameClient/Drawable.h" +#include "GameClient/FXList.h" +#include "GameClient/InGameUI.h" +#include "GameClient/ParticleSys.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Locomotor.h" +#include "GameLogic/Object.h" +#include "GameLogic/ObjectCreationList.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/EjectPilotDie.h" +#include "GameLogic/Module/ShipSlowDeathBehavior.h" +#include "GameLogic/Module/PhysicsUpdate.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Helicopter slow death update module data /////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + + + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ShipSlowDeathBehaviorModuleData::ShipSlowDeathBehaviorModuleData( void ) +{ + m_attachParticleBone.clear(); + m_attachParticleSystem = NULL; + m_attachParticleLoc.x = 0.0f; + m_attachParticleLoc.y = 0.0f; + m_attachParticleLoc.z = 0.0f; + m_oclEjectPilot = NULL; + m_fxHitGround = NULL; + m_oclHitGround = NULL; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +/*static*/ void ShipSlowDeathBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p ) +{ + SlowDeathBehaviorModuleData::buildFieldParse( p ); + + static const FieldParse dataFieldParse[] = + { + { "InitialDelay", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_initialDelay) }, + { "InitialDelayVariance", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_initialDelayVariance) }, + { "InitWobbleMaxAnglePitch", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_initWobbleMaxPitch) }, + { "InitWobbleMaxAngleYaw", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_initWobbleMaxYaw) }, + { "InitWobbleMaxAngleRoll", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_initWobbleMaxRoll) }, + { "InitWobbleInterval", INI::parseDurationUnsignedInt, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_initWobbleInterval) }, + + { "ToppleFrontMaxPitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleFrontMaxPitch) }, + { "ToppleBackMaxPitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleBackMaxPitch) }, + { "ToppleSideMaxRoll", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleSideMaxRoll) }, + { "ToppleDuration", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleDuration) }, + + { "SinkWobbleMaxAnglePitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxPitch) }, + { "SinkWobbleMaxAngleYaw", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxYaw) }, + { "SinkWobbleMaxAngleRoll", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxRoll) }, + { "SinkWobbleInterval", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleInterval) }, + + { "SinkHowFast", INI::parsePercentToReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_sinkHowFast) }, + + + { "AttachParticle", INI::parseParticleSystemTemplate, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleSystem ) }, + { "AttachParticleBone", INI::parseAsciiString, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleBone ) }, + { "AttachParticleLoc", INI::parseCoord3D, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleLoc ) }, + + { "OCLEjectPilot", INI::parseObjectCreationList, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_oclEjectPilot ) }, + + { "FXHitGround", INI::parseFXList, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_fxHitGround ) }, + { "OCLHitGround", INI::parseObjectCreationList, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_oclHitGround ) }, + + { "SoundSinkLoop", INI::parseAudioEventRTS, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_deathSound) }, + + { 0, 0, 0, 0 } + + }; + + p.add(dataFieldParse); + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Ship slow death update /////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ShipSlowDeathBehavior::ShipSlowDeathBehavior( Thing *thing, const ModuleData *moduleData ) + : SlowDeathBehavior( thing, moduleData ) +{ + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ShipSlowDeathBehavior::~ShipSlowDeathBehavior( void ) +{ + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ShipSlowDeathBehavior::beginSlowDeath( const DamageInfo *damageInfo ) +{ + // extending functionality + SlowDeathBehavior::beginSlowDeath( damageInfo ); + + // get the module data + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); + + UnsignedInt now = TheGameLogic->getFrame(); + m_initStartFrame = now; + m_toppleStartFrame = now + (d->m_initialDelay + GameLogicRandomValue(0, d->m_initialDelayVariance)); + + switch (d->m_toppleType) + { + case TOPPLE_RANDOM: + m_chosenToppleType = GameLogicRandomValue(TOPPLE_FRONT, TOPPLE_SIDE_RIGHT); + break; + case TOPPLE_SIDE: + m_chosenToppleType = GameLogicRandomValue(TOPPLE_SIDE_LEFT, TOPPLE_SIDE_RIGHT); + break; + default: + m_chosenToppleType = d->m_toppleType; + break; + } + + beginInitPhase(); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UpdateSleepTime ShipSlowDeathBehavior::update( void ) +{ +/// @todo srj use SLEEPY_UPDATE here + // call the base class cause we're extending functionality + SlowDeathBehavior::update(); + + // get out of here if we're not activated yet + if( isSlowDeathActivated() == FALSE ) + return UPDATE_SLEEP_NONE; + + // get the module data + const ShipSlowDeathBehaviorModuleData *d = getShipSlowDeathBehaviorModuleData(); + + + UnsignedInt now = TheGameLogic->getFrame(); + if (now < m_toppleStartFrame) { + doInitPhase(); + return UPDATE_SLEEP_NONE; + } + else if (m_sinkStartFrame <= 0) { + if (d->m_toppleDuration > 0) { + m_sinkStartFrame = now + d->m_toppleDuration; + beginTopplePhase(); + } + else { + m_sinkStartFrame = now; + } + } + if (now < m_sinkStartFrame) { + doTopplePhase(); + return UPDATE_SLEEP_NONE; + } + else { + if (!m_shipSinkStarted) + beginSinkPhase(); + + doSinkPhase(); + return UPDATE_SLEEP_NONE; + } + + return UPDATE_SLEEP_NONE; + +} + +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::beginInitPhase() +{ + +} +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::beginTopplePhase() +{ + +} +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::beginSinkPhase() +{ + +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::doInitPhase() +{ + //const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); + + //if (d->m_initWobbleInterval <= 0) + // return; + + //// do the wobble + + //UnsignedInt now = TheGameLogic->getFrame(); + //UnsignedInt timePassed = now - m_initStartFrame; + //Real progress = INT_TO_REAL(timePassed % d->m_initWobbleInterval) / INT_TO_REAL(d->m_initWobbleInterval); + + +} +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::doTopplePhase() +{ + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); + + if (d->m_toppleDuration <= 0) + return; + + Object* obj = getObject(); + + UnsignedInt now = TheGameLogic->getFrame(); + UnsignedInt timePassed = now - m_toppleStartFrame; + Real progress = INT_TO_REAL(timePassed) / INT_TO_REAL(d->m_toppleDuration); + + Matrix3D mtx = *obj->getTransformMatrix(); + + Real pitchRate; + Real yawRate; + Real rollRate; + + // Topple the boat + switch (m_chosenToppleType) + { + case TOPPLE_FRONT: + break; + case TOPPLE_BACK: + break; + case TOPPLE_SIDE_LEFT: + break; + case TOPPLE_SIDE_RIGHT: + break; + case TOPPLE_NONE: + return; + default: + DEBUG_CRASH(("Invalid topple type in ShipSlowDeathBehavior")); + } + + + +} +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::doSinkPhase() +{ + +} +// ------------------------------------------------------------------------------------------------ + + + + + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::crc( Xfer *xfer ) +{ + + // extend base class + SlowDeathBehavior::crc( xfer ); + +} + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + SlowDeathBehavior::xfer( xfer ); + + + xfer->xferUnsignedInt( &m_initStartFrame); + xfer->xferUnsignedInt( &m_toppleStartFrame); + xfer->xferUnsignedInt( &m_sinkStartFrame); + xfer->xferBool( &m_shipSinkStarted); + xfer->xferInt( &m_chosenToppleType); + +} + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::loadPostProcess( void ) +{ + + // extend base class + SlowDeathBehavior::loadPostProcess(); + +} From f819cfd77ae14a00b8d4c0ca4fd2ebaad63e1707 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 27 Jan 2026 20:44:05 +0100 Subject: [PATCH 3/4] Basic implementation done --- .../GameLogic/Module/ShipSlowDeathBehavior.h | 45 ++- .../Object/Update/ShipSlowDeathBehavior.cpp | 372 ++++++++++++++++-- 2 files changed, 386 insertions(+), 31 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h index 3d93d526d86..78cb76b12c4 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h @@ -80,28 +80,48 @@ class ShipSlowDeathBehaviorModuleData : public SlowDeathBehaviorModuleData UnsignedInt m_initialDelayVariance; ShipToppleType m_toppleType; - Real m_initWobbleMaxPitch; - Real m_initWobbleMaxYaw; - Real m_initWobbleMaxRoll; - UnsignedInt m_initWobbleInterval; + Real m_wobbleMaxPitch; + Real m_wobbleMaxYaw; + Real m_wobbleMaxRoll; + UnsignedInt m_wobbleInterval; + Real m_toppleFrontMinPitch; Real m_toppleFrontMaxPitch; + Real m_toppleBackMinPitch; Real m_toppleBackMaxPitch; + Real m_toppleSideMinRoll; Real m_toppleSideMaxRoll; + Real m_toppleHeightMinOffset; + Real m_toppleHeightMaxOffset; + //Real m_toppleDamping; + Real m_toppleAngleCorrectionRate; UnsignedInt m_toppleDuration; + UnsignedInt m_toppleDurationVariance; - Real m_sinkWobbleMaxPitch; - Real m_sinkWobbleMaxYaw; - Real m_sinkWobbleMaxRoll; - UnsignedInt m_sinkWobbleInterval; + Real m_toppleMinPushForce; + Real m_toppleMaxPushForce; + Real m_toppleMinPushForceSide; + Real m_toppleMaxPushForceSide; + + //Real m_sinkWobbleMaxPitch; + //Real m_sinkWobbleMaxYaw; + //Real m_sinkWobbleMaxRoll; + //UnsignedInt m_sinkWobbleInterval; Real m_sinkHowFast; AudioEventRTS m_deathSound; ///< Sound played during death sequence. const ObjectCreationList* m_oclEjectPilot; ///< OCL for ejecting pilot + const FXList* m_fxHitGround; ///< fx for hitting the ground const ObjectCreationList* m_oclHitGround; ///< OCL at hit ground event + const FXList* m_fxStartTopple; ///< fx after init phase + const ObjectCreationList* m_oclStartTopple; ///< OCL after init phase + + const FXList* m_fxStartSink; ///< fx after topple / before sinking phase + const ObjectCreationList* m_oclStartSink; ///< OCL after topple / before sinking phase + const ParticleSystemTemplate* m_attachParticleSystem; ///< particle system to attach AsciiString m_attachParticleBone; ///< bone to attach particle system to Coord3D m_attachParticleLoc; ///< loc attach particle system to if bone not present @@ -157,6 +177,8 @@ class ShipSlowDeathBehavior : public SlowDeathBehavior void doTopplePhase(); void doSinkPhase(); + void doWobble(); + void beginInitPhase(); void beginTopplePhase(); void beginSinkPhase(); @@ -179,4 +201,11 @@ class ShipSlowDeathBehavior : public SlowDeathBehavior UnsignedInt m_sinkStartFrame; Bool m_shipSinkStarted; Int m_chosenToppleType; + + Real m_toppleVarianceRoll; + + Real m_curPitch; + Real m_curYaw; + Real m_curRoll; + Real m_curZ; }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp index 18e3729c999..b8f385df5d8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp @@ -1,4 +1,4 @@ -/* +/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** @@ -29,7 +29,7 @@ // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine - +#define DEFINE_SHIP_TOPPLE_NAMES #include "Common/GameAudio.h" #include "Common/GlobalData.h" #include "Common/RandomValue.h" @@ -69,6 +69,11 @@ ShipSlowDeathBehaviorModuleData::ShipSlowDeathBehaviorModuleData( void ) m_oclEjectPilot = NULL; m_fxHitGround = NULL; m_oclHitGround = NULL; + m_fxStartTopple = NULL; + m_oclStartTopple = NULL; + m_fxStartSink = NULL; + m_oclStartSink = NULL; + //m_toppleDamping = 1.0; } // ------------------------------------------------------------------------------------------------ @@ -81,22 +86,44 @@ ShipSlowDeathBehaviorModuleData::ShipSlowDeathBehaviorModuleData( void ) { { "InitialDelay", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_initialDelay) }, { "InitialDelayVariance", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_initialDelayVariance) }, - { "InitWobbleMaxAnglePitch", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_initWobbleMaxPitch) }, - { "InitWobbleMaxAngleYaw", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_initWobbleMaxYaw) }, - { "InitWobbleMaxAngleRoll", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_initWobbleMaxRoll) }, - { "InitWobbleInterval", INI::parseDurationUnsignedInt, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_initWobbleInterval) }, + { "WobbleMaxAnglePitch", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_wobbleMaxPitch) }, + { "WobbleMaxAngleYaw", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_wobbleMaxYaw) }, + { "WobbleMaxAngleRoll", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_wobbleMaxRoll) }, + { "WobbleInterval", INI::parseDurationUnsignedInt, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_wobbleInterval) }, + + { "ToppleStyle", INI::parseIndexList, TheShipToppleNames, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleType) }, + { "ToppleFrontMinPitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleFrontMinPitch) }, { "ToppleFrontMaxPitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleFrontMaxPitch) }, + { "ToppleBackMinPitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleBackMinPitch) }, { "ToppleBackMaxPitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleBackMaxPitch) }, + { "ToppleSideMinRoll", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleSideMinRoll) }, { "ToppleSideMaxRoll", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleSideMaxRoll) }, + { "ToppleMinHeightOffset", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleHeightMinOffset) }, + { "ToppleMaxHeightOffset", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleHeightMaxOffset) }, { "ToppleDuration", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleDuration) }, + { "ToppleDurationVariance", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleDurationVariance) }, + + { "ToppleMinPushForce", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleMinPushForce) }, + { "ToppleMaxPushForce", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleMaxPushForce) }, + { "ToppleMinPushForceSide", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleMinPushForceSide) }, + { "ToppleMaxPushForceSide", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleMaxPushForceSide) }, + - { "SinkWobbleMaxAnglePitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxPitch) }, - { "SinkWobbleMaxAngleYaw", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxYaw) }, - { "SinkWobbleMaxAngleRoll", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxRoll) }, - { "SinkWobbleInterval", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleInterval) }, + { "FXStartTopple", INI::parseFXList, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_fxStartTopple) }, + { "OCLStartTopple", INI::parseObjectCreationList, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_oclStartTopple) }, + //{ "ToppleDampingDuration", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleDampingDuration) }, + //{ "ToppleDamping", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleDamping) }, + { "ToppleAngleCorrectionRate", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleAngleCorrectionRate) }, + + //{ "SinkWobbleMaxAnglePitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxPitch) }, + //{ "SinkWobbleMaxAngleYaw", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxYaw) }, + //{ "SinkWobbleMaxAngleRoll", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxRoll) }, + //{ "SinkWobbleInterval", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleInterval) }, { "SinkHowFast", INI::parsePercentToReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_sinkHowFast) }, + { "FXStartSink", INI::parseFXList, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_fxStartSink) }, + { "OCLStartSink", INI::parseObjectCreationList, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_oclStartSink) }, { "AttachParticle", INI::parseParticleSystemTemplate, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleSystem ) }, @@ -164,6 +191,8 @@ void ShipSlowDeathBehavior::beginSlowDeath( const DamageInfo *damageInfo ) break; } + m_toppleVarianceRoll = GameLogicRandomValueReal(0.0f, 1.0f); + beginInitPhase(); } @@ -187,6 +216,7 @@ UpdateSleepTime ShipSlowDeathBehavior::update( void ) UnsignedInt now = TheGameLogic->getFrame(); if (now < m_toppleStartFrame) { doInitPhase(); + doWobble(); return UPDATE_SLEEP_NONE; } else if (m_sinkStartFrame <= 0) { @@ -200,6 +230,7 @@ UpdateSleepTime ShipSlowDeathBehavior::update( void ) } if (now < m_sinkStartFrame) { doTopplePhase(); + doWobble(); return UPDATE_SLEEP_NONE; } else { @@ -207,13 +238,89 @@ UpdateSleepTime ShipSlowDeathBehavior::update( void ) beginSinkPhase(); doSinkPhase(); - return UPDATE_SLEEP_NONE; + doWobble(); + //return UPDATE_SLEEP_NONE; } + // Check for crashing onto ground + + Object* obj = getObject(); + if (obj->getHeightAboveTerrain() <= 1.0) { + + // make effect + FXList::doFXObj(d->m_fxHitGround, obj); + ObjectCreationList::create(d->m_oclHitGround, obj, NULL); + + // Stop the sound from playing. + TheAudio->removeAudioEvent(m_deathSound.getPlayingHandle()); + + // destroy the ship finally + TheGameLogic->destroyObject(obj); + } + + return UPDATE_SLEEP_NONE; } +// ------------- +// Utils +inline Real smoothStep(Real x) +{ + x = WWMath::Clamp(x, 0.0f, 1.0f); + return x * x * (3.0f - 2.0f * x); +} + +inline Real smoothPlateau(Real x, Real e) +{ + x = std::clamp(x, 0.0f, 1.0f); + e = std::clamp(e, 0.0f, 0.5f); + + if (e == 0.0f) + return (x > 0.0f && x < 1.0f) ? 1.0f : 0.0f; + + // Ease-in region + if (x < e) + { + Real t = x / e; // map to 0..1 + return smoothStep(t); // smoothstep + } + + // Flat top + if (x <= 1.0f - e) + { + return 1.0f; + } + + // Ease-out region + Real t = (x - (1.0f - e)) / e; // map to 0..1 + Real s = smoothStep(t); // smoothstep + return 1.0f - s; +} + +inline Real fastSin01(Real x) +{ + x = x * 2.0f * PI - PI; + + // Optional: wrap input to [-pi, pi] for stability + x = std::fmod(x + PI, 2.0f * PI); + if (x < 0.0f) x += 2.0f * PI; + x -= PI; + + // Parabolic approximation + const Real B = 4.0f / PI; + const Real C = -4.0f / (PI * PI); + + Real y = B * x + C * x * std::fabs(x); + + // Extra precision correction (improves peaks) + const Real P = 0.225f; + y = P * (y * std::fabs(y) - y) + y; + + return y; +} + + // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::beginInitPhase() { @@ -222,33 +329,57 @@ void ShipSlowDeathBehavior::beginInitPhase() // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::beginTopplePhase() { + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); + Object* obj = getObject(); + // make effect + FXList::doFXObj(d->m_fxStartTopple, obj); + ObjectCreationList::create(d->m_oclStartTopple, obj, NULL); } // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::beginSinkPhase() { + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); + Object* obj = getObject(); + // make effect + FXList::doFXObj(d->m_fxStartTopple, obj); + ObjectCreationList::create(d->m_oclStartTopple, obj, NULL); + + m_deathSound = d->m_deathSound; + + if (m_deathSound.getEventName().isEmpty() == false) + { + m_deathSound.setObjectID(getObject()->getID()); + m_deathSound.setPlayingHandle(TheAudio->addAudioEvent(&m_deathSound)); + } + + if (obj->getAIUpdateInterface()) { + Locomotor* locomotor = obj->getAIUpdateInterface()->getCurLocomotor(); + if (locomotor) + locomotor->setMaxLift(-TheGlobalData->m_gravity * (1.0f - d->m_sinkHowFast)); + } } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::doInitPhase() { - //const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); - - //if (d->m_initWobbleInterval <= 0) - // return; + DEBUG_LOG((">>> SSD doInitPhase 0")); - //// do the wobble + //const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); - //UnsignedInt now = TheGameLogic->getFrame(); - //UnsignedInt timePassed = now - m_initStartFrame; - //Real progress = INT_TO_REAL(timePassed % d->m_initWobbleInterval) / INT_TO_REAL(d->m_initWobbleInterval); + /*const Matrix3D* instMatrix = obj->getDrawable()->getInstanceMatrix(); + Matrix3D newInstMatrix; + newInstMatrix = *instMatrix; + newInstMatrix.Set_Translation(shudder);*/ } // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::doTopplePhase() { + DEBUG_LOG((">>> SSD doTopplePhase 0")); + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); if (d->m_toppleDuration <= 0) @@ -258,24 +389,53 @@ void ShipSlowDeathBehavior::doTopplePhase() UnsignedInt now = TheGameLogic->getFrame(); UnsignedInt timePassed = now - m_toppleStartFrame; - Real progress = INT_TO_REAL(timePassed) / INT_TO_REAL(d->m_toppleDuration); + Real p = INT_TO_REAL(timePassed) / INT_TO_REAL(d->m_toppleDuration); + + Real progressAngle = smoothStep(p); //TODO: apply sinus curve + Real progressHeight = smoothStep(p); + Real progressPush = smoothPlateau(p, 0.5); Matrix3D mtx = *obj->getTransformMatrix(); - Real pitchRate; - Real yawRate; - Real rollRate; + Real targetPitch = 0.0f; + Real targetYaw = 0.0f; + Real targetRoll = 0.0f; + Real targetZ = 0.0f; + + const Coord3D* dir = obj->getUnitDirectionVector2D(); + Coord3D force; + + Real accelForce; + //force.x = accelForce * dir->x; + //force.y = accelForce * dir->y; + force.z = 0.0f; // Topple the boat switch (m_chosenToppleType) { case TOPPLE_FRONT: + targetPitch = progressAngle * WWMath::Lerp(d->m_toppleFrontMinPitch, d->m_toppleFrontMaxPitch, m_toppleVarianceRoll); + accelForce = progressPush * WWMath::Lerp(d->m_toppleMinPushForce, d->m_toppleMaxPushForce, m_toppleVarianceRoll); + force.x = accelForce * -dir->x; + force.y = accelForce * -dir->y; break; case TOPPLE_BACK: + targetPitch = -progressAngle * WWMath::Lerp(d->m_toppleBackMinPitch, d->m_toppleBackMaxPitch, m_toppleVarianceRoll); + accelForce = progressPush * WWMath::Lerp(d->m_toppleMinPushForce, d->m_toppleMaxPushForce, m_toppleVarianceRoll); + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; break; case TOPPLE_SIDE_LEFT: + targetRoll = progressAngle * WWMath::Lerp(d->m_toppleSideMinRoll, d->m_toppleSideMaxRoll, m_toppleVarianceRoll); + accelForce = progressPush * WWMath::Lerp(d->m_toppleMinPushForceSide, d->m_toppleMaxPushForceSide, m_toppleVarianceRoll); + force.x = accelForce * -dir->y; + force.y = accelForce * dir->x; break; case TOPPLE_SIDE_RIGHT: + targetRoll = -progressAngle * WWMath::Lerp(d->m_toppleSideMinRoll, d->m_toppleSideMaxRoll, m_toppleVarianceRoll); + accelForce = progressPush * WWMath::Lerp(d->m_toppleMinPushForceSide, d->m_toppleMaxPushForceSide, m_toppleVarianceRoll); + force.x = accelForce * dir->y; + force.y = accelForce * -dir->x; break; case TOPPLE_NONE: return; @@ -283,18 +443,177 @@ void ShipSlowDeathBehavior::doTopplePhase() DEBUG_CRASH(("Invalid topple type in ShipSlowDeathBehavior")); } + PhysicsBehavior* physics = obj->getPhysics(); + DEBUG_ASSERTCRASH(physics, ("ShipSlowDeathBehavior: object '%s' does not have a physics module", + obj->getTemplate()->getName().str())); + physics->applyMotiveForce(&force); + + targetZ = progressHeight * WWMath::Lerp(d->m_toppleHeightMinOffset, d->m_toppleHeightMaxOffset, m_toppleVarianceRoll); + // const Coord3D* pos = obj->getPosition(); + + Real deltaPitch = targetPitch - m_curPitch; + Real deltaYaw = targetYaw - m_curYaw; + Real deltaRoll = targetRoll - m_curRoll; + Real deltaZ = targetZ - m_curZ; + + Real R2D = 180.0 / PI; + DEBUG_LOG((">>> SSD TopplePhase: target(P/Y/R/z) = (%f, %f, %f, %f), delta(P/Y/R/Z) = (%f, %f, %f, %f)", + targetPitch*R2D, targetYaw*R2D, targetRoll*R2D, targetZ, deltaPitch*R2D, deltaYaw*R2D, deltaRoll*R2D, deltaZ)); + + + mtx.Rotate_X(deltaRoll); + mtx.Rotate_Y(deltaPitch); + mtx.Rotate_Z(deltaYaw); + + //mtx.Adjust_Z_Translation(deltaZ); + + obj->setTransformMatrix(&mtx); + // obj->setPositionZ(pos->z + deltaZ); + + m_curPitch += deltaPitch; + m_curYaw += deltaYaw; + m_curRoll += deltaRoll; + m_curZ += deltaZ; + + if (obj->getAIUpdateInterface()) + { + Locomotor* locomotor = obj->getAIUpdateInterface()->getCurLocomotor(); + if (locomotor) { + locomotor->setPreferredHeight(locomotor->getPreferredHeight() + deltaZ); + } + } } + +inline Real getDelta(Real angle, Real delta) { + if (angle == 0 || delta == 0) + return 0.0f; + if (angle < 0) + return MIN(-angle, delta); + else + return -MIN(angle, delta); +} + // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::doSinkPhase() { + DEBUG_LOG((">>> SSD doSinkPhase 0")); + + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); + + Object* obj = getObject(); + + //if (d->m_toppleDamping < 1.0) { + { + Matrix3D mtx = *obj->getTransformMatrix(); + + //Real targetPitch = m_curPitch * d->m_toppleDamping; + //Real targetYaw = m_curYaw * d->m_toppleDamping; + //Real targetRoll = m_curRoll * d->m_toppleDamping; + + //Real deltaPitch = targetPitch - m_curPitch; + //Real deltaYaw = targetYaw - m_curYaw; + //Real deltaRoll = targetRoll - m_curRoll; + + Real angleRate = d->m_toppleAngleCorrectionRate; + + Real deltaPitch = getDelta(m_curPitch, angleRate); + Real deltaYaw = getDelta(m_curYaw, angleRate); + Real deltaRoll = getDelta(m_curRoll, angleRate); + + Real R2D = 180.0 / PI; + DEBUG_LOG((">>> SINK TOPPLE CORRECTION, curAngle(P/Y/R) = (%f, %f, %f), delta(P/Y/R) = (%f, %f, %f)", + m_curPitch*R2D, m_curYaw*R2D, m_curRoll*R2D, + deltaPitch*R2D, deltaYaw*R2D, deltaRoll*R2D + )); + + mtx.Rotate_X(deltaRoll); + mtx.Rotate_Y(deltaPitch); + mtx.Rotate_Z(deltaYaw); + + obj->setTransformMatrix(&mtx); + + m_curPitch += deltaPitch; + m_curYaw += deltaYaw; + m_curRoll += deltaRoll; + + } + + //// do the wobble + //if (d->m_sinkWobbleInterval > 0) { + // UnsignedInt now = TheGameLogic->getFrame(); + // UnsignedInt timePassed = now - m_sinkStartFrame; + // Real pInterval = INT_TO_REAL(timePassed % d->m_sinkWobbleInterval) / INT_TO_REAL(d->m_sinkWobbleInterval); + + // // short ease in + // Real pTotal = INT_TO_REAL(timePassed) / 30.0; // 1sec + + // Real progressTotal = smoothStep(pTotal); + + // Drawable* draw = getObject()->getDrawable(); + // if (draw) { + // Matrix3D mx = *draw->getInstanceMatrix(); + + // //Real pitch = progressInterval * progressTotal * d->m_initWobbleMaxPitch; + // //Real yaw = progressInterval * progressTotal * d->m_initWobbleMaxYaw; + // //Real roll = progressInterval * progressTotal * d->m_initWobbleMaxRoll; + + // Real pitch = sin(pInterval * TWO_PI) * progressTotal * d->m_sinkWobbleMaxPitch; + // Real yaw = cos(pInterval * TWO_PI) * progressTotal * d->m_sinkWobbleMaxYaw; + // Real roll = -sin(pInterval * TWO_PI * 2) * progressTotal * d->m_sinkWobbleMaxRoll; + + // mx.Make_Identity(); + // mx.Rotate_Z(roll); + // mx.Rotate_Y(yaw); + // mx.Rotate_X(pitch); + + // draw->setInstanceMatrix(&mx); + // } + // } } // ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::doWobble() +{ + + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); + if (d->m_wobbleInterval <= 0) + return; + // do the wobble + UnsignedInt now = TheGameLogic->getFrame(); + UnsignedInt timePassed = now - m_initStartFrame; + Real pInterval = INT_TO_REAL(timePassed % d->m_wobbleInterval) / INT_TO_REAL(d->m_wobbleInterval); + + // short ease in + Real pTotal = INT_TO_REAL(timePassed) / 30.0; // 1sec + Real progressTotal = smoothStep(pTotal); + + + Drawable* draw = getObject()->getDrawable(); + if (draw) { + Matrix3D mx = *draw->getInstanceMatrix(); + //Real pitch = progressInterval * progressTotal * d->m_initWobbleMaxPitch; + //Real yaw = progressInterval * progressTotal * d->m_initWobbleMaxYaw; + //Real roll = progressInterval * progressTotal * d->m_initWobbleMaxRoll; + + Real pitch = sin(pInterval * TWO_PI) * progressTotal * d->m_wobbleMaxPitch; + Real yaw = cos(pInterval * TWO_PI) * progressTotal * d->m_wobbleMaxYaw; + Real roll = -sin(pInterval * TWO_PI * 2) * progressTotal * d->m_wobbleMaxRoll; + + mx.Make_Identity(); + mx.Rotate_Z(roll); + mx.Rotate_Y(yaw); + mx.Rotate_X(pitch); + + draw->setInstanceMatrix(&mx); + } +} + +// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ @@ -331,6 +650,13 @@ void ShipSlowDeathBehavior::xfer( Xfer *xfer ) xfer->xferBool( &m_shipSinkStarted); xfer->xferInt( &m_chosenToppleType); + xfer->xferReal( &m_toppleVarianceRoll); + + xfer->xferReal( &m_curPitch); + xfer->xferReal( &m_curYaw); + xfer->xferReal( &m_curRoll); + xfer->xferReal( &m_curZ); + } // ------------------------------------------------------------------------------------------------ From 1166260afe83e524a9e1b5370437ec52ded724b7 Mon Sep 17 00:00:00 2001 From: andreasw Date: Thu, 29 Jan 2026 17:46:10 +0100 Subject: [PATCH 4/4] ShipSlowDeath finished initial version --- .../GameLogic/Module/ShipSlowDeathBehavior.h | 48 +----- .../Object/Update/ShipSlowDeathBehavior.cpp | 144 +++++++++++------- 2 files changed, 96 insertions(+), 96 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h index 78cb76b12c4..6433a9a5798 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h @@ -35,6 +35,7 @@ // FORWARD DECLARATIONS /////////////////////////////////////////////////////////////////////////// class ParticleSystemTemplate; +enum ModelConditionFlagType CPP_11(: Int); //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -122,37 +123,14 @@ class ShipSlowDeathBehaviorModuleData : public SlowDeathBehaviorModuleData const FXList* m_fxStartSink; ///< fx after topple / before sinking phase const ObjectCreationList* m_oclStartSink; ///< OCL after topple / before sinking phase - const ParticleSystemTemplate* m_attachParticleSystem; ///< particle system to attach - AsciiString m_attachParticleBone; ///< bone to attach particle system to - Coord3D m_attachParticleLoc; ///< loc attach particle system to if bone not present - - //Real m_spiralOrbitTurnRate; ///< (rads per frame) rate at which we do big circles down toward the ground - //Real m_spiralOrbitForwardSpeed; ///< (dist per frame) speed at which we move "forward" in the downward spiral - //Real m_spiralOrbitForwardSpeedDamping;///< every frame our forward speed in the orbit is adjusted by this amount - //Real m_minSelfSpin; ///< (rads per frame) min turning rate at which we spin around our center of gravity - //Real m_maxSelfSpin; ///< (rads per frame) max turning rate at which we spin around our center of gravity - //Real m_selfSpinUpdateDelay; ///< (frames) every this many frames we will update the self spin angle, but we'll keep it inbetween the min and max self spin - //Real m_selfSpinUpdateAmount; ///< (radian) when we update the self spin every SelfSpinUpdateDelay frames, we change it this much, but keep it between min and max self spin - //Real m_fallHowFast; ///< a fraction of gravity we use to modify the helicopert locmotor lift - //Real m_minBladeFlyOffDelay; ///< (frames) min frame that the blade will fly off at - //Real m_maxBladeFlyOffDelay; ///< (frames) max frame that the blade will fly off at - - //AsciiString m_bladeObjectName; ///< object name of the blade piece - //AsciiString m_bladeBone; ///< bone name of main propeller blade - - //const FXList *m_fxBlade; ///< blade fly off event fx list - //const ObjectCreationList *m_oclBlade; ///< OCL at blade fly off event - //const FXList *m_fxHitGround; ///< fx for hitting the ground - //const ObjectCreationList *m_oclHitGround; ///< OCL at hit ground event - //const FXList *m_fxFinalBlowUp; ///< the final blow up - //const ObjectCreationList *m_oclFinalBlowUp;///< OCL at final blow up event - //Real m_delayFromGroundToFinalDeath; ///< (frames) delay from when we hit the ground to final BOOM! - //AsciiString m_finalRubbleObject; ///< final rubble object to create after it's ALL over - //Real m_maxBraking; ///< max braking we may use during death spiral - - //// @todo propagate this up to SlowDeathBehaviorModuleData. I don't wanna do it today, cause its 4/3. jkmcd - //AudioEventRTS m_deathSound; ///< Sound played during death sequence. + const ParticleSystemTemplate* m_attachParticleSystem; ///< particle system to attach while sinking + std::vector m_attachParticleBoneNames; + + + ModelConditionFlagType m_conditionFlagInit; + ModelConditionFlagType m_conditionFlagTopple; + ModelConditionFlagType m_conditionFlagSink; }; //------------------------------------------------------------------------------------------------- @@ -183,16 +161,6 @@ class ShipSlowDeathBehavior : public SlowDeathBehavior void beginTopplePhase(); void beginSinkPhase(); - //Int m_orbitDirection; ///< -1 or +1 ... use ORBIT_DIRECTION_LEFT/RIGHT - //Real m_forwardAngle; /**< angle of the direction "forward" we want to move in our downspin - // which is independent of the actual direction angle of the object */ - //Real m_forwardSpeed; ///< the speed we're travelling forward in our spiral orbit - //Real m_selfSpin; ///< rads per frame that we change our facing direction - //Bool m_selfSpinTowardsMax; ///< TRUE when our self spin rate is increasing towards the MaxSelfSpin - //UnsignedInt m_lastSelfSpinUpdateFrame; ///< frame we last updated the self spin on - //UnsignedInt m_bladeFlyOffFrame; ///< frame we throw the blade off at - //UnsignedInt m_hitGroundFrame; ///< frame we hit the ground on - // @todo propagate this up to SlowDeathBehavior. I don't wanna do it today, cause its 4/3. jkmcd AudioEventRTS m_deathSound; ///< Sound played during death sequence. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp index b8f385df5d8..54032160d38 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp @@ -35,6 +35,7 @@ #include "Common/RandomValue.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" +#include "Common/ModelState.h" #include "Common/Xfer.h" #include "GameClient/Drawable.h" #include "GameClient/FXList.h" @@ -61,11 +62,8 @@ //------------------------------------------------------------------------------------------------- ShipSlowDeathBehaviorModuleData::ShipSlowDeathBehaviorModuleData( void ) { - m_attachParticleBone.clear(); + m_attachParticleBoneNames.clear(); m_attachParticleSystem = NULL; - m_attachParticleLoc.x = 0.0f; - m_attachParticleLoc.y = 0.0f; - m_attachParticleLoc.z = 0.0f; m_oclEjectPilot = NULL; m_fxHitGround = NULL; m_oclHitGround = NULL; @@ -74,6 +72,10 @@ ShipSlowDeathBehaviorModuleData::ShipSlowDeathBehaviorModuleData( void ) m_fxStartSink = NULL; m_oclStartSink = NULL; //m_toppleDamping = 1.0; + + m_conditionFlagInit = MODELCONDITION_RUBBLE; + m_conditionFlagTopple = MODELCONDITION_RUBBLE; + m_conditionFlagSink = MODELCONDITION_RUBBLE; } // ------------------------------------------------------------------------------------------------ @@ -86,6 +88,7 @@ ShipSlowDeathBehaviorModuleData::ShipSlowDeathBehaviorModuleData( void ) { { "InitialDelay", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_initialDelay) }, { "InitialDelayVariance", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_initialDelayVariance) }, + { "InitialConditionFlag", ModelConditionFlags::parseSingleBitFromINI, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_conditionFlagInit) }, { "WobbleMaxAnglePitch", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_wobbleMaxPitch) }, { "WobbleMaxAngleYaw", INI::parseAngleReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_wobbleMaxYaw) }, @@ -108,6 +111,7 @@ ShipSlowDeathBehaviorModuleData::ShipSlowDeathBehaviorModuleData( void ) { "ToppleMaxPushForce", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleMaxPushForce) }, { "ToppleMinPushForceSide", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleMinPushForceSide) }, { "ToppleMaxPushForceSide", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleMaxPushForceSide) }, + { "ToppleConditionFlag", ModelConditionFlags::parseSingleBitFromINI, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_conditionFlagTopple) }, { "FXStartTopple", INI::parseFXList, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_fxStartTopple) }, @@ -115,7 +119,7 @@ ShipSlowDeathBehaviorModuleData::ShipSlowDeathBehaviorModuleData( void ) //{ "ToppleDampingDuration", INI::parseDurationUnsignedInt, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleDampingDuration) }, //{ "ToppleDamping", INI::parseReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleDamping) }, - { "ToppleAngleCorrectionRate", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleAngleCorrectionRate) }, + { "ToppleAngleCorrectionRate", INI::parseAngularVelocityReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_toppleAngleCorrectionRate) }, //{ "SinkWobbleMaxAnglePitch", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxPitch) }, //{ "SinkWobbleMaxAngleYaw", INI::parseAngleReal, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_sinkWobbleMaxYaw) }, @@ -124,12 +128,11 @@ ShipSlowDeathBehaviorModuleData::ShipSlowDeathBehaviorModuleData( void ) { "SinkHowFast", INI::parsePercentToReal, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_sinkHowFast) }, { "FXStartSink", INI::parseFXList, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_fxStartSink) }, { "OCLStartSink", INI::parseObjectCreationList, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_oclStartSink) }, + { "SinkConditionFlag", ModelConditionFlags::parseSingleBitFromINI, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_conditionFlagSink) }, + { "SinkAttachParticle", INI::parseParticleSystemTemplate, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleSystem ) }, + { "SinkAttachParticleBones", INI::parseAsciiStringVector, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleBoneNames ) }, - { "AttachParticle", INI::parseParticleSystemTemplate, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleSystem ) }, - { "AttachParticleBone", INI::parseAsciiString, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleBone ) }, - { "AttachParticleLoc", INI::parseCoord3D, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleLoc ) }, - { "OCLEjectPilot", INI::parseObjectCreationList, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_oclEjectPilot ) }, { "FXHitGround", INI::parseFXList, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_fxHitGround ) }, @@ -324,26 +327,53 @@ inline Real fastSin01(Real x) // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::beginInitPhase() { + //DEBUG_LOG((">>> BEGIN INIT PHASE")); + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); + Drawable* draw = getObject()->getDrawable(); + if (draw) { + if (d->m_conditionFlagInit != MODELCONDITION_INVALID) + draw->clearAndSetModelConditionState(MODELCONDITION_RUBBLE, d->m_conditionFlagInit); + } } // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::beginTopplePhase() { + //DEBUG_LOG((">>> BEGIN TOPPLE PHASE")); + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); Object* obj = getObject(); // make effect FXList::doFXObj(d->m_fxStartTopple, obj); ObjectCreationList::create(d->m_oclStartTopple, obj, NULL); + Drawable* draw = getObject()->getDrawable(); + if (draw) { + + ModelConditionFlags flagsToClear; + ModelConditionFlags flagsToSet; + + if (d->m_conditionFlagInit != MODELCONDITION_INVALID) + flagsToClear.set(d->m_conditionFlagInit); + + if (d->m_conditionFlagTopple != MODELCONDITION_INVALID) { + flagsToSet.set(d->m_conditionFlagTopple); + + draw->clearAndSetModelConditionFlags(flagsToClear, flagsToSet); + } + } } // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::beginSinkPhase() { + //DEBUG_LOG((">>> BEGIN SINK PHASE")); + m_shipSinkStarted = TRUE; + const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); Object* obj = getObject(); // make effect - FXList::doFXObj(d->m_fxStartTopple, obj); - ObjectCreationList::create(d->m_oclStartTopple, obj, NULL); + //FXList::doFXObj(d->m_fxStartTopple, obj); + //ObjectCreationList::create(d->m_oclStartTopple, obj, NULL); m_deathSound = d->m_deathSound; @@ -359,12 +389,48 @@ void ShipSlowDeathBehavior::beginSinkPhase() locomotor->setMaxLift(-TheGlobalData->m_gravity * (1.0f - d->m_sinkHowFast)); } + Drawable* draw = obj->getDrawable(); + if (draw) { + + ModelConditionFlags flagsToClear; + ModelConditionFlags flagsToSet; + + if (d->m_conditionFlagTopple != MODELCONDITION_INVALID) + flagsToClear.set(d->m_conditionFlagInit); + + if (d->m_conditionFlagSink != MODELCONDITION_INVALID) { + flagsToSet.set(d->m_conditionFlagSink); + + draw->clearAndSetModelConditionFlags(flagsToClear, flagsToSet); + } + + if (d->m_attachParticleSystem) + { + // If no bones are specified, create one instance + if (d->m_attachParticleBoneNames.empty()) { + ParticleSystem* pSys = TheParticleSystemManager->createParticleSystem(d->m_attachParticleSystem); + if (pSys) + pSys->attachToObject(obj); + } + else { // one for each bone + Coord3D pos; + for (AsciiString boneName : d->m_attachParticleBoneNames) { + ParticleSystem* pSys = TheParticleSystemManager->createParticleSystem(d->m_attachParticleSystem); + if (pSys) { + if (draw->getPristineBonePositions(boneName.str(), 0, &pos, NULL, 1)) + pSys->setPosition(&pos); + pSys->attachToObject(obj); + } + } + } + } + } } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::doInitPhase() { - DEBUG_LOG((">>> SSD doInitPhase 0")); + //DEBUG_LOG((">>> SSD doInitPhase 0")); //const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); @@ -378,7 +444,7 @@ void ShipSlowDeathBehavior::doInitPhase() // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::doTopplePhase() { - DEBUG_LOG((">>> SSD doTopplePhase 0")); + //DEBUG_LOG((">>> SSD doTopplePhase 0")); const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); @@ -457,9 +523,9 @@ void ShipSlowDeathBehavior::doTopplePhase() Real deltaRoll = targetRoll - m_curRoll; Real deltaZ = targetZ - m_curZ; - Real R2D = 180.0 / PI; - DEBUG_LOG((">>> SSD TopplePhase: target(P/Y/R/z) = (%f, %f, %f, %f), delta(P/Y/R/Z) = (%f, %f, %f, %f)", - targetPitch*R2D, targetYaw*R2D, targetRoll*R2D, targetZ, deltaPitch*R2D, deltaYaw*R2D, deltaRoll*R2D, deltaZ)); + //Real R2D = 180.0 / PI; + //DEBUG_LOG((">>> SSD TopplePhase: target(P/Y/R/z) = (%f, %f, %f, %f), delta(P/Y/R/Z) = (%f, %f, %f, %f)", + // targetPitch*R2D, targetYaw*R2D, targetRoll*R2D, targetZ, deltaPitch*R2D, deltaYaw*R2D, deltaRoll*R2D, deltaZ)); mtx.Rotate_X(deltaRoll); @@ -498,7 +564,7 @@ inline Real getDelta(Real angle, Real delta) { // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::doSinkPhase() { - DEBUG_LOG((">>> SSD doSinkPhase 0")); + //DEBUG_LOG((">>> SSD doSinkPhase 0")); const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); @@ -522,11 +588,11 @@ void ShipSlowDeathBehavior::doSinkPhase() Real deltaYaw = getDelta(m_curYaw, angleRate); Real deltaRoll = getDelta(m_curRoll, angleRate); - Real R2D = 180.0 / PI; - DEBUG_LOG((">>> SINK TOPPLE CORRECTION, curAngle(P/Y/R) = (%f, %f, %f), delta(P/Y/R) = (%f, %f, %f)", - m_curPitch*R2D, m_curYaw*R2D, m_curRoll*R2D, - deltaPitch*R2D, deltaYaw*R2D, deltaRoll*R2D - )); + //Real R2D = 180.0 / PI; + //DEBUG_LOG((">>> SINK TOPPLE CORRECTION, curAngle(P/Y/R) = (%f, %f, %f), delta(P/Y/R) = (%f, %f, %f)", + // m_curPitch*R2D, m_curYaw*R2D, m_curRoll*R2D, + // deltaPitch*R2D, deltaYaw*R2D, deltaRoll*R2D + // )); mtx.Rotate_X(deltaRoll); mtx.Rotate_Y(deltaPitch); @@ -539,39 +605,6 @@ void ShipSlowDeathBehavior::doSinkPhase() m_curRoll += deltaRoll; } - - //// do the wobble - //if (d->m_sinkWobbleInterval > 0) { - // UnsignedInt now = TheGameLogic->getFrame(); - // UnsignedInt timePassed = now - m_sinkStartFrame; - // Real pInterval = INT_TO_REAL(timePassed % d->m_sinkWobbleInterval) / INT_TO_REAL(d->m_sinkWobbleInterval); - - // // short ease in - // Real pTotal = INT_TO_REAL(timePassed) / 30.0; // 1sec - - // Real progressTotal = smoothStep(pTotal); - - // Drawable* draw = getObject()->getDrawable(); - // if (draw) { - // Matrix3D mx = *draw->getInstanceMatrix(); - - // //Real pitch = progressInterval * progressTotal * d->m_initWobbleMaxPitch; - // //Real yaw = progressInterval * progressTotal * d->m_initWobbleMaxYaw; - // //Real roll = progressInterval * progressTotal * d->m_initWobbleMaxRoll; - - // Real pitch = sin(pInterval * TWO_PI) * progressTotal * d->m_sinkWobbleMaxPitch; - // Real yaw = cos(pInterval * TWO_PI) * progressTotal * d->m_sinkWobbleMaxYaw; - // Real roll = -sin(pInterval * TWO_PI * 2) * progressTotal * d->m_sinkWobbleMaxRoll; - - // mx.Make_Identity(); - // mx.Rotate_Z(roll); - // mx.Rotate_Y(yaw); - // mx.Rotate_X(pitch); - - // draw->setInstanceMatrix(&mx); - // } - // } - } // ------------------------------------------------------------------------------------------------ void ShipSlowDeathBehavior::doWobble() @@ -643,7 +676,6 @@ void ShipSlowDeathBehavior::xfer( Xfer *xfer ) // extend base class SlowDeathBehavior::xfer( xfer ); - xfer->xferUnsignedInt( &m_initStartFrame); xfer->xferUnsignedInt( &m_toppleStartFrame); xfer->xferUnsignedInt( &m_sinkStartFrame);