التوثيق

توثيق Interact system

نظام تفاعل ثلاثي الأبعاد + محرك حوارات للشخصيات، بدون أي اعتمادية. ضع تفاعلًا في أي مكان على الخريطة: على entity، أو على إحداثية حرة، أو على شخصية NPC تستدعيها من النداء نفسه. بدون ox_lib، وبدون فريمورك، ولا شيء تضبطه على السيرفر.

  • ESX
  • QBCore
  • ox
  • vRP
  • Standalone
01

التثبيت

  1. ضع السكربت

    حمّل السكربت من حسابك على Tebex، ثم انسخ مجلد avenida_interact/ إلى resources/[avenida]/ على سيرفرك.

  2. فعّله في server.cfg

    أضف ensure avenida_interact. لا شيء يحتاج إلى أن يُحمَّل قبله: منذ 1.1.0 لا اعتمادية للسكربت، فلا يهم ترتيب التحميل.

  3. اضبط قيمك الافتراضية

    افتح config/config.lua واضبط ما ينطبق على كل النقاط: النص، والمفتاح، ومسافتي showIcon وcanInteract، والأيقونة، واللون، والألوان المسماة، ومسافة ظهور الشخصيات، وConfig.Hud لاختيار أي HUD يُخفى أثناء الحوار.

  4. أزل الأمثلة

    تبقى الأمثلة في config/entities/ وconfig/peds/ وconfig/positions/ فعّالة ما دامت ملفاتها موجودة. احذف ما لا تريده، فالـ manifest يلتقط هذه المجلدات بنمط عام، ولا شيء آخر تعدله.

  5. جرّب إعادة التحميل الساخنة

    شغّل السيرفر ثم اكتب /restart avenida_interact داخل اللعبة. تختفي الأيقونات وتعود نظيفة، وتبقى النقاط التي سجّلتها ريسورساتك، دون إعادة تشغيل جلستك.

02

الإعداد

أول نقطة تفاعل لديك في 30 ثانية

أنشئ ملف myscript/client.lua في ريسورسك والصق هذا. تظهر علامة سماوية على بعد 30 مترًا، وتنفتح نافذة "E - Hello" على بعد مترين. الشيء الوحيد الذي يجب أن يعمل هو avenida_interact نفسه.

lua
-- myscript/client.lua
exports.avenida_interact:positionRegister({
    coords      = vec3(-265.0, -963.6, 31.2),
    showIcon    = 30.0,           -- marker fades in at 30m
    canInteract = 2.0,            -- label unrolls at 2m
    hintIcon    = 'interact',     -- see the icon list below
    hintColor   = '#22d3ee',      -- hex, or an alias from Config.Colors
    message     = 'Hello',
    bind        = 'E',
    onInteract  = function()
        TriggerEvent('chat:addMessage', { args = { 'Hello, world!' } })
    end,
})
03

Exports

Interact Points

  • positionRegister
    exports.avenida_interact:positionRegister(cfg) --> id

    يسجل نقطة على إحداثيات ثابتة. يقبل كل الحقول البصرية (hintIcon، hintColor، showIcon، canInteract، offsetZ، blips) إضافة إلى choices[] لعدة مفاتيح على النقطة نفسها. ويقبل canInteract رقمًا أو دالةً، فتستطيع ربط ظهور النافذة بوظيفة أو غرض أو مهلة. يعيد id.

  • pedRegister
    exports.avenida_interact:pedRegister(cfg) --> id

    يستدعي شخصية مضبوطة (نموذج، animDict وanimName أو scenario، وblip)، ويربط بها نقطة ويعيد رقمها. الشخصية لا تُقتل ولا تهرب ولا تسقط أرضًا. تظهر وتختفي عند حدّ Config.PedSpawnRange، أي 50 مترًا افتراضيًا. اضبط randomComponents = false على بائع له اسم معروف، وإلا رأى كل لاعب ملابس مختلفة.

  • pedRemove
    exports.avenida_interact:pedRemove(idx)

    أُضيف في 1.1.0. يزيل شخصية مسجّلة بـ pedRegister ويحذف تسجيلها، كي ينصرف صاحب المهمة المؤقّت عند انتهاء مهمته.

  • entityRegister
    exports.avenida_interact:entityRegister(cfg) --> id

    يربط نقطة بـ entity موجودة، تُمرَّر في cfg.entity. ويدعم cfg.bone لتعليق النافذة على عظمة بدلًا من مركز الـ entity. استعمله مع ما يستدعيه سكربت آخر: مركبة، وprop موضوع، وغرض ملقى.

  • entityRegisterByHash
    exports.avenida_interact:entityRegisterByHash(hash, cfg)

    watcher سلبي: ينطبق الإعداد على كل entity من هذا النموذج، الموجودة الآن والتي ستظهر لاحقًا. ويغطي خيط واحد كل الـ hash المسجلة، ويجسّها كل 5 ثوانٍ. هذا ما تريده لأجهزة الصراف وآلات البيع والأبواب، بدلًا من قائمة إحداثيات تصونها بنفسك.

  • entityRemove
    exports.avenida_interact:entityRemove(idx)

    يفك النقطة التي أعادها entityRegister. أما الـ entity نفسها فلا تُمس.

  • interactCreate
    exports.avenida_interact:interactCreate(cfg) --> id

    نقطة تُنشأ وتُزال عند الطلب. استعملها حين لا تعيش النقطة إلا وقتًا قصيرًا: هدف مهمة، وحقيبة على الأرض، وجثة تُفتَّش.

  • interactRemove
    exports.avenida_interact:interactRemove(id)

    يزيل نقطة أنشأها interactCreate، وعلامتها على الخريطة إن كانت أعلنت واحدة.

  • unlockLastInteract
    exports.avenida_interact:unlockLastInteract()

    تقفل النقطة نفسها بعد استعمالها، كي لا يشغّلها مرتين مفتاحٌ يبقى مضغوطًا. استدعِ هذا في نهاية إجرائك لإعادة تفعيلها. فإن بقي اللاعب في المدى عادت النافذة فورًا، وإلا انتظرت دخوله من جديد.

Prompt API

  • HandleTextUI
    exports.avenida_interact:HandleTextUI(id, data) --> duiHandler

    ينشئ أو يحدّث فقاعة ثلاثية الأبعاد مستقلة، بلا نقطة تفاعل خلفها. استعمله حين تريد التحكم في الظهور من خيطك أنت. يعيد الـ handler الذي تمرره إلى Draw3DSprite.

  • Draw3DSprite
    exports.avenida_interact:Draw3DSprite({ duiHandler, coords, maxDistance, hintOnly? })

    يرسم الفقاعة في فضاء العالم من خيط رسم، مصحَّحةً حسب نسبة الشاشة. ومع hintOnly = true تُرسم العلامة وحدها، وهي حالة النقطة العادية على المسافات البعيدة.

  • CloseTextUI
    exports.avenida_interact:CloseTextUI(id)

    يخفي الفقاعة لكنه يبقي الـ DUI في الذاكرة، فتكون إعادة الفتح فورية.

  • RemoveTextUI
    exports.avenida_interact:RemoveTextUI(id)

    يزيل فقاعة نهائيًا ويحرر الـ DUI. استعمله حين تعلم أنها لن تعود.

  • RemoveTextUIs
    exports.avenida_interact:RemoveTextUIs()

    يزيل كل الفقاعات النشطة. ويُستدعى تلقائيًا عند إيقاف الريسورس.

  • IsDuiVisible
    exports.avenida_interact:IsDuiVisible() --> boolean

    يعيد true ما دامت نافذة معروضة على الشاشة. اقرأه كي لا تتكدس نوافذك فوق تفاعل جارٍ.

Key handler

  • HandleHoldTextUI
    exports.avenida_interact:HandleHoldTextUI(id, data) --> duiHandler

    الطبقة التي بُنيت عليها النقاط: ترسم نافذة على Coords وتراقب مفتاحها من خيط إدارة كل 50ms. ويأخذ data الحقول BindToHold (رقم التحكم)، DistanceHold، Coords، وChoicesUI وChoicesCtrl لنافذة بعدة مفاتيح، وonCallback(id) وcanInteract(id, dist). وتنطلق النافذة عند الضغط بفاصل 500ms، وينفَّذ الـ callback داخل pcall، فخطأ من جهتك لا يكسر النافذة.

  • CloseHoldTextUI
    exports.avenida_interact:CloseHoldTextUI(id)

    يخفي النافذة دون تدمير النسخة، لخروج نظيف حين يغادر اللاعب المنطقة.

  • RemoveHoldTextUI
    exports.avenida_interact:RemoveHoldTextUI(id)

    يدمر نسخة واحدة ويحرر الـ DUI الخاص بها.

  • RemoveHoldTextUIs
    exports.avenida_interact:RemoveHoldTextUIs()

    يدمر كل النسخ النشطة.

04

وصفات

ضع نقطة على إحداثيات ثابتة مع علامتها

أبسط الحالات: نقطة على إحداثيات، مع علامتها معلنة في الجدول نفسه. تظهر العلامة على بعد 30 مترًا والنص على بعد مترين. ضعه في أي ملف client في ريسورسك.

lua
exports.avenida_interact:positionRegister({
    coords      = vec3(149.5, -1040.4, 29.4),
    showIcon    = 30.0,
    canInteract = 2.0,
    hintIcon    = 'bank',
    hintColor   = 'blue',
    message     = 'Use the ATM',
    bind        = 'E',
    onInteract  = function()
        TriggerEvent('myserver-banking:openATM')
    end,
    blips = {
        sprite  = 277,
        color   = 2,
        scale   = 0.8,
        text    = 'ATM',
        display = 4,
    },
})

غطِّ كل أجهزة الصراف بمفتاحين لكل جهاز

يسجّل الـ watcher إعدادك مرة واحدة لكل نموذج ويطبقه على كل prop مطابق، سواء ظهر الآن أو لاحقًا. ويحمل كل خيار نصه ولونه وcondition الخاص به، فلا تظهر السرقة إلا إذا سمح بها سكربت الشرطة عندك. وانتبه إلى اسم الحقل: يستعمل الخيار condition، لا canInteract.

lua
local ATM_PROPS = {
    'prop_atm_01', 'prop_atm_02', 'prop_atm_03', 'prop_fleeca_atm',
}

for _, prop in ipairs(ATM_PROPS) do
    exports.avenida_interact:entityRegisterByHash(prop, {
        canInteract = 1.2,
        offsetZ     = 1.0,
        hintIcon    = 'bank',
        hintColor   = 'blue',
        choices = {
            {
                bind       = 'E',
                message    = 'Use the ATM',
                onInteract = function()
                    TriggerEvent('myserver-banking:atmUse')
                end,
            },
            {
                bind      = 'G',
                message   = 'Rob the ATM',
                hintColor = 'red',
                condition = function()
                    return exports['myserver-police']:hasEnoughCops(2)
                end,
                onInteract = function(entity)
                    TriggerServerEvent('myserver-robbery:startATM', NetworkGetNetworkIdFromEntity(entity))
                end,
            },
        },
    })
end

استدعِ شخصية بحوار متفرع ونقطة GPS

صاحب مهمة كامل: تظهر الشخصية بحركتها وعلامتها، وتنفّذ أجوبة الحوار ما تشاء، وهنا الـ native SetNewWaypoint. ويغيّر changeDialog النص والأجوبة، فتستطيع التداخل بالعمق الذي تحتاجه المحادثة. ويُبقي randomComponents = false ملابسها واحدة لدى كل اللاعبين.

lua
exports.avenida_interact:pedRegister({
    coords           = vec4(-1393.81, -1064.09, 3.17, 266.09),
    model            = 'a_f_y_vinewood_04',
    animDict         = 'anim@amb@waving@male',
    animName         = 'ground_wave',
    randomComponents = false,
    message          = 'Talk with Maria',
    showIcon         = 10.0,
    canInteract      = 3.0,
    hintIcon         = 'talk',
    hintColor        = 'gold',
    blips = { sprite = 119, color = 50, text = 'Lumberjack', scale = 0.8 },
    dialogue = {
        name     = 'Maria',
        startMsg = 'Hello, are you ready to start your shift?',
        elements = {
            {
                label  = 'Yes, where do I start?',
                action = function(changeDialog, close)
                    SetNewWaypoint(-584.01, 5491.59)
                    changeDialog('The forest is on your GPS now.', {
                        { label = 'Got it!', action = function(_, close) close() end },
                    })
                end,
            },
            {
                label  = 'I\'ll come back later',
                action = function(_, close) close() end,
            },
        },
    },
})
05

أسئلة

هل فعلًا لا أحتاج إلى ox_lib؟

لا تحتاج إليه. منذ 1.1.0 ينفذ bridges/compat.lua إنشاء الـ DUI ونقاط القرب وأدوات الانتظار بـ natives خالصة، وسطر @ox_lib/init.lua معلَّق في الـ manifest. وإن فضلت المرور عبر ox_lib، اضبط Config.UseOxLib = true وأزل التعليق عن ذلك السطر. المساران مدعومان، ومسار الـ natives هو الافتراضي.

لا تظهر علامتي، فأين أبحث؟

أولًا، المدى: showIcon قيمته 4 أمتار افتراضيًا في Config.Defaults، وهو قصير إن كنت تنتظر علامة تُرى من بعيد، فمرر قيمتك أنت. ثانيًا، مع entityRegister يجب أن تكون الـ entity موجودة وقت النداء، بينما ينتظرها entityRegisterByHash، وهو الأنسب لـ props اللعبة الأصلية. ثالثًا، اضبط Config.Debug = true لتطبع الأخطاء الداخلية في الكونسول عند اللاعب.

يُتجاهل شرط الخيار عندي

يقرأ الخيار condition، لا canInteract. أما canInteract فهو حقل النقطة نفسها، ويقبل رقمًا أو دالةً. وداخل choices[] يسمى المانع condition = function() ... end، والخيار الذي يعيد شرطه false لا يُرسم أصلًا.

ما الألوان التي أستطيع استعمالها؟

أي قيمة hex مثل #22d3ee، أو اسم من Config.Colors الذي يأتي بـ red وblue وgreen وgold وpurple وwhite. هذا الجدول خارج الـ escrow، فأضف إليه لوحة ألوان سيرفرك واستعمل أسماءك في كل مكان. وأي hintIcon غير معروف يُرسم كنص، فالإيموجي يصلح أيقونة عابرة.

كانت النافذة ممطوطة على شاشتي العريضة

صُحح ذلك في 1.1.0. صار نسيج الـ DUI مثبتًا على 1920x1080 ويُصحَّح عند الرسم حسب نسبة الشاشة الحقيقية، ولم تعد الأيقونة تتغير مع المسافة. وكسبت صناديق أجوبة الحوار ارتفاعًا فوق 2560x1440. حدّث السكربت إن كنت ما زلت على 1.0.x.

تبقى نقاطي ظاهرة بعد إيقاف ريسورسي

تُتابَع الملكية بـ GetInvokingResource()، فتعود النقطة إلى الريسورس الذي استدعى الـ export. وإن سجّلت من handler مشترك يعمل في سياق ريسورس آخر، تبع التنظيفُ ذلك الريسورس الآخر. غلّف النداء داخل دالة من الريسورس المالك للنقطة. ومنذ 1.1.0 تُمسح النقاط عند تشغيل الريسورس أيضًا، فلم يعد الـ restart يترك أشباحًا.

كيف أربطه بـ ESX أو QBCore أو Qbox أو ox أو vRP؟

لا تحتاج إلى ربطه بشيء: السكربت لا يعرف الفريمورك الذي تشغّله ولا يخاطب سيرفرك أبدًا. اقرأ وظيفة اللاعب أو رتبته داخل canInteract أو داخل condition أحد الخيارات، واستدعِ أحداثك من onInteract. وكل ما يعمل على فريمورك سيرفرك يعمل خلف هاتين الدالتين، بلا تعديل.

كم نقطة أستطيع تسجيلها؟

قِسنا الأداء فكان 0.00ms في الخمول مع 100 نقطة. ولا يحدث الرسم إلا داخل مدى showIcon، وتعمل مراقبة المفاتيح كل 50ms للنسخ القريبة، فآلاف النقاط الموزعة على الخريطة لا تكلف شيئًا ما لم تتكدس كلها في شارع واحد.

السلة
Spirit RP
Discord