# -*- coding: utf-8 -*-
r"""Extrai as skills com EFEITO -- passivas, buffs, debuffs e controlo.

    python tools/extrair_buffs.py  ->  server/Data/Skills/SkillBuff.ini

A CADEIA, decifrada em 30/08 a partir da dica do "Foco" (5002) que o jogador mandou e de
duas capturas dele -- uma a lancar o buff e outra a apanha-lo a cair:

    skillLevel @140   duracao em segundos (double). ZERO quer dizer PASSIVA: o efeito
                      vale sempre, enquanto a skill estiver aprendida.
    skillLevel @264   ate 5 codigos, um por efeito
    skillData codigo  (tipo, valor1, valor2)
    05 09 [11]        1 liga o buff, 0 desliga

O `tipo` e o MESMO switch FUN_006076cb dos bonus de item, portanto o TIPO_DE_BONUS do db
serve tal e qual. O "Foco" da tipo 11 (Chance de Critico) com valor 2, 4, 6, 8, 10 conforme o
nivel da skill -- e a dica do client diz "+2,0" no nivel 1, que bate.

DURACAO CONFIRMADA NO FIO: a captura `cabandobuf.log` tem o 05 09 a ligar em +82,98 e a
desligar em +1282,97. Sao 1199,99 segundos contra os 1200 da tabela.

CUIDADO COM O @138. Lido como double da -2,0 e parece o valor do efeito com o sinal trocado.
Nao existe: sobrepoe-se ao @140. E a quarta vez que um double desalinhado engana neste
ficheiro. A forma de nao cair e olhar o campo nos CINCO NIVEIS e em skills de tipos
diferentes -- o que e real varia de forma explicavel.

NEM TUDO AQUI E UM BUFF DE QUEM LANCA. Ha debuffs (a 1002 poe proteccao fisica a -10 no
alvo) e controlo (tipos 85, 86 e 87 sao impossibilidade de mover, atacar e usar objectos).
O ficheiro guarda tudo e o servidor decide o que sabe aplicar.
"""
import io
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 lod_crypto as L                                              # noqa: E402

PASTA = r"C:\Games\Axion\ResUnpak\Interface\IE_Lod"
SUFIXO = "_pt"
XOR = 0

ORIGENS = {"axion": (PASTA, "_pt", 0),
           "arena": (r"C:\Games\Arena\ResUnpak\Interface\IE_Lod", "_en", 0x0D)}
"""Ver o mesmo bloco em tools/extrair_skills_lod.py."""


def escolher_origem():
    global PASTA, SUFIXO, XOR
    nome = "axion"
    if "--origem" in sys.argv:
        i = sys.argv.index("--origem")
        if i + 1 < len(sys.argv):
            nome = sys.argv[i + 1]
    if nome not in ORIGENS:
        print("origem desconhecida: %s" % nome)
        raise SystemExit(2)
    PASTA, SUFIXO, XOR = ORIGENS[nome]
    print("origem: %s (sufixo %s, xor 0x%02X)" % (nome, SUFIXO, XOR))
OFF_DURACAO, OFF_PERIODO, OFF_RANHURAS, N_RANHURAS = 140, 148, 264, 5


def efeitos():
    """{codigo: (tipo, valor1, valor2)} do skillData."""
    d = L.decrypt_lod_stream(open(os.path.join(PASTA, "skillData.lod" + SUFIXO), "rb").read(),
                             pular_zero=False, xor=XOR)
    n = struct.unpack_from("<I", d, 4)[0]
    saida, q = {}, 8
    for _ in range(n):
        if q + 44 > len(d):
            break
        b = d[q:q + 44]
        q += 44
        for _k in range(3):
            ln = struct.unpack_from("<I", d, q)[0]
            q += 4 + ln
        v1 = struct.unpack_from("<d", b, 24)[0]
        v2 = struct.unpack_from("<d", b, 32)[0]
        saida[struct.unpack_from("<I", b, 0)[0]] = (
            struct.unpack_from("<I", b, 4)[0],
            0.0 if v1 != v1 else v1, 0.0 if v2 != v2 else v2)
    return saida


def main() -> int:
    escolher_origem()
    import re
    efe = efeitos()
    d = L.decrypt_lod_stream(open(os.path.join(PASTA, "skillLevel.lod" + SUFIXO), "rb").read(),
                             pular_zero=False, xor=XOR)
    n = struct.unpack_from("<I", d, 4)[0]
    nomes = {}
    linhas = []
    q = 8
    for _ in range(n):
        if q + 284 > len(d):
            break
        b = d[q:q + 284]
        q += 284
        for _k in range(18):
            ln = struct.unpack_from("<I", d, q)[0]
            q += 4 + ln
        ln = struct.unpack_from("<I", d, q)[0]
        desc = re.sub(r"[\x00-\x1f]+", " ", d[q + 4:q + 4 + ln].decode("latin-1")).strip()
        q += 4 + ln
        dur = struct.unpack_from("<d", b, OFF_DURACAO)[0]
        # PERIODO -- de quantos em quantos segundos o efeito volta a bater. A Corrosao
        # (11002) tem duracao 10 e periodo 2, e o tooltip do client diz exactamente
        # "Duracao 10.0 / Periodo 2.0" com "Saude -5": sao CINCO tiques de -5, nao um -5
        # solto. Zero quer dizer efeito continuo, sem tique -- e o caso dos buffs.
        #
        # OUTRO DOUBLE DESALINHADO A ESPREITA, o quinto neste ficheiro: em [152] ha um float
        # que tambem le 2.0, e e a metade alta deste mesmo double. Ler ali daria o numero
        # certo por acaso nuns casos e lixo noutros.
        per = struct.unpack_from("<d", b, OFF_PERIODO)[0]
        if per != per or not (0 <= per < 10 ** 5):
            per = 0.0
        if dur != dur or not (0 <= dur < 10 ** 6):
            continue
        # DURACAO ZERO E PASSIVA, e nao "sem efeito". O jogador mandou a dica da "Habilidade
        # de evasao" (5003): "Tipo de habilidade: passiva", "Destreza +2,0" -- e a ficha dele
        # mostra "Destreza 13 (+2)". A cadeia e a mesma dos buffs, so nao tem prazo.
        #
        # A primeira versao desta ferramenta filtrava por `0 < dur` e deixava as passivas
        # todas de fora.
        tem_efeito = any(struct.unpack_from("<I", b, OFF_RANHURAS + 4 * k)[0]
                         for k in range(N_RANHURAS))
        if dur == 0 and not tem_efeito:
            continue
        s = struct.unpack_from("<I", b, 0)[0]
        nv = struct.unpack_from("<I", b, 4)[0]
        nomes.setdefault(s, desc[:60])
        for k in range(N_RANHURAS):
            c = struct.unpack_from("<I", b, OFF_RANHURAS + 4 * k)[0]
            if not c:
                continue
            tipo, v1, v2 = efe.get(c, (0, 0.0, 0.0))
            linhas.append((s, nv, round(dur, 3), round(per, 3), c, tipo, v1, v2, c in efe))
    nl, tb = chr(10), chr(9)
    destino = os.path.join(RAIZ, "server", "Data", "Skills", "SkillBuff.ini")
    with io.open(destino, "w", encoding="utf-8", newline="") as f:
        f.write("; SkillBuff -- skills com DURACAO: buffs, debuffs e controlo." + nl)
        f.write("; Gerado por tools/extrair_buffs.py. Ver o cabecalho dessa ferramenta." + nl)
        f.write("; `tipo` e o mesmo switch dos bonus de item (TIPO_DE_BONUS no db)." + nl)
        f.write("; `conhecido` = 0 quando o codigo nao tem linha no skillData." + nl)
        f.write(tb.join(["//skill_id", "nivel", "duracao", "periodo", "codigo",
                         "tipo", "valor", "valor2", "conhecido"]) + nl)
        for s, nv, dur, per, c, tipo, v1, v2, ok in linhas:
            f.write(tb.join([str(s), str(nv), "%g" % dur, "%g" % per, str(c), str(tipo),
                             "%g" % v1, "%g" % v2, "1" if ok else "0"]) + nl)
    print("skills com duracao: %d, linhas de efeito: %d"
          % (len({l[0] for l in linhas}), len(linhas)))
    print("escrito em %s" % os.path.normpath(destino))
    return 0


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