khanat-opennel-code/code/ryzom/tools/make_anim_melee_impact/main.cpp

478 lines
No EOL
13 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/>.
// ***************************************************************************
/*
This small tool was made for Graphist, to edit automatically *.animation_set sheets, so anims
get correct MeleeImpactDelay data
It uses an anim.txt file that was generated by inserting code (give at end of this file)
in the client.
This tool had to be "used one time only". hence its crappiest usage :)
*/
#include "nel/misc/path.h"
#include "nel/misc/file.h"
#include "nel/misc/common.h"
#include "nel/misc/algo.h"
using namespace std;
using namespace NLMISC;
// ***************************************************************************
// Config
bool ReplaceExistingMeleeImpactDelay= true;
float MeleeImpactTimeFactor= 0.35f;
// Inited in main()
map<string, string> StateNameToStateCode;
// ***************************************************************************
class CAnimCombatState
{
public:
void build(const string & line);
// A1, A2 etc...
string StateCode;
// Mean Animation Time of all sub animations of this state
float MeanAnimTime;
public:
bool operator<(const CAnimCombatState &o) const {return StateCode<o.StateCode;}
};
class CAnimCombatSet
{
public:
// name of the anim set
string Name;
// set of CAnimCombatState
set<CAnimCombatState> States;
public:
bool operator<(const CAnimCombatSet &o) const {return Name<o.Name;}
};
// ***************************************************************************
void CAnimCombatState::build(const string &line)
{
StateCode= line.substr(4, 2);
string time= line.substr(10, 5);
NLMISC::fromString(time, MeanAnimTime);
}
// ***************************************************************************
void makeAnimMeleeImpact(const std::string &animSetFile, const set<CAnimCombatSet> &combatAnimSets)
{
// look if this animSetFile is in the combat list to patch
string shortName= NLMISC::toLower(CFile::getFilenameWithoutExtension(animSetFile));
CAnimCombatSet key;
key.Name= shortName;
set<CAnimCombatSet>::const_iterator it= combatAnimSets.find(key);
if(it == combatAnimSets.end())
return;
const CAnimCombatSet &currentCombatAnimSet= *it;
InfoLog->displayRawNL("patching %s", animSetFile.c_str());
// *** Read the animset file.
CIFile iFile;
iFile.open(animSetFile, true);
// Read all text
static vector<string> animSetText;
animSetText.clear();
while(!iFile.eof())
{
char tmp[50000];
iFile.getline(tmp, 50000);
animSetText.push_back(tmp);
}
iFile.close();
bool someChangeDone= false;
// *** Parse the animSet
{
// For each line of the animSet
sint structLevel= 0;
sint meleeImpactDelayLine= -1;
string currentStateName;
for(uint j=0;j<animSetText.size();j++)
{
string line= animSetText[j];
string lineLwr= toLower(line);
// Find <LOG> TAg? => stop
if(line.find("<LOG>")!=string::npos)
break;
// Find a STRUCT start?
if(line.find("<STRUCT")!=string::npos)
{
// inc struct
structLevel++;
// if start a new State block
if(structLevel==2)
{
// reset info for this state
currentStateName.clear();
meleeImpactDelayLine= -1;
// try to get the name
const string tagStart= "name=\"";
std::string::size_type start= lineLwr.find(tagStart);
if(start!=string::npos)
{
start+= tagStart.size();
std::string::size_type end= lineLwr.find("\"", start);
if(end!=string::npos)
currentStateName= lineLwr.substr(start, end-start);
}
}
}
// Find a STRUCT end?
if(line.find("</STRUCT>")!=string::npos)
{
// if end a state block, may add or replace MeleeDelayImpact
if(structLevel==2 && !currentStateName.empty())
{
// If the state is not in the combat state, no need to patch anything
static CAnimCombatState key;
// must translate for instance "attack1" to "A1"
key.StateCode= StateNameToStateCode[currentStateName];
set<CAnimCombatState>::const_iterator it= currentCombatAnimSet.States.find(key);
if(it!=currentCombatAnimSet.States.end())
{
// else take the mean anim time
string format= " <ATOM Name=\"MeleeImpactDelay\" Value=\"%.3f\"/>";
string newLine= toString(format.c_str(), it->MeanAnimTime * MeleeImpactTimeFactor);
// melee impact delay doesn't exist?
if(meleeImpactDelayLine==-1)
{
// add just before this line the Melee Impact Atom
animSetText.insert(animSetText.begin()+j, newLine);
j++;
someChangeDone= true;
}
// else exist and want to replace?
else if(ReplaceExistingMeleeImpactDelay)
{
animSetText[meleeImpactDelayLine]= newLine;
someChangeDone= true;
}
}
}
// dec struct level
structLevel--;
}
// if we are in level 2 structure, try to get the line to modify (if exist)
if(structLevel==2)
{
if( line.find("Name=\"MeleeImpactDelay\"")!=string::npos )
meleeImpactDelayLine= j;
}
}
}
// *** Write the animset file.
if(someChangeDone)
{
COFile oFile;
oFile.open(animSetFile, false, true);
// Write all text
for(uint i=0;i<animSetText.size();i++)
{
string str= animSetText[i];
str+= "\n";
oFile.serialBuffer((uint8*)str.c_str(), (uint)str.size());
}
}
}
// ***************************************************************************
int usage()
{
printf("Usage: make_anim_melee_impact animset_dir");
return -1;
}
// ***************************************************************************
int main(int argc, char *argv[])
{
NLMISC::createDebug();
// make_anim_melee_impact animset_dir
if(argc!=2)
return usage();
string animSetDir= argv[1];
// *** parse the anim.txt file
set<CAnimCombatSet> combatAnimSets;
CIFile animFile;
if(!animFile.open("anim.txt", true))
{
nlwarning("Can't open anim.txt file. abort");
return 0;
}
else
{
char tmp[5000];
CAnimCombatSet lastAnimSet;
// parse all lines
while(!animFile.eof())
{
animFile.getline(tmp, 5000);
string line= tmp;
if(line.empty())
continue;
// new anim set?
if(line[0]!=' ')
{
// insert the last anim state
if(!lastAnimSet.States.empty())
combatAnimSets.insert(lastAnimSet);
lastAnimSet.States.clear();
lastAnimSet.Name= line;
}
// new anim state?
else if(!lastAnimSet.Name.empty())
{
CAnimCombatState state;
state.build(line);
lastAnimSet.States.insert(state);
}
}
// append the last anim set if needed
if(!lastAnimSet.States.empty())
combatAnimSets.insert(lastAnimSet);
animFile.close();
}
// *** Get the list of .animset to make by race
vector<string> files;
files.clear();
CPath::getPathContent(animSetDir, true, false, true, files);
vector<string> animSetList;
InfoLog->displayRawNL("");
InfoLog->displayRawNL("*****************************");
InfoLog->displayRawNL("**** .animation_set list ****");
InfoLog->displayRawNL("*****************************");
for(uint i=0;i<files.size();i++)
{
if(testWildCard(files[i], "*.animation_set"))
{
animSetList.push_back(files[i]);
InfoLog->displayRawNL(animSetList.back().c_str());
}
}
// *** Init StateNameToStateCode
StateNameToStateCode["attack1"]= "A1";
StateNameToStateCode["attack2"]= "A2";
StateNameToStateCode["walk atk"]= "Wa";
StateNameToStateCode["run atk"]= "Ra";
StateNameToStateCode["backward atk"]= "Ba";
StateNameToStateCode["default atk low"]= "Dl";
StateNameToStateCode["default atk middle"]= "Dm";
StateNameToStateCode["default atk high"]= "Dh";
StateNameToStateCode["powerful atk low"]= "Pl";
StateNameToStateCode["powerful atk middle"]= "Pm";
StateNameToStateCode["powerful atk high"]= "Ph";
StateNameToStateCode["area atk low"]= "Al";
StateNameToStateCode["area atk middle"]= "Am";
StateNameToStateCode["area atk high"]= "Ah";
// *** For each animset, test if can replace some anim
InfoLog->displayRawNL("");
InfoLog->displayRawNL("**************************");
InfoLog->displayRawNL("**** Starting Process ****");
InfoLog->displayRawNL("**************************");
for(uint i=0;i<animSetList.size();i++)
{
makeAnimMeleeImpact(animSetList[i], combatAnimSets);
}
return 0;
}
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
/*
To generate the anim.txt file, this code has to be inserted in the client, in
entity_animation_manager.cpp, CEntityAnimationManager::load(), after
if(_AnimationSet)
{
_AnimationSet->build();
....
}
** insert here ***
animSet Sheets and 3D anim data (fauna_animations.bnp and characters_animations.bnp) must be up to date
*/
/*
// *************************************
// CODE TO GENERATE MELEE IMPACT DELAY
// *************************************
CFileDisplayer animLog("anim.txt", true);
TAnimStateId walkAtk= CAnimationStateSheet::getAnimationStateId("walk atk");
TAnimStateId runAtk= CAnimationStateSheet::getAnimationStateId("run atk");
TAnimStateId backAtk= CAnimationStateSheet::getAnimationStateId("backward atk");
TAnimStateId stateCombat[]= {CAnimationStateSheet::Attack1, CAnimationStateSheet::Attack2,
walkAtk, runAtk, backAtk,
CAnimationStateSheet::DefaultAtkLow,CAnimationStateSheet::DefaultAtkHigh,
CAnimationStateSheet::DefaultAtkMiddle,CAnimationStateSheet::PowerfulAtkLow,
CAnimationStateSheet::PowerfulAtkHigh,CAnimationStateSheet::PowerfulAtkMiddle,
CAnimationStateSheet::AreaAtkLow,CAnimationStateSheet::AreaAtkHigh,
CAnimationStateSheet::AreaAtkMiddle};
string stateCombatCode[]= {"A1", "A2", "Wa", "Ra", "Ba",
"Dl", "Dh", "Dm", "Pl", "Ph", "Pm", "Al", "Ah", "Am"};
const uint32 nSC= sizeof(stateCombat) / sizeof(stateCombat[0]);
nlctassert(nSC==sizeof(stateCombatCode) / sizeof(stateCombatCode[0]));
{
float roughEval= 0.f;
uint nbRoughEval= 0;
TAnimSet::iterator it= _AnimSet.begin();
for(;it!=_AnimSet.end();it++)
{
CAnimationSet &animSet= it->second;
bool animSetDisplayed= false;
for(uint i=0;i<nSC;i++)
{
// per anim state
CAnimationState *state= const_cast<CAnimationState*>(animSet.getAnimationState(stateCombat[i]));
if(state)
{
// first compute mean and anim name
float mean= 0.f;
uint nbValid= 0;
string animName;
bool extended= false;
for(uint j=0;j<state->getNumAnimation();j++)
{
CAnimation *anim= state->getAnimationByIndex(j);
NL3D::UAnimation *anim3d= NULL;
if(anim)
anim3d= _AnimationSet->getAnimation(anim->id());
if(anim && anim3d)
{
// name
string name= NLMISC::toLower(_AnimationSet->getAnimationName(anim->id()));
if(animName.empty())
animName= name;
else if(!extended)
{
extended= true;
animName+= ", ...";
}
// meanLength and nb
float timeLen= anim3d->getEndTime()-anim3d->getBeginTime();
mean+= timeLen;
nbValid++;
}
}
if(nbValid)
mean/=nbValid;
// compute standard and max deviation
float stdDev=0.f, maxDev=0.f;
for(uint j=0;j<state->getNumAnimation();j++)
{
CAnimation *anim= state->getAnimationByIndex(j);
NL3D::UAnimation *anim3d= NULL;
if(anim)
anim3d= _AnimationSet->getAnimation(anim->id());
if(anim && anim3d)
{
float timeLen= anim3d->getEndTime()-anim3d->getBeginTime();
stdDev+= (float)fabs(timeLen - mean);
maxDev= max(maxDev, (float)fabs(timeLen - mean));
}
}
if(nbValid)
stdDev/= nbValid;
// valid?
if(nbValid)
{
// display first animSetName
if(!animSetDisplayed)
{
string msg= toString("%s\n", it->first.c_str() );
animLog.display(CLog::TDisplayInfo(), msg.c_str());
animSetDisplayed= true;
}
// then stats for this state
string msg= toString(" %s: mn%.03f, md%.03f, sd%.03f, ev%.03f (%s)\n", stateCombatCode[i].c_str(),
mean, maxDev, stdDev, mean*0.4f, animName.c_str());
animLog.display(CLog::TDisplayInfo(), msg.c_str());
roughEval+= mean;
nbRoughEval++;
}
}
}
}
if(nbRoughEval)
{
roughEval/= nbRoughEval;
nlinfo(" AnimDBG RoughEval: mn%.03f, ev%.03f",
roughEval, roughEval*0.4f);
}
}
// *************************************
// CODE TO GENERATE MELEE IMPACT DELAY
// *************************************
*/