# Intégrer CXG-GTFAT à d’autres ressources

Les fichiers `bridge/*.lua` sont des points d’adaptation modifiables pour connecter les permissions, la persistance, les notifications, les stations et les formats d’apparence. Les correctifs des menus sont des modifications manuelles propres à certaines versions ; la ressource ne modifie pas les autres ressources à l’installation.

## Menus d’apparence

Des correctifs sont préparés pour les dépôts et révisions suivants :

| Menu | Révision source | Action complémentaire |
| --- | --- | --- |
| illenium-appearance | 1b003ec169b15f145d519438ba7c6454bf746f27 | Appliquer le correctif Lua de sauvegarde des composants. |
| fivem-appearance | b06da32881ed49042909b38a778c10dd9bd9eaed | Appliquer le correctif et reconstruire le bundle TypeScript du jeu avec le flux `build:game`. |
| qb-clothing | 8cca4009dd473ab30c30e443ecad50830b816ed7 | Appliquer le correctif aux limites de sauvegarde de skin et d’outfit. |
| esx_skin / skinchanger | fe59ca0bd6da59e2ec6eb4a8d06ece312e96ae7a | Le correctif est relatif à la racine du dépôt esx_core. |

Le dossier `integrations` du paquet contient `illenium-appearance.patch`, `fivem-appearance.patch`, `qb-clothing.patch` et `skinchanger.patch`. Ils ne sont pas appliqués automatiquement. Dans un checkout de développement du menu, vérifiez le bon correctif avant de l’appliquer :

```sh
git apply --check /ruta/al/parche-correcto.patch
git apply /ruta/al/parche-correcto.patch
```

Si la vérification échoue, arrêtez-vous et adaptez la modification à la version installée ; ne forcez pas le correctif. Les forks et révisions plus récentes peuvent différer. Le correctif fivem-appearance nécessite de reconstruire le bundle avec son flux `build:game`. Ces correctifs ne certifient pas toutes les versions et ne remplacent pas un test sur votre serveur.

La sauvegarde assainit le decal technique afin qu’un menu ne l’enregistre pas comme choix ordinaire du joueur. Les menus qui gèrent leur propre table d’apparence doivent appeler l’assainissement au moment de la sauvegarde. Le poids reste séparé des tenues.

## Sauvegarder une apparence personnalisée

L’export client `SanitizeAppearance` renvoie une copie de l’apparence. Les formats inclus sont components, qb et esx :

```lua
local appearanceCopy = exports['CXG_GTFAT']:SanitizeAppearance(
    ped,
    appearance,
    'components'
)

GuardarApariencia(appearanceCopy)
```

`GuardarApariencia` est un appel d’exemple ; remplacez-le par la fonction de sauvegarde de votre menu. components accepte un tableau de composants ou un objet contenant la propriété `components` ; chaque composant utilise `component_id`, `drawable`, `texture` et éventuellement `palette`. Le format qb utilise le champ `decals` avec `item` et `texture` ; esx utilise `decals_1` et `decals_2`. L’export renvoie une copie profonde, conserve les autres champs et ne modifie pas temporairement le ped. Si l’adaptateur échoue ou si le format est inconnu, il renvoie la copie inchangée et consigne un diagnostic.

## Enregistrer un ped d’aperçu

Si le menu utilise un ped d’aperçu distinct du personnage, enregistrez l’aperçu du personnage actif. Désenregistrez-le avant de supprimer 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` et `EliminarPreview` sont des appels d’exemple ; remplacez-les par les fonctions de votre menu. Enregistrez uniquement l’aperçu du personnage actif, pas les peds du monde ni les aperçus d’autres personnages. Vous pouvez aussi définir `Config.GetPreviewPed` pour renvoyer le ped actuel ou nil. `RefreshAppearance(previewPed)` demande la réévaluation d’un ped déjà géré ; il n’active pas Fat à lui seul.

## Exports pour les ressources serveur

Les exports serveur sont des API privilégiées pour les autres ressources serveur. Ne les relayez pas directement depuis un événement que le client peut appeler.

```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’argument `source` est l’identifiant du joueur côté serveur, et non une valeur envoyée par le client. Toutes les valeurs sont en kg. `GetWeight` renvoie le poids ou nil, error. `SetWeight` et `AddWeight` renvoient le poids appliqué normalisé en kg ou nil, error ; `SetWeight` rejette les valeurs hors plage et arrondit au pas le plus proche, les égalités étant arrondies vers le haut. `AddWeight` accepte des incréments négatifs. `ResetWeight` écrit `defaultKg` et renvoie aussi le poids appliqué en kg ; il ne supprime pas la valeur stockée.

Pour ouvrir l’interface d’un joueur depuis une autre ressource serveur :

```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
```

Un identifiant de station facultatif peut être fourni en deuxième argument : `OpenWeightUI`(source, 'gimnasio'). Sans station, la politique d’accès des commandes s’applique, même si leur enregistrement est désactivé. Un résultat true confirme l’autorisation et l’envoi de la demande d’ouverture, pas le rendu de l’interface chez le client.

### Exports serveur

| Export | Utilisation |
| --- | --- |
| `GetWeight(source)` | Renvoie kg ou nil, error. |
| `SetWeight(source, kg)` | Définit un poids validé et renvoie la valeur appliquée en kg ou nil, error. |
| `AddWeight(source, deltaKg)` | Ajoute une différence et renvoie la valeur appliquée en kg ou nil, error. |
| `ResetWeight(source)` | Enregistre et renvoie le poids initial configuré en kg ou nil, error. |
| `GetWeightSettings()` | Renvoie une copie des limites actives. |
| `OpenWeightUI(source, stationId?)` | Demande l’ouverture de la balance selon la politique applicable. |
| `RefreshCharacter`(source) | Recharge l’identité et le poids lorsque le framework bascule sur le personnage prévu. |
| `ReconcileStorage`(source) | Réconcilie le stockage après un état incertain si l’adaptateur peut confirmer les écritures précédentes. |

`RefreshCharacter` est facultatif pour les intégrations multpersonnages ; appelez-le après que le framework a rendu disponible l’identité prévue. La ressource ne connecte pas automatiquement Qbox.

Les erreurs peuvent inclure `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`. Ne traitez pas une erreur de stockage comme un poids confirmé et ne relancez pas automatiquement une écriture ayant renvoyé `storage_timeout` ou `storage_unknown`.

## Exports 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` renvoie nil, '`not_synced`' jusqu’à la réception d’une valeur confirmée par le serveur. `GetFatStatus(ped?)` et `GetReservedDecals(ped)` fournissent des informations en lecture seule ; ils ne prouvent pas que la géométrie a été rendue. `GetReservedDecals` peut servir à masquer les decals réservés dans les menus. `RefreshAppearance(previewPed?)` actualise uniquement l’apparence gérée du joueur ou d’un aperçu enregistré.

`RegisterPreviewPed(ped)` et `UnregisterPreviewPed(ped)` permettent à un menu de confier son entité d’aperçu au sélecteur. Seule la ressource qui a enregistré le ped peut le désenregistrer. Les aperçus enregistrés sont nettoyés lorsque la ressource propriétaire s’arrête.

`SanitizeAppearance(ped, appearance, format)` renvoie une copie de l’apparence ; nil, error n’indique pas un échec. `RegisterPreviewPed` et `UnregisterPreviewPed` renvoient true si l’opération réussit et false si le ped ne peut pas être enregistré ou retiré.

## Bridges modifiables

| Fichier | Adaptation |
| --- | --- |
| `bridge/server.lua` | `CanAccess`, identité, chargement/sauvegarde du poids et hooks serveur. |
| `bridge/client.lua` | Ped d’aperçu, notifications et hooks de changement de poids/interface. |
| `bridge/interaction.lua` | Enregistrement des stations dans un target ou remplacement du TextUI. |
| `bridge/appearance.lua` | Lecture/écriture des decals dans un format d’apparence personnalisé. |

Par défaut, `CanAccess` ne reconnaît que ace et everyone. Pour jobs, groups ou custom, implémentez la vérification avec l’API réelle du serveur et renvoyez exactement true lorsque l’accès est accordé. Les exceptions et les autres valeurs refusent l’accès. Les champs de politique job, group ou custom sont des données interprétées par votre adaptateur.

Un fournisseur de stockage personnalisé implémente les signatures `LoadWeight`(`characterId`, context, done), `SaveWeight`(`characterId`, kg, context, done) et, s’il doit réconcilier les écritures incertaines, `ReconcileWeight`(`characterId`, context, done). Le chargement appelle done(true, kg) (utilisez kg = nil en l’absence de valeur) ou done(false, '`storage_failed`'). La sauvegarde appelle done(true) uniquement après confirmation, sinon done(false, '`storage_failed`'). La réconciliation appelle done(true, true) uniquement si aucune écriture précédente ne pourra être appliquée plus tard ; sinon appelez done(false, '`storage_unknown`'). Pour les véritables sauvegardes asynchrones, définissez `AsyncStorage` = true ; le callback doit se terminer dans un contexte FiveM prenant en charge l’attente. L’API attend jusqu’à 10 secondes avant de signaler un délai dépassé. Ne réessayez pas automatiquement après `storage_timeout` ; réconciliez d’abord avec un fournisseur garantissant qu’une écriture antérieure ne sera pas appliquée plus tard. Ne déclarez pas un fournisseur asynchrone s’il renvoie avant d’avoir commencé ou ordonné l’écriture.

Les hooks `OnWeightChanged`, `OnCharacterChanged`, `OnUIOpened`, `OnUIClosed` et Log servent à observer ou consigner les changements. Ils n’accordent pas l’accès et n’annulent pas les opérations confirmées.

## Vérifications avant activation

Les correctifs et adaptateurs doivent être validés avec les versions et ressources réelles du serveur. Avant d’activer l’intégration pour les joueurs :

1. Répétez le changement Normal/Fat avec des personnages freemode masculins et féminins.
2. Avec Fat actif, sauvegardez puis rechargez l’apparence et la tenue. Vérifiez que le decal technique n’est pas enregistré comme choix ordinaire et que le poids reste indépendant de la tenue.
3. Avec la persistance par personnage activée, redémarrez la ressource et reconnectez-vous. Vérifiez que le même personnage récupère son poids et qu’un autre ne l’hérite pas.
4. Ouvrez le menu d’apparence avec un aperçu enregistré et vérifiez qu’il représente le personnage actuel. Lors de l’annulation ou de la fermeture du menu, désenregistrez-le avant de supprimer l’entité.

Ces vérifications sur votre serveur sont nécessaires pour valider votre combinaison de menu, de framework et de vêtements. La présence d’un correctif ne certifie pas à elle seule ces résultats.
