# Integrate CXG-GTFAT with other resources

The `bridge/*.lua` files are editable adaptation points for permissions, persistence, notifications, stations, and appearance formats. Menu patches are manual changes for specific versions; the resource does not modify other resources when installed.

## Appearance menus

Patches are prepared for these repositories and revisions:

| Menu | Source revision | Additional action |
| --- | --- | --- |
| illenium-appearance | 1b003ec169b15f145d519438ba7c6454bf746f27 | Apply the Lua component-save patch. |
| fivem-appearance | b06da32881ed49042909b38a778c10dd9bd9eaed | Apply the patch and rebuild the game's TypeScript bundle with the `build:game` workflow. |
| qb-clothing | 8cca4009dd473ab30c30e443ecad50830b816ed7 | Apply the patch at the skin and outfit save boundaries. |
| esx_skin / skinchanger | fe59ca0bd6da59e2ec6eb4a8d06ece312e96ae7a | The patch is relative to the esx_core repository root. |

The package's `integrations` folder contains `illenium-appearance.patch`, `fivem-appearance.patch`, `qb-clothing.patch`, and `skinchanger.patch`. They are not applied automatically. In a development checkout of the menu, check the matching patch before applying it:

~~~sh
git apply --check /path/to/the-correct-patch.patch
git apply /path/to/the-correct-patch.patch
~~~

If the check fails, stop and adapt the change to the installed version; do not force the patch. Forks and newer revisions may differ. The fivem-appearance patch requires rebuilding the bundle with that menu's `build:game` workflow. These patches do not certify every version or replace testing on your server.

Saving sanitizes the technical decal so a menu does not persist it as the player's ordinary selection. Menus with their own appearance table must call the sanitizer at their save point. Weight is stored separately from outfits.

## Save a custom appearance

The client export `SanitizeAppearance` returns a copy of the appearance. Supported formats are components, qb, and esx:

~~~lua
local appearanceCopy = exports['CXG_GTFAT']:SanitizeAppearance(
    ped,
    appearance,
    'components'
)

GuardarApariencia(appearanceCopy)
~~~

`GuardarApariencia` is an example call; replace it with your menu's save function. components accepts an array of components or an object with a `components` property; each component uses `component_id`, `drawable`, `texture`, and optionally `palette`. The qb format uses the `decals` field with `item` and `texture`; esx uses `decals_1` and `decals_2`. The export returns a deep copy and preserves other fields without temporarily changing the ped. If the format adapter fails or the format is unrecognized, it returns the unchanged copy and logs a diagnostic.

## Register a preview ped

If the menu uses a preview ped separate from the character, register the preview for the active character. Unregister it before deleting the entity:

~~~lua
local previewPed = ObtenerPreviewActual()

if previewPed and DoesEntityExist(previewPed) then
    exports['CXG_GTFAT']:RegisterPreviewPed(previewPed)
end

-- When the menu closes or is cancelled, before deleting the ped:
exports['CXG_GTFAT']:UnregisterPreviewPed(previewPed)
EliminarPreview(previewPed)
~~~

`ObtenerPreviewActual` and `EliminarPreview` are example calls; replace them with your menu's functions. Register only the active character's preview, not world peds or previews for other characters. Alternatively, set `Config.GetPreviewPed` to return the current ped or nil. `RefreshAppearance(previewPed)` requests a refresh of a ped that is already managed; it does not activate Fat by itself.

## Server-resource exports

Server exports are privileged APIs for other server resources. Do not forward them directly from an event that a client can invoke.

~~~lua
local playerSource = source -- Player ID on the server
local kg, err = exports['CXG_GTFAT']:GetWeight(playerSource)
if kg == nil then
    print('No confirmed weight:', err)
end

local appliedKg, setError = exports['CXG_GTFAT']:SetWeight(playerSource, 92.5)
if appliedKg == nil then
    print('Could not set weight:', setError)
end

local addedKg, addError = exports['CXG_GTFAT']:AddWeight(playerSource, -2.0)
if addedKg == nil then
    print('Could not add weight:', addError)
end

local resetKg, resetError = exports['CXG_GTFAT']:ResetWeight(playerSource)
if resetKg == nil then
    print('Could not reset weight:', resetError)
end
~~~

The `source` argument is the server player ID, not a value sent by the client. All values are in kg. `GetWeight` returns the weight or nil, error. `SetWeight` and `AddWeight` return the normalized applied weight in kg or nil, error; `SetWeight` rejects out-of-range values and rounds to the nearest step, with ties rounded up. `AddWeight` accepts negative increments. `ResetWeight` writes `defaultKg` and also returns the applied weight in kg; it does not delete the stored value.

To open the UI for a player from another server resource:

~~~lua
local playerSource = source -- Player ID on the server
local opened, err = exports['CXG_GTFAT']:OpenWeightUI(playerSource)
if not opened then
    print('Could not open the scale:', err)
end
~~~

An optional station ID can be passed as the second argument: `OpenWeightUI`(source, 'gym'). Without a station, it applies the command access policy even if command registration is disabled. A true result confirms authorization and that the open request was sent; it does not confirm that the client has rendered the interface.

### Server exports

| Export | Use |
| --- | --- |
| `GetWeight(source)` | Returns kg or nil, error. |
| `SetWeight(source, kg)` | Sets a validated weight and returns the applied kg or nil, error. |
| `AddWeight(source, deltaKg)` | Adds a delta and returns the applied kg or nil, error. |
| `ResetWeight(source)` | Saves and returns the configured default weight in kg or nil, error. |
| `GetWeightSettings()` | Returns a copy of the active limits. |
| `OpenWeightUI(source, stationId?)` | Requests to open the scale under the applicable policy. |
| `RefreshCharacter`(source) | Reloads identity and weight after the framework switches to the intended character. |
| `ReconcileStorage`(source) | Reconciles storage after an uncertain state when the adapter can confirm earlier writes. |

`RefreshCharacter` is optional for multicharacter integrations; call it after the framework has made the intended identity available. The resource does not connect to Qbox automatically.

Errors may include `not_ready`, `invalid_source`, `player_unavailable`, `character_unavailable`, `invalid_weight`, `out_of_range`, `permission_denied`, `context_denied`, `busy`, `storage_failed`, `storage_timeout`, `storage_unknown`, or `not_synced`. Do not treat a storage error as confirmed weight or automatically retry a write with a `storage_timeout` or `storage_unknown` result.

## Client exports

~~~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` returns nil, '`not_synced`' until a server-confirmed value arrives. `GetFatStatus(ped?)` and `GetReservedDecals(ped)` provide read-only information; they do not prove that the geometry rendered. `GetReservedDecals` can be used to hide reserved decals in menus. `RefreshAppearance(previewPed?)` only refreshes the managed player appearance or a registered preview.

`RegisterPreviewPed(ped)` and `UnregisterPreviewPed(ped)` let a menu hand its preview entity to the selector. Only the resource that registered the ped can unregister it. Registered previews are cleaned up when their owning resource stops.

`SanitizeAppearance(ped, appearance, format)` returns a copy of the appearance; nil, error is not its failure indicator. `RegisterPreviewPed` and `UnregisterPreviewPed` return true when the operation succeeds and false when the ped cannot be registered or removed.

## Editable bridges

| File | Adaptation |
| --- | --- |
| `bridge/server.lua` | `CanAccess`, identity, weight loading/saving, and server hooks. |
| `bridge/client.lua` | Preview ped, notifications, and weight/interface change hooks. |
| `bridge/interaction.lua` | Station registration with a target or replacement of TextUI. |
| `bridge/appearance.lua` | Read/write decals in a custom appearance format. |

By default, `CanAccess` recognizes only ace and everyone. For jobs, groups, or custom access, implement the check with the server's actual API and return exactly true when access is granted. Exceptions and other values deny access. Job, group, and custom policy fields are data interpreted by your adapter.

A custom storage provider implements the signatures `LoadWeight`(`characterId`, context, done), `SaveWeight`(`characterId`, kg, context, done), and, if it needs to reconcile uncertain writes, `ReconcileWeight`(`characterId`, context, done). Loading calls done(true, kg) (use kg = nil when no value exists) or done(false, '`storage_failed`'). Saving calls done(true) only after confirmation, or done(false, '`storage_failed`'). Reconciliation calls done(true, true) only when no earlier write can complete later; if that cannot be guaranteed, call done(false, '`storage_unknown`'). For genuinely asynchronous saves, set `AsyncStorage` = true; the callback must complete in a FiveM context that supports waiting. The API allows up to 10 seconds before reporting a timeout. Do not automatically retry after `storage_timeout`; first reconcile with a provider that guarantees an earlier write will not apply later. Do not mark a provider asynchronous if it returns before starting or ordering the write.

The `OnWeightChanged`, `OnCharacterChanged`, `OnUIOpened`, `OnUIClosed`, and Log hooks are for observing or logging changes. Hooks do not grant access or undo confirmed operations.

## Checks before enabling it

Patches and adapters need validation against the actual server resources and versions. Before enabling the integration for players:

1. Repeat the Normal/Fat change with male and female freemode characters.
2. With Fat active, save and reload the appearance and outfit. Confirm that the technical decal is not saved as an ordinary selection and that weight remains separate from the outfit.
3. With per-character persistence enabled, restart the resource and reconnect. Confirm the same character recovers its weight and another character does not inherit it.
4. Open the appearance menu with a registered preview and confirm it represents the current character. When cancelling or closing the menu, unregister it before deleting the entity.

Completing these steps on your server is necessary to validate your specific combination of menu, framework, and clothing. The existence of a patch alone does not certify those results.
