# Opcoes especiais de item ("Caracteristicas especiais")

Investigado em 27/08 a partir de duas capturas de ecra do jogador: uma arma
`[+9 A poderosa arma Akemon]` e um `[Colar Do Marshaldin]`, ambos com uma linha
**"Multiplicador de critico"** entre as caracteristicas especiais (+0,15 na arma, +0,02 no
colar).

## O que se procurava

Saber de onde vem esse numero, para que um item que aumente o multiplicador de critico
tenha efeito no combate em vez de ser ignorado.

## O que ficou provado

**Nao e atributo fixo do item.** A tabela de atributos (`itemAttr.lod`, exportada para
`Data/Items/ItemOption.ini`) tem uma linha por (item, refino) e so guarda dano, dano magico,
velocidade de ataque, alcance, defesa e defesa magica. Confirmado contra a propria captura de
ecra: a arma Akemon (item 21212) com refino 9 tem `dano_min 603,91` e `dano_max 674,89`, e a
imagem mostra "Ataque: 603 - 674"; o alcance 160 tambem bate. Os numeros base estao todos
certos -- as caracteristicas especiais e que nao estao la.

**Nao esta em mais nenhuma tabela exportada.** As cinco colunas `bonus8..bonus12` do
ItemOption estao a zero em 34.220 das 34.336 linhas. Nenhuma outra tabela tem coluna de
critico.

**A string existe, mas nada em dados a referencia.** `string.lod` tem o id 31370 =
"Multiplicador de critico" (e as traducoes: "Critical Damage", "Kritischer Multiplikator",
"Kritik carpan"). Procurando esse id como u32 em todos os `.lod` decifrados, ele aparece uma
unica vez: dentro da propria entrada do string.lod. Nenhuma tabela o aponta, portanto o
mapeamento opcao -> nome vive no executavel.

## O que os dois ficheiros novos parecem ser

    uniqueOptionType.lod    321 registos de 10 bytes
                            [0:2] indice sequencial   [2:6] codigo da opcao   [6:8] flag 0/1
                            codigos: 40101 40102 40103 40105 40110 40201 40202 ...
                            o padrao 40+grupo+subtipo e um codigo estruturado, nao um id de
                            string -- nenhum desses numeros existe em ficheiro de strings

    uniqueOptionDrop.lod     97 registos
                            listas de indices do uniqueOptionType (3, 7, 11, 15, 19...)
                            com pesos e limites

O nome do segundo -- *drop* de opcoes unicas -- explica tudo o resto: as caracteristicas
especiais nao pertencem ao ITEM, pertencem a INSTANCIA. Sao sorteadas quando o item cai, de
uma destas 97 tabelas, e ficam guardadas na instancia do lado do servidor. E por isso que
duas armas Akemon iguais podem ter opcoes diferentes, e por isso que nenhuma tabela do
client liga item a opcao: essa ligacao nao existe no client.

## O que ja esta preparado do nosso lado

`entity_sim.multiplicador_critico(entity_id)` soma o campo `critico_mult` de cada peca
equipada a base de 1,2. Hoje devolve sempre 1,2 porque nada preenche esse campo. Quando
houver instancias de item com opcoes, basta que a leitura do equipamento traga `critico_mult`
e o combate passa a respeita-lo sem mais nenhuma alteracao.

## RESOLVIDO em 27/08: os nomes das 321 opcoes

O mapeamento estava mesmo no executavel, num `switch`. Encontrado assim:

1. `KarosOptionNames.java` procurou, em todo o AMo.exe, os sitios onde cada id de string de
   atributo aparece como constante. **Os 20 cairam todos na mesma funcao**, `FUN_006076cb` --
   foi isso que a identificou.
2. `KarosDecompOpcoes.java` descompilou-a. E um `switch` que recebe um indice e devolve o id
   de string. 98 casos.

Os dois correm em HEADLESS, sem interface -- a porta 8080 do GhidraMCP so abre quando ha um
programa carregado no CodeBrowser, e isso exige cliques:

    analyzeHeadless D:\Cabal\Tools\ghidra_projects KarosOnline
        -process AMo.exe -noanalysis
        -scriptPath D:\Cabal\Tools\ghidra_projects
        -postScript KarosOptionNames.java

**A regra e `grupo % 100`.** O codigo 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, ou seja 304 das 321 linhas. 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. Ficam com `?` -- nao se inventou nenhum.

### RESOLVIDO em 28/08: era falsa oposicao, e as duas leituras estavam certas

O switch tem **98 casos e vai ate 136** -- ha `case 100`, `case 101`, `case 108`, `case 136`.
Ele serve DOIS consumidores diferentes:

    uniqueOptionType   argumento = grupo % 100    so produz 0..99    opcoes SORTEADAS no drop
    skillData          argumento = coluna `tipo`  chega a 136        opcoes FIXAS do item

A asa que o jogador viu vinha do segundo caminho, por isso mostrava um numero que o primeiro
nunca alcanca. `grupo % 100` continua certo para o uniqueOptionType, e nao ha nada a corrigir
em `Data/Items/UniqueOptionType.ini`.

O caso 108 e mesmo `string 37077 = "Poder de ataque de habilidade"`, confirmado contra o
`string.lod`.

## 28/08: a cadeia completa das opcoes FIXAS

As "[Asas de anjo (24 horas)]" (item 999900058) mostram tres linhas na caixa de texto --
Saude +1004,00, Mana +328,00, Poder de ataque de habilidade +3%. O `bruto` do item na nossa
base vem todo a ZERO depois do byte 20, ou seja o servidor nao manda opcao nenhuma: quem as
desenha e o CLIENT, das suas proprias tabelas. Sao fixas do item, iguais em toda copia dele.

    itemAttr, 5 ranhuras no fim do registo  ->  30000300   (uma skill passiva)
    skillLevel, registo dessa skill, +264   ->  5 ids de skillData: 30003101, 30003201,
                                                30108003
    skillData (id, tipo, fixo, percentual)  ->  46 fixo 1004 | 47 fixo 328 | 108 pct 3
    FUN_006076cb(tipo)                      ->  "Saude", "Mana", "Poder de ataque de
                                                habilidade"

Os tres valores batem ao centimo com o ecra. As cinco colunas `bonus8..bonus12` do
ItemOption, que estavam anotadas como "a zero em 34.220 das 34.336 linhas", eram estas
ranhuras: nao sao numeros, sao ids de skill. Lidas como double davam denormais absurdos --
o da asa saia 1,48e-316 -- e foi esse numero impossivel que denunciou o que elas eram.

O que a caixa de texto mostra em PERCENTAGEM vem de outro campo do mesmo registo: valor fixo
em +24, percentual em +32. O decompilado escolhe entre os dois (`if fixo == 0 entao usa o
percentual`) e muda o formato de `+%.1f` para `+%.1f%%`.

Extraido por `tools/importar_bonus_de_item.py` para `Data/Items/ItemBonus.ini`: 7221 linhas
em 1410 itens. Aplicado pelo servidor em `db.bonus_do_equipamento` ->
`world_server.bonus_de_opcoes` -> ficha, e `Heroi.aplicar_bonus_de_equipamento` para o
maximo de HP/MP.

O QUE FICA DE FORA, de proposito:

  - **43 dos 803 ids de ranhura nao resolvem** pela cadeia (5%). Ficam sem linha.
  - **60 dos 4146 registos do skillData** nao terminam com o enchimento que serve de ancora
    e por isso nao entram. Nao se inventou nada para eles.
  - **7 tipos** (61, 67, 68, 69, 76, 77, 78) nao aparecem nos ramos que o decompilador
    mostrou e ficam com nome `?`.
  - **Os percentuais nao sao aplicados**: um percentual so tem sentido contra a base a que se
    aplica, e o unico que aparece hoje -- poder de ataque de habilidade -- depende de skills,
    que ainda nao existem. Ficam guardados em `percentuais_de_opcoes` e nao entram na ficha.
  - **A duracao dos itens temporarios nao existe**: as asas sao de 24 horas (86400 s no campo
    31 do itemBase, ja decodificado) e o nosso servidor nunca as expira.

### NOTA HISTORICA: a duvida de 27/08

Ha uma segunda leitura possivel, e uma observacao do jogador aponta para ela. As duas:

    caso = grupo % 100      48 de 53 grupos    91 %     <- a que esta aplicada
    caso = indice da linha  98 de 321 linhas   31 %

A cobertura favorece a primeira com folga, e os nomes que dela saem tem uma estrutura
coerente: os grupos 401..410, 701..721 e 201..210 dao os MESMOS atributos pela MESMA ordem,
ou seja tres categorias a oferecer o mesmo leque -- que e como estes jogos se organizam.

MAS o jogador diz que a asa que equipou no oficial mostrava uma opcao de "poder de
habilidade", e o caso 108 e exatamente "Poder de ataque de habilidade". `grupo % 100` NUNCA
chega a 108, porque o resto e sempre menor que 100. Pela regra do indice, a linha 108 e o
grupo 207 -- que na regra aplicada ficou como "A forca atual de um ataque fisico".

Tentou-se desempatar pela asa (item 999900040) e ela nao serve: nao tem opcao nenhuma nos
dados. Nem no Item.ini -- o blob decifra em id, nivel 25, tipo 17, duracao 604800 (7 dias) e
mais nada -- nem no ItemOption.ini, que so lhe da dano 13-30, magico 25-40 e defesa 50/8, nem
nos 7 pacotes 06 06 que a transportam, com 27 bytes uteis e o resto a zero. O que confirma,
mais uma vez, que a opcao e da INSTANCIA e vive no servidor.

O que decide: um item com opcao visto na loja, com o NOME do item e o TEXTO EXATO da linha
anotados, mais a gravacao desse momento. Se o numero que aparecer no registo for 108 e a
linha disser "Poder de ataque de habilidade", a regra e o indice; se for 207, e o resto.

Resultado em `Data/Items/UniqueOptionType.ini`, coluna `descricao`.

## O QUE FALTA: ligar a opcao a INSTANCIA do item

O pacote da loja de consignacao e o `06 1e`, ~1700 bytes, com registos de **166 bytes** cada
(medido pelo espacamento dos nomes de vendedor, que sao texto ASCII e servem de baliza).

Numa captura de 10 desses pacotes -- 27/08, loja de consignacao -- os registos vem quase
todos a ZERO na zona depois do nome. Ou os itens que estavam a venda nao tinham
caracteristicas especiais, ou elas viajam noutro pacote que so sai quando se abre a caixa de
texto de um item concreto.

Para fechar isto falta uma coisa simples: **abrir na loja a caixa de texto de um item que
mostre "Multiplicador de critico", anotar o valor exato que ela mostra, e gravar esse
momento**. Com o valor visto no ecra e o registo correspondente no fio, a correspondencia
entre grau e numero fecha-se, e com ela a escala de todas as opcoes.

`tools/achar_opcoes_no_fio.py` ja varre uma captura a procura dos codigos conhecidos.

## 27/08, a captura decisiva: o servidor manda o RESULTADO, nao as opcoes

O jogador equipou uma "[+7 A poderosa arma osso de dragao]" (item 21210, refino 7) no oficial
e gravou. A caixa de texto dela declara quatro caracteristicas especiais:

    Poder magico. ataques   +5,00
    Vitalidade              +3,00
    Forca de Ataque Fisico  +5,00
    Classificacao de ataque +2,00

Procurou-se o codigo dessas opcoes em todos os pacotes que transportam o item. NAO ESTA LA.
O `06 06` (158 bytes) traz o item_id em [13:17] e o refino em [19], e o resto vem a zero.

O que MUDA quando a arma entra e o `08 02`, a ficha de atributos -- e o que ele traz e o
TOTAL JA SOMADO, nao os pares (codigo, valor):

    offset  90  float   55,225 -> 60,225   +5,00   Forca de Ataque Fisico
    offset  98  float   14,000 -> 16,000   +2,00   Classificacao de ataque
    offset 118  float   56,110 -> 61,110   +5,00   Poder magico. ataques
    offset  28  u16          0 -> 3        +3      Vitalidade (o bonus, nao o total)
    offset  62  float   10,000 -> 160,00           alcance da arma
    offset 102  float   61,495 -> 81,495   +20,00  defesa

ISTO MUDA A CONCLUSAO ANTERIOR. Nao e preciso decifrar as opcoes por instancia para o client
mostrar a ficha certa nem para o combate bater: basta mandar os atributos somados no 08 02,
que e o que o servidor oficial faz. A tabela de opcoes so passa a ser precisa no dia em que
o nosso servidor GERAR itens com opcoes proprias -- ou seja, quando houver drop.

De passagem, os cinco offsets que ja conheciamos do 08 02 (62, 90, 94, 102, 118) foram
confirmados um a um por esta captura, medidos de forma independente.

## Como avancar, por ordem de custo

1. **Medir no fio.** A arma Akemon +9 com "+0,15" foi vista na LOJA DE CONSIGNACAO do
   oficial, nao e do jogador. Se alguma vez estiver na mao de alguem que a possa equipar,
   bater num mob conhecido com e sem ela da o efeito real do multiplicador. Bom para validar;
   nao chega para gerar itens.

2. **Ver como o servidor oficial manda as opcoes.** Quando um item com opcoes entra no
   inventario, o pacote de item tem de as trazer -- o client desenha a caixa de texto a partir
   de alguma coisa. Comparar o pacote de um item com opcoes e de um sem, byte a byte, deve
   revelar onde vao os pares (codigo, valor).

3. **Ghidra no AMo.exe.** Procurar quem le o `uniqueOptionType` e onde o codigo 40xxx vira
   texto. Da o mapeamento completo dos 321 tipos de uma vez, mas e o caminho mais caro.

O caminho 2 e o melhor: usa o metodo que ja funcionou para tudo o resto neste projeto, e um
item com opcoes visto no fio resolve as duas pontas ao mesmo tempo.

## 28/08: o Carat e a moeda, e nao esta no inventario

O item 999999 chama-se "Carat" e a descricao dele no proprio catalogo do jogo e **"Moeda do
mundo Karosa"**. O `empilha_max` e 1410065408, que e 10 mil milhoes truncado a 32 bits --
numero de moeda, nao de item. O jogador confirmou: "quilates, e o carat a moeda do jogo, nao
tem alz".

MEDIDO em 28/08 na captura de 27/08 (770 recolhas):

  - aparece 770 vezes no `06 03` (apanhar), com o id em [14:18] BE
  - **nunca aparece no `06 05`**, o pacote do inventario -- zero ocorrencias

Ou seja: o Carat NAO e um item da mochila. E apanhado como item mas guardado noutro sitio,
provavelmente um contador do personagem. Falta achar o campo: nem `dbo.personagem` tem
coluna de moeda, nem se localizou o numero no 08 01 ou no 08 02.

Isto destranca as 58 lojas que ja estao extraidas com precos e paradas por nao haver saldo.
