Line data Source code
1 : /* Copyright (C) 2021 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 "UnitAnimation.h"
21 :
22 : #include "graphics/Model.h"
23 : #include "graphics/ObjectEntry.h"
24 : #include "graphics/SkeletonAnim.h"
25 : #include "graphics/SkeletonAnimDef.h"
26 : #include "graphics/Unit.h"
27 : #include "lib/rand.h"
28 : #include "ps/CStr.h"
29 : #include "ps/Game.h"
30 : #include "simulation2/Simulation2.h"
31 : #include "simulation2/components/ICmpSoundManager.h"
32 :
33 : #include <cstdlib>
34 : #include <cmath>
35 :
36 : // Randomly modify the speed, so that units won't stay perfectly
37 : // synchronised if they're playing animations of the same length
38 0 : static float DesyncSpeed(float speed, float desync)
39 : {
40 0 : if (desync == 0.0f)
41 0 : return speed;
42 :
43 0 : return speed * (1.f - desync + 2.f*desync*(rand(0, 256)/255.f));
44 : }
45 :
46 0 : CUnitAnimation::CUnitAnimation(entity_id_t ent, CModel* model, CObjectEntry* object)
47 : : m_Entity(ent), m_State("idle"), m_Looping(true),
48 0 : m_Speed(1.f), m_SyncRepeatTime(0.f), m_OriginalSpeed(1.f), m_Desync(0.f)
49 : {
50 0 : ReloadUnit(model, object);
51 0 : }
52 :
53 0 : void CUnitAnimation::SetEntityID(entity_id_t ent)
54 : {
55 0 : m_Entity = ent;
56 0 : }
57 :
58 0 : void CUnitAnimation::AddModel(CModel* model, const CObjectEntry* object)
59 : {
60 : SModelAnimState state;
61 :
62 0 : state.model = model;
63 0 : state.object = object;
64 0 : state.anim = object->GetRandomAnimation(m_State, m_AnimationID);
65 0 : state.time = 0.f;
66 0 : state.pastLoadPos = false;
67 0 : state.pastActionPos = false;
68 0 : state.pastSoundPos = false;
69 :
70 0 : ENSURE(state.anim != NULL); // there must always be an idle animation
71 :
72 0 : m_AnimStates.push_back(state);
73 :
74 0 : model->SetAnimation(state.anim, !m_Looping);
75 :
76 : // Detect if this unit has any non-static animations
77 0 : for (CSkeletonAnim* anim : object->GetAnimations(m_State))
78 0 : if (anim->m_AnimDef != NULL)
79 0 : m_AnimStatesAreStatic = false;
80 :
81 : // Recursively add all props
82 0 : const std::vector<CModel::Prop>& props = model->GetProps();
83 0 : for (const CModel::Prop& prop : props)
84 : {
85 0 : CModel* propModel = prop.m_Model->ToCModel();
86 0 : if (propModel)
87 0 : AddModel(propModel, prop.m_ObjectEntry);
88 : }
89 0 : }
90 :
91 0 : void CUnitAnimation::ReloadAnimation()
92 : {
93 0 : ReloadUnit(m_Model, m_Object);
94 0 : }
95 :
96 0 : void CUnitAnimation::ReloadUnit(CModel* model, const CObjectEntry* object)
97 : {
98 0 : m_Model = model;
99 0 : m_Object = object;
100 :
101 0 : m_AnimStates.clear();
102 0 : m_AnimStatesAreStatic = true;
103 0 : PickAnimationID();
104 0 : AddModel(m_Model, m_Object);
105 0 : }
106 :
107 0 : void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed, float desync, const CStrW& actionSound)
108 : {
109 0 : m_Looping = !once;
110 0 : m_OriginalSpeed = speed;
111 0 : m_Desync = desync;
112 0 : m_ActionSound = actionSound;
113 :
114 0 : m_Speed = DesyncSpeed(m_OriginalSpeed, m_Desync);
115 0 : m_SyncRepeatTime = 0.f;
116 :
117 0 : if (name != m_State)
118 : {
119 0 : m_State = name;
120 0 : ReloadAnimation();
121 : }
122 0 : }
123 :
124 0 : void CUnitAnimation::SetAnimationSyncRepeat(float repeatTime)
125 : {
126 0 : m_SyncRepeatTime = repeatTime;
127 0 : }
128 :
129 0 : void CUnitAnimation::SetAnimationSyncOffset(float actionTime)
130 : {
131 0 : if (m_AnimStatesAreStatic)
132 0 : return;
133 :
134 : // Update all the synced prop models to each coincide with actionTime
135 0 : for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it)
136 : {
137 0 : CSkeletonAnimDef* animDef = it->anim->m_AnimDef;
138 0 : if (animDef == NULL)
139 0 : continue; // ignore static animations
140 :
141 0 : float duration = animDef->GetDuration();
142 :
143 0 : float actionPos = it->anim->m_ActionPos;
144 0 : bool hasActionPos = (actionPos != -1.f);
145 :
146 0 : if (!hasActionPos)
147 0 : continue;
148 :
149 0 : float speed = duration / m_SyncRepeatTime;
150 :
151 : // Need to offset so that start+actionTime*speed = actionPos
152 0 : float start = actionPos - actionTime*speed;
153 : // Wrap it so that it's within the animation
154 0 : start = fmodf(start, duration);
155 0 : if (start < 0)
156 0 : start += duration;
157 :
158 0 : it->time = start;
159 : }
160 : }
161 :
162 0 : void CUnitAnimation::Update(float time)
163 : {
164 0 : if (m_AnimStatesAreStatic)
165 0 : return;
166 :
167 0 : bool shouldPlaySound = false;
168 :
169 : // Advance all of the prop models independently
170 0 : for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it)
171 : {
172 0 : CSkeletonAnimDef* animDef = it->anim->m_AnimDef;
173 0 : if (!animDef)
174 0 : continue; // ignore static animations
175 :
176 0 : float duration = animDef->GetDuration();
177 0 : float actionPos = it->anim->m_ActionPos;
178 0 : float loadPos = it->anim->m_ActionPos2;
179 : // SoundPos is either the m_SoundPos when available, or m_ActionPos. Else no sound is played.
180 : // TODO: Should they be totally independent?
181 0 : float soundPos = it->anim->m_SoundPos != -1.f ? it->anim->m_SoundPos : actionPos;
182 0 : bool hasActionPos = (actionPos != -1.f);
183 0 : bool hasLoadPos = (loadPos != -1.f);
184 0 : bool hasSoundPos = (soundPos != -1.f);
185 :
186 : // Find the current animation speed
187 : float speed;
188 0 : if (m_SyncRepeatTime && hasActionPos)
189 0 : speed = duration / m_SyncRepeatTime;
190 : else
191 0 : speed = m_Speed * it->anim->m_Speed;
192 :
193 : // Convert from real time to scaled animation time
194 0 : float advance = time * speed;
195 0 : float nextPos = it->time + advance;
196 :
197 : // If we're going to advance past the load point in this update, then load the ammo
198 0 : if (hasLoadPos && !it->pastLoadPos && nextPos >= loadPos)
199 : {
200 0 : it->model->ShowAmmoProp();
201 0 : it->pastLoadPos = true;
202 : }
203 :
204 : // If we're going to advance past the action point in this update, then perform the action.
205 0 : if (hasActionPos && !it->pastActionPos && nextPos >= actionPos)
206 : {
207 0 : if (hasLoadPos)
208 0 : it->model->HideAmmoProp();
209 :
210 0 : it->pastActionPos = true;
211 : }
212 :
213 : // If we're going to advance past the sound point in this update, then play the sound.
214 0 : if (hasSoundPos && !it->pastSoundPos && nextPos >= soundPos && !m_ActionSound.empty())
215 : {
216 0 : shouldPlaySound = true;
217 0 : it->pastSoundPos = true;
218 : }
219 :
220 0 : if (nextPos < duration)
221 : {
222 : // If we're still within the current animation, then simply update it
223 0 : it->time += advance;
224 0 : it->model->UpdateTo(it->time);
225 : }
226 0 : else if (m_Looping)
227 : {
228 : // If we've finished the current animation and want to loop...
229 :
230 : // Wrap the timer around
231 0 : it->time = std::fmod(nextPos, duration);
232 :
233 : // Pick a new random animation
234 : CSkeletonAnim* anim;
235 0 : if (it->model == m_Model)
236 : {
237 : // we're handling the root model
238 : // choose animations from the complete state
239 0 : CStr oldID = m_AnimationID;
240 0 : PickAnimationID();
241 0 : anim = it->object->GetRandomAnimation(m_State, m_AnimationID);
242 0 : if (oldID != m_AnimationID)
243 0 : for (SModelAnimState animState : m_AnimStates)
244 0 : if (animState.model != m_Model)
245 0 : animState.model->SetAnimation(animState.object->GetRandomAnimation(m_State, m_AnimationID));
246 : }
247 : else
248 : // choose animations that match the root
249 0 : anim = it->object->GetRandomAnimation(m_State, m_AnimationID);
250 :
251 0 : if (anim != it->anim)
252 : {
253 0 : it->anim = anim;
254 0 : it->model->SetAnimation(anim, !m_Looping);
255 : }
256 :
257 0 : it->pastActionPos = false;
258 0 : it->pastLoadPos = false;
259 0 : it->pastSoundPos = false;
260 :
261 0 : it->model->UpdateTo(it->time);
262 : }
263 : else
264 : {
265 : // If we've finished the current animation and don't want to loop...
266 :
267 : // Update to very nearly the end of the last frame (but not quite the end else we'll wrap around when skinning)
268 0 : float nearlyEnd = duration - 1.f;
269 0 : if (std::abs(it->time - nearlyEnd) > 1.f)
270 : {
271 0 : it->time = nearlyEnd;
272 0 : it->model->UpdateTo(it->time);
273 : }
274 : }
275 : }
276 :
277 0 : if (!shouldPlaySound)
278 0 : return;
279 :
280 0 : CmpPtr<ICmpSoundManager> cmpSoundManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
281 0 : if (cmpSoundManager)
282 0 : cmpSoundManager->PlaySoundGroup(m_ActionSound, m_Entity);
283 : }
284 :
285 0 : void CUnitAnimation::PickAnimationID()
286 : {
287 0 : m_AnimationID = m_Object->GetRandomAnimation(m_State)->m_ID;
288 3 : }
|