# -*- coding: utf-8 -*-
r"""Extrai os tipos de opcao especial de item do client (uniqueOptionType.lod).

    python tools/importar_opcoes.py             mostra
    python tools/importar_opcoes.py --aplicar   grava Data/Items/UniqueOptionType.ini

O QUE E ISTO. Sao as "Caracteristicas especiais" que aparecem na caixa de texto de um item --
"Multiplicador de critico +0,15", "Classificacao de ataque +2,00", "Forca de Ataque Fisico
+5,00%". Investigado em 27/08 a partir de capturas de ecra do jogador; ver
docs/opcoes-de-item.md para a historia toda.

FORMATO DO REGISTO, 10 bytes, medido em 27/08:

    [0:2]  indice sequencial   u16 LE   1, 2, 3, ... 321
    [2:6]  codigo da opcao     u32 LE   20001 .. 72110
    [6:8]  flag                u16 LE   0 em 274 casos, 1 em 47
    [8:10] sempre zero

O CODIGO E ESTRUTURADO: grupo * 100 + grau. Sao 53 grupos, e dentro de cada um os graus
repetem-se sempre no mesmo padrao -- 1, 2, 3 nos grupos pequenos e 1, 2, 3, 5, 10 nos
maiores. Isso e o grau da opcao, nao opcoes diferentes: o mesmo atributo em cinco forcas.

O QUE FALTA. O nome de cada grupo. Ele nao esta em dados: o id de string do "Multiplicador de
critico" (31370) so aparece dentro da propria entrada do string.lod e nenhuma tabela o
referencia, portanto o mapeamento codigo -> texto vive no executavel. Os nomes existem e
estao identificados -- o bloco 21103..21123 do string.lod tem Forca, Destreza, Vitalidade,
Inteligencia, Sabedoria, Forca de Ataque Fisico, Protecao fisica, Poder magico, Classificacao
de ataque, Chance de evasao, Chance de bloqueio, Chance de Critico -- so falta saber qual
grupo e qual.

Assim que uma captura mostrar um item COM opcoes a entrar no inventario, os pares
(codigo, valor) aparecem no pacote e a correspondencia fecha-se sem tocar no executavel.
"""
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, "tools"))
import itembase                                                     # noqa: E402

# NOME DE CADA GRUPO. Decifrado em 27/08 pelo Ghidra, em modo headless sobre o AMo.exe.
#
# O nome nao esta em dados: os codigos nao aparecem sequer como constantes no executavel, e o
# id de string do "Multiplicador de critico" (31370) so aparece dentro da propria entrada do
# string.lod. A ligacao acontece em CODIGO -- num switch dentro de FUN_006076cb, que recebe um
# indice e devolve o id de string. Todos os ids de atributo que se procuraram caem nessa
# funcao, o que a identificou.
#
# A REGRA E `grupo % 100`. O codigo de opcao e grupo*100 + grau; a CENTENA do grupo (2, 4, 7)
# e a categoria da opcao, e o RESTO e o tipo de atributo, que entra direto no switch:
#
#     grupo 712 -> caso 12 -> string 31370 -> "Multiplicador de critico"
#     grupo 401 -> caso  1 -> string 21103 -> "Forca"
#     grupo 446 -> caso 46 -> string 31455 -> "Saude"
#
# Bate em 48 dos 53 grupos. Os cinco que faltam sao o 200 e o 700 (o caso 0 nao existe no
# switch) e o 437, 439 e 490, cujos casos nao aparecem nos ramos que o decompilador mostrou --
# provavelmente ficaram num `default` ou noutra funcao.
#
# Reproduzir: tools/ghidra_projects/KarosOptionNames.java acha a funcao, KarosDecompOpcoes.java
# descompila-a. Correm em headless, sem interface:
#     analyzeHeadless <dir> KarosOnline -process AMo.exe -noanalysis #         -scriptPath <dir> -postScript KarosOptionNames.java
NOMES = {
    201: "Forca",
    202: "Vitalidade",
    204: "Sabedoria",
    205: "Inteligencia",
    206: "Forca de Ataque Fisico",
    207: "A forca atual de um ataque fisico",
    208: "Poder magico. ataques",
    209: "O poder atual do Ataque Magico",
    210: "Classificacao de ataque",
    401: "Forca",
    402: "Vitalidade",
    403: "Destreza",
    404: "Sabedoria",
    405: "Inteligencia",
    406: "Forca de Ataque Fisico",
    408: "Poder magico. ataques",
    410: "Classificacao de ataque",
    413: "Velocidade de ataque",
    414: "Velocidade de invocacao",
    415: "Alcance de habilidade",
    416: "Classificacao de protecao",
    417: "Protecao fisica",
    419: "Magico. protecao",
    427: "Velocidade de movimento",
    435: "Efeito negativo",
    446: "Saude",
    447: "Mana",
    701: "Forca",
    702: "Vitalidade",
    703: "Destreza",
    704: "Sabedoria",
    705: "Inteligencia",
    706: "Forca de Ataque Fisico",
    707: "A forca atual de um ataque fisico",
    708: "Poder magico. ataques",
    709: "O poder atual do Ataque Magico",
    710: "Classificacao de ataque",
    711: "Chance de Critico",
    712: "Multiplicador de critico",
    713: "Velocidade de ataque",
    714: "Velocidade de invocacao",
    715: "Alcance de habilidade",
    716: "Classificacao de protecao",
    717: "Protecao fisica",
    718: "Protecao fisica atual",
    719: "Magico. protecao",
    720: "Protecao magica atual",
    721: "Chance de evasao",
}

TAM = 10
CR = chr(13) + chr(10)
SAIDA = os.path.join(RAIZ, "server", "Data", "Items", "UniqueOptionType.ini")


def ler():
    d = itembase.abrir("uniqueOptionType", pular_zero=True)
    n = struct.unpack_from("<I", d, 0)[0]
    saida = []
    for i in range(n):
        o = 4 + i * TAM
        if o + TAM > len(d):
            break
        cod = struct.unpack_from("<I", d, o + 2)[0]
        saida.append({
            "indice": struct.unpack_from("<H", d, o)[0],
            "codigo": cod,
            "grupo": cod // 100,
            "grau": cod % 100,
            "flag": struct.unpack_from("<H", d, o + 6)[0],
            "descricao": NOMES.get(cod // 100, "?"),
        })
    return saida


def main() -> int:
    linhas = ler()
    print("%d tipos de opcao no client" % len(linhas))
    grupos = {}
    for l in linhas:
        grupos.setdefault(l["grupo"], []).append(l["grau"])
    print("%d grupos, graus por grupo:" % len(grupos))
    for g in sorted(grupos):
        print("   grupo %-6d graus %s" % (g, sorted(grupos[g])))

    if "--aplicar" not in sys.argv:
        print()
        print("(usa --aplicar para gravar %s)" % os.path.relpath(SAIDA, RAIZ))
        return 0

    with open(SAIDA, "w", encoding="latin-1", newline="") as f:
        def w(s):
            f.write(s)
        w("; UniqueOptionType -- os tipos de opcao especial de item, do uniqueOptionType.lod." + CR)
        w("; colunas: indice, codigo, grupo, grau, flag, descricao" + CR)
        w(";" + CR)
        w("; O codigo e grupo*100 + grau. O grau e a FORCA da opcao (1, 2, 3, 5, 10), nao" + CR)
        w("; uma opcao diferente." + CR)
        w("; A CENTENA do grupo e a categoria e o RESTO e o atributo: 712 -> 12 ->" + CR)
        w("; Multiplicador de critico. A descricao vem do switch de FUN_006076cb do" + CR)
        w("; AMo.exe, lido pelo Ghidra em 27/08. Sai ? nos 5 grupos que faltam la." + CR)
        w("; Ver docs/opcoes-de-item.md." + CR)
        w(CR)
        w("//indice\tcodigo\tgrupo\tgrau\tflag\tdescricao" + CR)
        for l in linhas:
            w("%d\t%d\t%d\t%d\t%d\t%s%s"
              % (l["indice"], l["codigo"], l["grupo"], l["grau"], l["flag"],
                 l["descricao"], CR))
    print()
    print("gravado: %s (%d linhas)" % (SAIDA, len(linhas)))
    return 0


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