Miscellaneous Forums > Scripting and Reverse Engineering

[FF8] Monster stats and level formulas

(1/3) > >>

myst6re:
Hello everyone! I need your help this time: In dat files, monster stats are represented by 4 numbers, which are used in an equation with monster level. Does anyone know the right equation for each monster attribute?

What we know (from Ifrit editor):

--- Code: ---HP = Math.floor(number_0 * level * level / 20) + number_1 * level + number_2
EXP = number_0 * (5 * (level - level_party_avg)/level_party_avg + 4))
EXP_LAST = Math.floor(number_1 * (5 * (level - level_party_last)/level_party_last + 4))
# If the result > 255, STR = 255
STR = level * number_0 / 10 + level / number_1 - level / number_3 + number_2) / 4
MAG = Math.floor(level * number_0) + Math.floor(level/number_1) - Math.floor(Math.floor(Math.floor(level*level/2)/number_2)/4))
VIT = level * number_0 + Math.floor(level/number_1) + number_2 - Math.floor(level/number_3)
SPR = level * number_0 + Math.floor(level/number_1) + number_2 - Math.floor(level/number_3)
SPD = level * number_0 + Math.floor(level/number_1) + number_2 - Math.floor(level/number_3)
EVA = level * number_0 + Math.floor(level/number_1) + number_2 - Math.floor(level/number_3)

--- End code ---

I think HP and EXP are good. For the rest...

JBedford128:
Edit: Okay, so I'll preface this by saying sometimes they match the Ultimania stats (which give stats at Lv1, every ten levels, and the max level), sometimes they are way off.  I do know that some changes were made from JP>EN so this could be a product of that. Also I'm suspicious of my math.floors, I may have added them at random to see what gave me the right results.

Here's the formulas I used (in Lua) for the FFWiki, probably with more brackets than I need. You can see the results of these and judge whether they are correct on the individual enemy articles on the wiki: example.

STR, MAG:
local calc = math.floor(((lv*a/10)+(lv/b)-math.floor((lv^2)/(2*d))+c)/4)
return math.min(calc, 255)

VIT, SPR, SPD, EVA:
local calc = math.floor(lv/b)-math.floor(lv/d)+(lv*a)+c
return math.min(calc, 255)

JWP:
I noticed when poking around the memory at the actual values that some of the values in Ifrit were 1 off but it's probably a rounding issue, I could take a look in the code and see where it pushes the value from.

EDIT:
Function that gets the values in FF8_EN.exe (Steam version) is at 0x48C3F0 and takes 3 parameters:
param 1: level
param 2: pointer to section 7 of a dat file
param 3: integer (0 = str, 1 = mag (think this actually vit), 2 = vit (think this is actually mag), 3 = spr, 4 = spd, 5 = eva)

Is the information on http://wiki.qhimm.com/view/FF8/FileFormat_DAT#Section_7:_Informations_.26_stats in the correct order?
Since I'd expect the formulas for str and mag to be the same rather than str and vit - I'm guessing mag and vit should be swapped since the order they usually appear in is str, vit, mag, spr, spd, luck, eva (although monsters have no luck).

For the following sections:
ESI = pointer to the 4 bytes needed, so ESI = 1st byte, ESI+1 = 2nd byte, ESI+2 = 3rd byte, ESI+3 = 4th byte
arg.1 = level
result ends up in EAX
ok, here is the ASM for the mag, spr, spd and eva cases (it gets capped to 255 after this but I've omitted that part) - I think this is actually for vit, spr, spd and eva, see note above:

--- Code: ---0048C447  |.  8B7C24 10     MOV EDI,DWORD PTR SS:[ARG.1]             ; default case of switch FF8_EN.48C43D
0048C44B  |.  33C9          XOR ECX,ECX
0048C44D  |.  8A4E 03       MOV CL,BYTE PTR DS:[ESI+3]
0048C450  |.  8BC7          MOV EAX,EDI
0048C452  |.  99            CDQ
0048C453  |.  F7F9          IDIV ECX
0048C455  |.  33DB          XOR EBX,EBX
0048C457  |.  8A5E 01       MOV BL,BYTE PTR DS:[ESI+1]
0048C45A  |.  8BC8          MOV ECX,EAX
0048C45C  |.  8BC7          MOV EAX,EDI
0048C45E  |.  99            CDQ
0048C45F  |.  F7FB          IDIV EBX
0048C461  |.  33D2          XOR EDX,EDX
0048C463  |.  8A16          MOV DL,BYTE PTR DS:[ESI]
0048C465  |.  0FAFD7        IMUL EDX,EDI
0048C468  |.  2BC1          SUB EAX,ECX
0048C46A  |.  33C9          XOR ECX,ECX
0048C46C  |.  8A4E 02       MOV CL,BYTE PTR DS:[ESI+2]

--- End code ---

if you're not using integer arithmetic, this is the same as: floor(lvl/b) - floor(lvl/d) + a*lvl + c - which is the same as JBedford128's formula above

ASM for str and vit (it gets capped to 255 after this but I've omitted that part): - I think this is actually for str and mag, see note above

--- Code: ---0048C480  |.  8B7C24 10     MOV EDI,DWORD PTR SS:[ARG.1]             ; cases 0, 2 of switch FF8_EN.48C43D
0048C484  |.  33C9          XOR ECX,ECX //ecx = 0
0048C486  |.  8BC7          MOV EAX,EDI //eax = lvl
0048C488  |.  8A4E 03       MOV CL,BYTE PTR DS:[ESI+3] //cl = d
0048C48B  |.  0FAFC7        IMUL EAX,EDI EAX //edx:eax = lvl^2
0048C48E  |.  99            CDQ //sign extend eax into edx
0048C48F  |.  F7F9          IDIV ECX //eax = floor(lvl^2/d)
0048C491  |.  33DB          XOR EBX,EBX //ebx = 0
0048C493  |.  8A5E 01       MOV BL,BYTE PTR DS:[ESI+1] bl = b
0048C496  |.  99            CDQ //sign extend eax into edx
0048C497  |.  2BC2          SUB EAX,EDX //not sure about this one edx will either be 0xFFFFFFFF or 0x00000000 eax ends up as eax or eax + 1
0048C499  |.  8BC8          MOV ECX,EAX //ecx = floor(lvl^2/d)
0048C49B  |.  8BC7          MOV EAX,EDI //eax = lvl
0048C49D  |.  99            CDQ //sign extend eax into edx
0048C49E  |.  F7FB          IDIV EBX //eax = floor(lvl/b)
0048C4A0  |.  D1F9          SAR ECX,1 //ecx = floor(floor(lvl^2/d)/2)
0048C4A2  |.  8BD8          MOV EBX,EAX //ebx = floor(lvl/b)
0048C4A4  |.  B8 67666666   MOV EAX,0x66666667 //eax = 0x66666667
0048C4A9  |.  2BD9          SUB EBX,ECX //ebx = floor(lvl/b) - floor(floor(lvl^2/d)/2)
0048C4AB  |.  33C9          XOR ECX,ECX //ecx = 0
0048C4AD  |.  8A0E          MOV CL,BYTE PTR DS:[ESI] cl = a
0048C4AF  |.  0FAFCF        IMUL ECX,EDI //edx:ecx = lvl*a
0048C4B2  |.  F7E9          IMUL ECX //edx:eax = lvl*a*0x66666667
0048C4B4  |.  C1FA 02       SAR EDX,2 //edx = floor(lvl*a*0x66666667/2^34)
0048C4B7  |.  8BC2          MOV EAX,EDX //eax = floor(lvl*a*0x66666667/2^34)
0048C4B9  |.  03D3          ADD EDX,EBX //edx = floor(lvl*a*0x66666667/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2)
0048C4BB  |.  C1E8 1F       SHR EAX,1F //eax = sign bit of eax
0048C4BE  |.  03C2          ADD EAX,EDX //not sure what effect this has
0048C4C0  |.  33D2          XOR EDX,EDX //edx = 0
0048C4C2  |.  8A56 02       MOV DL,BYTE PTR DS:[ESI+2] dl = c
0048C4C5  |.  03C2          ADD EAX,EDX //eax = floor(lvl*a*0x66666667/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c
0048C4C7  |.  99            CDQ //sign extend eax into edx
0048C4C8  |.  83E2 03       AND EDX,0x00000003 //edx = either 0x00000000%4 or 0xFFFFFFFF%4, so 0 or 3 depending on sign of eax
0048C4CB  |.  03C2          ADD EAX,EDX //eax = floor(lvl*a*0x66666667/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c
0048C4CD  |.  C1F8 02       SAR EAX,2//eax = floor((floor(lvl*a*0x66666667/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c)/4)

--- End code ---

So something like:
floor((floor((lvl*a*0x66666667)/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c)/4)
as a rough guess.
There are a couple of things I'm not sure about above though and probably some mistakes in the annotations above. Someone that's better at signed arithmetic would probably help :P.
The formula actually looks fairly close to what JBedford128 had (note that 0x66666667/2^34 is ~1/10 - it's so close that you could probably just use divide by 10 and it probably was in the original code - I suspect it's a compiler optimization for division by 10) - the only main difference is the flooring.
I'd say that:
floor((floor(lvl*a/10) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c)/4) probably works.
or if you're using integer arithmetic:
((lvl*a)/10 + lvl/b - (lvl^2/d)/2 + c)/4
I do wonder if this differs from the PSX code though.

myst6re:

--- Quote from: JBedford128 on 2016-04-14 21:04:05 ---Edit: Okay, so I'll preface this by saying sometimes they match the Ultimania stats (which give stats at Lv1, every ten levels, and the max level), sometimes they are way off.  I do know that some changes were made from JP>EN so this could be a product of that. Also I'm suspicious of my math.floors, I may have added them at random to see what gave me the right results.

--- End quote ---

I compared french PS and japanese version, here are the differences (in hex):

Tri-Point
Mag (or vit?) | JP: 01 14 64 02 | FR: 01 14 23 02

Left Orb (or Right?)
Spd | JP: 00 01 14 0a | FR: 00 01 05 03

Right Orb (or Left?)
Spd | JP: 00 01 28 0a | FR: 00 01 0a 03

Jumbo Cactuar
HP | JP: 00 00 3c 00 | FR: 00 00 1e 1e

Anakronox
HP | JP: 00 00 04 1e | FR: 00 00 03 1e

Left Probe / Right Probe
Spd | JP: 00 03 32 06 | FR: 00 03 1e 06

Trauma
Spd | JP: 00 01 24 01 | FR: 00 01 18 01

Fujin (119)
Str | JP: 46 05 55 82 | FR: 41 05 3c 82

Raijin (120)
Str | JP: 50 05 78 82 | FR: 50 05 6e 82

myst6re:
Ok, I can confirm STR/MAG formula:

--- Code: ---result = floor((floor(level * a/10) + floor(level / b) - floor((level * level) / (d * 2)) + c)/4)
result = min(result, 255)
--- End code ---

And VIT/SPR/SPD/EVA formula:

--- Code: ---result = floor(level / b) - floor(level / d) + a * level + c
result = min(result, 255)
--- End code ---

My EXP formula seems correct too (where "a" the value from the DAT file):

--- Code: ---EXP = floor(a * (5 * (level - level_party_avg) / level_party_avg + 4))
EXP_LAST = floor(a * (5 * (level - level_party_last) / level_party_last + 4))
--- End code ---

Note: values on the Ultimania guide seems to be EXP / 4 and EXP_LAST / 4, for level 1 members.

But still, the HP formula is sometimes wrong, like JWP says, in Iron Giant for example. Now I use (I was wrong in my first post):

--- Code: ---HP = floor(a * lvl * lvl/20) + (a + c * 100) * lvl + b * 10 + d * 1000
--- End code ---