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/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/Include/GameLogic/Module/ShipSlowDeathBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h new file mode 100644 index 00000000000..6433a9a5798 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ShipSlowDeathBehavior.h @@ -0,0 +1,179 @@ +/* +** 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 ModelConditionFlagType CPP_11(: Int); + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +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_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_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 while sinking + + std::vector m_attachParticleBoneNames; + + + ModelConditionFlagType m_conditionFlagInit; + ModelConditionFlagType m_conditionFlagTopple; + ModelConditionFlagType m_conditionFlagSink; +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +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 doWobble(); + + void beginInitPhase(); + void beginTopplePhase(); + void beginSinkPhase(); + + // @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; + + Real m_toppleVarianceRoll; + + Real m_curPitch; + Real m_curYaw; + Real m_curRoll; + Real m_curZ; +}; 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/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(); 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..54032160d38 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ShipSlowDeathBehavior.cpp @@ -0,0 +1,703 @@ +/* +** 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 +#define DEFINE_SHIP_TOPPLE_NAMES +#include "Common/GameAudio.h" +#include "Common/GlobalData.h" +#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" +#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_attachParticleBoneNames.clear(); + m_attachParticleSystem = NULL; + 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; + + m_conditionFlagInit = MODELCONDITION_RUBBLE; + m_conditionFlagTopple = MODELCONDITION_RUBBLE; + m_conditionFlagSink = MODELCONDITION_RUBBLE; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +/*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) }, + { "InitialConditionFlag", ModelConditionFlags::parseSingleBitFromINI, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_conditionFlagInit) }, + + { "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) }, + { "ToppleConditionFlag", ModelConditionFlags::parseSingleBitFromINI, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_conditionFlagTopple) }, + + + { "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::parseAngularVelocityReal, 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) }, + { "SinkConditionFlag", ModelConditionFlags::parseSingleBitFromINI, NULL, offsetof(ShipSlowDeathBehaviorModuleData, m_conditionFlagSink) }, + + { "SinkAttachParticle", INI::parseParticleSystemTemplate, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleSystem ) }, + { "SinkAttachParticleBones", INI::parseAsciiStringVector, NULL, offsetof( ShipSlowDeathBehaviorModuleData, m_attachParticleBoneNames ) }, + + { "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; + } + + m_toppleVarianceRoll = GameLogicRandomValueReal(0.0f, 1.0f); + + 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(); + doWobble(); + 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(); + doWobble(); + return UPDATE_SLEEP_NONE; + } + else { + if (!m_shipSinkStarted) + beginSinkPhase(); + + doSinkPhase(); + 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() +{ + //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); + + 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)); + } + + 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")); + + //const ShipSlowDeathBehaviorModuleData* d = getShipSlowDeathBehaviorModuleData(); + + + /*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) + return; + + Object* obj = getObject(); + + UnsignedInt now = TheGameLogic->getFrame(); + UnsignedInt timePassed = now - m_toppleStartFrame; + 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 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; + default: + 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; + + } +} +// ------------------------------------------------------------------------------------------------ +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); + } +} + +// ------------------------------------------------------------------------------------------------ + + +// ------------------------------------------------------------------------------------------------ +/** 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); + + xfer->xferReal( &m_toppleVarianceRoll); + + xfer->xferReal( &m_curPitch); + xfer->xferReal( &m_curYaw); + xfer->xferReal( &m_curRoll); + xfer->xferReal( &m_curZ); + +} + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void ShipSlowDeathBehavior::loadPostProcess( void ) +{ + + // extend base class + SlowDeathBehavior::loadPostProcess(); + +}