Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / client / src / animation_state.cpp
blob82aa27a54fd093e4fcd821ca3e7ac469ddbb54bc
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
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.
8 //
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/>.
19 #include "stdpch.h"
21 /////////////
22 // INCLUDE //
23 /////////////
25 #include "animation_state.h"
26 #include "debug_client.h"
27 #include "client_cfg.h"
28 // misc
29 #include "nel/misc/debug.h"
30 // Georges
31 #include "nel/georges/u_form_elm.h"
33 #ifdef DEBUG_NEW
34 #define new DEBUG_NEW
35 #endif
37 ///////////
38 // USING //
39 ///////////
41 using namespace NLGEORGES;
42 using namespace std;
44 ////////////
45 // METHOD //
46 ////////////
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;
60 // Default
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)
89 return true;
90 // disable Fyros male Sit
91 if(race==EGSPD::CPeople::Fyros && gender==GSGENDER::male && isSit)
92 return true;
93 // disable Matis (male/female) Sit
94 if(race==EGSPD::CPeople::Matis && isSit)
95 return true;
97 // otherwise, allow every other racial animation
98 return false;
102 //-----------------------------------------------
103 // CAnimationState :
104 // Constructor.
105 //-----------------------------------------------
106 CAnimationState::CAnimationState()
108 _Sheet = NULL;
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())
118 return;
120 // if this animation has already been traversed, then don't need to recurse (already done)
121 if(traversedAnims[idAnim])
122 return;
124 // mark this anim as traversed (if not from rootcall)
125 if(!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 //-----------------------------------------------
136 // init
137 //-----------------------------------------------
138 void CAnimationState::init(CAnimationStateSheet *sheet, NL3D::UAnimationSet *animationSet)
140 uint32 i;
142 _Sheet = sheet;
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);
173 }// init//
175 //-----------------------------------------------
176 // buildAnimFilter
177 //-----------------------------------------------
178 void CAnimationState::buildAnimFilter(vector<uint> &filteredRootAnimList, vector<bool> &animFilterStates, uint32 jobSpecialisation, EGSPD::CPeople::TPeople race, GSGENDER::EGender gender) const
180 uint i;
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())
224 return 0;
226 // 1 chance by 2 to choose the first animation
227 if(filteredRootAnimList.size()==1 || (rand()%2))
228 return filteredRootAnimList[0];
229 else
230 return filteredRootAnimList[rand()%filteredRootAnimList.size()];
231 }// chooseAnimationIndex //
233 //-----------------------------------------------
234 // chooseAnim :
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.
245 if(angToDest == 0.0)
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
264 if(nextAnim >= 0)
266 // Check the next animation is valid
267 if((uint)nextAnim<_Animations.size())
268 return (CAnimation::TAnimId) nextAnim;
269 else
271 nlwarning("CAnimationState:chooseAnim: next animation index(%d) is invalid", nextAnim);
272 return (CAnimation::TAnimId) chooseAnimationIndex(filteredRootAnimList);
275 else
276 return (CAnimation::TAnimId) chooseAnimationIndex(filteredRootAnimList);
278 // This is a rotation.
279 else
281 uint i;
282 uint best = 0;
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];
288 double angTmp;
289 if(anim.virtualRot() != 0.0)
290 angTmp = fabs(fabs(angToDest)-anim.virtualRot());
291 else
292 angTmp = fabs(fabs(angToDest)-anim.getRot());
293 if(angTmp < bestAng)
295 bestAng = angTmp;
296 best = i;
300 // Return the id for the closest animation for this angle.
301 return (CAnimation::TAnimId)best;
303 }// chooseAnim //
305 //-----------------------------------------------
306 // getAnimationByIndex
307 //-----------------------------------------------
308 CAnimation *CAnimationState::getAnimationByIndex(uint index)
310 if (index >= _Animations.size()) return NULL;
311 return &_Animations[index];
312 }// getAnimationByIndex