// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdpch.h" ///////////// // INCLUDE // ///////////// #include "animation_state.h" #include "debug_client.h" #include "client_cfg.h" // misc #include "nel/misc/debug.h" // Georges #include "nel/georges/u_form_elm.h" #ifdef DEBUG_NEW #define new DEBUG_NEW #endif /////////// // USING // /////////// using namespace NLGEORGES; using namespace std; //////////// // METHOD // //////////// //----------------------------------------------- //----------------------------------------------- EGSPD::CPeople::TPeople CAnimationState::_FallBackToDefaultRace[2][CAnimationStateSheet::StaticStateCount]; bool CAnimationState::_FallBackToDefaultRaceInited= false; //----------------------------------------------- //----------------------------------------------- // Yoyo: ugly: because of players, we have to disable racial animation if they don't want them void CAnimationState::initFallBackToDefaultRace() { _FallBackToDefaultRaceInited= true; // Default for(uint i=0;i &traversedAnims, bool rootCall) { if(idAnim<0 || idAnim>=(sint)_Animations.size()) return; // if this animation has already been traversed, then don't need to recurse (already done) if(traversedAnims[idAnim]) return; // mark this anim as traversed (if not from rootcall) if(!rootCall) traversedAnims[idAnim]= true; // mark, and traverse recurs CAnimation &anim= _Animations[idAnim]; const std::vector &nextAnim= anim.getNextAnimList(); for(uint i=0;iAnimations.size()); for (i = 0; i < _Animations.size(); ++i) { _Animations[i].init(&sheet->Animations[i], animationSet); } // **** build list of root animation // flag each animation to know if it is reachable through "NextAnim" system std::vector traversedAnims; traversedAnims.clear(); traversedAnims.resize(_Animations.size(), false); for (i = 0; i < _Animations.size(); ++i) { recursMarkTraverseNext(i, traversedAnims, true); } // Build the final list of root animaions _RootAnimations.clear(); for (i = 0; i < _Animations.size(); ++i) { // root animations are not reachable. NB: also consider always the 0th animation as a root one if(i==0 || !traversedAnims[i]) _RootAnimations.push_back(i); } }// init// //----------------------------------------------- // buildAnimFilter //----------------------------------------------- void CAnimationState::buildAnimFilter(vector &filteredRootAnimList, vector &animFilterStates, uint32 jobSpecialisation, EGSPD::CPeople::TPeople race, GSGENDER::EGender gender) const { uint i; // If the user doesn't want Racial Animation, force player race according to old animations // Plus force old race animation for some anim/race/gender case if(!ClientCfg.EnableRacialAnimation || isOldRaceAnimationForced(race, gender) ) { // avoid problem with Gender= Neutral (beast). uint uGender= gender; uint uState= state(); NLMISC::clamp(uGender, 0U, 1U); NLMISC::clamp(uState, 0U, uint(CAnimationStateSheet::StaticStateCount-1)); // According to the state, and the sex of user, the choice may differ race= _FallBackToDefaultRace[uGender][uState]; } // Mark each animation if ok or not animFilterStates.resize(_Animations.size()); for(i=0;i<_Animations.size();i++) { animFilterStates[i]= _Animations[i].filterOk(jobSpecialisation, race); } // build list of filtered root animation filteredRootAnimList.clear(); for(i=0;i<_RootAnimations.size();i++) { uint idAnim= _RootAnimations[i]; // if this root animation is filtered, add it if(idAnim<_Animations.size() && animFilterStates[idAnim]) filteredRootAnimList.push_back(idAnim); } }// buildAnimFilter // //----------------------------------------------- // chooseAnimationIndex : // Choose a valid animation index in state. // (there are more chances for the first animation) // \warning This method does not check if _Animations is empty. //----------------------------------------------- uint CAnimationState::chooseAnimationIndex(const vector &filteredRootAnimList) const { // error, none match filter, fallback to the first one if(filteredRootAnimList.empty()) return 0; // 1 chance by 2 to choose the first animation if(filteredRootAnimList.size()==1 || (rand()%2)) return filteredRootAnimList[0]; else return filteredRootAnimList[rand()%filteredRootAnimList.size()]; }// chooseAnimationIndex // //----------------------------------------------- // chooseAnim : // Choose an animation in the list. // \return TAnimId : Id of the animation. //----------------------------------------------- CAnimation::TAnimId CAnimationState::chooseAnim(uint32 jobSpecialisation, EGSPD::CPeople::TPeople race, GSGENDER::EGender gender, double angToDest, CAnimation::TAnimId currentAnimIndex) const { // If there is not animation return CAnimation::UnknownAnim. if(_Animations.empty()) return CAnimation::UnknownAnim; // The animation to choose is not a rotation, no need to choose according to an angle. if(angToDest == 0.0) { // static to avoid reallocation each time static vector filteredRootAnimList; static vector animFilterStates; buildAnimFilter(filteredRootAnimList, animFilterStates, jobSpecialisation, race, gender); // Do not check if there is a sequence if(currentAnimIndex == CAnimation::UnknownAnim) return (CAnimation::TAnimId) chooseAnimationIndex(filteredRootAnimList); // Check the current animation index given is valid. if((uint)currentAnimIndex >= _Animations.size()) { nlwarning("CAnimationState:chooseAnim: currentAnimIndex(%d) >= size", currentAnimIndex); return (CAnimation::TAnimId) chooseAnimationIndex(filteredRootAnimList); } // Get the next animation to play from the animation given. sint8 nextAnim = _Animations[(uint)currentAnimIndex].getNextAnim(animFilterStates); // Check the is a next animation defined if(nextAnim >= 0) { // Check the next animation is valid if((uint)nextAnim<_Animations.size()) return (CAnimation::TAnimId) nextAnim; else { nlwarning("CAnimationState:chooseAnim: next animation index(%d) is invalid", nextAnim); return (CAnimation::TAnimId) chooseAnimationIndex(filteredRootAnimList); } } else return (CAnimation::TAnimId) chooseAnimationIndex(filteredRootAnimList); } // This is a rotation. else { uint i; uint best = 0; const uint count = (uint)_Animations.size (); double bestAng = 1000.0; // Big value to be > to the first element. for (i=0; i= _Animations.size()) return NULL; return &_Animations[index]; }// getAnimationByIndex