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 116 : static void ClassInit(CComponentManager& UNUSED(componentManager))
51 : {
52 116 : }
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 116 : static std::string GetSchema()
73 : {
74 : 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 116 : "</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 : {
134 0 : PROFILE("Decay::Interpolate");
135 :
136 0 : if (!m_Active)
137 0 : 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 0 : 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 0 : break;
216 : }
217 : }
218 0 : }
219 : };
220 :
221 119 : REGISTER_COMPONENT_TYPE(Decay)
|