1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 // ***************************************************************************
22 This small tool was made for Graphist, to edit automatically *.animation_set sheets, so anims
23 get correct MeleeImpactDelay data
25 It uses an anim.txt file that was generated by inserting code (give at end of this file)
28 This tool had to be "used one time only". hence its crappiest usage :)
32 #include "nel/misc/path.h"
33 #include "nel/misc/file.h"
34 #include "nel/misc/common.h"
35 #include "nel/misc/algo.h"
39 using namespace NLMISC
;
42 // ***************************************************************************
44 bool ReplaceExistingMeleeImpactDelay
= true;
45 float MeleeImpactTimeFactor
= 0.35f
;
48 map
<string
, string
> StateNameToStateCode
;
51 // ***************************************************************************
52 class CAnimCombatState
55 void build(const string
& line
);
59 // Mean Animation Time of all sub animations of this state
63 bool operator<(const CAnimCombatState
&o
) const {return StateCode
<o
.StateCode
;}
69 // name of the anim set
72 // set of CAnimCombatState
73 set
<CAnimCombatState
> States
;
76 bool operator<(const CAnimCombatSet
&o
) const {return Name
<o
.Name
;}
80 // ***************************************************************************
81 void CAnimCombatState::build(const string
&line
)
83 StateCode
= line
.substr(4, 2);
84 string time
= line
.substr(10, 5);
85 NLMISC::fromString(time
, MeanAnimTime
);
89 // ***************************************************************************
90 void makeAnimMeleeImpact(const std::string
&animSetFile
, const set
<CAnimCombatSet
> &combatAnimSets
)
92 // look if this animSetFile is in the combat list to patch
93 string shortName
= NLMISC::toLowerAscii(CFile::getFilenameWithoutExtension(animSetFile
));
96 set
<CAnimCombatSet
>::const_iterator it
= combatAnimSets
.find(key
);
97 if(it
== combatAnimSets
.end())
100 const CAnimCombatSet
¤tCombatAnimSet
= *it
;
102 InfoLog
->displayRawNL("patching %s", animSetFile
.c_str());
105 // *** Read the animset file.
107 iFile
.open(animSetFile
, true);
109 static vector
<string
> animSetText
;
114 iFile
.getline(tmp
, 50000);
115 animSetText
.push_back(tmp
);
120 bool someChangeDone
= false;
122 // *** Parse the animSet
124 // For each line of the animSet
126 sint meleeImpactDelayLine
= -1;
127 string currentStateName
;
128 for(uint j
=0;j
<animSetText
.size();j
++)
130 string line
= animSetText
[j
];
131 string lineLwr
= toLowerAscii(line
);
133 // Find <LOG> TAg? => stop
134 if(line
.find("<LOG>")!=string::npos
)
137 // Find a STRUCT start?
138 if(line
.find("<STRUCT")!=string::npos
)
143 // if start a new State block
146 // reset info for this state
147 currentStateName
.clear();
148 meleeImpactDelayLine
= -1;
150 // try to get the name
151 const string tagStart
= "name=\"";
152 std::string::size_type start
= lineLwr
.find(tagStart
);
153 if(start
!=string::npos
)
155 start
+= tagStart
.size();
156 std::string::size_type end
= lineLwr
.find("\"", start
);
157 if(end
!=string::npos
)
158 currentStateName
= lineLwr
.substr(start
, end
-start
);
163 // Find a STRUCT end?
164 if(line
.find("</STRUCT>")!=string::npos
)
166 // if end a state block, may add or replace MeleeDelayImpact
167 if(structLevel
==2 && !currentStateName
.empty())
169 // If the state is not in the combat state, no need to patch anything
170 static CAnimCombatState key
;
171 // must translate for instance "attack1" to "A1"
172 key
.StateCode
= StateNameToStateCode
[currentStateName
];
173 set
<CAnimCombatState
>::const_iterator it
= currentCombatAnimSet
.States
.find(key
);
174 if(it
!=currentCombatAnimSet
.States
.end())
176 // else take the mean anim time
177 string format
= " <ATOM Name=\"MeleeImpactDelay\" Value=\"%.3f\"/>";
178 string newLine
= toString(format
.c_str(), it
->MeanAnimTime
* MeleeImpactTimeFactor
);
180 // melee impact delay doesn't exist?
181 if(meleeImpactDelayLine
==-1)
183 // add just before this line the Melee Impact Atom
184 animSetText
.insert(animSetText
.begin()+j
, newLine
);
186 someChangeDone
= true;
188 // else exist and want to replace?
189 else if(ReplaceExistingMeleeImpactDelay
)
191 animSetText
[meleeImpactDelayLine
]= newLine
;
192 someChangeDone
= true;
201 // if we are in level 2 structure, try to get the line to modify (if exist)
204 if( line
.find("Name=\"MeleeImpactDelay\"")!=string::npos
)
205 meleeImpactDelayLine
= j
;
210 // *** Write the animset file.
214 oFile
.open(animSetFile
, false, true);
216 for(uint i
=0;i
<animSetText
.size();i
++)
218 string str
= animSetText
[i
];
220 oFile
.serialBuffer((uint8
*)str
.c_str(), (uint
)str
.size());
226 // ***************************************************************************
229 printf("Usage: make_anim_melee_impact animset_dir");
234 // ***************************************************************************
235 int main(int argc
, char *argv
[])
237 NLMISC::createDebug();
239 // make_anim_melee_impact animset_dir
242 string animSetDir
= argv
[1];
244 // *** parse the anim.txt file
245 set
<CAnimCombatSet
> combatAnimSets
;
247 if(!animFile
.open("anim.txt", true))
249 nlwarning("Can't open anim.txt file. abort");
255 CAnimCombatSet lastAnimSet
;
257 while(!animFile
.eof())
259 animFile
.getline(tmp
, 5000);
267 // insert the last anim state
268 if(!lastAnimSet
.States
.empty())
269 combatAnimSets
.insert(lastAnimSet
);
270 lastAnimSet
.States
.clear();
271 lastAnimSet
.Name
= line
;
274 else if(!lastAnimSet
.Name
.empty())
276 CAnimCombatState state
;
278 lastAnimSet
.States
.insert(state
);
282 // append the last anim set if needed
283 if(!lastAnimSet
.States
.empty())
284 combatAnimSets
.insert(lastAnimSet
);
289 // *** Get the list of .animset to make by race
290 vector
<string
> files
;
292 CPath::getPathContent(animSetDir
, true, false, true, files
);
293 vector
<string
> animSetList
;
294 InfoLog
->displayRawNL("");
295 InfoLog
->displayRawNL("*****************************");
296 InfoLog
->displayRawNL("**** .animation_set list ****");
297 InfoLog
->displayRawNL("*****************************");
298 for(uint i
=0;i
<files
.size();i
++)
300 if(testWildCard(files
[i
], "*.animation_set"))
302 animSetList
.push_back(files
[i
]);
303 InfoLog
->displayRawNL(animSetList
.back().c_str());
307 // *** Init StateNameToStateCode
308 StateNameToStateCode
["attack1"]= "A1";
309 StateNameToStateCode
["attack2"]= "A2";
310 StateNameToStateCode
["walk atk"]= "Wa";
311 StateNameToStateCode
["run atk"]= "Ra";
312 StateNameToStateCode
["backward atk"]= "Ba";
313 StateNameToStateCode
["default atk low"]= "Dl";
314 StateNameToStateCode
["default atk middle"]= "Dm";
315 StateNameToStateCode
["default atk high"]= "Dh";
316 StateNameToStateCode
["powerful atk low"]= "Pl";
317 StateNameToStateCode
["powerful atk middle"]= "Pm";
318 StateNameToStateCode
["powerful atk high"]= "Ph";
319 StateNameToStateCode
["area atk low"]= "Al";
320 StateNameToStateCode
["area atk middle"]= "Am";
321 StateNameToStateCode
["area atk high"]= "Ah";
324 // *** For each animset, test if can replace some anim
325 InfoLog
->displayRawNL("");
326 InfoLog
->displayRawNL("**************************");
327 InfoLog
->displayRawNL("**** Starting Process ****");
328 InfoLog
->displayRawNL("**************************");
329 for(uint i
=0;i
<animSetList
.size();i
++)
331 makeAnimMeleeImpact(animSetList
[i
], combatAnimSets
);
340 // ***************************************************************************
341 // ***************************************************************************
342 // ***************************************************************************
343 // ***************************************************************************
346 To generate the anim.txt file, this code has to be inserted in the client, in
347 entity_animation_manager.cpp, CEntityAnimationManager::load(), after
352 _AnimationSet->build();
359 animSet Sheets and 3D anim data (fauna_animations.bnp and characters_animations.bnp) must be up to date
365 // *************************************
366 // CODE TO GENERATE MELEE IMPACT DELAY
367 // *************************************
368 CFileDisplayer animLog("anim.txt", true);
369 TAnimStateId walkAtk= CAnimationStateSheet::getAnimationStateId("walk atk");
370 TAnimStateId runAtk= CAnimationStateSheet::getAnimationStateId("run atk");
371 TAnimStateId backAtk= CAnimationStateSheet::getAnimationStateId("backward atk");
372 TAnimStateId stateCombat[]= {CAnimationStateSheet::Attack1, CAnimationStateSheet::Attack2,
373 walkAtk, runAtk, backAtk,
374 CAnimationStateSheet::DefaultAtkLow,CAnimationStateSheet::DefaultAtkHigh,
375 CAnimationStateSheet::DefaultAtkMiddle,CAnimationStateSheet::PowerfulAtkLow,
376 CAnimationStateSheet::PowerfulAtkHigh,CAnimationStateSheet::PowerfulAtkMiddle,
377 CAnimationStateSheet::AreaAtkLow,CAnimationStateSheet::AreaAtkHigh,
378 CAnimationStateSheet::AreaAtkMiddle};
379 string stateCombatCode[]= {"A1", "A2", "Wa", "Ra", "Ba",
380 "Dl", "Dh", "Dm", "Pl", "Ph", "Pm", "Al", "Ah", "Am"};
381 const uint32 nSC= sizeof(stateCombat) / sizeof(stateCombat[0]);
382 nlctassert(nSC==sizeof(stateCombatCode) / sizeof(stateCombatCode[0]));
384 float roughEval= 0.f;
386 TAnimSet::iterator it= _AnimSet.begin();
387 for(;it!=_AnimSet.end();it++)
389 CAnimationSet &animSet= it->second;
390 bool animSetDisplayed= false;
391 for(uint i=0;i<nSC;i++)
394 CAnimationState *state= const_cast<CAnimationState*>(animSet.getAnimationState(stateCombat[i]));
397 // first compute mean and anim name
401 bool extended= false;
402 for(uint j=0;j<state->getNumAnimation();j++)
404 CAnimation *anim= state->getAnimationByIndex(j);
405 NL3D::UAnimation *anim3d= NULL;
407 anim3d= _AnimationSet->getAnimation(anim->id());
411 string name= NLMISC::toLowerAscii(_AnimationSet->getAnimationName(anim->id()));
421 float timeLen= anim3d->getEndTime()-anim3d->getBeginTime();
429 // compute standard and max deviation
430 float stdDev=0.f, maxDev=0.f;
431 for(uint j=0;j<state->getNumAnimation();j++)
433 CAnimation *anim= state->getAnimationByIndex(j);
434 NL3D::UAnimation *anim3d= NULL;
436 anim3d= _AnimationSet->getAnimation(anim->id());
439 float timeLen= anim3d->getEndTime()-anim3d->getBeginTime();
440 stdDev+= (float)fabs(timeLen - mean);
441 maxDev= max(maxDev, (float)fabs(timeLen - mean));
450 // display first animSetName
451 if(!animSetDisplayed)
453 string msg= toString("%s\n", it->first.c_str() );
454 animLog.display(CLog::TDisplayInfo(), msg.c_str());
455 animSetDisplayed= true;
458 // then stats for this state
459 string msg= toString(" %s: mn%.03f, md%.03f, sd%.03f, ev%.03f (%s)\n", stateCombatCode[i].c_str(),
460 mean, maxDev, stdDev, mean*0.4f, animName.c_str());
461 animLog.display(CLog::TDisplayInfo(), msg.c_str());
471 roughEval/= nbRoughEval;
472 nlinfo(" AnimDBG RoughEval: mn%.03f, ev%.03f",
473 roughEval, roughEval*0.4f);
476 // *************************************
477 // CODE TO GENERATE MELEE IMPACT DELAY
478 // *************************************