# -*- coding: utf-8 -*-
r"""Descobre a formula de dano medindo o servidor OFICIAL, com e sem equipamento.

    python tools/medir_dano.py                      a captura corrente do oficial
    python tools/medir_dano.py <log>                uma captura a escolha
    python tools/medir_dano.py <log_a> <log_b>      compara oficial com o nosso

O QUE ELA RESPONDE

  1. O dano que o monstro me tira depende da minha DEFESA?
     Corta a gravacao nos instantes em que o jogador equipou ou desequipou alguma coisa
     (06 00 e 06 01 no fio) e compara o dano medio de cada trecho. Se a defesa contar, o
     dano sobe assim que a peca sai.

  2. O dano que EU causo depende da minha ARMA e dos meus atributos?
     Mesma ideia do outro lado: agrupa os golpes que o jogador deu, por trecho.

  3. Qual e a forma da formula?
     Para cada trecho tira minimo, mediana, maximo e a dispersao. Uma formula do tipo
     ataque - defesa da uma faixa estreita que desliza; uma do tipo ataque * (1 - k*defesa)
     encolhe a faixa toda junto. Ver o resumo no fim.

COMO GRAVAR PARA ISTO DAR RESPOSTA

  Uma sessao so, sem sair do jogo, com o gravador ligado desde o inicio:

    a) com o set todo vestido, deixa o MESMO monstro bater-te umas 15 vezes
    b) tira uma peca de defesa (so uma), deixa bater outras 15
    c) tira o resto do set, mais 15
    d) volta a vestir tudo, ataca TU o mesmo monstro umas 15 vezes
    e) troca de arma e ataca outras 15

  O mesmo monstro do principio ao fim, e de preferencia o mesmo nivel de personagem: e a
  unica forma de a diferenca que sobrar ser do equipamento e nao de outra coisa.
"""
import collections
import os
import struct
import sys

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "analise"))
from ler_hook import pacotes                                        # noqa: E402

OFICIAL = "C:" + os.sep + "RoshHook" + os.sep + "traffic.log"
OP_DANO = bytes([5, 2])
OP_APARECER = bytes([4, 2])
OP_EQUIPAR = bytes([6, 0])
OP_DESEQUIPAR = bytes([6, 1])
OP_HP = bytes([8, 6])


def _p(v, q):
    if not v:
        return 0
    v = sorted(v)
    return v[min(len(v) - 1, int(q * len(v)))]


def ler(caminho):
    """Golpes e trocas de equipamento, em ordem de tempo."""
    eventos = []
    npc_de = {}
    for t, op, c in pacotes(caminho, direcao='RECV'):
        if op == OP_APARECER and len(c) >= 74:
            npc_de[struct.unpack_from('<I', c, 8)[0]] = struct.unpack_from('<I', c, 70)[0]
        elif op == OP_DANO and len(c) == 51:
            # [24:28] BE u32 = bandeira<<8 | dano. O byte baixo e o dano.
            bruto = struct.unpack_from('>I', c, 24)[0]
            eventos.append((t, 'dano',
                            struct.unpack_from('>I', c, 7)[0],      # quem bateu
                            struct.unpack_from('<H', c, 22)[0],     # em quem
                            bruto & 0xFF, (bruto >> 8) & 0xFF))
        elif op in (OP_EQUIPAR, OP_DESEQUIPAR):
            eventos.append((t, 'equipou' if op == OP_EQUIPAR else 'tirou', 0, 0, 0, 0))
    eventos.sort(key=lambda e: e[0])
    return eventos, npc_de


def trechos(eventos):
    """Corta a gravacao a cada troca de equipamento."""
    saida = []
    atual = {'inicio': None, 'fim': None, 'golpes': []}
    for ev in eventos:
        if ev[1] in ('equipou', 'tirou'):
            if atual['golpes']:
                saida.append(atual)
            atual = {'inicio': ev[0], 'fim': None, 'golpes': [], 'apos': ev[1]}
        else:
            if atual['inicio'] is None:
                atual['inicio'] = ev[0]
                atual['apos'] = 'inicio da gravacao'
            atual['fim'] = ev[0]
            atual['golpes'].append(ev)
    if atual['golpes']:
        saida.append(atual)
    return saida


def relatorio(caminho, rotulo):
    if not os.path.exists(caminho):
        print("%s: captura nao existe (%s)" % (rotulo, caminho))
        return None
    eventos, npc_de = ler(caminho)
    tr = trechos(eventos)
    print("=" * 74)
    print("%s -- %s  (%.1f MB)" % (rotulo, os.path.basename(caminho),
                                   os.path.getsize(caminho) / 1e6))
    print("   %d pacotes de dano, %d trocas de equipamento, %d trechos"
          % (sum(1 for e in eventos if e[1] == 'dano'),
             sum(1 for e in eventos if e[1] != 'dano'), len(tr)))
    if len(tr) <= 1:
        print()
        print("   SEM TROCA DE EQUIPAMENTO na gravacao -- nao da para separar o efeito da")
        print("   defesa. Ver o cabecalho deste arquivo para o que gravar.")

    # quem e o jogador: quem aparece como ALVO e nunca tem npc_id de monstro
    alvos = collections.Counter(e[3] for e in eventos if e[1] == 'dano')
    atacs = collections.Counter(e[2] for e in eventos if e[1] == 'dano')
    jogador = None
    for cand, _n in alvos.most_common():
        if npc_de.get(cand, 0) <= 1000:
            jogador = cand
            break

    for lado, titulo in (('sofrido', 'DANO QUE O MONSTRO ME TIRA'),
                         ('causado', 'DANO QUE EU TIRO AO MONSTRO')):
        print()
        print("   %s" % titulo)
        print("   %-24s %6s %6s %7s %6s %6s  %s"
              % ("trecho", "n", "min", "mediana", "max", "media", "bandeiras"))
        houve = False
        for i, t in enumerate(tr, 1):
            g = [e for e in t['golpes']
                 if (e[3] == jogador) == (lado == 'sofrido') and e[4] > 0]
            if not g:
                continue
            houve = True
            d = [e[4] for e in g]
            band = collections.Counter(e[5] for e in g)
            print("   %-24s %6d %6d %7d %6d %6.1f  %s"
                  % ("%d) apos %s" % (i, t.get('apos', '?'))[:24], len(d), min(d),
                     _p(d, .5), max(d), sum(d) / len(d),
                     ",".join("%d:%d" % kv for kv in band.most_common(3))))
        if not houve:
            print("   %-24s (nenhum golpe deste lado na gravacao)" % "")
    return tr


def main() -> int:
    args = sys.argv[1:]
    if not args:
        args = [OFICIAL]
    rotulos = ['OFICIAL', 'NOSSO']
    for i, cam in enumerate(args[:2]):
        relatorio(cam, rotulos[i] if i < len(rotulos) else cam)
    print()
    print("=" * 74)
    print("COMO LER: se o dano sofrido SOBE quando uma peca sai, a defesa entra na formula.")
    print("Se ficar igual, ou a peca nao dava defesa, ou o servidor ignora-a. O dano causado")
    print("le-se do mesmo modo com a troca de arma.")
    return 0


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