LCOV - code coverage report
Current view: top level - source/simulation2/components - CCmpDecay.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 3 72 4.2 %
Date: 2022-06-14 00:41:00 Functions: 1 10 10.0 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "simulation2/system/Component.h"
      21             : #include "ICmpDecay.h"
      22             : 
      23             : #include "simulation2/MessageTypes.h"
      24             : 
      25             : #include "ICmpPosition.h"
      26             : #include "ICmpTerrain.h"
      27             : #include "ICmpVisual.h"
      28             : 
      29             : #include "ps/Profile.h"
      30             : 
      31             : /**
      32             :  * Fairly basic decay implementation, for units and buildings etc.
      33             :  * The decaying entity remains stationary for some time period, then falls downwards
      34             :  * with some initial speed and some acceleration, until it has fully sunk.
      35             :  * The sinking depth is determined from the actor's bounding box and the terrain.
      36             :  *
      37             :  * This currently doesn't work with entities whose ICmpPosition has an initial Y offset.
      38             :  *
      39             :  * This isn't very efficient (we'll store data and iterate every frame for every entity,
      40             :  * not just for corpse entities) - it could be designed more optimally if that's a real problem.
      41             :  *
      42             :  * Eventually we might want to adjust the decay rate based on user configuration (low-spec
      43             :  * machines could have fewer corpses), number of corpses, etc.
      44             :  *
      45             :  * Must not be used on network-synchronised entities, unless \<Inactive\> is present.
      46             :  */
      47           0 : class CCmpDecay final : public ICmpDecay
      48             : {
      49             : public:
      50           0 :     static void ClassInit(CComponentManager& UNUSED(componentManager))
      51             :     {
      52           0 :     }
      53             : 
      54           0 :     DEFAULT_COMPONENT_ALLOCATOR(Decay)
      55             : 
      56             :     bool m_Active;
      57             :     bool m_ShipSink;
      58             :     float m_DelayTime;
      59             :     float m_SinkRate;
      60             :     float m_SinkAccel;
      61             : 
      62             :     entity_pos_t m_InitialXRotation;
      63             :     entity_pos_t m_InitialZRotation;
      64             : 
      65             :     // Used to randomize ship-like sinking
      66             :     float m_SinkingAngleX;
      67             :     float m_SinkingAngleZ;
      68             : 
      69             :     float m_CurrentTime;
      70             :     float m_TotalSinkDepth; // distance we need to sink (derived from bounding box), or -1 if undetermined
      71             : 
      72             :     static std::string GetSchema()
      73             :     {
      74         115 :         return
      75             :             "<element name='Active' a:help='If false, the entity will not do any decaying'>"
      76             :                 "<data type='boolean'/>"
      77             :             "</element>"
      78             :             "<element name='SinkingAnim' a:help='If true, the entity will decay in a ship-like manner'>"
      79             :                 "<data type='boolean'/>"
      80             :             "</element>"
      81             :             "<element name='DelayTime' a:help='Time to wait before starting to sink, in seconds'>"
      82             :                 "<ref name='nonNegativeDecimal'/>"
      83             :             "</element>"
      84             :             "<element name='SinkRate' a:help='Initial rate of sinking, in metres per second'>"
      85             :                 "<ref name='nonNegativeDecimal'/>"
      86             :             "</element>"
      87             :             "<element name='SinkAccel' a:help='Acceleration rate of sinking, in metres per second per second'>"
      88             :                 "<ref name='nonNegativeDecimal'/>"
      89         115 :             "</element>";
      90             :     }
      91             : 
      92           0 :     void Init(const CParamNode& paramNode) override
      93             :     {
      94           0 :         m_Active = paramNode.GetChild("Active").ToBool();
      95           0 :         m_ShipSink = paramNode.GetChild("SinkingAnim").ToBool();
      96           0 :         m_DelayTime = paramNode.GetChild("DelayTime").ToFixed().ToFloat();
      97           0 :         m_SinkRate = paramNode.GetChild("SinkRate").ToFixed().ToFloat();
      98           0 :         m_SinkAccel = paramNode.GetChild("SinkAccel").ToFixed().ToFloat();
      99             : 
     100           0 :         m_CurrentTime = 0.f;
     101           0 :         m_TotalSinkDepth = -1.f;
     102             : 
     103             :         // Detect unsafe misconfiguration
     104           0 :         if (m_Active && !ENTITY_IS_LOCAL(GetEntityId()))
     105             :         {
     106           0 :             debug_warn(L"CCmpDecay must not be used on non-local (network-synchronised) entities");
     107           0 :             m_Active = false;
     108             :         }
     109             : 
     110           0 :         if (m_Active)
     111           0 :             GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, true);
     112           0 :     }
     113             : 
     114           0 :     void Deinit() override
     115             :     {
     116           0 :     }
     117             : 
     118           0 :     void Serialize(ISerializer& UNUSED(serialize)) override
     119             :     {
     120             :         // This component isn't network-synchronised, so don't serialize anything
     121           0 :     }
     122             : 
     123           0 :     void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) override
     124             :     {
     125           0 :         Init(paramNode);
     126           0 :     }
     127             : 
     128           0 :     void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
     129             :     {
     130           0 :         switch (msg.GetType())
     131             :         {
     132           0 :         case MT_Interpolate:
     133           0 :         {
     134           0 :             PROFILE("Decay::Interpolate");
     135             : 
     136           0 :             if (!m_Active)
     137             :                 break;
     138             : 
     139           0 :             const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
     140             : 
     141           0 :             CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     142           0 :             if (!cmpPosition || !cmpPosition->IsInWorld())
     143             :             {
     144             :                 // If there's no position (this usually shouldn't happen), destroy the unit immediately
     145           0 :                 GetSimContext().GetComponentManager().DestroyComponentsSoon(GetEntityId());
     146             :                 break;
     147             :             }
     148             : 
     149             :             // Compute the depth the first time this is called
     150             :             // (This is a bit of an ugly place to do it but at least we'll be sure
     151             :             // the actor component was loaded already)
     152           0 :             if (m_TotalSinkDepth < 0.f)
     153             :             {
     154           0 :                 m_TotalSinkDepth = 1.f; // minimum so we always sink at least a little
     155             : 
     156           0 :                 CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle());
     157           0 :                 if (cmpVisual)
     158             :                 {
     159           0 :                     CBoundingBoxAligned bound = cmpVisual->GetBounds();
     160           0 :                     m_TotalSinkDepth = std::max(m_TotalSinkDepth, bound[1].Y - bound[0].Y);
     161             :                 }
     162             : 
     163             :                 // If this is a floating unit, we want it to sink all the way under the terrain,
     164             :                 // so find the difference between its current position and the terrain
     165             : 
     166           0 :                 CFixedVector3D pos = cmpPosition->GetPosition();
     167             : 
     168           0 :                 CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     169           0 :                 if (cmpTerrain)
     170             :                 {
     171           0 :                     fixed ground = cmpTerrain->GetGroundLevel(pos.X, pos.Z);
     172           0 :                     m_TotalSinkDepth += std::max(0.f, (pos.Y - ground).ToFloat());
     173             :                 }
     174             : 
     175             :                 // Sink it further down if it sinks like a ship, as it will rotate.
     176           0 :                 if (m_ShipSink)
     177             :                 {
     178             :                     // lacking randomness we'll trick
     179           0 :                     m_SinkingAngleX = (pos.X.ToInt_RoundToNearest() % 30 - 15) / 15.0;
     180           0 :                     m_SinkingAngleZ = (pos.Z.ToInt_RoundToNearest() % 30) / 40.0;
     181           0 :                     m_TotalSinkDepth += 10.f;
     182             :                 }
     183             :                 // probably 0 in both cases but we'll remember it anyway.
     184           0 :                 m_InitialXRotation = cmpPosition->GetRotation().X;
     185           0 :                 m_InitialZRotation = cmpPosition->GetRotation().Z;
     186             :             }
     187             : 
     188           0 :             m_CurrentTime += msgData.deltaSimTime;
     189             : 
     190           0 :             if (m_CurrentTime >= m_DelayTime)
     191             :             {
     192           0 :                 float t = m_CurrentTime - m_DelayTime;
     193           0 :                 float depth = (m_SinkRate * t) + (m_SinkAccel * t * t);
     194             : 
     195           0 :                 if (m_ShipSink)
     196             :                 {
     197             :                     // exponential sinking with tilting
     198           0 :                     float tilt_time = t > 5.f ? 5.f : t;
     199           0 :                     float tiltSink = tilt_time * tilt_time / 5.f;
     200           0 :                     entity_pos_t RotX = entity_pos_t::FromFloat(((m_InitialXRotation.ToFloat() * (5.f - tiltSink)) + (m_SinkingAngleX * tiltSink)) / 5.f);
     201           0 :                     entity_pos_t RotZ = entity_pos_t::FromFloat(((m_InitialZRotation.ToFloat() * (3.f - tilt_time)) + (m_SinkingAngleZ * tilt_time)) / 3.f);
     202           0 :                     cmpPosition->SetXZRotation(RotX,RotZ);
     203             : 
     204           0 :                     depth = m_SinkRate * (exp(t - 1.f) - 0.54881163609f) + (m_SinkAccel * exp(t - 4.f) - 0.01831563888f);
     205           0 :                     if (depth < 0.f)
     206           0 :                         depth = 0.f;
     207             :                 }
     208             : 
     209           0 :                 cmpPosition->SetHeightOffset(entity_pos_t::FromFloat(-depth));
     210             : 
     211           0 :                 if (depth > m_TotalSinkDepth)
     212           0 :                     GetSimContext().GetComponentManager().DestroyComponentsSoon(GetEntityId());
     213             :             }
     214             : 
     215             :             break;
     216             :         }
     217             :         }
     218           0 :     }
     219             : };
     220             : 
     221         230 : REGISTER_COMPONENT_TYPE(Decay)

Generated by: LCOV version 1.13