# CXG-GTFAT in andere Ressourcen integrieren

Die Dateien `bridge/*.lua` sind bearbeitbare Anpassungspunkte für Berechtigungen, Speicherung, Hinweise, Stationen und Erscheinungsbild-Formate. Menü-Patches sind manuelle Änderungen für bestimmte Versionen; die Ressource verändert bei der Installation keine anderen Ressourcen.

## Erscheinungsbild-Menüs

Für folgende Repositories und Revisionen sind Patches vorbereitet:

| Menü | Quellrevision | Zusätzliche Aktion |
| --- | --- | --- |
| illenium-appearance | 1b003ec169b15f145d519438ba7c6454bf746f27 | Lua-Patch zum Speichern der Komponenten anwenden. |
| fivem-appearance | b06da32881ed49042909b38a778c10dd9bd9eaed | Patch anwenden und das TypeScript-Spielbundle mit dem Ablauf `build:game` neu erstellen. |
| qb-clothing | 8cca4009dd473ab30c30e443ecad50830b816ed7 | Patch an den Speichergrenzen von Skin und Outfit anwenden. |
| esx_skin / skinchanger | fe59ca0bd6da59e2ec6eb4a8d06ece312e96ae7a | Der Patch bezieht sich auf das Stammverzeichnis des esx_core-Repositorys. |

Der Ordner `integrations` des Pakets enthält `illenium-appearance.patch`, `fivem-appearance.patch`, `qb-clothing.patch` und `skinchanger.patch`. Sie werden nicht automatisch angewendet. Prüfe in einem Entwicklungs-Checkout des Menüs vor dem Anwenden den passenden Patch:

```sh
git apply --check /ruta/al/parche-correcto.patch
git apply /ruta/al/parche-correcto.patch
```

Schlägt die Prüfung fehl, halte an und passe die Änderung an die installierte Version an; erzwinge den Patch nicht. Forks und neuere Revisionen können abweichen. Der Patch für fivem-appearance erfordert ein erneutes Erstellen des Bundles über dessen `build:game`-Ablauf. Die Patches zertifizieren nicht alle Versionen und ersetzen keinen Test auf deinem Server.

Beim Speichern wird das technische Decal bereinigt, damit ein Menü es nicht als normale Auswahl des Spielers speichert. Menüs mit eigener Erscheinungsbild-Tabelle müssen die Bereinigung an ihrem Speicherpunkt aufrufen. Das Gewicht wird getrennt von Outfits gespeichert.

## Ein eigenes Erscheinungsbild speichern

Der Client-Export `SanitizeAppearance` gibt eine Kopie des Erscheinungsbilds zurück. Unterstützte Formate sind components, qb und esx:

```lua
local appearanceCopy = exports['CXG_GTFAT']:SanitizeAppearance(
    ped,
    appearance,
    'components'
)

GuardarApariencia(appearanceCopy)
```

`GuardarApariencia` ist ein Beispielaufruf; ersetze ihn durch die Speicherfunktion deines Menüs. components akzeptiert ein Komponenten-Array oder ein Objekt mit der Eigenschaft `components`; jede Komponente verwendet `component_id`, `drawable`, `texture` und optional `palette`. Das qb-Format nutzt das Feld `decals` mit `item` und `texture`; esx verwendet `decals_1` und `decals_2`. Der Export gibt eine tiefe Kopie zurück, erhält die übrigen Felder und verändert den Ped nicht vorübergehend. Bei einem Fehler des Formatadapters oder unbekanntem Format gibt er die unveränderte Kopie zurück und protokolliert eine Diagnose.

## Einen Vorschau-Ped registrieren

Wenn das Menü einen vom Charakter getrennten Vorschau-Ped verwendet, registriere die Vorschau des aktiven Charakters. Hebe die Registrierung auf, bevor du die Entität löschst:

```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` und `EliminarPreview` sind Beispielaufrufe; ersetze sie durch die Funktionen deines Menüs. Registriere nur die Vorschau des aktiven Charakters, keine Welt-Peds oder Vorschauen anderer Charaktere. Alternativ kannst du `Config.GetPreviewPed` so definieren, dass es den aktuellen Ped oder nil zurückgibt. `RefreshAppearance(previewPed)` fordert eine erneute Prüfung eines bereits verwalteten Peds an; es aktiviert Fat nicht selbst.

## Exports für Server-Ressourcen

Server-Exports sind privilegierte APIs für andere Server-Ressourcen. Leite sie nicht direkt über ein Ereignis weiter, das ein Client auslösen kann.

```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
```

Das Argument `source` ist die Spieler-ID auf dem Server und kein vom Client gesendeter Wert. Alle Werte sind in kg. `GetWeight` gibt das Gewicht oder nil, error zurück. `SetWeight` und `AddWeight` geben das normalisierte angewendete Gewicht in kg oder nil, error zurück; `SetWeight` weist Werte außerhalb des Bereichs zurück und rundet auf den nächsten Schritt, bei Gleichstand aufwärts. `AddWeight` erlaubt negative Änderungen. `ResetWeight` schreibt `defaultKg` und gibt ebenfalls das angewendete Gewicht in kg zurück; der gespeicherte Wert wird nicht gelöscht.

So öffnest du die Oberfläche für einen Spieler aus einer anderen Server-Ressource:

```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
```

Als zweites Argument kann optional die Stations-ID übergeben werden: `OpenWeightUI`(source, 'gimnasio'). Ohne Station gilt die Zugriffsrichtlinie der Befehle, auch wenn die Befehlsregistrierung deaktiviert ist. Das Ergebnis true bestätigt die Berechtigung und das Senden der Öffnungsanforderung, nicht das Rendern der Oberfläche beim Client.

### Server-Exports

| Export | Verwendung |
| --- | --- |
| `GetWeight(source)` | Gibt kg oder nil, error zurück. |
| `SetWeight(source, kg)` | Setzt ein geprüftes Gewicht und gibt den angewendeten Wert in kg oder nil, error zurück. |
| `AddWeight(source, deltaKg)` | Addiert eine Differenz und gibt den angewendeten Wert in kg oder nil, error zurück. |
| `ResetWeight(source)` | Speichert und gibt das konfigurierte Standardgewicht in kg oder nil, error zurück. |
| `GetWeightSettings()` | Gibt eine Kopie der aktiven Grenzwerte zurück. |
| `OpenWeightUI(source, stationId?)` | Fordert das Öffnen der Waage gemäß der geltenden Richtlinie an. |
| `RefreshCharacter`(source) | Lädt Identität und Gewicht neu, nachdem das Framework zum vorgesehenen Charakter gewechselt ist. |
| `ReconcileStorage`(source) | Gleicht den Speicher bei unklarem Zustand ab, sofern der Adapter frühere Schreibvorgänge bestätigen kann. |

`RefreshCharacter` ist für Integrationen mit mehreren Charakteren optional. Rufe es auf, nachdem das Framework die vorgesehene Identität bereitgestellt hat. Die Ressource bindet Qbox nicht automatisch an.

Fehler können `not_ready`, `invalid_source`, `player_unavailable`, `character_unavailable`, `invalid_weight`, `out_of_range`, `permission_denied`, `context_denied`, `busy`, `storage_failed`, `storage_timeout`, `storage_unknown` oder `not_synced` enthalten. Werte einen Speicherfehler nicht als bestätigtes Gewicht und wiederhole Schreibvorgänge nach `storage_timeout` oder `storage_unknown` nicht automatisch.

## 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` gibt nil, '`not_synced`' zurück, bis ein vom Server bestätigter Wert eintrifft. `GetFatStatus(ped?)` und `GetReservedDecals(ped)` liefern lesende Informationen; sie beweisen nicht, dass die Geometrie gerendert wurde. Mit `GetReservedDecals` können reservierte Decals in Menüs ausgeblendet werden. `RefreshAppearance(previewPed?)` aktualisiert nur das verwaltete Erscheinungsbild des Spielers oder einer registrierten Vorschau.

`RegisterPreviewPed(ped)` und `UnregisterPreviewPed(ped)` ermöglichen einem Menü, seine Vorschau-Entität an den Selektor zu übergeben. Nur die Ressource, die den Ped registriert hat, darf die Registrierung aufheben. Registrierte Vorschauen werden beim Stoppen ihrer Eigentümer-Ressource bereinigt.

`SanitizeAppearance(ped, appearance, format)` gibt eine Kopie des Erscheinungsbilds zurück; nil, error zeigt keinen Fehler an. `RegisterPreviewPed` und `UnregisterPreviewPed` geben bei Erfolg true und dann false zurück, wenn der Ped nicht registriert oder entfernt werden kann.

## Bearbeitbare Bridges

| Datei | Anpassung |
| --- | --- |
| `bridge/server.lua` | `CanAccess`, Identität, Laden/Speichern des Gewichts und Server-Hooks. |
| `bridge/client.lua` | Vorschau-Ped, Benachrichtigungen und Hooks für Gewichts-/Oberflächenänderungen. |
| `bridge/interaction.lua` | Registrierung von Stationen in einem Target oder Ersatz von TextUI. |
| `bridge/appearance.lua` | Decals in einem eigenen Erscheinungsbild-Format lesen und schreiben. |

Standardmäßig erkennt `CanAccess` nur ace und everyone. Für jobs, groups oder custom implementiere die Prüfung mit der tatsächlichen Server-API und gib bei gewährtem Zugriff genau true zurück. Ausnahmen und andere Werte verweigern den Zugriff. Job-, Gruppen- und Custom-Richtlinienfelder werden von deinem Adapter interpretiert.

Ein eigener Speicheranbieter implementiert die Signaturen `LoadWeight`(`characterId`, context, done), `SaveWeight`(`characterId`, kg, context, done) und bei Bedarf für den Abgleich unsicherer Schreibvorgänge `ReconcileWeight`(`characterId`, context, done). Beim Laden wird done(true, kg) (kg = nil, falls kein Wert vorhanden ist) oder done(false, '`storage_failed`') aufgerufen. Beim Speichern wird done(true) erst nach Bestätigung oder andernfalls done(false, '`storage_failed`') aufgerufen. Der Abgleich ruft done(true, true) nur dann auf, wenn kein früherer Schreibvorgang später noch abgeschlossen werden kann; ist das nicht sichergestellt, muss done(false, '`storage_unknown`') aufgerufen werden. Für tatsächlich asynchrone Speicherungen setze `AsyncStorage` = true; der Callback muss in einem FiveM-Kontext abgeschlossen werden, der Warten unterstützt. Die API wartet bis zu 10 Sekunden, bevor sie einen Timeout meldet. Wiederhole nach `storage_timeout` nichts automatisch; gleiche zuerst mit einem Anbieter ab, der garantiert, dass ein früherer Schreibvorgang nicht später angewendet wird. Kennzeichne einen Anbieter nicht als asynchron, wenn er vor dem Start oder der Einreihung des Schreibvorgangs zurückkehrt.

Die Hooks `OnWeightChanged`, `OnCharacterChanged`, `OnUIOpened`, `OnUIClosed` und Log dienen der Beobachtung und Protokollierung. Sie erteilen keinen Zugriff und machen bestätigte Vorgänge nicht rückgängig.

## Vor der Aktivierung prüfen

Patches und Adapter müssen mit den tatsächlichen Server-Ressourcen und Versionen geprüft werden. Vor der Freigabe für Spieler:

1. Prüfe den Wechsel zwischen Normal und Fat mit männlichen und weiblichen Freemode-Charakteren.
2. Speichere bei aktivem Fat das Erscheinungsbild und Outfit und lade beides erneut. Stelle sicher, dass das technische Decal nicht als normale Auswahl gespeichert wurde und das Gewicht vom Outfit getrennt bleibt.
3. Starte bei aktivierter Speicherung pro Charakter die Ressource neu und verbinde dich erneut. Prüfe, ob derselbe Charakter sein Gewicht zurückerhält und ein anderer es nicht übernimmt.
4. Öffne das Erscheinungsbild-Menü mit einer registrierten Vorschau und prüfe, ob sie den aktuellen Charakter darstellt. Hebe beim Abbrechen oder Schließen des Menüs die Registrierung auf, bevor du die Entität löschst.

Diese Schritte auf deinem Server sind erforderlich, um die konkrete Kombination aus Menü, Framework und Kleidung zu prüfen. Ein vorhandener Patch allein bestätigt diese Ergebnisse nicht.
