308 lines
10 KiB
C++
308 lines
10 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#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"
|
|
|
|
///////////
|
|
// 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<CAnimationStateSheet::StaticStateCount;i++)
|
|
{
|
|
_FallBackToDefaultRace[0][i]= EGSPD::CPeople::Matis;
|
|
_FallBackToDefaultRace[1][i]= EGSPD::CPeople::Matis;
|
|
}
|
|
|
|
// Special for Sit mode. Use fyros for female, and tryker for male
|
|
_FallBackToDefaultRace[0][CAnimationStateSheet::SitMode]= EGSPD::CPeople::Tryker;
|
|
_FallBackToDefaultRace[1][CAnimationStateSheet::SitMode]= EGSPD::CPeople::Fyros;
|
|
_FallBackToDefaultRace[0][CAnimationStateSheet::SitEnd]= EGSPD::CPeople::Tryker;
|
|
_FallBackToDefaultRace[1][CAnimationStateSheet::SitEnd]= EGSPD::CPeople::Fyros;
|
|
_FallBackToDefaultRace[0][CAnimationStateSheet::Idle]= EGSPD::CPeople::Tryker;
|
|
_FallBackToDefaultRace[1][CAnimationStateSheet::Idle]= EGSPD::CPeople::Fyros;
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
//-----------------------------------------------
|
|
// Yoyo: ugly: because of players, we have to disable some racial animation since they don't want them AT ALL
|
|
bool CAnimationState::isOldRaceAnimationForced(EGSPD::CPeople::TPeople race, GSGENDER::EGender gender) const
|
|
{
|
|
TAnimStateId animState= state();
|
|
// consider Idle as sit.... This is false if the animation MODE is not SIT, but don't care,
|
|
// since idle is never a run/walk state....
|
|
bool isSit= animState==CAnimationStateSheet::SitMode ||
|
|
animState==CAnimationStateSheet::SitEnd ||
|
|
animState==CAnimationStateSheet::Idle;
|
|
// disable Tryker male run/walk
|
|
if(race==EGSPD::CPeople::Tryker && gender==GSGENDER::male && !isSit)
|
|
return true;
|
|
// disable Fyros male Sit
|
|
if(race==EGSPD::CPeople::Fyros && gender==GSGENDER::male && isSit)
|
|
return true;
|
|
// disable Matis (male/female) Sit
|
|
if(race==EGSPD::CPeople::Matis && isSit)
|
|
return true;
|
|
|
|
// otherwise, allow every other racial animation
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------
|
|
// CAnimationState :
|
|
// Constructor.
|
|
//-----------------------------------------------
|
|
CAnimationState::CAnimationState()
|
|
{
|
|
_Sheet = NULL;
|
|
}// CAnimationState //
|
|
|
|
|
|
//-----------------------------------------------
|
|
// recursMarkTraverseNext
|
|
//-----------------------------------------------
|
|
void CAnimationState::recursMarkTraverseNext(sint idAnim, std::vector<bool> &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<sint8> &nextAnim= anim.getNextAnimList();
|
|
for(uint i=0;i<nextAnim.size();i++)
|
|
recursMarkTraverseNext(nextAnim[i], traversedAnims, false);
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// init
|
|
//-----------------------------------------------
|
|
void CAnimationState::init(CAnimationStateSheet *sheet, NL3D::UAnimationSet *animationSet)
|
|
{
|
|
uint32 i;
|
|
|
|
_Sheet = sheet;
|
|
|
|
// **** init if needed _FallBackToDefaultRace
|
|
if(!_FallBackToDefaultRaceInited)
|
|
initFallBackToDefaultRace();
|
|
|
|
// **** build animation list
|
|
_Animations.resize(sheet->Animations.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<bool> 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<uint> &filteredRootAnimList, vector<bool> &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<uint> &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<uint> filteredRootAnimList;
|
|
static vector<bool> 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<count; i++)
|
|
{
|
|
const CAnimation &anim = _Animations[i];
|
|
double angTmp;
|
|
if(anim.virtualRot() != 0.0)
|
|
angTmp = fabs(fabs(angToDest)-anim.virtualRot());
|
|
else
|
|
angTmp = fabs(fabs(angToDest)-anim.getRot());
|
|
if(angTmp < bestAng)
|
|
{
|
|
bestAng = angTmp;
|
|
best = i;
|
|
}
|
|
}
|
|
|
|
// Return the id for the closest animation for this angle.
|
|
return (CAnimation::TAnimId)best;
|
|
}
|
|
}// chooseAnim //
|
|
|
|
//-----------------------------------------------
|
|
// getAnimationByIndex
|
|
//-----------------------------------------------
|
|
CAnimation *CAnimationState::getAnimationByIndex(uint index)
|
|
{
|
|
if (index >= _Animations.size()) return NULL;
|
|
return &_Animations[index];
|
|
}// getAnimationByIndex
|