CoxaOT Global - cliente
=======================

build ....... 26
Servidor .... Crystal Server 15.25 (fork msholl/crystalserver, branch coxaot)
Login ....... https://global.coxaot.com/login.php   (HTTPS, via MyAAC)
Jogo ........ 188.220.168.161:7172
Cliente ..... OTClient Redemption, fork msholl/otclient, branch coxaot-crystal
              binario: windows-solution-opengl (GUI), run 31107188803
              commit ab27d69f (barra de XP, correcao definitiva)
Bot ......... nExBot, em mods/game_bot/default_configs/nExBot
Store ....... icones vem do SERVIDOR por HTTP (coinImagesURL do config.lua
              aponta para https://global.coxaot.com/images/store/)


COMO USAR
---------
1. Extraia a pasta inteira em qualquer lugar (nao precisa instalar).
   Se ja tinha uma versao antiga, APAGUE a pasta antes -- extrair por cima
   mantem o init.lua velho e o problema "volta".
2. Rode CoxaOT-Crystal.exe.
3. Na PRIMEIRA execucao o cliente avisa que faltam os assets do Tibia 15.25 e
   se oferece para baixar (~395 MB, do repo dudantas/tibia-client). Aceite.
   Acontece uma vez so; fica em data/things/1525/.
4. Entre com a sua conta (o campo de conta e o EMAIL do cadastro).

O host, a porta e a versao ficam escondidos na tela de login de proposito: o
init.lua tem uma unica entrada em Servers_init, o que faz o cliente chamar
setUniqueServer() e travar neste servidor.


BOT
---
Ctrl+B abre o bot. O nExBot ja vem embarcado: o game_bot copia sozinho a pasta
para o perfil do jogador no primeiro boot (bot.lua:179 -> createDefaultConfigs),
entao e' so escolher "nExBot" na lista e marcar Enable.


ONDE FICAM AS COISAS
--------------------
Settings/config .. %APPDATA%/coxaot/coxaot-crystal/coxaot-crystal/
Log do cliente ... <pasta do exe>/coxaot-crystal.log
Assets ........... <pasta do exe>/data/things/1525/


HISTORICO
---------
build 1  Apontava para 188.220.168.161:7171 (login classico). NAO FUNCIONA com
         cliente 15.x: o OTClient em versao >= 1405 manda o cabecalho de tamanho
         em BLOCOS de 8 (protocol.cpp:152, writeHeaderSize), mas o Crystal so
         aplica "size * 8 + 4" a partir do 2o pacote (connection.cpp:225) -- no
         primeiro ele le como contagem de bytes. Desalinha, o RSA falha e a
         conexao cai calada: "Your connection has been lost (ERROR 2)".

build 2  Login HTTP no login.php do MyAAC, que e' o caminho que o proprio
         DOCKER.md do Crystal manda usar. Ainda sem TLS: a senha ia em texto puro.

build 3  HTTPS em global.coxaot.com (Let's Encrypt). httpLogin virou FALSE --
         esse campo NAO significa "login por HTTP", ele escolhe o transporte:
         true = HTTP puro, false = TLS (httplogin.cpp:228).

build 4  Icones da Store. O cliente monta o caminho sozinho ("/13/" para
         categoria, "/64/" para oferta -- game_store.lua:797 e 339) e, quando o
         arquivo nao existe, cai em dynamic-image-error, que e' o "?" que
         aparecia no lugar de cada icone. Os 1973 PNGs vieram do MyAAC, que ja
         usa a mesma estrutura de pastas por tamanho.


build 5  Os icones da Store ainda apareciam como "?" no build 4 mesmo com os
         PNGs no pacote. Duas correcoes no setImagenHttp de game_store.lua:
         o ramo local ignorava o parametro isIcon e usava sempre
         setImageSource, enquanto o caminho que funciona (icone da Home) usa
         setIcon; e agora ele LOGA o caminho que nao encontrou, para o
         coxaot-crystal.log dizer exatamente qual arquivo falta.

build 6  Tres correcoes num redownload so:

         1. BARRA DE XP. O servidor manda o percentual em centesimos quando
            GameLevelPercentU16 esta ligado (protocolgame.cpp: getLevelPercent()
            * 100). O cliente lia sem dividir (protocolgameparse.cpp:2593),
            enquanto skills (2694) e ciclopedia (5569) ja dividiam. Com 39,90%
            chegando como 3990 a barra estourava sempre cheia e o skills.lua
            exibia "100 - 3990 = -3890 percent to go" -- aquele numero NEGATIVO
            no tooltip era porcentagem, nao experiencia.

         2. AS DUAS CAIXAS DE ERRO DA PRIMEIRA EXECUCAO. O entergame.lua chama
            setClientVersion() no startup para pre-carregar outfits, antes de o
            client_assets baixar os arquivos; o load() falhava, mostrava
            "Couldn't load assets" e ainda chamava setClientVersion(0), que se
            redisparava e gerava a SEGUNDA caixa, a do /data/things/0/. Agora
            sai calado enquanto os assets nao chegaram -- mas continua avisando
            se eles estiverem instalados e quebrados, que e' quando o aviso
            serve para alguma coisa.

         3. OS 21 MB DE ICONES DA STORE SAIRAM (65 MB -> 44 MB). Eram peso
            morto desde o build 5: quem manda a URL das imagens e' o SERVIDOR,
            pelo coinImagesURL, e o cliente sempre busca por HTTP. O ramo local
            que consumiria esses PNGs nunca executa.

         O nome exibido passou a ser "CoxaOT Global", igual ao serverName do
         servidor. O compactName continua "coxaot-crystal" DE PROPOSITO: e' ele
         que define a pasta de settings e o nome do .log, e mudar faria todo
         jogador perder hotkeys e layout sem ganhar nada visivel. Por isso o
         executavel e o log seguem com "crystal" no nome.

build 7  CORRIGE UMA REGRESSAO DO BUILD 6 que impedia o login em instalacao
         nova: "Things are not loaded, please put assets in things/1525/".

         O que aconteceu: ao silenciar as caixas de erro no build 6 eu tirei o
         g_game.setClientVersion(0) do fim do load(). Ele parecia so uma
         consequencia do erro, mas era LOAD-BEARING: o entergame.lua chama
         setClientVersion(1525) logo antes de checar isLoaded(), e o
         setClientVersion so dispara onClientVersionChange -- ou seja, so chama
         o load() de novo -- quando o valor MUDA. Sem zerar, a versao seguia
         1525 desde o startup, entao depois que o client_assets baixava os
         assets o load() nunca rodava outra vez e o login era barrado.

         Quem ja tinha os assets instalados nao era afetado (o load do startup
         funcionava). So quebrava em instalacao nova -- exatamente quem apagou a
         pasta antiga antes de extrair, como eu mesmo recomendei.

         As duas caixas de erro continuam silenciadas: o bloco "version == 0"
         no topo do load() e quem impede a segunda.

         Mesmo executavel do build 6 (nada foi recompilado, a mudanca e' Lua).

build 8  Monstro morto ficava listado no battle com a barra de vida vazia.

         Causa: quando um tile passa das 10 coisas que o protocolo enderecca, o
         servidor manda uma REDESCRICAO do tile inteiro (correcao feita no lado
         do servidor hoje). Ao aplicar essa redescricao, o Tile::clean() do
         cliente tira a criatura do tile SEM disparar onDisappear -- que e' o
         evento que o modulo do battle escuta. A lista so se corrigia quando o
         jogador andava, porque andar dispara onPositionChange e reconstroi tudo.

         Agora ha uma varredura a cada 2s que compara as entradas do battle com
         os spectators do mapa (a mesma fonte que o checkCreatures usa) e remove
         so o que sobrou. Nao reconstroi a janela: nao pisca e nao perde a
         ordenacao.

         Mesmo executavel do build 6 (mudanca e' Lua).


build 9  Duas adicoes, nada recompilado (so' Lua e dados).

         1. MAPA COMPLETO NO MINIMAPA. Vem um /minimap-full.otmm no pacote,
            exportado do proprio RME (File > Export > Export Minimap, formato
            OTMM, All Floors) a partir do world.otbm do servidor: 9.997 blocos,
            os 16 andares, ~19 milhoes de tiles com cor. O formato e' o nativo
            do cliente -- assinatura 0x4D4D544F, versao 1, bloco de 64x64 e
            MinimapTile de 3 bytes (flags/color/speed), identico dos dois lados.

            O game_minimap carrega esse arquivo DEPOIS do /minimap.otmm do
            jogador, nao no lugar dele: quem ja jogava tem um minimap.otmm
            proprio na pasta de settings, que tem precedencia na busca de
            recursos -- sem isso so' quem instalasse do zero veria o mapa
            completo. O loadOtmm SOMA os tiles no minimapa em memoria.

         2. CRIATURA E BOSS DO DIA NA TELA DE LOGIN. Modulo novo
            modules/client_boosted, que le'
            https://global.coxaot.com/boosted.php e mostra os dois com a
            imagem. Some ao entrar no jogo e volta ao deslogar.

            A imagem vem do outfit.php do site, que renderiza dos assets 15.25
            do proprio servidor. Nao da' para usar o outfit-images.ots.me que o
            MyAAC traz de fabrica: ele nao tem sprite acima do looktype ~650 e
            devolve 200 com ZERO byte -- as criaturas do 15.x usam 1300+, e era
            por isso que a criatura do dia sumia do site quase todo dia.

            Tudo best-effort: se a rede ou o site falhar, a janela nao aparece
            e o login segue normal.

build 10 A BARRA DE XP, DE VERDADE -- e o conserto de um erro meu do build 6.

         O LocalPlayer::getLevelPercent() do cliente JA dividia por 100: o
         m_levelPercent guarda CENTESIMOS de proposito. No build 6 eu dividi
         tambem no parsePlayerStats, entao 96,50% virava 9650 -> 96 -> 0. A
         janela de skills continuava certa (usa o argumento do evento) mas a
         barra da statsbar zerava, e ao logar o refresh() do skills.lua tambem
         passa pelo getter -- por isso "relogou, zerou".

         O que estava errado desde o inicio era so' o ARGUMENTO mandado ao Lua
         no onLevelChange: ia o valor cru, e o skills.lua faz "100 - percent"
         para o tooltip, o que dava "-3890 percent to go".

         Agora o parse guarda o valor cru (como o getter espera) e a conversao
         acontece num lugar so', na emissao do evento em LocalPlayer::setLevel.

         Quem achou foi o LOG DO CLIENTE: uma linha com onLevelChange
         chegando com percent=96 e getLevelPercent() devolvendo 0 na MESMA
         chamada -- impossivel com um divisor so'.

         A VARREDURA DO BATTLE (build 8) NUNCA FUNCIONOU. O BattleListManager e'
         um "local" declarado na linha 82 e eu pus a funcao na linha 20; em Lua
         um local so' e' visivel a partir da declaracao, entao a referencia caia
         no global, que e' nil, e a varredura morria no primeiro tick com
         "attempt to index global BattleListManager". Ou seja, a melhora que se
         viu no build 8 veio inteira da correcao do SERVIDOR. O bloco foi movido
         para depois da declaracao.

build 11 O botao "Get Tibia Coins" da Store levava para o repositorio do
         OTClient no GitHub -- e o valor que vem de fabrica no
         game_store.lua (WEBSITE_GETCOINS). Agora vai para
         https://global.coxaot.com/coins, a nossa pagina de compra por Pix.

         O mesmo destino foi ligado no Services.getCoinsUrl do init.lua, que
         e' o botao equivalente no Market e na lista de personagens; vinha
         comentado.

         Nada recompilado, mesmo executavel do build 10.

build 12 AUTO-UPDATE LIGADO. A partir daqui nao e' mais preciso rebaixar o zip
         inteiro a cada correcao: no boot o cliente consulta
         https://global.coxaot.com/updater.php, compara o CRC32 de cada
         arquivo local com o manifesto do servidor e baixa SO' o que mudou.
         Uma correcao de Lua passa a custar alguns KB em vez de 47 MB.

         O modulo updater ja vinha no OTClient, so' estava desligado (a linha
         Services.updater no init.lua vinha comentada).

         O servidor responde com keepFiles = true DE PROPOSITO: sem isso o
         updater apaga todo arquivo local que nao esteja no manifesto, e
         levaria junto o log, as configuracoes do bot e o que o jogador tiver
         posto na pasta.

         Os assets do Tibia (data/things, ~400 MB) ficam fora do manifesto: o
         updater ja preserva sozinho tudo que casa com "data/things", e nao
         faria sentido versiona-los aqui.

         Mesmo executavel do build 10.

build 13 O painel "Boosted" do menu de baixo mostrava so' um "?" nas duas
         vagas, e o rodape dizia "OTClient Redemption".

         O "?" era o placeholder (images/icon-questionmark) esperando o
         webservice. O cliente pede {"type":"boostedcreature"} ao
         Services.status e desenha a criatura pelo RACEID, com os assets
         locais -- nao e' imagem baixada. O login.php do MyAAC ja respondia
         certo (creatureraceid + bossraceid); o que faltava era o
         Services.status, que vinha COMENTADO no init.lua. Ligado agora, e de
         quebra o contador de jogadores online do topo passa a funcionar.

         O modulo client_boosted, que eu tinha escrito no build 9 para mostrar
         a criatura do dia na tela de login, SAIU: o painel embutido faz a
         mesma coisa melhor, desenhando do proprio asset em vez de baixar PNG
         do site. Ter os dois seria redundante. Os arquivos continuam no
         pacote, so' nao sao mais carregados.

         Mesmo executavel do build 10.

build 14 Sai o painel "Enabling Boosted Creature Panel" do menu de baixo.

         Era a dica que o OTClient mostra para quem NAO configurou o
         webservice. A condicao de origem no bottommenu.lua e'
         "if not Services.status and default_info", mas alguem a comentou e
         deixou "if default_info" solto -- entao a dica aparecia sempre.

         Pior: esse mesmo bloco faz monsterOutfit:setVisible(false) e poe o
         icon-questionmark no lugar, o que contribuia para o "?" das duas
         vagas de Boosted.

         Restaurada a condicao original. Com o Services.status ligado (build
         13) a dica nao aparece mais e o espaco fica para a criatura do dia.
         Quem algum dia rodar este cliente sem webservice volta a ver a dica,
         que e' exatamente para isso que ela existe.

         Mesmo executavel do build 10.

build 15 Conserta duas regressoes que EU causei no build 14.

         1. O SPRITE DO BOSS SUMIU. As variaveis monsterImage e bossImage eram
            resolvidas DENTRO do bloco da dica. Ao restaurar a condicao, o
            bloco parou de rodar e as duas ficaram nil -- e o
            applyToBoostedSlot chama imageWidget:setVisible(false) nelas. O
            erro derrubava a chamada da criatura no meio (o outfit ja tinha
            sido aplicado, por isso ela aparecia) e a do boss, que vem depois,
            nunca acontecia. Agora as duas sao resolvidas sempre, fora do if.

         2. CAIXA "Random Hint" VAZIA no rodape. Sem a dica, nada preenchia a
            showOffWindow e sobrava o titulo padrao do .otui. Agora ela e'
            escondida quando nao ha o que mostrar.

         Mesmo executavel do build 10.

build 16 O painel do rodape agora mostra avisos NOSSOS em vez de sumir.

         Esconde-lo (build 15) deixava um vao cinza de 605px no rodape, porque
         a barra continua desenhando o fundo dela e a janela de eventos esta
         ancorada a' direita do painel escondido.

         Entao em vez de esconder, o painel foi reaproveitado: tres avisos que
         giram a cada abertura -- o servidor e o site, a compra de coins por
         Pix e as vantagens do VIP. Substituem a dica de configuracao do
         OTClient, que so' servia para quem ainda nao ligou o webservice.

         O "?" no lugar da criatura ficou condicionado a nao haver
         Services.status, que e' o unico caso em que ele faz sentido.

         Para mexer nos textos: modules/client_bottommenu/bottommenu.lua,
         tabela default_info no topo. Acrescentar item e' so' copiar um bloco.

         Mesmo executavel do build 10.

build 17 O menu de baixo (nome do servidor, avisos, Event Schedule e Boosted)
         continuava na tela DEPOIS do login, por cima do jogo.

         Quem escondia era so' o client_entergame, de fora e atravessando um
         g_modules.getModule("client_bottommenu"):isLoaded(). Basta esse
         caminho falhar -- e ele falha calado -- para o painel ficar.

         Agora o proprio modulo escuta onGameStart/onGameEnd e se esconde
         sozinho, sem depender de ninguem. A chamada do entergame continua e
         nao atrapalha: esconder duas vezes nao faz mal.

         Mesmo executavel do build 10.

build 18 O menu de baixo AINDA ficava na tela depois do login. O build 17 ligou
         o proprio modulo ao onGameStart, mas o log do cliente mostrou que nao
         houve erro nenhum de Lua e que o painel estava todo preenchido -- ou
         seja, o init() rodou inteiro e o connect FOI feito. Como isso descarta
         a explicacao do build 17, aqui vao duas defesas:

         1. O connect passou para a PRIMEIRA linha depois do displayUI. A partir
            do displayUI o painel ja' esta na tela; deixar o connect no fim do
            init significa que qualquer erro no meio deixaria o painel orfao.

         2. O hide() agora varre o rootWidget e esconde TODO painel com id
            bottomMenu, nao so' o que a variavel do modulo aponta. Se sobrar um
            painel de uma instancia anterior -- recarga de modulos pelo updater,
            init rodando duas vezes -- a variavel so' alcanca o ultimo, e o
            antigo fica por cima do jogo sem ninguem para escondê-lo.

         O hide() tambem passou a registrar no log quantos paineis escondeu.
         Se o problema persistir, essa linha diz na hora qual e' o caso: se ela
         nao aparecer, o hide nao esta sendo chamado; se disser 2, sobrou painel.

         Mesmo executavel do build 10.

         NO MESMO DIA, NO SERVIDOR (nao precisa atualizar o cliente): corrigido
         o bug do corpo invisivel e do sqm que virava parede. O cliente estava
         recebendo, junto com a morte do monstro, um evento 0x75 do tipo 13
         ("spell desbloqueada") que o OTClient nao conhece. Como o tamanho do
         payload depende do tipo, ele nao tem como pular e lanca -- DESCARTANDO
         O RESTO DO PACOTE. E o resto do pacote era justamente o 0x6C (tirar o
         monstro morto do tile) e o 0x6A (por o corpo). Dai o corpo invisivel e
         a criatura fantasma segurando o sqm. Achado no coxaot-crystal.log, que
         mostrava os bytes descartados com o "Loot of a rat" ainda dentro.

build 19 A tela de escolha de personagem dizia "Premium Account (X days left)".
         Agora diz "VIP (X days left)".

         O numero ja' estava certo e nao mudou: o cliente calcula premDays a
         partir do premiumuntil que o login.php do MyAAC manda, e esse campo e' o
         lastday da conta -- que e' exatamente a validade do VIP. Neste servidor
         o freePremium esta ligado, entao premium e' de graca para todo mundo e
         nao ha o que anunciar; quem se paga e' o VIP. So' o nome estava errado.

         DE QUEBRA, UM DEFEITO QUE IA APARECER AGORA: premDays e' o PISO em dias.
         Com menos de 24h restantes ele vale 0, e o codigo tratava 0 como "conta
         premium gratuita", exibindo "Gratis Premium Account". Ou seja, dizia a
         coisa errada bem para quem esta acabando -- e diria para TODO jogador
         novo uma hora depois de ganhar o dia de VIP de boas-vindas. Agora esse
         caso exibe "VIP (less than 1 day left)" e acende o destaque de aviso.

         Mesmo executavel do build 10.

build 20 Botao direito numa pilha de moeda agora CONVERTE na hora.

         Antes o clique direito virava a mira de "usar com" e ainda exigia um
         segundo clique na propria pilha para converter. Agora e' um clique so':
         100 gold -> 1 platinum, 100 platinum -> 1 crystal, e o caminho de volta
         (1 crystal -> 100 platinum, 1 platinum -> 100 gold).

         O servidor sempre esteve certo -- o data/scripts/actions/items/
         change_gold.lua ja fazia a conversao. Quem pedia alvo era o CLIENTE: o
         isMultiUse() da moeda vem da flag multiuse do appearances.dat, que e'
         dado do Tibia, nao nosso. Por isso a excecao e' por id (3031/3035/3043).

         A correcao ficou no startUseWith, que e' o funil por onde passam os 17
         pontos que decidem entre usar e usar-com -- mapa, container, inventario,
         hotkey e action bar. Poe a regra num lugar so em vez de repeti-la
         dezessete vezes e esquecer a decima oitava.

         O menu de contexto usa a MESMA regra, senao ele ofereceria "Use with ..."
         e o clique converteria -- dizendo uma coisa e fazendo outra.

         Mesmo executavel do build 10.

build 21 CONSERTA A REGRESSAO DO BUILD 20: converter moeda parou de funcionar de
         vez, com "You cannot use this object" em qualquer clique.

         O build 20 trocou o "usar com" por g_game.use na moeda. So' que o
         SERVIDOR recusa use puro em item multiuse: o Game::playerUseItem
         (game.cpp:4143) responde CANNOTUSETHISOBJECT antes de chegar em acao
         nenhuma. As duas funcoes sao espelho uma da outra --

             playerUseItem   exige  !item->isMultiUse()
             playerUseItemEx exige   item->isMultiUse()

         -- entao moeda, que e' multiuse pela flag do appearances.dat, so' pode
         passar pelo caminho Ex. Eu tinha conferido o funil do CLIENTE com
         cuidado e nao conferi se o servidor aceitaria; o teste era um clique.

         Agora o clique direito manda a moeda "usar com" ELA MESMA, que e'
         exatamente o que o jogador fazia a mao com a mira -- so' que num clique.
         Mesmo caminho de rede que ja funcionava, sem tocar no servidor.

         Item virtual de hotkey/action bar ficou de fora: ele nao tem posicao,
         entao nao serve de alvo para si mesmo. Ali continua a mira de sempre.

         Mesmo executavel do build 10.

build 22 Oferta de Tibia Coin no Market saia com quantidade fora do multiplo de 25
         -- pedia 25 e criava 21 -- e a oferta ficava PRESA: comprar tambem exige
         multiplo de 25, entao ninguem conseguia aceitar.

         E' um problema de ORDEM, e por isso as vezes funcionava. O rotulo da
         quantidade e' escrito pelo updateCreateCount, que so' encaixa no passo se
         o passo ja' for 25 -- mas quem poe o passo em 25 e' o onPiecePriceEdit,
         que so' roda quando o jogador digita o PRECO. Como o jogador arrasta a
         barra primeiro e digita o preco depois, o rotulo ficava com o valor cru
         da barra (21, 61, 149, 317, 444...). E o createMarketOffer le' o ROTULO,
         nao a barra.

         Duas correcoes: o onPiecePriceEdit reencaixa o rotulo ao ligar o passo, e
         o createMarketOffer encaixa de novo antes de montar a oferta -- este e' o
         unico ponto que fala com o servidor, entao e' onde a garantia vale.

         O encaixe usa PISO, para nunca vender mais do que se pediu, com uma
         excecao: abaixo do primeiro degrau ele sobe para 25, senao arrastar a
         barra ate 21 deixaria o campo em 0.

         Ofertas fora do passo que ja estao no market podem ser CANCELADAS pelo
         dono normalmente -- o cancelamento devolve as coins integralmente
         (game.cpp:10477, addCoins Transferable).

         Mesmo executavel do build 10.

build 25 A QUANTIDADE DA OFERTA ERA LIDA EM BASE 8. Uma linha.

             local amount = tonumber(n:gsub("%D", ""))

         O gsub devolve DOIS valores -- a string e a CONTAGEM de substituicoes -- e,
         como a chamada e' o ultimo argumento, os dois iam para o tonumber. O
         segundo parametro do tonumber e' a BASE. O rotulo e' "Amount: 200", que tem
         8 caracteres nao-digitos, entao a contagem era 8 e o numero era lido em
         OCTAL. Corrigido com um par de parenteses, que trunca para um valor so'.

         Bate com todas as ofertas erradas que ficaram no banco:

             pediu 25  -> 21     pediu 674 -> 444
             pediu 200 -> 128    pediu 475 -> 317
             pediu 175 -> 125    pediu 225 -> 149
             pediu 75  -> 61

         Explica tambem por que as vezes dava "Invalid amount or price": 8 e 9 nao
         existem em octal, entao qualquer quantidade com esses digitos virava nil.

         E explica o debito "errado" que o dono notou: o servidor recebeu 128 e
         debitou 128 certinho -- ele nunca soube que o pedido era 200.

         OS BUILDS 23 E 24 FORAM RETIRADOS. Eles trocavam o rotulo por caixa de
         digitar, atacando um diagnostico errado meu (achei que a barra de 150px nao
         alcancava valor exato). O 23 quebrou a criacao de oferta e o cliente foi
         revertido para o 22. Este build parte do 22 e muda SO' esta linha -- a
         interface nao foi tocada.

         Mesmo executavel do build 10.

build 26 Sprite proprio da loot pouch (item 23721), azul no lugar da vermelha
         padrao do Tibia. Arte do dono.

         COMO FOI FEITO, porque nao e' obvio e vai se repetir:

         Os assets do 15.25 NAO sao .dat/.spr -- o Object Builder nao abre. Sao
         um appearances.dat em protobuf mais 4927 folhas sprites-<sha256>.bmp.lzma,
         indexadas pelo catalog-content.json. Cada folha e' um BMP 384x384 BGRA
         de baixo para cima, comprimido em LZMA1 cru atras de um cabecalho de 32
         bytes da CIP.

         O item 23721 tem UM sprite (id 212383), 32x32, no slot 40 da folha
         sprites-7aebe531...bmp.lzma. Trocamos so' os pixels desse slot, pelo
         /root/trocar-sprite.py. Nada de editor grafico: qualquer Assets Editor
         reserializa o appearances.dat INTEIRO ao salvar e um campo que ele nao
         conheca some calado, levando junto os outros 42 mil itens.

         Como o nome do arquivo nao mudou, o catalog-content.json continua valido
         e nenhum outro asset foi tocado. Conferido com /root/conferir-folha.py:
         494 pixels diferentes dentro do sprite alvo, ZERO fora dele.

         O transparente e' gravado como magenta puro (FF00FF) com alpha 0, que e'
         a convencao da propria folha -- o cliente reconhece FF00FF e zera.

         DISTRIBUICAO: os assets vem do GitHub (dudantas/tibia-client, tag
         15.25.0a00a0) e ficam em data/things/1525, que o gerar-manifesto.py
         pulava de proposito. Foi criada a lista INCLUIR_MESMO_ASSIM la' dentro,
         com o caminho exato desta folha. SEM ISSO a proxima publicacao deixaria
         a folha fora do manifesto e a pouch voltaria a ser vermelha sem aviso.

         Instalacao NOVA pega a pouch vermelha na primeira sessao: o updater roda
         no boot e o download dos assets so' acontece quando se clica para entrar,
         sobrescrevendo o arquivo. No boot seguinte o CRC nao bate e o updater
         devolve o nosso. Some sozinho, mas e' bom saber.

         Mesmo executavel do build 10.

PENDENCIAS CONHECIDAS
---------------------
- O parseClientEvent do cliente so' conhece os tipos de evento 1 a 10; os tipos
  11 (bounty task), 12 (weekly task) e 13 (spell) fazem ele lancar e descartar o
  resto do pacote. O servidor foi ensinado a nao mandar esses tres, o que resolve
  para quem joga aqui, mas o certo e' o cliente aprender a le-los -- e, de
  preferencia, nao deixar um subtipo desconhecido derrubar o pacote inteiro.
- A issue opentibiabr/otclient#1738, que reportava a barra
  de XP junto com outros dois defeitos, tambem menciona fragmentos exibindo o
  saldo do banco e a forge abrindo vazia pelo botao (pela forge da ilha
  funciona). Esses dois ainda nao foram investigados.
