# -*- coding: utf-8 -*-
"""Importa a tabela de drop do RvR e cria o esqueleto do drop dos monstros.

    python tools/importar_drop.py            mostra
    python tools/importar_drop.py --aplicar  grava

    -> server/Data/Drop/RvrDrop.ini    66 linhas, do client
    -> server/Data/Drop/MobDrop.ini    vazia, so o cabecalho

NAO HA DROP DE MONSTRO NO CLIENT. Procurado em 28/08:

  - varridos os 231 offsets possiveis dos 234 bytes de cada um dos 1227 registos do
    npcInfo.lod: nenhum campo aponta para o catalogo de itens. Os quatro offsets que
    "batem" devolvem o MESMO valor em dezenas de monstros (1024, 1002) -- e coincidencia
    de numeros pequenos, do genero que ja nos enganou antes, e nao um campo.

  - varridas as 114 tabelas .lod a procura da assinatura de uma tabela de drop, que e ter
    ids de monstro E ids de item no mesmo ficheiro. A unica com os dois em quantidade e o
    combinationNPC, e e NPC -> receita de combinacao.

  - o AMo.exe tem CDropItem e seis filhas (Normal, Weapon, Armor, Money, Fleta, GodBeast)
    mais CItemDropPacket. Sao classes de DESENHO e de rede: o client sabe pintar o saco no
    chao e receber a noticia dele. Nao tem com que decidir o que cai.

Decidir o que cai e do servidor, como a agressividade e a defesa dos monstros.

O QUE O rvrDrop VALE: E O FORMATO DOS PROPRIOS AUTORES DO JOGO

So serve o RvR, e sao 66 linhas. O que importa e o desenho, que copiamos em vez de inventar:

    [0:4]   grupo     1001..1006
    [4:8]   idx       posicao dentro do grupo, recomeca em 1
    [8:12]  item_id   do catalogo
    [12:16] qtd       1 em todas as 66, mas o campo existe
    [16:20] peso      inteiro

O ITEM 9999999 E O "NAO CAIU NADA"

Nao esta no catalogo, e ha exatamente uma linha dessas em cada um dos 6 grupos. E ela que
da a taxa de vazio, e a taxa e ALTA: entre 64,5% e 79,9%. Um abate larga alguma coisa uma
vez em cada tres ou cinco.

O PESO E RELATIVO. O DENOMINADOR E A SOMA DO GRUPO.

Ia ficar escrito aqui que a soma da sempre um milhao, porque o grupo 1001 da exatamente
1.000.000. Fui conferir os outros cinco e dao 100.000. Nao ha base fixa: o denominador e a
soma do proprio grupo. Da numero redondo porque a linha do 9999999 e ajustada para isso.

Quem for sortear tem de somar o grupo primeiro. Assumir 1.000.000 daria dez vezes menos
drop em cinco dos seis grupos, e em silencio.

Nao e hipotese: aconteceu ao escrever isto. A primeira versao desta nota dizia que o vazio
era de 6,5% em cinco grupos, porque dividi por um milhao em todos. Sao 65%. O ensaio da
importacao e que apanhou -- por isso o bloco de grupos do RvrDrop.ini passou a ser CALCULADO
na hora, e nao escrito a mao: um numero escrito a mao mente sem ninguem dar por isso.

O QUE FICA POR SABER

Alz nao aparece aqui, apesar de existir a classe CDropMoney no client. Ou o dinheiro vem
por outro caminho, ou o RvR nao larga dinheiro. Nao inventei coluna para ele: quando se
medir, acrescenta-se.

Tambem nao se sabe se os monstros apontam para GRUPOS partilhados (como o RvR faz) ou se
cada um tem a sua lista. O MobDrop.ini fica com mob_id direto, que e o caso simples; se a
medicao mostrar grupos partilhados, mete-se a camada nessa altura.
"""
import collections
import os
import struct
import sys

RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(RAIZ, "server"))
sys.path.insert(0, os.path.join(RAIZ, "tools"))
import data_files as D                                              # noqa: E402
import lod_crypto                                                   # noqa: E402

BASE = os.path.join("C:" + os.sep, "Games", "Karos", "ResUnpak", "Interface", "IE_Lod")
TAM, CAB = 20, 4
NADA = 9999999
TAB = chr(9)
ASPA = chr(34)
CR = chr(13) + chr(10)


def ler():
    d = lod_crypto.decrypt_lod_stream(open(os.path.join(BASE, "rvrDrop.lod_pt"), "rb").read())
    n = struct.unpack_from("<I", d, 0)[0]
    return [struct.unpack_from("<5I", d, CAB + k * TAM) for k in range(n)]


def celula(v):
    s = "" if v is None else str(v)
    return (ASPA + s + ASPA) if (" " in s or TAB in s) else s


def main() -> int:
    aplicar = "--aplicar" in sys.argv
    D.carregar()
    itens = dict((r["item_id"], str(r.get("nome") or "")) for r in D.tabela("Item"))
    regs = ler()
    soma = collections.defaultdict(int)
    for g, _i, _it, _q, p in regs:
        soma[g] += p

    print("rvrDrop: %d linhas, %d grupos" % (len(regs), len(soma)))
    print("itens reconhecidos no catalogo: %d de %d (o resto e o sentinela %d)"
          % (sum(1 for r in regs if r[2] in itens), len(regs), NADA))
    print()
    print("%-7s %-7s %-12s %-11s %-8s %s" % ("grupo", "linhas", "soma", "vazio", "vazio%", "nota"))
    for g in sorted(soma):
        vazio = sum(p for gg, _i, it, _q, p in regs if gg == g and it == NADA)
        print("%-7d %-7d %-12s %-11s %-8.1f %s"
              % (g, sum(1 for r in regs if r[0] == g), "{:,}".format(soma[g]),
                 "{:,}".format(vazio), 100.0 * vazio / soma[g],
                 "soma = 1.000.000" if soma[g] == 1000000 else "soma = 100.000"))
    print()
    print("%-7s %-5s %-11s %-5s %-10s %-8s %s"
          % ("grupo", "idx", "item_id", "qtd", "peso", "pct", "nome"))
    for g, i, it, q, p in regs[:12]:
        print("%-7d %-5d %-11d %-5d %-10d %-8.2f %s"
              % (g, i, it, q, p, 100.0 * p / soma[g],
                 itens.get(it, "(nada -- sentinela)")[:34]))

    if not aplicar:
        print()
        print("nada gravado. rode com --aplicar")
        return 0

    destino = os.path.join(RAIZ, "server", "Data", "Drop")
    os.makedirs(destino, exist_ok=True)

    # ---------------------------------------------------------------- RvrDrop
    cam = os.path.join(destino, "RvrDrop.ini")
    with open(cam, "w", encoding="utf-8", newline="") as fp:
        w = fp.write
        for l in [
            "; RvrDrop -- a tabela de drop do RvR, do rvrDrop.lod do client.",
            ";",
            "; E o UNICO drop que existe no client, e serve so o RvR. O drop dos monstros NAO",
            "; esta no client: procurado nos 234 bytes do npcInfo e nas 114 tabelas .lod, e o",
            "; AMo.exe so tem as classes de desenho (CDropNormal, CDropWeapon, CDropArmor,",
            "; CDropMoney, CDropFleta, CDropGodBeast) e o CItemDropPacket. O client pinta o saco",
            "; e recebe a noticia; quem decide o que cai e o servidor.",
            ";",
            "; Fica aqui pelo FORMATO, que e o dos proprios autores do jogo -- copiar o desenho",
            "; deles vale mais do que inventar um nosso. O MobDrop.ini usa as mesmas colunas.",
            ";",
            "; O ITEM 9999999 E O 'NAO CAIU NADA'. Nao esta no catalogo, e ha exatamente uma",
            "; linha dessas em cada grupo. E ela que da a taxa de vazio.",
            ";",
            "; O PESO E RELATIVO, E O DENOMINADOR E A SOMA DO GRUPO. Este bloco e CALCULADO na",
            "; importacao, e nao escrito a mao -- a versao a mao ja dizia 6,5% onde sao 65%:",
            ";",
        ] + [
            ";     grupo %d  %2d linhas   soma %9s   vazio %9s = %.1f%%"
            % (g, sum(1 for r in regs if r[0] == g), "{:,}".format(soma[g]),
               "{:,}".format(sum(p for gg, _i, it, _q, p in regs if gg == g and it == NADA)),
               100.0 * sum(p for gg, _i, it, _q, p in regs if gg == g and it == NADA) / soma[g])
            for g in sorted(soma)
        ] + [
            ";",
            "; A taxa de vazio e ALTA e parecida nos seis: um abate larga alguma coisa uma vez",
            "; em cada tres a cinco. E o unico numero de balanceamento de drop que temos do jogo",
            "; oficial, e vale como ponto de partida para os monstros ate haver medicao.",
            ";",
            "; NAO HA BASE FIXA. O 1001 da um milhao e os outros cinco dao cem mil. Quem sortear",
            "; tem de somar o grupo primeiro: assumir 1.000.000 dava dez vezes menos drop em",
            "; cinco dos seis grupos, e sem dar erro nenhum.",
            ";",
            "; A coluna pct_calc e DERIVADA, calculada na importacao (peso / soma do grupo). Esta",
            "; aqui so para se ler a tabela a olho. O servidor le o PESO. Se alguem editar o peso",
            "; a mao, o pct_calc fica velho e mente -- e por isso que se chama assim.",
            ";",
            "; Alz nao aparece, apesar de existir CDropMoney no client. Ou vem por outro caminho,",
            "; ou o RvR nao larga dinheiro. Nao inventei coluna: acrescenta-se quando se medir.",
            "",
        ]:
            w(l + CR)
        w("//" + TAB.join(["grupo", "idx", "item_id", "qtd", "peso", "pct_calc", "nome"]) + CR)
        for g, i, it, q, p in regs:
            nome = itens.get(it, "(nada)")
            w(TAB.join([str(g), str(i), str(it), str(q), str(p),
                        "%.3f" % (100.0 * p / soma[g]), celula(nome)]) + CR)

    # ---------------------------------------------------------------- MobDrop
    cam2 = os.path.join(destino, "MobDrop.ini")
    if os.path.exists(cam2):
        print("MobDrop.ini ja existe -- NAO foi tocado.")
    else:
        with open(cam2, "w", encoding="utf-8", newline="") as fp:
            w = fp.write
            for l in [
                "; MobDrop -- o que cada monstro larga. VAZIA DE PROPOSITO.",
                ";",
                "; Nao ha nada para importar: o drop dos monstros nao existe no client (ver a",
                "; explicacao toda no RvrDrop.ini, ao lado). Cada linha aqui tera de ser MEDIDA.",
                ";",
                "; O esquema e copiado do rvrDrop, que e o formato dos autores do jogo:",
                ";",
                ";     mob_id     o monstro (a mesma chave do Monsters/Mob.ini)",
                ";     idx        posicao na lista do monstro, recomeca em 1",
                ";     item_id    do catalogo, ou 9999999 para 'nao caiu nada'",
                ";     qtd        quantas unidades",
                ";     peso       inteiro RELATIVO. O denominador e a soma do mob, e nao um",
                ";                numero fixo -- no rvrDrop a soma e 1.000.000 num grupo e",
                ";                100.000 nos outros cinco. Somar sempre antes de sortear.",
                ";     fonte      onde se mediu, como no Mob.ini. Sem fonte a linha e palpite.",
                ";",
                "; PONHA A LINHA DO 9999999 EM CADA MONSTRO. Sem ela a soma dos pesos e so a dos",
                "; itens e todo o abate larga alguma coisa, que nao e como o jogo funciona.",
                ";",
                "; DUAS COISAS POR DECIDIR, e nenhuma se decide sem dados:",
                ";",
                ";   Alz nao tem coluna. Existe CDropMoney no client, mas o rvrDrop nao traz",
                ";   dinheiro nenhum. Quando se medir se o Alz vem no mesmo sorteio ou a parte,",
                ";   acrescenta-se a coluna, ou uma tabela propria.",
                ";",
                ";   O RvR agrupa: varios sitios partilham o grupo 1002. Nao se sabe se os",
                ";   monstros fazem o mesmo. Isto aqui poe mob_id direto, que e o caso simples;",
                ";   se a medicao mostrar listas repetidas entre monstros, mete-se a camada de",
                ";   grupo nessa altura em vez de duplicar linhas.",
                ";",
                "; COMO ENCHER: a ferramenta de captura ja grava o que e preciso. Cada saco que",
                "; aparece no chao e um CItemDropPacket. Sao precisos muitos abates do MESMO",
                "; monstro para o peso significar alguma coisa -- dez abates nao medem 1%.",
                "",
            ]:
                w(l + CR)
            w("//" + TAB.join(["mob_id", "idx", "item_id", "qtd", "peso", "fonte"]) + CR)

    D.carregar()
    for nome_tab, caminho in (("RvrDrop", cam), ("MobDrop", cam2)):
        t = D.tabela(nome_tab)
        print("gravado: %-44s %8s bytes   relido: %d linhas"
              % (caminho, "{:,}".format(os.path.getsize(caminho)), len(t)))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
