Default Player Level Tables

tofuman

Administrator
Staff member
Some players may find it informative. Here are the default player level tables Which contain the starting stats and which stats increase upon leveling up.

They are in CSV format so can be opened in any spreadsheet program or just plain old notepad if you're hardcore.

**EDIT: I may have the column headers in the wrong order. I'll check and verify a bit later.
***Columns are correct but the client uses these values against others to get the final figure. So not sure how much use these tables will be for the general player.
 

Soly

Member
Only XP is used directly from it, to get a desired level stats the base stats and every level increment needs to be added. Probably too much work xD

tofuman said:
...the client uses these values against others to get the final figure...

I have a question about that... why? (I guess the answer is Sega, as always)

1. I noticed the base stats of some classes have a "boost", like atp being +10 from what is set in the pltleveltbl base stats
2. The increments per level don't seem to be 1:1, I made some guesses about the increments, easy enough to "guess" it
Code:
switch (increment) // apparently not for ATA
{
	case 1: return 2;
	case 2: return 3;
	case 3: return 6;
	...
}

Using something like the previous snippet I was able to represent accurately the stats displayed on the client (some classes, not all, I think HP was wrong too). What about this? Is this how it works or I'm failing somewhere.

Why all this stuff? I was planning to make a mag/character stat calculator (actually got it pretty far but went lazy on it).
But there is already this calculator (some jp link)? Yeah I'm aware, but I don't know how that was made, and can't be changed (or can it?).

Also, I don't want to manually retrieve every level stat for every class, that would be a pain there. I would like something that I can throw a new plyleveltbl and it will get the right stats, right away.
 

Aleron Ives

Member
The stat caps are also skewed as compared to what your final stat is. You can never reach the HP or TP caps on any class, because if you could, God/HP and God/TP wouldn't do anything anymore, just as God/Power doesn't do anything when your ATP is maxed. Your maximum HP/TP are solely determined by the sum of your level up bonuses combined with your initial HP/TP and the HP/TP Materials you've used (and your MST bonus in the case of TP).
 

Matt

Administrator
Staff member
HP seems weird. For example, HUcast goes like this for his HP:

44 -> 52 -> 60 -> 68...

in the early levels, but according to these tables he only gains 3 HP every level, which can't be right.

Am I reading this wrong, perhaps?
 

Soly

Member
Well... from what Tofu said, and from I already experienced making this stat calculator (refer to my reply :p) ... that thing is weird.

Also as I said, my switch cases were not as I put there cuz I don't recall what they were, but perhaps if you find a different increment (in game) and can relate it to a different increment (in the table), and they are consistent, could build up something like that again.
 

Aleron Ives

Member
Maybe the HP and ATA values are swapped, or something. Remember that the level table shows true ATA and not the ATA / 10 that the game displays. Classes with terrible ATA, such as HUnewearl, actually gain ~2 ATA every time they level, but since the in-game value is divided by ten, you have to level up five times before you see ATA +1 in the level up message.
 

Soly

Member
ATA is correct in the tables, I am aware of the x/10 which I accounted for in the program and it was shown correctly.
For example if I had 174.2 (1742, wanted to keep the decimals) the game would have 174 (same level and class).
 

qwerty

Member
lv1 values of each class are stored at offset 0
atp / mst / evp / hp / dfp / ata / lck, 2 bytes each
thas is 14 * 12 = 168 bytes in total

as of hp
every lv up will give an extra 1 point
take HUcast for example
HUcast's lv1 hp is 22
and first 3 lv up give 3, 3, 3
so
lv2: 22 + 3 + 1 = 26
lv3: 26 + 3 + 1 = 30
lv4: 30 + 3 + 1 = 34
since HUcast is a hunter, final HP for lv1~lv4 are
lv1: 22 * 2 = 44
lv2: 26 * 2 = 52
lv3: 30 * 2 = 60
lv4: 34 * 2 = 68
 

Soly

Member
I'm aware of the file layout, but yeah this whole calculation ..once again sega shows up xD
 

qwerty

Member
finally dug this python script out of my old harddisks, that i use to generate per level data for each classes directly from PlyLevelTbl.prs
Code:
#! /usr/bin/env python3
# -*- coding: utf-8 -*-


import ctypes
import struct
import math


# prs_decompress is a naive python port of Fuzziqer Software's PRS Utility
# see http://www.fuzziqersoftware.com/projects.html for details


def prs_decompress(src):
    dest = bytearray(prs_decompress_size(src))
    r3 = 0
    r5 = 0
    bitpos = 9
    sourceptr = 0
    destptr = 0
    currentbyte = src[0]
    flag = 0
    offset = 0
    sourceptr += 1
    while True:
        bitpos -= 1
        if bitpos == 0:
            currentbyte = src[sourceptr]
            bitpos = 8
            sourceptr += 1
        flag = currentbyte & 1
        currentbyte = currentbyte >> 1
        if flag != 0:
            dest[destptr] = src[sourceptr]
            sourceptr += 1
            destptr += 1
            continue
        bitpos -= 1
        if bitpos == 0:
            currentbyte = src[sourceptr]
            bitpos = 8
            sourceptr += 1
        flag = currentbyte & 1
        currentbyte = currentbyte >> 1
        if flag != 0:
            r3 = src[sourceptr] & 0xFF
            offset = ((src[sourceptr+1] & 0xFF) << 8) | r3
            sourceptr += 2
            if offset == 0:
                return dest
            r3 = r3 & 0x00000007
            r5 = ctypes.c_int32((offset >> 3) | 0xFFFFE000).value
            if r3 == 0:
                flag = 0
                r3 = src[sourceptr] & 0xFF
                sourceptr += 1
                r3 += 1
            else:
                r3 += 2
            r5 += destptr
        else:
            r3 = 0
            for x in range(2):
                bitpos -= 1
                if bitpos == 0:
                    currentbyte = src[sourceptr]
                    bitpos = 8
                    sourceptr += 1
                flag = currentbyte & 1
                currentbyte = currentbyte >> 1
                offset = r3 << 1
                r3 = offset | flag
            offset = ctypes.c_int32(src[sourceptr] | 0xFFFFFF00).value
            r3 += 2
            sourceptr += 1
            r5 = offset + destptr
        if r3 == 0:
            continue
        t = r3
        for x in range(t):
            dest[destptr] = dest[r5]
            r5 += 1
            r3 += 1
            destptr += 1


def prs_decompress_size(src):
    r3 = 0
    bitpos = 9
    sourceptr = 0
    destptr = 0
    currentbyte = src[0]
    flag = 0
    offset = 0
    sourceptr += 1
    while True:
        bitpos -= 1
        if bitpos == 0:
            currentbyte = src[sourceptr]
            bitpos = 8
            sourceptr += 1
        flag = currentbyte & 1
        currentbyte = currentbyte >> 1
        if flag != 0:
            sourceptr += 1
            destptr += 1
            continue
        bitpos -= 1
        if bitpos == 0:
            currentbyte = src[sourceptr]
            bitpos = 8
            sourceptr += 1
        flag = currentbyte & 1
        currentbyte = currentbyte >> 1
        if flag != 0:
            r3 = src[sourceptr]
            offset = (src[sourceptr+1] << 8) | r3
            sourceptr += 2
            if offset == 0:
                return destptr
            r3 = r3 & 0x00000007
            if r3 == 0:
                r3 = src[sourceptr]
                sourceptr += 1
                r3 += 1
            else:
                r3 += 2
        else:
            r3 = 0
            for x in range(2):
                bitpos -= 1
                if bitpos == 0:
                    currentbyte = src[sourceptr]
                    bitpos = 8
                    sourceptr += 1
                flag = currentbyte & 1
                currentbyte = currentbyte >> 1
                offset = r3 << 1
                r3 = offset | flag
            offset = ctypes.c_int32(src[sourceptr] | 0xFFFFFF00).value
            r3 += 2
            sourceptr += 1
        if r3 == 0:
            continue
        t = r3
        for x in range(t):
            r3 += 1
            destptr += 1


def gen_level_data(data):
    char_data = {}
    class_name = ('HUmar', 'HUnewearl', 'HUcast',
                  'RAmar', 'RAcast', 'RAcaseal',
                  'FOmarl', 'FOnewm', 'FOnewearl',
                  'HUcaseal', 'FOmar', 'RAmarl')
    for i in range(12):
        rawdata = []
        minstat = ()  # ATP, DFP, MST, ATA, EVP, LCK
        maxstat = ()
        mat = ()
        k = class_name[i]
        if k == 'HUmar':
            minstat = (10, 0, 0, 650, 0, 0)
            maxstat = (1397, 579, 732, 2000, 756, 100)
            mat = (250, 125, 125)
        elif k == 'HUnewearl':
            minstat = (10, 0, 0, 610, 0, 0)
            maxstat = (1237, 589, 1177, 1990, 811, 100)
            mat = (150, 125, 125)
        elif k == 'HUcast':
            minstat = (10, 0, 0, 610, 0, 0)
            maxstat = (1639, 601, 0, 1910, 660, 100)
            mat = (150, 125, 0)
        elif k == 'HUcaseal':
            minstat = (10, 0, 0, 680, 0, 0)
            maxstat = (1301, 525, 0, 2180, 877, 100)
            mat = (150, 125, 0)
        elif k == 'RAmar':
            minstat = (5, 0, 0, 760, 0, 0)
            maxstat = (1260, 515, 665, 2490, 715, 100)
            mat = (250, 125, 125)
        elif k == 'RAmarl':
            minstat = (5, 0, 0, 680, 0, 0)
            maxstat = (1145, 577, 1031, 2410, 900, 100)
            mat = (250, 125, 125)
        elif k == 'RAcast':
            minstat = (5, 0, 0, 710, 0, 0)
            maxstat = (1350, 606, 0, 2240, 699, 100)
            mat = (150, 125, 0)
        elif k == 'RAcaseal':
            minstat = (5, 0, 0, 730, 0, 0)
            maxstat = (1175, 688, 0, 2310, 787, 100)
            mat = (150, 125, 0)
        elif k == 'FOmar':
            minstat = (3, 0, 0, 620, 0, 0)
            maxstat = (1002, 470, 1340, 1630, 651, 100)
            mat = (250, 125, 125)
        elif k == 'FOmarl':
            minstat = (3, 0, 0, 620, 0, 0)
            maxstat = (872, 498, 1284, 1700, 588, 100)
            mat = (250, 125, 125)
        elif k == 'FOnewm':
            minstat = (3, 0, 0, 600, 0, 0)
            maxstat = (814, 463, 1500, 1800, 679, 100)
            mat = (150, 125, 125)
        elif k == 'FOnewearl':
            minstat = (3, 0, 0, 600, 0, 0)
            maxstat = (585, 390, 1750, 1860, 883, 100)
            mat = (150, 125, 125)

        (atp, mst, evp, hp, dfp, ata, lck) = struct.unpack_from('HHHHHHH', data, i*14)
        rawdata.append([atp, dfp, mst, ata, evp, lck, hp])  # level 1

        for j in range(199):
            (d_atp, d_mst, d_evp, d_hp, d_dfp, d_ata) = struct.unpack_from('BBBBBB', data, 0xE4 + i*200*12 + j*12)
            atp += d_atp
            mst += d_mst
            evp += d_evp
            hp += d_hp + 1
            dfp += d_dfp
            ata += d_ata
            rawdata.append((atp, dfp, mst, ata, evp, lck, hp))

        char_data[k] = {
            'min': minstat,
            'max': maxstat,
            'mat': mat,
            'raw': rawdata,
            'data': [None, ] * 200,
        }

    for k in char_data:
        for i in range(len(char_data[k]['raw'])):
            (atp, dfp, mst, ata, evp, lck, hp) = char_data[k]['raw'][i]
            if k == 'HUmar':
                atp += 10
                ata += 650
                hp *= 2
            elif k == 'HUnewearl':
                atp += 10
                ata += 610
                hp *= 2
            elif k == 'HUcast':
                atp += 10
                ata += 610
                hp *= 2
            elif k == 'HUcaseal':
                atp += 10
                ata += 680
                hp *= 2
            elif k == 'RAmar':
                atp += 5
                ata += 760
                hp *= 1.85
            elif k == 'RAmarl':
                atp += 5
                ata += 680
                hp *= 1.85
            elif k == 'RAcast':
                atp += 5
                ata += 710
                hp *= 1.85
            elif k == 'RAcaseal':
                atp += 5
                ata += 730
                hp *= 1.85
            elif k == 'FOmar':
                atp += 3
                ata += 620
                hp *= 1.45
            elif k == 'FOmarl':
                atp += 3
                ata += 620
                hp *= 1.45
            elif k == 'FOnewm':
                atp += 3
                ata += 600
                hp *= 1.45
            elif k == 'FOnewearl':
                atp += 3
                ata += 600
                hp *= 1.45
            hp = math.floor(hp)
            char_data[k]['data'][i] = (atp, dfp, mst, ata, evp, lck, hp)

    return char_data


if __name__ == '__main__':
    with open('PlyLevelTbl.prs', 'rb') as f:
        data = f.read()
        data = prs_decompress(data)
        lv_data = gen_level_data(data)
        buf = []
        buf.append('function CharData() {')
        for k in ('HUmar', 'HUnewearl', 'HUcast', 'HUcaseal',
                  'RAmar', 'RAmarl', 'RAcast', 'RAcaseal',
                  'FOmar', 'FOmarl', 'FOnewm', 'FOnewearl'):
            buf.append('  this.%s = {' % k)
            buf.append('    // stat, hp, tp')
            buf.append('    mat : [%s],' % ', '.join(map(str, lv_data[k]['mat'])))
            buf.append('    data : [')
            buf.append('      // min [ATP, DFP, MST, ATA, EVP, LCK]')
            buf.append('      [%s],' % ', '.join(map(str, lv_data[k]['min'])))
            buf.append('      // 1~200 [ATP, DFP, MST, ATA, EVP, LCK, HP]')
            for l in range(len(lv_data[k]['data'])):
                buf.append('      [%s], // %d' % (', '.join(map(str, lv_data[k]['data'][l])), l + 1))
            buf.append('      // max [ATP, DFP, MST, ATA, EVP, LCK]')
            buf.append('      [%s],' % ', '.join(map(str, lv_data[k]['max'])))
            buf.append('    ],')
            buf.append('  };')
        buf.append('};')
        with open('chardata.js', 'w') as out:
            out.write('\n'.join(buf))
hunters have +10 atp from base, rangers have +5 and forces have +3
because hunters have a base variable atp 5~10, rangers have 1~5 and forces have 1~3, the same as weapon's min~max atp range
these values are used in damage calculating
but since psobb only shows max atp in char info
that's why you'll only notice +10 / +5 / +3

as of ata, i have no idea why these base values
i just got them by equipping 4 limits on lv1 characters, and multiply by 10 of course

pseudo code for total tp calculating
Code:
switch (class) {
    case humar:
    case hunewearl:
    case ramar:
    case ramarl:
        base_tp = mst + lv - 1;
        total_tp = base_tp + tp_by_mat + tp_by_units;
        break;
    case fomar:
    case fomarl:
    case fonewm:
    case fonewearl:
        base_tp = Math.floor((mst + lv - 1) * 1.5);
        total_tp = base_tp + tp_by_mat + tp_by_units;
        break;
    default:
        base_tp = 0;
        total_tp = 0;
        break;
}
 

Soly

Member
You can get the original table from the original teth package or the file within mine.
Afaik, they are no different from what Tofu had posted, I mean, Tofu posted csv file, an export of PlyLevelTbl.prs
 
Top