So its been another week, and I'm still plodding along. Sorry, nothing much to show yet, but I can say what I've been working on. I decided to see what it would take to write a generic animation engine for bots (which would have uses beyond bots). In other words, given a set of inputs, the engine would tell you what animation if any you should be playing/sending to Paragon Chat. For example, feed it a bunch of sequence bits and a time index, and then for any future time the engine will tell you based on the game sequence database what animation should be playing now. That kind of thing. I actually once had an almost fully functioning set of code to do that, but the last time it worked was in I20 and the code is also a mess. I mean literally a mess. Take a look at this:
def fixedpoint4byte(s):
fp1 = ord(s[3])
fp2 = ord(s[2])
fp3 = ord(s[1])
fp4 = ord(s[0])
fpsign = 1.0
if fp1 == 0 and fp2 == 0 and fp3 == 0 and fp4 == 0:
return 0
if (fp1 & 0x80) != 0:
fp1 = fp1 & 0x7f
fpsign = -1.0
## sys.stderr.write("Debug: negative sign triggered in fixedpoint routine\n")
fpE = fp1 * 2
if (fp2 & 0x80) != 0:
fpE = fpE + 1
fpE = fpE - 0x80
fpM = (fp2 & 0x7f) * 65536.0 + fp3 * 256.0 + fp4
fpS = 2 ** (22 - fpE)
# sys.stderr.write("Debug: fpE:"+str(fpE)+" fpM:"+str(fpM)+" fpS:"+str(fpS)+"\n")
# print "Debug: fpE:"+str(fpE)+" fpM:"+str(fpM)+" fpS:"+str(fpS),
fpval = ((2 ** (fpE + 1)) + fpM / fpS) * fpsign
return fpval
Seriously. That's the function I wrote, way, way, WAY back in I9 when I was first trying to reverse engineer pigg files. All I remember was trying to figure out how those numbers were encoded and trying out different theories until I found one that worked (trying to do this before you know what the numbers are supposed to represent yet makes it a bit more interesting). Of course, this is doing things the really hard way, treating the problem like code breaking. There are vastly simpler ways to do this, if you're willing to, say, trace the program itself (which didn't occur to me at the time).
This function was the one I was looking for, for a while:
def loadAnimTables(piggdict,subdir):
# subdir = "player_library/animations/male" for male players
global global_dver
piggfilename = piggdict['PiggFileName']
piggfilenameshort = piggfilename.split('/')[-1]
pf_debug = open('C:/CoH/decode/working/'+piggfilenameshort+'.sizes.'+global_dver+'.txt','wb')
anim_dict = {}
piggf = open(piggdict['PiggFileName'],'rb')
for pfile in piggdict.keys():
if pfile[0:30].lower() == subdir:
piggf.seek(piggdict[pfile]['foffset'])
fbz = piggf.read(piggdict[pfile]['fsizecomp'])
fraw = zlib.decompress(fbz)
fcount = fixedpoint4byte(fraw[520:524])
sys.stderr.write(pfile+","+str(fcount)+"\n")
anim_dict[pfile[26:].lower()] = {'frames':fcount}
pf_debug.write(pfile+": "+str(fcount)+"\n")
#sys.stderr.write(pfile[0:30].lower()+"\n")
piggf.close()
pf_debug.close()
return anim_dict
What does this do? This scans the pigg files looking for all of the *.anim files, which are animation files. This part: "fcount = fixedpoint4byte(fraw[520:524])" - that's some very hard-fought for knowledge. It took about a week for me to figure this out back in I11ish, but that's how long that animation takes to play from beginning to end, in frames. Why is that important? Because many animation sequence entries basically say "play until the end, then move to this sequence." That means the data in the sequence tables itself is insufficient to know how long to play sequence X before moving to sequence Y. But if you know how long the animation associated with sequence X is, you can time that accordingly**.
With that piece of information, plus a sequence decoder, you could write a set of functions that could load the sequence data and animation data from the CoH client piggs, then perform basic calculations on animation sequences. Paragon Chat itself must have the ability to do this to do what it does, more or less. Once I untangle all my old (really, really horrible) code from my pigg diving days, and make it more readable and less embarrassing, I should be able to replicate that functionality in a way that would be useful to bots. And also other things***. But its been slow going, first because my pigg decode code is organic, includes code for every version of pigg from I9 to I23 in a giant mish-mash, and I forget what the purpose of some of it was. And my free time is still fairly limited on what I can spend on it. But I'ma keep cranking away, and we'll see if I can't make something useful eventually.
** Actually, for things like emotes this is often not necessary because those kinds of animations usually contained explicit start and stop frame counters. Interestingly it tended to be powers-related animations that had stop=0 meaning play to the end. Which is why I needed that bit of information to calculate animation-based root times and compare them to powers-based cast times, which oddly no one before me ever seems to have thought to do.
*** Hypothetically speaking, it could be used to spit out custom emote.cfg files for people, based on what sorts of animations they wanted to have. With a little extra work, it could be used to spit out an emote.cfg that contained every animation connected to every power in the powers database (whether the animation actually worked or not). Stuff like that.