# Integrar o CXG-GTFAT a outros recursos

Os arquivos `bridge/*.lua` são pontos editáveis de adaptação para conectar permissões, persistência, avisos, estações e formatos de aparência. Os patches de menus são alterações manuais para versões específicas; o recurso não modifica outros recursos durante a instalação.

## Menus de aparência

Há patches preparados para estes repositórios e revisões:

| Menu | Revisão de origem | Ação adicional |
| --- | --- | --- |
| illenium-appearance | 1b003ec169b15f145d519438ba7c6454bf746f27 | Aplicar o patch Lua de salvamento de componentes. |
| fivem-appearance | b06da32881ed49042909b38a778c10dd9bd9eaed | Aplicar o patch e reconstruir o bundle TypeScript do jogo pelo fluxo `build:game`. |
| qb-clothing | 8cca4009dd473ab30c30e443ecad50830b816ed7 | Aplicar o patch nos pontos de salvamento da skin e do outfit. |
| esx_skin / skinchanger | fe59ca0bd6da59e2ec6eb4a8d06ece312e96ae7a | O patch é relativo à raiz do repositório esx_core. |

A pasta `integrations` do pacote contém `illenium-appearance.patch`, `fivem-appearance.patch`, `qb-clothing.patch` e `skinchanger.patch`. Eles não são aplicados automaticamente. Em um checkout de desenvolvimento do menu, confira o patch correspondente antes de aplicá-lo:

~~~sh
git apply --check /caminho/do-patch-correto.patch
git apply /caminho/do-patch-correto.patch
~~~

Se a verificação falhar, pare e adapte a alteração à versão instalada; não force a aplicação. Forks e revisões mais recentes podem ser diferentes. O patch fivem-appearance exige reconstruir o bundle pelo fluxo `build:game` do próprio menu. Esses patches não certificam todas as versões nem substituem testes no seu servidor.

O salvamento higieniza o decal técnico para impedir que o menu o persista como uma escolha comum do jogador. Menus que mantêm sua própria tabela de aparência devem chamar a higienização no ponto de salvamento. O peso fica separado dos outfits.

## Salvar uma aparência própria

O export do cliente `SanitizeAppearance` retorna uma cópia da aparência. Os formatos incluídos são components, qb e esx:

~~~lua
local appearanceCopy = exports['CXG_GTFAT']:SanitizeAppearance(
    ped,
    appearance,
    'components'
)

GuardarApariencia(appearanceCopy)
~~~

`GuardarApariencia` é uma chamada de exemplo; substitua-a pela função de salvamento do seu menu. components aceita uma matriz de componentes ou um objeto com a propriedade `components`; cada componente usa `component_id`, `drawable`, `texture` e, opcionalmente, `palette`. O formato qb usa o campo `decals` com `item` e `texture`; esx usa `decals_1` e `decals_2`. O export retorna uma cópia profunda, preserva os outros campos e não altera temporariamente o ped. Se o adaptador falhar ou o formato não for reconhecido, retorna a cópia sem alterações e registra um diagnóstico.

## Registrar um ped de pré-visualização

Se o menu usa um ped de pré-visualização separado do personagem, registre o preview do personagem ativo. Remova o registro antes de excluir a entidade:

~~~lua
local previewPed = ObtenerPreviewActual()

if previewPed and DoesEntityExist(previewPed) then
    exports['CXG_GTFAT']:RegisterPreviewPed(previewPed)
end

-- Ao fechar ou cancelar o menu, antes de excluir o ped:
exports['CXG_GTFAT']:UnregisterPreviewPed(previewPed)
EliminarPreview(previewPed)
~~~

`ObtenerPreviewActual` e `EliminarPreview` são chamadas de exemplo; substitua-as pelas funções do seu menu. Registre somente o preview do personagem ativo, não peds do mundo nem previews de outros personagens. Como alternativa, defina `Config.GetPreviewPed` para retornar o ped atual ou nil. `RefreshAppearance(previewPed)` solicita reavaliação de um ped já gerenciado; não ativa Fat por si só.

## Exports para recursos do servidor

Os exports do servidor são APIs privilegiadas para outros recursos do servidor. Não os encaminhe diretamente por um evento que possa ser chamado pelo cliente.

~~~lua
local playerSource = source -- ID do jogador no servidor
local kg, err = exports['CXG_GTFAT']:GetWeight(playerSource)
if kg == nil then
    print('Peso não confirmado:', err)
end

local appliedKg, setError = exports['CXG_GTFAT']:SetWeight(playerSource, 92.5)
if appliedKg == nil then
    print('Não foi possível definir o peso:', setError)
end

local addedKg, addError = exports['CXG_GTFAT']:AddWeight(playerSource, -2.0)
if addedKg == nil then
    print('Não foi possível adicionar peso:', addError)
end

local resetKg, resetError = exports['CXG_GTFAT']:ResetWeight(playerSource)
if resetKg == nil then
    print('Não foi possível redefinir o peso:', resetError)
end
~~~

O argumento `source` é o ID do jogador no servidor, não um valor enviado pelo cliente. Todos os valores estão em kg. `GetWeight` retorna o peso ou nil, error. `SetWeight` e `AddWeight` retornam o peso aplicado normalizado em kg ou nil, error; `SetWeight` rejeita valores fora da faixa e arredonda para o incremento mais próximo, arredondando empates para cima. `AddWeight` permite incrementos negativos. `ResetWeight` grava `defaultKg` e também retorna o peso aplicado em kg; não exclui o valor armazenado.

Para abrir a interface de um jogador a partir de outro recurso do servidor:

~~~lua
local playerSource = source -- ID do jogador no servidor
local opened, err = exports['CXG_GTFAT']:OpenWeightUI(playerSource)
if not opened then
    print('Não foi possível abrir a balança:', err)
end
~~~

É possível passar o ID opcional de uma estação como segundo argumento: `OpenWeightUI`(source, 'academia'). Sem estação, aplica a política de acesso dos comandos, mesmo se o registro de comandos estiver desativado. O resultado true confirma a autorização e o envio do pedido para abrir, mas não confirma que a interface já foi renderizada no cliente.

### Exports do servidor

| Export | Uso |
| --- | --- |
| `GetWeight(source)` | Retorna kg ou nil, error. |
| `SetWeight(source, kg)` | Define um peso validado e retorna o valor aplicado em kg ou nil, error. |
| `AddWeight(source, deltaKg)` | Soma uma diferença e retorna o valor aplicado em kg ou nil, error. |
| `ResetWeight(source)` | Salva e retorna o peso padrão configurado em kg ou nil, error. |
| `GetWeightSettings()` | Retorna uma cópia dos limites ativos. |
| `OpenWeightUI(source, stationId?)` | Solicita abrir a balança conforme a política aplicável. |
| `RefreshCharacter`(source) | Recarrega identidade e peso após o framework trocar para o personagem desejado. |
| `ReconcileStorage`(source) | Reconcilia o armazenamento após um estado incerto quando o adaptador consegue confirmar gravações anteriores. |

`RefreshCharacter` é opcional para integrações com vários personagens; chame-o depois que o framework disponibilizar a identidade correta. O recurso não se conecta ao Qbox automaticamente.

Os erros podem incluir `not_ready`, `invalid_source`, `player_unavailable`, `character_unavailable`, `invalid_weight`, `out_of_range`, `permission_denied`, `context_denied`, `busy`, `storage_failed`, `storage_timeout`, `storage_unknown` ou `not_synced`. Não considere um erro de armazenamento como peso confirmado nem tente automaticamente outra gravação após `storage_timeout` ou `storage_unknown`.

## Exports do cliente

~~~lua
local kg, err = exports['CXG_GTFAT']:GetWeight()
local settings = exports['CXG_GTFAT']:GetWeightSettings()
local isOpen = exports['CXG_GTFAT']:IsWeightUIOpen()
local closed = exports['CXG_GTFAT']:CloseWeightUI()
local status, statusError = exports['CXG_GTFAT']:GetFatStatus()
local reserved, reservedError = exports['CXG_GTFAT']:GetReservedDecals(PlayerPedId())
~~~

`GetWeight` retorna nil, '`not_synced`' até receber o valor confirmado pelo servidor. `GetFatStatus(ped?)` e `GetReservedDecals(ped)` oferecem informações de leitura; não comprovam que a geometria foi renderizada. `GetReservedDecals` pode ajudar a ocultar os decals reservados nos menus. `RefreshAppearance(previewPed?)` atualiza somente a aparência gerenciada do jogador ou de um preview registrado.

`RegisterPreviewPed(ped)` e `UnregisterPreviewPed(ped)` permitem que um menu entregue sua entidade de preview ao seletor. Só o recurso que registrou o ped pode removê-lo. Os previews registrados são limpos quando o recurso proprietário é encerrado.

`SanitizeAppearance(ped, appearance, format)` retorna uma cópia da aparência; nil, error não indica falha. `RegisterPreviewPed` e `UnregisterPreviewPed` retornam true quando a operação é concluída e false quando o ped não pode ser registrado ou removido.

## Bridges editáveis

| Arquivo | Adaptação |
| --- | --- |
| `bridge/server.lua` | `CanAccess`, identidade, carregamento/salvamento do peso e hooks do servidor. |
| `bridge/client.lua` | Ped de preview, notificações e hooks de alteração do peso/interface. |
| `bridge/interaction.lua` | Registro de estações em um target ou substituição do TextUI. |
| `bridge/appearance.lua` | Leitura/gravação de decals em um formato de aparência próprio. |

Por padrão, `CanAccess` reconhece somente ace e everyone. Para jobs, groups ou custom, implemente a consulta com a API real do servidor e retorne exatamente true quando conceder acesso. Exceções e outros valores negam o acesso. Os campos de política job, group e custom são dados interpretados pelo adaptador.

Um provedor de armazenamento customizado implementa as assinaturas `LoadWeight`(`characterId`, context, done), `SaveWeight`(`characterId`, kg, context, done) e, se precisar reconciliar gravações incertas, `ReconcileWeight`(`characterId`, context, done). O carregamento chama done(true, kg) (use kg = nil quando não houver valor) ou done(false, '`storage_failed`'). O salvamento chama done(true) somente após confirmação ou done(false, '`storage_failed`'). A reconciliação chama done(true, true) somente quando nenhuma gravação anterior poderá ser concluída posteriormente; se não puder garantir isso, chame done(false, '`storage_unknown`'). Para salvamentos realmente assíncronos, defina `AsyncStorage` = true; o callback deve concluir em um contexto FiveM que permita espera. A API aguarda até 10 segundos antes de informar timeout. Não tente novamente automaticamente após `storage_timeout`; primeiro reconcilie com um provedor que garanta que uma gravação anterior não será aplicada depois. Não declare um provedor assíncrono se ele retornar antes de iniciar ou ordenar a gravação.

Os hooks `OnWeightChanged`, `OnCharacterChanged`, `OnUIOpened`, `OnUIClosed` e Log servem para observar ou registrar alterações. Hooks não concedem acesso nem desfazem operações confirmadas.

## Verificações antes de habilitar

Patches e adaptadores precisam ser validados com as versões e os recursos reais do servidor. Antes de liberar a integração aos jogadores:

1. Repita a mudança Normal/Fat com personagens freemode masculinos e femininos.
2. Com Fat ativo, salve e recarregue a aparência e o outfit. Confirme que o decal técnico não foi salvo como uma escolha comum e que o peso continua separado do outfit.
3. Com a persistência por personagem ativada, reinicie o recurso e reconecte. Confirme que o mesmo personagem recupera o peso e que outro personagem não herda esse valor.
4. Abra o menu de aparência com um preview registrado e confirme que ele representa o personagem atual. Ao cancelar ou fechar o menu, remova o registro antes de excluir a entidade.

Concluir essas etapas no seu servidor é necessário para validar a combinação específica do menu, framework e roupas. A existência de um patch, por si só, não certifica esses resultados.
