// 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); MeanAnimTime= (float)atof(time.c_str()); } // *************************************************************************** void makeAnimMeleeImpact(const std::string &animSetFile, const set<CAnimCombatSet> &combatAnimSets) { // look if this animSetFile is in the combat list to patch string shortName= CFile::getFilenameWithoutExtension(animSetFile); strlwr(shortName); CAnimCombatSet key; key.Name= shortName; set<CAnimCombatSet>::const_iterator it= combatAnimSets.find(key); if(it == combatAnimSets.end()) return; const CAnimCombatSet ¤tCombatAnimSet= *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= _AnimationSet->getAnimationName(anim->id()); strlwr(name); 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 // ************************************* */