# دمج CXG-GTFAT مع موارد أخرى

ملفات `bridge/*.lua` نقاط قابلة للتعديل لربط الصلاحيات والحفظ الدائم والإشعارات والمحطات وتنسيقات المظهر. تعديلات القوائم يدوية ومخصصة لإصدارات بعينها؛ ولا يغيّر المورد موارد أخرى عند تثبيته.

## قوائم المظهر

تتوفر تصحيحات لهذه المستودعات والمراجعات:

| القائمة | مراجعة المصدر | إجراء إضافي |
| --- | --- | --- |
| illenium-appearance | 1b003ec169b15f145d519438ba7c6454bf746f27 | تطبيق تصحيح Lua لحفظ المكوّنات. |
| fivem-appearance | b06da32881ed49042909b38a778c10dd9bd9eaed | تطبيق التصحيح وإعادة بناء حزمة TypeScript للعبة باستخدام سير العمل `build:game`. |
| qb-clothing | 8cca4009dd473ab30c30e443ecad50830b816ed7 | تطبيق التصحيح عند حفظ skin وoutfit. |
| esx_skin / skinchanger | fe59ca0bd6da59e2ec6eb4a8d06ece312e96ae7a | مسار التصحيح نسبةً إلى جذر مستودع esx_core. |

يحتوي مجلد `integrations` في الحزمة على `illenium-appearance.patch` و`fivem-appearance.patch` و`qb-clothing.patch` و`skinchanger.patch`. لا تُطبّق تلقائياً. في نسخة تطوير من القائمة، تحقّق من التصحيح الصحيح قبل تطبيقه:

```sh
git apply --check /ruta/al/parche-correcto.patch
git apply /ruta/al/parche-correcto.patch
```

إذا فشل التحقق، فتوقف وكيّف التعديل مع الإصدار المثبّت؛ ولا تفرض التصحيح. قد تختلف الفروع والمراجعات الأحدث. يتطلب تصحيح fivem-appearance إعادة بناء الحزمة عبر سير عمل `build:game` وفق تعليمات تلك القائمة. لا تؤكد هذه التصحيحات التوافق مع كل الإصدارات ولا تحل محل الاختبار على خادمك.

ينظّف الحفظ decal التقني كي لا تحفظه القائمة كاختيار عادي للاعب. يجب أن تستدعي القوائم التي تحتفظ بجدول مظهر خاص بها أداة التنظيف في موضع الحفظ. يُحفظ الوزن منفصلاً عن الأزياء.

## حفظ مظهر مخصص

يعيد export العميل `SanitizeAppearance` نسخة من المظهر. التنسيقات المتضمنة هي components وqb وesx:

```lua
local appearanceCopy = exports['CXG_GTFAT']:SanitizeAppearance(
    ped,
    appearance,
    'components'
)

GuardarApariencia(appearanceCopy)
```

`GuardarApariencia` استدعاء مثالي؛ استبدله بوظيفة الحفظ الخاصة بقائمتك. يقبل components مصفوفة مكونات أو كائناً يحوي الخاصية `components`؛ ويستخدم كل مكوّن `component_id` و`drawable` و`texture`، ويمكن أن يستخدم `palette`. يستخدم تنسيق qb الحقل `decals` مع `item` و`texture`، بينما يستخدم esx الحقلين `decals_1` و`decals_2`. يعيد export نسخة عميقة ويحافظ على الحقول الأخرى دون تغيير ped مؤقتاً. إذا فشل محوّل التنسيق أو لم يُعرف التنسيق، يعيد النسخة دون تعديل ويسجل تشخيصاً.

## تسجيل ped للمعاينة

إذا استخدمت القائمة ped معاينة منفصلاً عن الشخصية، فسجّل معاينة الشخصية النشطة. ألغِ التسجيل قبل حذف الكيان:

```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` و`EliminarPreview` استدعاءان مثاليان؛ استبدلهما بوظائف قائمتك. سجّل معاينة الشخصية النشطة فقط، لا شخصيات ped في العالم أو معاينات الشخصيات الأخرى. يمكنك بدلاً من ذلك ضبط `Config.GetPreviewPed` لإعادة ped الحالي أو nil. يطلب `RefreshAppearance(previewPed)` إعادة تقييم ped تتم إدارته مسبقاً؛ ولا يفعّل Fat بمفرده.

## exports لموارد الخادم

exports الخادم واجهات API ذات صلاحيات مرتفعة لموارد الخادم الأخرى. لا تمررها مباشرة عبر حدث يستطيع العميل استدعاءه.

```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
```

تمثل `source` معرّف اللاعب على الخادم، وليس قيمة يرسلها العميل. جميع القيم بوحدة kg. يعيد `GetWeight` الوزن أو nil, error. ويعيد `SetWeight` و`AddWeight` الوزن المطبّع المطبق بوحدة kg أو nil, error؛ ويرفض `SetWeight` القيم خارج النطاق ويقرّب إلى أقرب خطوة مع تقريب المنتصف إلى الأعلى. يقبل `AddWeight` زيادات سالبة. يكتب `ResetWeight` قيمة `defaultKg` ويعيد الوزن المطبق بوحدة kg؛ لكنه لا يحذف القيمة المخزنة.

لفتح الواجهة للاعب من مورد خادم آخر:

```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
```

يمكن تمرير معرّف محطة اختياري كوسيط ثانٍ: `OpenWeightUI`(source, 'gimnasio'). عند عدم تحديد محطة، تطبق سياسة وصول الأوامر حتى لو كان تسجيل الأوامر معطلاً. تؤكد نتيجة true التفويض وإرسال طلب الفتح، ولا تؤكد أن العميل عرض الواجهة.

### exports الخادم

| Export | الاستخدام |
| --- | --- |
| `GetWeight(source)` | يعيد kg أو nil, error. |
| `SetWeight(source, kg)` | يضبط وزناً تم التحقق منه ويعيد kg المطبق أو nil, error. |
| `AddWeight(source, deltaKg)` | يضيف فرقاً ويعيد kg المطبق أو nil, error. |
| `ResetWeight(source)` | يحفظ الوزن الافتراضي المضبوط ويعيده بوحدة kg أو nil, error. |
| `GetWeightSettings()` | يعيد نسخة من الحدود النشطة. |
| `OpenWeightUI(source, stationId?)` | يطلب فتح الميزان وفق السياسة المعمول بها. |
| `RefreshCharacter`(source) | يعيد تحميل المعرّف والوزن بعد تبديل إطار العمل إلى الشخصية المطلوبة. |
| `ReconcileStorage`(source) | يوفّق التخزين بعد حالة غير مؤكدة عندما يستطيع المحوّل تأكيد الكتابات السابقة. |

`RefreshCharacter` اختياري للتكاملات متعددة الشخصيات؛ استدعه بعد أن يتيح إطار العمل المعرّف المطلوب. لا يتصل المورد بـQbox تلقائياً.

قد تتضمن الأخطاء `not_ready` أو `invalid_source` أو `player_unavailable` أو `character_unavailable` أو `invalid_weight` أو `out_of_range` أو `permission_denied` أو `context_denied` أو `busy` أو `storage_failed` أو `storage_timeout` أو `storage_unknown` أو `not_synced`. لا تعتبر خطأ التخزين وزناً مؤكداً ولا تعِد الكتابة تلقائياً بعد `storage_timeout` أو `storage_unknown`.

## 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` القيمة nil, '`not_synced`' حتى وصول قيمة مؤكدة من الخادم. يوفّر `GetFatStatus(ped?)` و`GetReservedDecals(ped)` معلومات للقراءة فقط ولا يثبتان أن الهندسة ظهرت فعلاً. يمكن استخدام `GetReservedDecals` لإخفاء decals المحجوزة في القوائم. يحدّث `RefreshAppearance(previewPed?)` مظهر اللاعب المدار أو معاينة مسجّلة فقط.

يسمح `RegisterPreviewPed(ped)` و`UnregisterPreviewPed(ped)` للقائمة بتمرير كيان المعاينة إلى المحدد. وحده المورد الذي سجّل ped يستطيع إلغاء تسجيله. تُنظّف المعاينات المسجلة عند توقف المورد المالك.

يعيد `SanitizeAppearance(ped, appearance, format)` نسخة من المظهر؛ ولا تشير nil, error إلى فشل. يعيد `RegisterPreviewPed` و`UnregisterPreviewPed` true عند النجاح وfalse إذا تعذّر تسجيل ped أو إزالته.

## Bridges قابلة للتعديل

| الملف | التكييف |
| --- | --- |
| `bridge/server.lua` | `CanAccess` والمعرّف وتحميل/حفظ الوزن وhooks الخادم. |
| `bridge/client.lua` | ped المعاينة والإشعارات وhooks تغير الوزن/الواجهة. |
| `bridge/interaction.lua` | تسجيل المحطات في target أو استبدال TextUI. |
| `bridge/appearance.lua` | قراءة/كتابة decals بتنسيق مظهر مخصص. |

افتراضياً، لا يتعرف `CanAccess` إلا على ace وeveryone. لـjobs أو groups أو custom، نفّذ الفحص باستخدام API الخادم الفعلي وأعد true تحديداً عند منح الوصول. تؤدي الاستثناءات والقيم الأخرى إلى الرفض. يفسر المحوّل حقول سياسات job وgroup وcustom.

يطبّق مزوّد تخزين مخصص التواقيع `LoadWeight`(`characterId`, context, done) و`SaveWeight`(`characterId`, kg, context, done)، وكذلك `ReconcileWeight`(`characterId`, context, done) عند الحاجة إلى التوفيق بين الكتابات غير المؤكدة. يستدعي التحميل done(true, kg) (استخدم kg = nil عند غياب قيمة) أو done(false, '`storage_failed`'). ولا يستدعي الحفظ done(true) إلا بعد تأكيده، وإلا يستدعي done(false, '`storage_failed`'). ولا يستدعي التوفيق done(true, true) إلا إذا استحال اكتمال كتابة سابقة لاحقاً؛ وإلا فاستدعِ done(false, '`storage_unknown`'). للحفظ غير المتزامن فعلياً، عيّن `AsyncStorage` = true؛ ويجب أن يكتمل callback ضمن سياق FiveM يدعم الانتظار. تسمح الواجهة بمهلة تصل إلى 10 ثوانٍ قبل الإبلاغ عن timeout. لا تكرر تلقائياً بعد `storage_timeout`؛ بل وفّق أولاً مع مزوّد يضمن ألا تُطبّق كتابة سابقة لاحقاً. لا تصف مزوّداً بأنه غير متزامن إذا أعاد التحكم قبل بدء الكتابة أو ترتيبها.

تستخدم hooks `OnWeightChanged` و`OnCharacterChanged` و`OnUIOpened` و`OnUIClosed` وLog للمراقبة أو التسجيل. لا تمنح hooks صلاحيات ولا تلغي العمليات المؤكدة.

## التحقق قبل التفعيل

يجب التحقق من التصحيحات والمحوّلات باستخدام إصدارات وموارد الخادم الفعلية. قبل إتاحة التكامل للاعبين:

1. أعد تجربة التحول بين Normal وFat باستخدام شخصيات freemode ذكورية وأنثوية.
2. مع تفعيل Fat، احفظ المظهر والزي ثم أعد تحميلهما. تأكد من عدم حفظ decal التقني كاختيار عادي ومن بقاء الوزن مستقلاً عن الزي.
3. مع تفعيل الحفظ لكل شخصية، أعد تشغيل المورد ثم اتصل مجدداً. تأكد من استعادة الشخصية نفسها لوزنها وعدم انتقاله إلى شخصية أخرى.
4. افتح قائمة المظهر مع معاينة مسجلة وتأكد من أنها تمثل الشخصية الحالية. عند إلغاء القائمة أو إغلاقها، ألغِ تسجيل المعاينة قبل حذف الكيان.

يلزم إكمال هذه الخطوات على خادمك للتحقق من المجموعة المحددة من القائمة وإطار العمل والملابس. وجود تصحيح لا يؤكد هذه النتائج وحده.
