المخازن والتخزين FiveM: سكريبت المخازن المشتركة
تصميم مستودعات ومخازن مشتركة في FiveM. مخابئ العصابات، خزائن الوظائف، حدود الوزن وأفضل سكربتات التخزين لـ ox_inventory والمزيد.
Agency Scripts
المؤسس والمطور الرئيسي في Agency Scripts
أنظمة التخزين في خوادم اللعب التمثيلي
كل خادم لعب أدوار يحتاج إلى نظام تخزين قوي يتجاوز المخزون الأساسي للاعب. المخزونات الشخصية لها سعة محدودة، ويجمع اللاعبون عناصر من خلال الوظائف، التصنيع، التداول، والنهب يريدون الاحتفاظ بها لكن لا يمكنهم حملها طوال الوقت. تحل أنظمة المخازن والمستودعات هذه المشكلة بتوفير مواقع فعلية في عالم اللعبة حيث يمكن للاعبين إيداع واسترجاع العناصر. تخدم هذه الأنظمة أغراض لعب متعددة: خزائن التخزين الشخصية تعطي اللاعبين مساحة إضافية، مستودعات التنظيم تسمح للعصابات والشركات بتجميع مواردها، ووحدات التخزين المؤجرة تخلق سوق عقاري يولد إيرادات متكررة لاقتصاد الخادم. يجب أن يتعامل التنفيذ الفني مع الوصول المتزامن، حدود الوزن والخانات، أذونات الوصول، والتكامل مع إطار المخزون الموجود في خادمك.
مخطط قاعدة البيانات للتخزين
تصميم قاعدة بيانات التخزين الخاصة بك لدعم أنواع تخزين متعددة مع ملكية مرنة وضوابط وصول. يحتاج كل وحدة تخزين إلى معرف فريد، مرجع مالك يمكن أن يكون player citizenid أو معرف منظمة، حدود السعة المحددة بالوزن وعدد الفتحات، وبيانات الجرد الفعلية المرسلة كـ JSON. تضمين جدول نظام الإيجار الذي يتتبع حالة الدفع للوحدات المؤجرة، مع قفل تلقائي عند تأخر الإيجار. يسمح جدول التحكم في الوصول للمالكين بمنح لاعبين آخرين إذن الوصول إلى وحدات التخزين الخاصة بهم:
CREATE TABLE IF NOT EXISTS storage_units (
id INT AUTO_INCREMENT PRIMARY KEY,
unit_id VARCHAR(50) UNIQUE NOT NULL,
unit_type ENUM('personal', 'organization', 'rental', 'property') DEFAULT 'personal',
owner_id VARCHAR(50) NOT NULL,
label VARCHAR(100) DEFAULT NULL,
max_weight INT DEFAULT 100000,
max_slots INT DEFAULT 50,
items LONGTEXT DEFAULT '[]',
location VARCHAR(50) NOT NULL,
is_locked BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_owner (owner_id),
INDEX idx_location (location),
INDEX idx_type (unit_type)
);
CREATE TABLE IF NOT EXISTS storage_access (
unit_id VARCHAR(50) NOT NULL,
citizenid VARCHAR(50) NOT NULL,
permission ENUM('view', 'deposit', 'full') DEFAULT 'deposit',
granted_by VARCHAR(50) NOT NULL,
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (unit_id, citizenid)
);
CREATE TABLE IF NOT EXISTS storage_rentals (
unit_id VARCHAR(50) PRIMARY KEY,
tenant_id VARCHAR(50) NOT NULL,
rent_amount INT NOT NULL,
rent_interval ENUM('daily', 'weekly') DEFAULT 'weekly',
last_paid TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
next_due TIMESTAMP NOT NULL,
is_overdue BOOLEAN DEFAULT FALSE,
INDEX idx_tenant (tenant_id),
INDEX idx_due (next_due)
);
تكوين موقع المستودع
حدد مواقع المخازن حول الخريطة مع مراعاة سياق الدور. المناطق الصناعية بالقرب من الميناء مناسبة للمخازن التجارية الكبيرة. الأحياء السكنية تناسب خزائن التخزين الشخصية. المواقع تحت الأرض أو المخفية تعمل كمخازن عصابات. يحتاج كل موقع إلى تكوين لموقعه الفيزيائي، نوع وحدات التخزين المتاحة، أسعار وحدات الإيجار، والهيكل الداخلي الذي يتم تحميله عند دخول اللاعب. استخدم وكلاء الداخلية في GTA أو MLOs مخصصة للداخلية حتى يدخل اللاعبون فعليًا إلى مبنى بدلاً من فتح قائمة في الشارع:
Config.StorageLocations = {
['industrial_1'] = {
label = 'Elysian Island Storage',
entrance = vector3(-96.67, -1601.77, 29.41),
blipSprite = 473,
blipColor = 2,
units = {
{id = 'ind1_small_1', type = 'rental', size = 'small', maxWeight = 50000, maxSlots = 30, rent = 500, interval = 'weekly'},
{id = 'ind1_small_2', type = 'rental', size = 'small', maxWeight = 50000, maxSlots = 30, rent = 500, interval = 'weekly'},
{id = 'ind1_medium_1', type = 'rental', size = 'medium', maxWeight = 150000, maxSlots = 75, rent = 1500, interval = 'weekly'},
{id = 'ind1_large_1', type = 'rental', size = 'large', maxWeight = 300000, maxSlots = 150, rent = 3500, interval = 'weekly'},
},
},
['gang_southside'] = {
label = 'Southside Stash',
entrance = vector3(93.41, -1961.08, 20.82),
blipSprite = 0, -- No blip for hidden locations
restrictedTo = {'ballas', 'gsf'}, -- Gang-only access
units = {
{id = 'gang_ss_main', type = 'organization', size = 'large', maxWeight = 500000, maxSlots = 200},
},
},
}
تكامل الجرد
يجب أن يتكامل نظام المخزن الخاص بك بسلاسة مع أي إطار جرد يستخدمه خادمك، سواء كان ox_inventory أو qb-inventory أو حل مخصص. يجب أن تعكس واجهة التخزين واجهة الجرد المألوفة حتى يفهم اللاعبون بشكل حدسي كيفية نقل العناصر بين جردهم الشخصي ووحدة التخزين. نفذ نقل العناصر بالسحب والإفلات مع اختيار الكمية للعناصر القابلة للتكديس. عند فتح اللاعب لوحدة تخزين، قم بتحميل العناصر المخزنة من قاعدة البيانات وعرضها جنبًا إلى جنب مع جرد اللاعب الشخصي. يجب التحقق من جميع تحركات العناصر على جانب الخادم لمنع استغلال التكرار. تحقق من أن الجرد المصدر يحتوي فعليًا على العنصر الذي يتم نقله، وأن الوجهة لديها سعة كافية من حيث الوزن والفتحات، وأن العنصر غير معلم بأنه غير قابل للتداول أو مرتبط باللاعب. استخدم معاملات قاعدة البيانات لنقل العناصر بحيث يحدث الإزالة من جرد واحد والإضافة إلى آخر بشكل ذري:
RegisterNetEvent('storage:server:moveItem', function(unitId, direction, itemName, amount, fromSlot, toSlot)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
-- Validate access
if not HasStorageAccess(Player.PlayerData.citizenid, unitId, 'full') then
TriggerClientEvent('QBCore:Notify', src, 'Access denied', 'error')
return
end
-- Load storage unit
local unit = GetStorageUnit(unitId)
if not unit or unit.is_locked then
TriggerClientEvent('QBCore:Notify', src, 'Storage is locked', 'error')
return
end
amount = math.floor(tonumber(amount) or 0)
if amount <= 0 then return end
if direction == 'to_storage' then
-- Player -> Storage
local playerItem = Player.Functions.GetItemBySlot(fromSlot)
if not playerItem or playerItem.name ~= itemName then return end
if playerItem.amount < amount then return end
local itemWeight = QBCore.Shared.Items[itemName].weight * amount
local currentWeight = CalculateStorageWeight(unit.items)
if currentWeight + itemWeight > unit.max_weight then
TriggerClientEvent('QBCore:Notify', src, 'Storage is full (weight)', 'error')
return
end
-- Atomic transfer
Player.Functions.RemoveItem(itemName, amount, fromSlot)
AddItemToStorage(unitId, itemName, amount, playerItem.info, toSlot)
elseif direction == 'from_storage' then
-- Storage -> Player
local storageItem = GetStorageItemBySlot(unit.items, fromSlot)
if not storageItem or storageItem.name ~= itemName then return end
if storageItem.amount < amount then return end
if not Player.Functions.AddItem(itemName, amount, toSlot, storageItem.info) then
TriggerClientEvent('QBCore:Notify', src, 'Inventory full', 'error')
return
end
RemoveItemFromStorage(unitId, itemName, amount, fromSlot)
end
-- Refresh UI for all viewers
RefreshStorageViewers(unitId)
end)
نظام دفع الإيجار
تتطلب وحدات التخزين المؤجرة نظام دفع متكرر يقوم تلقائيًا بخصم المبالغ من المستأجرين والتعامل مع الحسابات المتأخرة. شغّل مهمة مجدولة على الخادم تتحقق من جميع وحدات الإيجار بفاصل منتظم، وتعالج المدفوعات للوحدات المستحقة، وتقفل الوحدات المتأخرة. عند استحقاق الدفع، حاول خصم مبلغ الإيجار من حساب المستأجر البنكي. إذا كان الحساب البنكي لا يحتوي على أموال كافية، يتم وضع علامة على الإيجار كمتأخر وإرسال إشعار للاعب. امنح المستأجرين المتأخرين فترة سماح لبضعة أيام قبل قفل وحدتهم. الوحدة المقفلة تمنع المستأجر من الوصول إلى العناصر المخزنة حتى يتم دفع الرصيد المستحق. إذا بقي الإيجار غير مدفوع بعد فترة أطول، يمكن بيع المحتويات بالمزاد أو مسحها، مما يخلق تجربة لعب ثانوية مثيرة. نفذ سجل تاريخ المدفوعات حتى يتمكن المستأجرون من مراجعة رسوم الإيجار وتواريخ الدفع من خلال واجهة إدارة التخزين.
التنظيم والتخزين المشترك
تحتاج المنظمات مثل العصابات، الشركات، والإدارات الحكومية إلى تخزين مشترك يمكن لعدة أعضاء الوصول إليه بمستويات أذونات مناسبة. يمتلك قائد المنظمة السيطرة الكاملة ويمكنه إضافة أو إزالة الأعضاء من قائمة الوصول، تعيين مستويات أذونات فردية، وعرض سجلات التدقيق لجميع العناصر المودعة أو المسحوبة. قد يكون لدى الأعضاء العاديين وصول للإيداع فقط حيث يمكنهم إضافة العناصر لكن لا يمكنهم إزالتها، وهو مفيد لأعضاء العصابة الذين يساهمون بالمواد في المخزون المشترك. يحصل الأعضاء الموثوقون على وصول كامل لكل من الإيداع والسحب. نفذ سجل تدقيق شامل يسجل كل حركة عنصر داخل وخارج التخزين المشترك، بما في ذلك اللاعب الذي قام بالإجراء، العنصر والكمية، والطابع الزمني. يساعد هذا السجل قادة المنظمة على تتبع من يساهم ومن قد يسرق من المخزون المشترك. يجب أن يكون السجل قابلاً للعرض من خلال واجهة التخزين مع خيارات تصفية حسب اللاعب، نوع العنصر، ونطاق التاريخ.
ترقيات السعة والمستويات
السماح للاعبين بترقية سعة التخزين الخاصة بهم من خلال نظام ترقية يوفر حافز تقدم. ابدأ بوحدات تخزين صغيرة ذات وزن وسعة فتحات محدودة، ثم قدم مستويات ترقية تزيد هذه الحدود مقابل سعر. يجب أن تكلف كل درجة ترقية أكثر تدريجيًا، مما يخلق مصرفًا ماليًا ذا معنى لاقتصاد الخادم. يمكن للترقيات زيادة الوزن الأقصى، إضافة فتحات إضافية، أو فتح ميزات خاصة مثل التخزين بدرجة حرارة محكومة للعناصر القابلة للتلف أو الأمان المعزز الذي يجعل الوحدة مقاومة للسطو. عرض الدرجة الحالية والترقيات المتاحة في واجهة إدارة التخزين، مع تسعير واضح والفوائد التي توفرها كل درجة. فكر في ربط بعض درجات الترقية بالإنجازات أو معالم وقت اللعب بدلاً من التكلفة المالية فقط، مما يكافئ اللاعبين المخلصين بمزايا تخزين مميزة لا يمكنهم شراؤها ببساطة بالمال.
آليات الأمان وكسر الدخول
يجب ألا تكون وحدات التخزين آمنة تماماً من النشاط الإجرامي. نفذ آلية اقتحام تسمح للاعبين بمحاولة الوصول إلى وحدات التخزين التي لا يملكونها، مما يخلق مخاطرة على القيم المخزنة ويولد سيناريوهات سرقة. يجب أن تتطلب عملية الاقتحام أدوات محددة مثل مفاتيح القفل أو أجهزة تجاوز إلكترونية، وتأخذ وقتاً كبيراً مع مؤشرات مرئية وصوتية تنبه اللاعبين القريبين، وتطلق إشعاراً لمالك التخزين وللشرطة. يجب أن تكون وحدات التخزين ذات المستوى الأعلى وتلك التي تحتوي على ترقيات أمنية أصعب تدريجياً في الاقتحام، مما يتطلب أدوات أفضل ووقتاً أطول. إذا نجح الاقتحام، يحصل المجرم على وصول مؤقت إلى مخزون الوحدة ويمكنه أخذ العناصر، لكن النظام يجب أن يسجل الاقتحام ويوفر للمالك أدلة مثل معلومات تعريف جزئية يمكن للشرطة التحقيق فيها. هذا يخلق حلقة لعب كاملة حول غارات المستودعات، التحقيق، والعواقب التي تثري ديناميكيات الجريمة وإنفاذ القانون في الخادم.