# Integrare CXG-GTFAT con altre risorse

I file `bridge/*.lua` sono punti modificabili per collegare permessi, persistenza, notifiche, stazioni e formati dell’aspetto. Le patch per i menu sono modifiche manuali per versioni specifiche; durante l’installazione la risorsa non modifica altri resource.

## Menu dell’aspetto

Sono disponibili patch per questi repository e revisioni:

| Menu | Revisione sorgente | Azione aggiuntiva |
| --- | --- | --- |
| illenium-appearance | 1b003ec169b15f145d519438ba7c6454bf746f27 | Applicare la patch Lua per il salvataggio dei componenti. |
| fivem-appearance | b06da32881ed49042909b38a778c10dd9bd9eaed | Applicare la patch e ricostruire il bundle TypeScript del gioco con il flusso `build:game`. |
| qb-clothing | 8cca4009dd473ab30c30e443ecad50830b816ed7 | Applicare la patch ai punti di salvataggio di skin e outfit. |
| esx_skin / skinchanger | fe59ca0bd6da59e2ec6eb4a8d06ece312e96ae7a | La patch è relativa alla radice del repository esx_core. |

La cartella `integrations` del pacchetto contiene `illenium-appearance.patch`, `fivem-appearance.patch`, `qb-clothing.patch` e `skinchanger.patch`. Non vengono applicate automaticamente. In un checkout di sviluppo del menu, verificare la patch giusta prima di applicarla:

```sh
git apply --check /ruta/al/parche-correcto.patch
git apply /ruta/al/parche-correcto.patch
```

Se il controllo non riesce, fermarsi e adattare la modifica alla versione installata; non forzare la patch. Fork e revisioni più recenti possono differire. La patch fivem-appearance richiede di ricostruire il bundle con il flusso `build:game` del menu. Le patch non certificano tutte le versioni né sostituiscono un test sul server.

Il salvataggio rimuove il decal tecnico dall’aspetto salvato, evitando che il menu lo persista come scelta ordinaria del giocatore. I menu che mantengono una propria tabella dell’aspetto devono chiamare la sanitizzazione nel punto di salvataggio. Il peso resta separato dagli outfit.

## Salvare un aspetto personalizzato

L’export client `SanitizeAppearance` restituisce una copia dell’aspetto. I formati inclusi sono components, qb ed esx:

```lua
local appearanceCopy = exports['CXG_GTFAT']:SanitizeAppearance(
    ped,
    appearance,
    'components'
)

GuardarApariencia(appearanceCopy)
```

`GuardarApariencia` è una chiamata di esempio: sostituirla con la funzione di salvataggio del menu. components accetta un array di componenti o un oggetto con la proprietà `components`; ogni componente usa `component_id`, `drawable`, `texture` e, facoltativamente, `palette`. Il formato qb usa il campo `decals` con `item` e `texture`; esx usa `decals_1` e `decals_2`. L’export restituisce una copia profonda, conserva gli altri campi e non modifica temporaneamente il ped. Se l’adattatore fallisce o il formato non è riconosciuto, restituisce la copia invariata e registra una diagnosi.

## Registrare un ped di anteprima

Se il menu usa un ped di anteprima distinto dal personaggio, registrare l’anteprima del personaggio attivo. Rimuoverne la registrazione prima di eliminare l’entità:

```lua
local previewPed = ObtenerPreviewActual()

if previewPed and DoesEntityExist(previewPed) then
    exports['CXG_GTFAT']:RegisterPreviewPed(previewPed)
end

-- Al cerrar o cancelar el menú, antes de eliminar el ped:
exports['CXG_GTFAT']:UnregisterPreviewPed(previewPed)
EliminarPreview(previewPed)
```

`ObtenerPreviewActual` ed `EliminarPreview` sono chiamate di esempio: sostituirle con le funzioni del menu. Registrare solo l’anteprima del personaggio attivo, non i ped del mondo né le anteprime di altri personaggi. In alternativa, impostare `Config.GetPreviewPed` in modo che restituisca il ped corrente o nil. `RefreshAppearance(previewPed)` richiede una nuova valutazione di un ped già gestito; non attiva Fat da solo.

## Export per risorse server

Gli export server sono API privilegiate per altre risorse server. Non inoltrarli direttamente da un evento che il client può invocare.

```lua
local playerSource = source -- ID del jugador en el servidor
local kg, err = exports['CXG_GTFAT']:GetWeight(playerSource)
if kg == nil then
    print('No hay un peso confirmado:', err)
end

local appliedKg, setError = exports['CXG_GTFAT']:SetWeight(playerSource, 92.5)
if appliedKg == nil then
    print('No se pudo fijar el peso:', setError)
end

local addedKg, addError = exports['CXG_GTFAT']:AddWeight(playerSource, -2.0)
if addedKg == nil then
    print('No se pudo sumar peso:', addError)
end

local resetKg, resetError = exports['CXG_GTFAT']:ResetWeight(playerSource)
if resetKg == nil then
    print('No se pudo restablecer el peso:', resetError)
end
```

L’argomento `source` è l’ID del giocatore sul server, non un valore inviato dal client. Tutti i valori sono in kg. `GetWeight` restituisce il peso oppure nil, error. `SetWeight` e `AddWeight` restituiscono il peso applicato normalizzato in kg oppure nil, error; `SetWeight` rifiuta valori fuori intervallo e arrotonda all’incremento più vicino, con i pareggi arrotondati per eccesso. `AddWeight` accetta incrementi negativi. `ResetWeight` imposta `defaultKg` e restituisce anche il peso applicato in kg; non elimina il valore memorizzato.

Per aprire l’interfaccia a un giocatore da un’altra risorsa server:

```lua
local playerSource = source -- ID del jugador en el servidor
local opened, err = exports['CXG_GTFAT']:OpenWeightUI(playerSource)
if not opened then
    print('No se pudo abrir la báscula:', err)
end
```

Come secondo argomento si può passare un ID stazione facoltativo: `OpenWeightUI`(source, 'gimnasio'). Senza stazione, si applica la policy di accesso dei comandi anche se la registrazione dei comandi è disabilitata. Il risultato true conferma l’autorizzazione e l’invio della richiesta di apertura, non che l’interfaccia sia già stata renderizzata dal client.

### Export server

| Export | Uso |
| --- | --- |
| `GetWeight(source)` | Restituisce kg oppure nil, error. |
| `SetWeight(source, kg)` | Imposta un peso convalidato e restituisce i kg applicati oppure nil, error. |
| `AddWeight(source, deltaKg)` | Aggiunge una variazione e restituisce i kg applicati oppure nil, error. |
| `ResetWeight(source)` | Salva e restituisce il peso iniziale configurato in kg oppure nil, error. |
| `GetWeightSettings()` | Restituisce una copia dei limiti attivi. |
| `OpenWeightUI(source, stationId?)` | Richiede l’apertura della bilancia secondo la policy applicabile. |
| `RefreshCharacter`(source) | Ricarica identità e peso dopo il cambio del personaggio previsto da parte del framework. |
| `ReconcileStorage`(source) | Riconcilia l’archiviazione dopo uno stato incerto se l’adattatore può confermare scritture precedenti. |

`RefreshCharacter` è facoltativo per le integrazioni multicharacter; chiamarlo dopo che il framework ha reso disponibile l’identità prevista. La risorsa non collega automaticamente Qbox.

Gli errori possono includere `not_ready`, `invalid_source`, `player_unavailable`, `character_unavailable`, `invalid_weight`, `out_of_range`, `permission_denied`, `context_denied`, `busy`, `storage_failed`, `storage_timeout`, `storage_unknown` o `not_synced`. Non interpretare un errore di archiviazione come peso confermato e non ripetere automaticamente una scrittura con risultato `storage_timeout` o `storage_unknown`.

## Export client

```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` restituisce nil, '`not_synced`' finché non riceve un valore confermato dal server. `GetFatStatus(ped?)` e `GetReservedDecals(ped)` forniscono informazioni in sola lettura e non dimostrano che la geometria sia stata renderizzata. `GetReservedDecals` può servire a nascondere i decal riservati nei menu. `RefreshAppearance(previewPed?)` aggiorna soltanto l’aspetto gestito del giocatore o di un’anteprima registrata.

`RegisterPreviewPed(ped)` e `UnregisterPreviewPed(ped)` permettono a un menu di consegnare l’entità di anteprima al selettore. Solo la risorsa che ha registrato il ped può rimuoverlo. Le anteprime registrate vengono eliminate quando si arresta la risorsa proprietaria.

`SanitizeAppearance(ped, appearance, format)` restituisce una copia dell’aspetto; nil, error non segnala un errore. `RegisterPreviewPed` e `UnregisterPreviewPed` restituiscono true se l’operazione riesce e false se il ped non può essere registrato o rimosso.

## Bridge modificabili

| File | Adattamento |
| --- | --- |
| `bridge/server.lua` | `CanAccess`, identità, caricamento/salvataggio del peso e hook server. |
| `bridge/client.lua` | Ped di anteprima, notifiche e hook per variazioni di peso/interfaccia. |
| `bridge/interaction.lua` | Registrazione delle stazioni in un target o sostituzione di TextUI. |
| `bridge/appearance.lua` | Lettura/scrittura dei decal in un formato di aspetto personalizzato. |

Per impostazione predefinita, `CanAccess` riconosce solo ace ed everyone. Per jobs, groups o custom, implementare il controllo con l’API reale del server e restituire esattamente true quando l’accesso è concesso. Le eccezioni e gli altri valori negano l’accesso. I campi di policy job, group e custom sono dati interpretati dall’adattatore.

Un provider di archiviazione personalizzato implementa le firme `LoadWeight`(`characterId`, context, done), `SaveWeight`(`characterId`, kg, context, done) e, se serve riconciliare scritture incerte, `ReconcileWeight`(`characterId`, context, done). Il caricamento chiama done(true, kg) (usare kg = nil se non esiste un valore) oppure done(false, '`storage_failed`'). Il salvataggio chiama done(true) solo dopo la conferma, altrimenti done(false, '`storage_failed`'). La riconciliazione chiama done(true, true) solo se nessuna scrittura precedente potrà completarsi in seguito; se non è possibile garantirlo, chiamare done(false, '`storage_unknown`'). Per salvataggi realmente asincroni, impostare `AsyncStorage` = true; il callback deve terminare in un contesto FiveM che supporti l’attesa. L’API attende fino a 10 secondi prima di segnalare un timeout. Non ripetere automaticamente dopo `storage_timeout`; prima riconciliare con un provider che garantisca che una scrittura precedente non verrà applicata in seguito. Non dichiarare asincrono un provider che restituisce il controllo prima di avviare o accodare la scrittura.

Gli hook `OnWeightChanged`, `OnCharacterChanged`, `OnUIOpened`, `OnUIClosed` e Log servono a osservare o registrare le modifiche. Gli hook non concedono accesso né annullano operazioni confermate.

## Verifiche prima dell’attivazione

Patch e adattatori vanno verificati con le versioni e le risorse effettivamente presenti sul server. Prima di abilitare l’integrazione per i giocatori:

1. Ripetere il cambio Normal/Fat con personaggi freemode maschili e femminili.
2. Con Fat attivo, salvare e ricaricare aspetto e outfit. Verificare che il decal tecnico non venga salvato come scelta normale e che il peso rimanga separato dall’outfit.
3. Con la persistenza per personaggio attiva, riavviare la risorsa e riconnettersi. Verificare che lo stesso personaggio recuperi il proprio peso e che un altro non lo erediti.
4. Aprire il menu dell’aspetto con un’anteprima registrata e controllare che rappresenti il personaggio corrente. Quando si annulla o chiude il menu, rimuovere la registrazione prima di eliminare l’entità.

Questi controlli sul server sono necessari per verificare la specifica combinazione di menu, framework e abiti. La presenza di una patch non certifica da sola questi risultati.
