نظام قفل الأبواب في FiveM: سكريبت أبواب ومفاتيح آمنة
إضافة أقفال أبواب إلى أي خادم FiveM. الإعدادات، المفاتيح، الأبواب الخاصة بالوظائف وأفضل سكربتات أقفال الأبواب لمحطات الشرطة، المنازل وداخل الأعمال.
Agency Scripts
المؤسس والمطور الرئيسي في Agency Scripts
لماذا تهم أقفال الأبواب في اللعب التمثيلي
أنظمة قفل الأبواب هي واحدة من أكثر المكونات التي لا تُقدَّر حقها لكنها أساسية في أي سيرفر FiveM جاد للعب الأدوار. بدون إدارة صحيحة للأبواب، يمكن للاعبين الدخول إلى مخازن الأسلحة الخاصة بالشرطة، وغرف موظفي المستشفى، ومخابئ العصابات دون قيود، مما يكسر الانغماس تمامًا. يسمح نظام قفل الأبواب المنفذ جيدًا لمالكي السيرفر بالتحكم في الوصول إلى كل داخلية على الخريطة، من المباني الحكومية إلى الأعمال التجارية المملوكة للاعبين. يخلق حدودًا طبيعية تدفع التفاعلات في اللعب مثل تبادل المفاتيح، وسيناريوهات فتح الأقفال، والاقتحامات المنسقة. في هذا الدليل، سنمر ببناء نظام قفل أبواب كامل باستخدام ox_doorlock كأساس، ثم نوسعه بآليات فتح أقفال مخصصة، ومصادقة بطاقة المفاتيح، ومجموعات الأبواب، والتحكم في الوصول الخاص بالأعمال.
إعداد ox_doorlock
مورد ox_doorlock هو الحل الأكثر اعتمادًا لقفل الأبواب في نظام FiveM، يقدم API نظيف، واجهة إدارة مدمجة لوضع الأبواب، ودعم للأبواب المزدوجة وأبواب الجراج المتحركة. قبل كتابة أي كود مخصص، تحتاج إلى أساس قوي. قم بتثبيت ox_doorlock جنبًا إلى جنب مع ox_lib التي يعتمد عليها لمكونات الواجهة ووظائف الأدوات. الخاص بك server.cfg يحتاج إلى ضمان بدء الموارد بالترتيب الصحيح، مع تشغيل قاعدة البيانات أولاً، ثم ox_lib، وأخيرًا ox_doorlock. بمجرد التشغيل، يتيح أمر الإدارة المدمج الاقتراب من أي باب في عالم اللعبة وتسجيله بنقرة واحدة.
-- server.cfg load order
ensure oxmysql
ensure ox_lib
ensure ox_doorlock
-- Grant admin access for door placement
add_ace group.admin command.doorlock allow
بعد تسجيل الأبواب من خلال واجهة الإدارة، يقوم ox_doorlock بتخزين كل إدخال في قاعدة البيانات مع الإحداثيات، الاتجاه، هاش النموذج، وحالة القفل. يمكنك الاستعلام والتلاعب بهذه البيانات برمجياً. المورد يوفر تصديرات وأحداث لتبديل الأقفال، فحص الحالة، وإدارة الوصول. فهم هيكل البيانات أمر حاسم قبل بناء ميزات مخصصة فوقه.
-- Checking if a specific door is locked
local doorId = 1
local isLocked = exports.ox_doorlock:getDoorState(doorId)
-- Toggle a door lock from server side
exports.ox_doorlock:setDoorState(doorId, not isLocked)
-- Listen for door state changes
AddEventHandler('ox_doorlock:stateChanged', function(id, state, source)
print(('Door %d changed to %s by player %s'):format(id, state and 'locked' or 'unlocked', source))
end)
بناء آلية فتح الأقفال
يضيف فتح الأقفال طبقة لعب تجعل الأبواب المغلقة ذات معنى للشخصيات الإجرامية. بدلاً من أن تكون الأبواب حواجز مطلقة، تصبح تحديات تعتمد على المهارة. أفضل نهج هو لعبة مصغرة تتطلب توقيتًا ودقة، مما يجعل النتيجة تبدو مكتسبة بدلاً من عشوائية. سنستخدم نظام skillcheck المدمج في ox_lib لإنشاء تدفق فتح الأقفال الذي يتطلب من اللاعب أن يمتلك عنصر مفتاح القفل في مخزونهم، ويشغل رسومًا متحركة، ويجري اختبار مهارة متعدد المراحل. يجب أن يكون لفشل اختبار المهارة عواقب مثل كسر مفتاح القفل أو تنبيه الشرطة القريبة عبر حدث dispatch.
-- client/lockpicking.lua
local function AttemptLockpick(doorId)
local hasLockpick = exports.ox_inventory:Search('count', 'lockpick')
if hasLockpick < 1 then
lib.notify({ title = 'No Lockpick', type = 'error' })
return
end
-- Play lockpicking animation
local ped = PlayerPedId()
TaskPlayAnim(ped, 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@', 'machinic_loop_mechandlenry', 8.0, -8.0, -1, 1, 0, false, false, false)
-- Multi-stage skillcheck: easy, medium, hard
local success = lib.skillCheck({'easy', 'medium', 'hard'}, {'w', 'a', 's', 'd'})
ClearPedTasks(ped)
if success then
TriggerServerEvent('doorlock:lockpick:success', doorId)
lib.notify({ title = 'Lock Picked', description = 'The door clicks open.', type = 'success' })
else
TriggerServerEvent('doorlock:lockpick:fail', doorId)
lib.notify({ title = 'Failed', description = 'The lockpick snapped.', type = 'error' })
end
end
على جانب الخادم، تحتاج إلى التحقق من محاولة فتح القفل، إزالة عنصر مفتاح القفل عند الفشل، فتح الباب عند النجاح، واختيارياً إرسال تنبيه للشرطة. تحديد المعدل مهم هنا لمنع محاولات الإغراق. قم بتخزين فترة تبريد لكل لاعب لكل باب حتى لا يتمكنوا من إعادة المحاولة فوراً بعد الفشل. يجب على معالج الخادم أيضاً التحقق من أن اللاعب فعلاً بالقرب من الباب الذي يدعي أنه يفتحه، لمنع الاستغلال عن بُعد.
-- server/lockpicking.lua
local cooldowns = {}
RegisterNetEvent('doorlock:lockpick:success', function(doorId)
local src = source
local playerCoords = GetEntityCoords(GetPlayerPed(src))
-- Verify proximity to door (anti-cheat)
local doorData = exports.ox_doorlock:getDoor(doorId)
if not doorData then return end
if #(playerCoords - doorData.coords) > 3.0 then return end
-- Check cooldown
local key = ('%s:%s'):format(src, doorId)
if cooldowns[key] and os.time() - cooldowns[key] < 30 then return end
exports.ox_doorlock:setDoorState(doorId, 0) -- Unlock
cooldowns[key] = os.time()
-- Auto-relock after 60 seconds
SetTimeout(60000, function()
exports.ox_doorlock:setDoorState(doorId, 1)
end)
end)
RegisterNetEvent('doorlock:lockpick:fail', function(doorId)
local src = source
exports.ox_inventory:RemoveItem(src, 'lockpick', 1)
-- Alert police dispatch
TriggerEvent('dispatch:alert', {
coords = GetEntityCoords(GetPlayerPed(src)),
message = 'Attempted break-in reported',
code = '10-31',
job = 'police'
})
end)
تنفيذ نظام بطاقة المفاتيح
توفر بطاقات المفاتيح آلية تحكم في الوصول أكثر تنظيمًا مقارنة بعناصر المفتاح البسيطة. تعمل بشكل جيد للمباني الشركاتية، والمرافق الحكومية، والمناطق المقيدة حيث يجب ربط الوصول بمستويات تصاريح محددة. يخصص نظام بطاقة المفتاح مستوى أمني من 1 إلى 5 لكل باب، ويجب أن يمتلك اللاعبون بطاقة مفتاح بمستوى تصاريح مساوي أو أعلى لفتحه. هذا يخلق تسلسلًا هرميًا طبيعيًا حيث تفتح بطاقة المفتاح من المستوى 3 جميع الأبواب من المستويات 1 و2 و3 لكنها لا يمكنها الوصول إلى المناطق المقيدة من المستوى 4 أو 5. يمكن إصدار بطاقات المفاتيح بواسطة أنظمة الوظائف، أو العثور عليها كغنائم، أو تصنيعها من خلال نظام تقدم.
-- shared/config.lua
Config = {}
Config.KeycardLevels = {
{ name = 'keycard_1', label = 'Green Keycard', level = 1 },
{ name = 'keycard_2', label = 'Blue Keycard', level = 2 },
{ name = 'keycard_3', label = 'Yellow Keycard', level = 3 },
{ name = 'keycard_4', label = 'Red Keycard', level = 4 },
{ name = 'keycard_5', label = 'Black Keycard', level = 5 },
}
Config.DoorSecurity = {
[10] = { level = 1, name = 'Office Lobby' },
[11] = { level = 2, name = 'Server Room' },
[12] = { level = 3, name = 'Executive Floor' },
[13] = { level = 4, name = 'Vault Anteroom' },
[14] = { level = 5, name = 'Main Vault' },
}
-- client/keycard.lua
local function TryKeycardAccess(doorId)
local security = Config.DoorSecurity[doorId]
if not security then return false end
for _, card in ipairs(Config.KeycardLevels) do
if card.level >= security.level then
local count = exports.ox_inventory:Search('count', card.name)
if count > 0 then
-- Play card swipe animation
lib.requestAnimDict('anim@heists@keycard@')
TaskPlayAnim(PlayerPedId(), 'anim@heists@keycard@', 'exit', 5.0, 1.0, -1, 16, 0, 0, 0, 0)
Wait(1200)
ClearPedTasks(PlayerPedId())
return true, card
end
end
end
return false
end
مجموعات الأبواب والوصول للأعمال
تصبح إدارة الأبواب الفردية غير قابلة للتوسع عندما يحتوي خادمك على مئات الأبواب المغلقة. تحل مجموعات الأبواب هذه المشكلة من خلال السماح لك بتعيين عدة أبواب إلى مجموعة مسماة والتحكم في الوصول إلى المجموعة بأكملها دفعة واحدة. قد يحتوي مركز الشرطة على 15 بابًا مغلقًا يجب أن تكون جميعها متاحة لوظيفة الشرطة. بدلاً من تكوين كل باب بشكل فردي، تعينهم جميعًا إلى مجموعة "police_station" وتمنح الوصول بناءً على اسم الوظيفة. عندما يوظف المسؤول ضابطًا جديدًا، يحصل تلقائيًا على الوصول إلى كل باب في المجموعة دون أي تكوين يدوي.
-- server/door_groups.lua
local DoorGroups = {
police_station = {
doors = {1, 2, 3, 4, 5, 6, 7, 8},
access = {
{ type = 'job', name = 'police', minGrade = 0 },
{ type = 'job', name = 'sheriff', minGrade = 0 },
}
},
pillbox_hospital = {
doors = {20, 21, 22, 23, 24},
access = {
{ type = 'job', name = 'ambulance', minGrade = 0 },
{ type = 'job', name = 'doctor', minGrade = 2 },
}
},
vangelico = {
doors = {30, 31},
access = {
{ type = 'job', name = 'jeweler', minGrade = 0 },
{ type = 'item', name = 'vangelico_key' },
}
},
}
function HasGroupAccess(source, groupName)
local group = DoorGroups[groupName]
if not group then return false end
for _, rule in ipairs(group.access) do
if rule.type == 'job' then
local job = GetPlayerJob(source)
if job and job.name == rule.name and job.grade >= (rule.minGrade or 0) then
return true
end
elseif rule.type == 'item' then
local count = exports.ox_inventory:GetItem(source, rule.name, nil, true)
if count and count > 0 then return true end
end
end
return false
end
تضيف أبواب الأعمال بعدًا آخر بربط الوصول إلى الأبواب بسجلات الملكية. عند شراء لاعب لعمل تجاري، يجب أن يحصل تلقائيًا على التحكم في جميع الأبواب المرتبطة بتلك الملكية. يحتاج النظام إلى التحقق من ملكية العمل في الوقت الفعلي لأن العقارات يمكن بيعها أو نقلها. نفذ رد نداء يستعلم جدول الأعمال الخاص بك للتحقق من المالك الحالي، ثم خزّن النتيجة مع TTL قصير لتجنب الضغط على قاعدة البيانات عند كل تفاعل مع الباب.
مزامنة حالات الأبواب عبر العملاء
مزامنة الأبواب هي واحدة من الجوانب الأكثر تعقيدًا في نظام قفل الأبواب. عندما يفتح لاعب بابًا، يجب أن يرى كل لاعب قريب الباب مفتوحًا. تتحكم أوامر الأبواب الأصلية في FiveM في حالة الباب على أساس كل عميل على حدة، مما يعني أن كل عميل يدير حالات الأبواب بشكل مستقل. بدون مزامنة صحيحة، يرى لاعب بابًا مفتوحًا بينما يرى آخر الباب مغلقًا. يتعامل ox_doorlock مع مزامنة الحالة الأساسية، لكن الإضافات المخصصة تحتاج إلى الحذر بشأن نشر الحالة. استخدم state bags أو أحداث مخولة من الخادم لبث تغييرات الأبواب إلى كل اللاعبين ضمن نطاق معقول. تجنب المزامنة مع كامل السيرفر عند كل تبديل باب لأن ذلك يخلق حملًا غير ضروري على الشبكة في السيرفرات الكبيرة التي تحتوي على مئات الأبواب.
-- server: broadcast door state to nearby players only
local function SyncDoorToNearby(doorId, state, coords, range)
range = range or 100.0
local players = GetPlayers()
for _, playerId in ipairs(players) do
local ped = GetPlayerPed(playerId)
if ped and DoesEntityExist(ped) then
local playerCoords = GetEntityCoords(ped)
if #(playerCoords - coords) <= range then
TriggerClientEvent('doorlock:sync', tonumber(playerId), doorId, state)
end
end
end
end
-- client: apply synced door state
RegisterNetEvent('doorlock:sync', function(doorId, state)
local door = exports.ox_doorlock:getDoor(doorId)
if door then
door.state = state
end
end)
الاعتبارات الأمنية ومكافحة الاستغلال
أنظمة قفل الأبواب هدف شائع للمخترقين لأن تجاوز الباب غالباً ما يمنح الوصول إلى عناصر مقيدة أو أسلحة أو أموال. يجب التحقق من كل طلب تبديل باب على جانب الخادم. لا تثق أبداً بمعرفات الأبواب المرسلة من العميل دون التحقق من قرب اللاعب وحقوق الوصول الخاصة به. نفذ تسجيل لكل تغيير في حالة الباب حتى يتمكن المسؤولون من مراجعة الأنماط المشبوهة، مثل لاعب يفتح أبواب الخزنة التي لا يجب أن يصل إليها. حدد معدل التفاعل مع الأبواب لمنع محاولات القوة الغاشمة على نظام فتح الأقفال. فكر في إضافة مسجل أحداث على جانب الخادم يسجل معرف Steam الخاص باللاعب، معرف الباب، الطابع الزمني، وطريقة الوصول لأغراض التدقيق. تصبح هذه السجلات لا تقدر بثمن عند التحقيق في الاستغلالات أو حل النزاعات بين اللاعبين حول من وصل إلى ماذا ومتى.
تجميع كل شيء معًا
يجمع نظام قفل الأبواب الجاهز للإنتاج كل هذه المكونات في إطار عمل موحد. نقطة الدخول هي تفاعل نظام الهدف، إما ox_target أو qb-target، الذي يكتشف عندما ينظر اللاعب إلى باب مسجل ويعرض خيارات سياقية بناءً على مستوى وصوله. إذا كان لديهم وصول وظيفي، عرض زر الفتح. إذا كان لديهم بطاقة مفتاح، عرض خيار السحب. إذا كان لديهم قفل فتح والأبواب موسومة كقابلة للفتح بالقفل، عرض خيار القفل. كل مسار يتغذى إلى المعالج المناسب، الذي يتحقق على الخادم ويزامن النتيجة مع العملاء القريبين. يجب أن يكون التكوين مدفوعاً بالبيانات من خلال إدخالات قاعدة البيانات أو ملفات التكوين المشتركة بحيث يمكن لمالكي الخوادم إضافة، إزالة، وتعديل وصول الأبواب دون لمس الكود. تعني هذه البنية المعيارية أنه يمكنك استبدال لعبة القفل المصغر، إضافة طرق وصول جديدة مثل الماسحات البيومترية، أو التكامل مع أنظمة مخزون مختلفة تماماً دون إعادة كتابة المنطق الأساسي.