1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program 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 Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
25 #include "animation_state.h"
26 #include "debug_client.h"
27 #include "client_cfg.h"
29 #include "nel/misc/debug.h"
31 #include "nel/georges/u_form_elm.h"
41 using namespace NLGEORGES
;
48 //-----------------------------------------------
49 //-----------------------------------------------
50 EGSPD::CPeople::TPeople
CAnimationState::_FallBackToDefaultRace
[2][CAnimationStateSheet::StaticStateCount
];
51 bool CAnimationState::_FallBackToDefaultRaceInited
= false;
53 //-----------------------------------------------
54 //-----------------------------------------------
55 // Yoyo: ugly: because of players, we have to disable racial animation if they don't want them
56 void CAnimationState::initFallBackToDefaultRace()
58 _FallBackToDefaultRaceInited
= true;
61 for(uint i
=0;i
<CAnimationStateSheet::StaticStateCount
;i
++)
63 _FallBackToDefaultRace
[0][i
]= EGSPD::CPeople::Matis
;
64 _FallBackToDefaultRace
[1][i
]= EGSPD::CPeople::Matis
;
67 // Special for Sit mode. Use fyros for female, and tryker for male
68 _FallBackToDefaultRace
[0][CAnimationStateSheet::SitMode
]= EGSPD::CPeople::Tryker
;
69 _FallBackToDefaultRace
[1][CAnimationStateSheet::SitMode
]= EGSPD::CPeople::Fyros
;
70 _FallBackToDefaultRace
[0][CAnimationStateSheet::SitEnd
]= EGSPD::CPeople::Tryker
;
71 _FallBackToDefaultRace
[1][CAnimationStateSheet::SitEnd
]= EGSPD::CPeople::Fyros
;
72 _FallBackToDefaultRace
[0][CAnimationStateSheet::Idle
]= EGSPD::CPeople::Tryker
;
73 _FallBackToDefaultRace
[1][CAnimationStateSheet::Idle
]= EGSPD::CPeople::Fyros
;
76 //-----------------------------------------------
77 //-----------------------------------------------
78 // Yoyo: ugly: because of players, we have to disable some racial animation since they don't want them AT ALL
79 bool CAnimationState::isOldRaceAnimationForced(EGSPD::CPeople::TPeople race
, GSGENDER::EGender gender
) const
81 TAnimStateId animState
= state();
82 // consider Idle as sit.... This is false if the animation MODE is not SIT, but don't care,
83 // since idle is never a run/walk state....
84 bool isSit
= animState
==CAnimationStateSheet::SitMode
||
85 animState
==CAnimationStateSheet::SitEnd
||
86 animState
==CAnimationStateSheet::Idle
;
87 // disable Tryker male run/walk
88 if(race
==EGSPD::CPeople::Tryker
&& gender
==GSGENDER::male
&& !isSit
)
90 // disable Fyros male Sit
91 if(race
==EGSPD::CPeople::Fyros
&& gender
==GSGENDER::male
&& isSit
)
93 // disable Matis (male/female) Sit
94 if(race
==EGSPD::CPeople::Matis
&& isSit
)
97 // otherwise, allow every other racial animation
102 //-----------------------------------------------
105 //-----------------------------------------------
106 CAnimationState::CAnimationState()
109 }// CAnimationState //
112 //-----------------------------------------------
113 // recursMarkTraverseNext
114 //-----------------------------------------------
115 void CAnimationState::recursMarkTraverseNext(sint idAnim
, std::vector
<bool> &traversedAnims
, bool rootCall
)
117 if(idAnim
<0 || idAnim
>=(sint
)_Animations
.size())
120 // if this animation has already been traversed, then don't need to recurse (already done)
121 if(traversedAnims
[idAnim
])
124 // mark this anim as traversed (if not from rootcall)
126 traversedAnims
[idAnim
]= true;
128 // mark, and traverse recurs
129 CAnimation
&anim
= _Animations
[idAnim
];
130 const std::vector
<sint8
> &nextAnim
= anim
.getNextAnimList();
131 for(uint i
=0;i
<nextAnim
.size();i
++)
132 recursMarkTraverseNext(nextAnim
[i
], traversedAnims
, false);
135 //-----------------------------------------------
137 //-----------------------------------------------
138 void CAnimationState::init(CAnimationStateSheet
*sheet
, NL3D::UAnimationSet
*animationSet
)
144 // **** init if needed _FallBackToDefaultRace
145 if(!_FallBackToDefaultRaceInited
)
146 initFallBackToDefaultRace();
148 // **** build animation list
149 _Animations
.resize(sheet
->Animations
.size());
150 for (i
= 0; i
< _Animations
.size(); ++i
)
152 _Animations
[i
].init(&sheet
->Animations
[i
], animationSet
);
155 // **** build list of root animation
156 // flag each animation to know if it is reachable through "NextAnim" system
157 std::vector
<bool> traversedAnims
;
158 traversedAnims
.clear();
159 traversedAnims
.resize(_Animations
.size(), false);
160 for (i
= 0; i
< _Animations
.size(); ++i
)
162 recursMarkTraverseNext(i
, traversedAnims
, true);
164 // Build the final list of root animaions
165 _RootAnimations
.clear();
166 for (i
= 0; i
< _Animations
.size(); ++i
)
168 // root animations are not reachable. NB: also consider always the 0th animation as a root one
169 if(i
==0 || !traversedAnims
[i
])
170 _RootAnimations
.push_back(i
);
175 //-----------------------------------------------
177 //-----------------------------------------------
178 void CAnimationState::buildAnimFilter(vector
<uint
> &filteredRootAnimList
, vector
<bool> &animFilterStates
, uint32 jobSpecialisation
, EGSPD::CPeople::TPeople race
, GSGENDER::EGender gender
) const
182 // If the user doesn't want Racial Animation, force player race according to old animations
183 // Plus force old race animation for some anim/race/gender case
184 if(!ClientCfg
.EnableRacialAnimation
|| isOldRaceAnimationForced(race
, gender
) )
186 // avoid problem with Gender= Neutral (beast).
187 uint uGender
= gender
;
188 uint uState
= state();
189 NLMISC::clamp(uGender
, 0U, 1U);
190 NLMISC::clamp(uState
, 0U, uint(CAnimationStateSheet::StaticStateCount
-1));
191 // According to the state, and the sex of user, the choice may differ
192 race
= _FallBackToDefaultRace
[uGender
][uState
];
195 // Mark each animation if ok or not
196 animFilterStates
.resize(_Animations
.size());
197 for(i
=0;i
<_Animations
.size();i
++)
199 animFilterStates
[i
]= _Animations
[i
].filterOk(jobSpecialisation
, race
);
202 // build list of filtered root animation
203 filteredRootAnimList
.clear();
204 for(i
=0;i
<_RootAnimations
.size();i
++)
206 uint idAnim
= _RootAnimations
[i
];
207 // if this root animation is filtered, add it
208 if(idAnim
<_Animations
.size() && animFilterStates
[idAnim
])
209 filteredRootAnimList
.push_back(idAnim
);
212 }// buildAnimFilter //
214 //-----------------------------------------------
215 // chooseAnimationIndex :
216 // Choose a valid animation index in state.
217 // (there are more chances for the first animation)
218 // \warning This method does not check if _Animations is empty.
219 //-----------------------------------------------
220 uint
CAnimationState::chooseAnimationIndex(const vector
<uint
> &filteredRootAnimList
) const
222 // error, none match filter, fallback to the first one
223 if(filteredRootAnimList
.empty())
226 // 1 chance by 2 to choose the first animation
227 if(filteredRootAnimList
.size()==1 || (rand()%2))
228 return filteredRootAnimList
[0];
230 return filteredRootAnimList
[rand()%filteredRootAnimList
.size()];
231 }// chooseAnimationIndex //
233 //-----------------------------------------------
235 // Choose an animation in the list.
236 // \return TAnimId : Id of the animation.
237 //-----------------------------------------------
238 CAnimation::TAnimId
CAnimationState::chooseAnim(uint32 jobSpecialisation
, EGSPD::CPeople::TPeople race
, GSGENDER::EGender gender
, double angToDest
, CAnimation::TAnimId currentAnimIndex
) const
240 // If there is not animation return CAnimation::UnknownAnim.
241 if(_Animations
.empty())
242 return CAnimation::UnknownAnim
;
244 // The animation to choose is not a rotation, no need to choose according to an angle.
247 // static to avoid reallocation each time
248 static vector
<uint
> filteredRootAnimList
;
249 static vector
<bool> animFilterStates
;
250 buildAnimFilter(filteredRootAnimList
, animFilterStates
, jobSpecialisation
, race
, gender
);
252 // Do not check if there is a sequence
253 if(currentAnimIndex
== CAnimation::UnknownAnim
)
254 return (CAnimation::TAnimId
) chooseAnimationIndex(filteredRootAnimList
);
255 // Check the current animation index given is valid.
256 if((uint
)currentAnimIndex
>= _Animations
.size())
258 nlwarning("CAnimationState:chooseAnim: currentAnimIndex(%d) >= size", currentAnimIndex
);
259 return (CAnimation::TAnimId
) chooseAnimationIndex(filteredRootAnimList
);
261 // Get the next animation to play from the animation given.
262 sint8 nextAnim
= _Animations
[(uint
)currentAnimIndex
].getNextAnim(animFilterStates
);
263 // Check the is a next animation defined
266 // Check the next animation is valid
267 if((uint
)nextAnim
<_Animations
.size())
268 return (CAnimation::TAnimId
) nextAnim
;
271 nlwarning("CAnimationState:chooseAnim: next animation index(%d) is invalid", nextAnim
);
272 return (CAnimation::TAnimId
) chooseAnimationIndex(filteredRootAnimList
);
276 return (CAnimation::TAnimId
) chooseAnimationIndex(filteredRootAnimList
);
278 // This is a rotation.
283 const uint count
= (uint
)_Animations
.size ();
284 double bestAng
= 1000.0; // Big value to be > to the first element.
285 for (i
=0; i
<count
; i
++)
287 const CAnimation
&anim
= _Animations
[i
];
289 if(anim
.virtualRot() != 0.0)
290 angTmp
= fabs(fabs(angToDest
)-anim
.virtualRot());
292 angTmp
= fabs(fabs(angToDest
)-anim
.getRot());
300 // Return the id for the closest animation for this angle.
301 return (CAnimation::TAnimId
)best
;
305 //-----------------------------------------------
306 // getAnimationByIndex
307 //-----------------------------------------------
308 CAnimation
*CAnimationState::getAnimationByIndex(uint index
)
310 if (index
>= _Animations
.size()) return NULL
;
311 return &_Animations
[index
];
312 }// getAnimationByIndex