سكريبت ماكينات البيع FiveM: طعام، مشروبات وأكثر
إضافة ماكينات البيع التي تعمل إلى خادم FiveM الخاص بك. الوجبات الخفيفة، المشروبات، العناصر المخصصة، وضع المجسمات وأفضل سكربتات البيع للتثبيت في دقائق.
Agency Scripts
المؤسس والمطور الرئيسي في Agency Scripts
لماذا تضيف آلات البيع عمقًا لخادمك
تؤدي آلات البيع وأنظمة المتاجر الآلية دورًا حيويًا في أي اقتصاد لعب أدوار. توفر وصولًا على مدار الساعة إلى عناصر أساسية مثل الطعام، الماء، السجائر، والإمدادات الأساسية دون الحاجة إلى موظف متجر متصل. بالإضافة إلى الراحة، تخلق نقاط تجمع طبيعية حيث يتجمع اللاعبون، مما يؤدي إلى لقاءات لعب أدوار عفوية في محطات الوقود، المستشفيات، ومباني المكاتب. يتجاوز نظام آلة البيع المنفذ جيدًا قائمة شراء بسيطة. يشمل رسوم شراء واقعية، إدارة المخزون التي تخلق ندرة، تسعير ديناميكي يستجيب للعرض والطلب، أنواع مختلفة من الآلات التي تبيع فئات منتجات مختلفة، ميكانيكيات السرقة للعب الأدوار الإجرامي، ووظائف إعادة التزويد التي توفر وظائف ذات معنى للاعبين. يغطي هذا الدرس بناء نظام كامل للآلات والمتاجر من اكتشاف الدعائم إلى دمج الجرد وإدارة الاقتصاد.
كشف وتكوين دعامات آلات البيع
عالم GTA V مليء بعناصر آلات البيع التي يمكنك تحويلها إلى كائنات تفاعلية. تشمل نماذج العناصر الرئيسية prop_vend_soda_01 و prop_vend_soda_02 لآلات الصودا، prop_vend_water_01 لماكينات توزيع المياه، prop_vend_coffe_01 لآلات القهوة، و prop_vend_snak_01 لآلات الوجبات الخفيفة. بدلاً من وضع نقاط التفاعل يدويًا في كل موقع آلة عبر الخريطة، استخدم نظام كشف يعتمد على نصف القطر يفحص نماذج المجسمات هذه بالقرب من اللاعب. عندما يقترب اللاعب من أي مجسم مطابق ضمن نطاق التفاعل، ينشط النظام مطالبة أو منطقة هدف. يعمل هذا الأسلوب تلقائيًا مع كل مثيل من هذه المجسمات عبر الخريطة بأكملها دون تكوين يدوي، ويكتشف حتى الديكورات الداخلية المخصصة MLO التي تتضمن هذه النماذج. عرّف كتالوجات منتجات مختلفة لكل نوع مجسم بحيث تبيع آلات الصودا المشروبات فقط، وآلات الوجبات الخفيفة تبيع المواد الغذائية، وآلات القهوة تبيع أنواع القهوة.
-- shared/config.lua
Config = {}
Config.InteractDistance = 1.5
Config.UseAnimation = true
Config.AnimDuration = 3000 -- ms
Config.Machines = {
soda = {
models = {
`prop_vend_soda_01`,
`prop_vend_soda_02`,
},
label = 'Soda Machine',
items = {
{name = 'cola', label = 'Cola', price = 5, stock = 20},
{name = 'sprunk', label = 'Sprunk', price = 5, stock = 20},
{name = 'ecola', label = 'E-Cola', price = 6, stock = 15},
{name = 'energydrink',label = 'Energy Drink', price = 8, stock = 10},
},
},
snack = {
models = {`prop_vend_snak_01`},
label = 'Snack Machine',
items = {
{name = 'chips', label = 'Chips', price = 4, stock = 25},
{name = 'candy', label = 'Candy Bar', price = 3, stock = 30},
{name = 'sandwich', label = 'Sandwich', price = 7, stock = 15},
{name = 'granola', label = 'Granola Bar', price = 5, stock = 20},
},
},
coffee = {
models = {`prop_vend_coffe_01`},
label = 'Coffee Machine',
items = {
{name = 'coffee', label = 'Black Coffee', price = 4, stock = 30},
{name = 'latte', label = 'Latte', price = 6, stock = 20},
{name = 'espresso', label = 'Espresso', price = 5, stock = 25},
},
},
water = {
models = {`prop_vend_water_01`},
label = 'Water Cooler',
items = {
{name = 'water', label = 'Water Bottle', price = 3, stock = 40},
},
},
}
نظام التفاعل والرسوم المتحركة على جانب العميل
عندما يقترب اللاعب من آلة بيع ويضغط مفتاح التفاعل، تحدث عدة أشياء بالتتابع لخلق تجربة شراء غامرة. أولاً، يتم تقييد حركة اللاعب لمنعه من الابتعاد أثناء الرسوم المتحركة. ثم يدير شخصية اللاعب وجهها نحو الآلة باستخدام TaskTurnPedToFaceEntity. يتم تشغيل رسوم متحركة للشراء باستخدام MINI@SPRUNK قاموس الرسوم المتحركة لماكينات الصودا أو ANIM@AM_HOLD_UP@MALE للتفاعلات العامة. تقوم الآلة نفسها بتشغيل تأثير صوتي باستخدام PlaySoundFromEntity لمحاكاة صوت الصرف الميكانيكي. بعد اكتمال الرسوم المتحركة، يرسل العميل طلب شراء إلى الخادم، الذي يتحقق من المعاملة ويضيف العنصر إلى مخزون اللاعب. إذا كان لدى الجهاز تفاعل فتحة العملات، أضف ملحقًا صغيرًا إلى يد اللاعب أثناء الرسوم المتحركة باستخدام AttachEntityToEntity لزيادة الدقة البصرية. يجب أن تستغرق التسلسل بأكمله حوالي 3 ثوانٍ، طويلة بما يكفي لتشعر بالواقعية وقصيرة بما يكفي لعدم إحباط اللاعبين.
-- client/interaction.lua
local isBuying = false
function PurchaseFromMachine(machineEntity, machineType, itemIndex)
if isBuying then return end
isBuying = true
local ped = PlayerPedId()
local machineCoords = GetEntityCoords(machineEntity)
-- Face the machine
TaskTurnPedToFaceEntity(ped, machineEntity, 1000)
Wait(1000)
-- Play animation
if Config.UseAnimation then
RequestAnimDict('mini@sprunk')
while not HasAnimDictLoaded('mini@sprunk') do Wait(10) end
TaskPlayAnim(ped, 'mini@sprunk', 'plyr_buy_drink_pt1',
8.0, -8.0, 2000, 0, 0, false, false, false)
Wait(1500)
TaskPlayAnim(ped, 'mini@sprunk', 'plyr_buy_drink_pt2',
8.0, -8.0, 1500, 0, 0, false, false, false)
-- Machine sound effect
PlaySoundFromEntity(-1, 'vending_machine_purchase',
machineEntity, 'dlc_vw_table_games', false, 0)
Wait(1500)
end
-- Request purchase from server
TriggerServerEvent('vendingmachine:purchase', machineType, itemIndex)
ClearPedTasks(ped)
isBuying = false
end
إدارة المخزون والاقتصاد على جانب الخادم
إدارة المخزون تحول آلات البيع من موزعات عناصر غير محدودة إلى عناصر اقتصادية ديناميكية. كل موقع آلة يتتبع مخزونه الخاص بشكل مستقل. عندما تنفد آلة من عنصر معين، يرى اللاعبون أنه تم وضع علامة عليه على أنه نفد في قائمة الشراء. مستويات المخزون تبقى في قاعدة البيانات عبر إعادة تشغيل الخادم. وظيفة إعادة التزويد تسمح للاعبين بالعمل كسائقي توصيل يلتقطون الإمدادات من مستودع ويقودونها إلى الآلات الفارغة في جميع أنحاء المدينة. هذا يخلق حلقة اقتصادية كاملة: الموردون يعيدون تزويد الآلات، الآلات تبيع للمستهلكين، الإيرادات تعود إلى مالك الآلة، وسائق التوصيل يكسب أجراً. التسعير الديناميكي يضيف طبقة أخرى حيث ترتفع أسعار العناصر الشعبية تدريجياً مع انخفاض المخزون، وتُعاد الأسعار إلى وضعها الطبيعي عند إعادة تزويد الآلة. هذا يوازن أنماط الاستهلاك بشكل طبيعي ويخلق حالة استعجال عندما يكون المخزون منخفضاً على عنصر مرغوب.
-- server/stock.lua
local machineStock = {}
function GetMachineKey(machineType, coords)
return machineType .. ':' ..
math.floor(coords.x) .. ':' ..
math.floor(coords.y) .. ':' ..
math.floor(coords.z)
end
function GetStock(machineKey, itemIndex)
if not machineStock[machineKey] then
-- Load from database or initialize defaults
machineStock[machineKey] = {}
end
return machineStock[machineKey][itemIndex] or
Config.Machines[machineKey:match('^(%w+):')].items[itemIndex].stock
end
RegisterNetEvent('vendingmachine:purchase', function(machineType, itemIndex)
local src = source
local config = Config.Machines[machineType]
if not config or not config.items[itemIndex] then return end
local item = config.items[itemIndex]
local machineKey = machineType -- simplified; use coords in production
-- Check stock
local stock = GetStock(machineKey, itemIndex)
if stock <= 0 then
TriggerClientEvent('ox_lib:notify', src, {
title = 'Sold Out',
description = item.label .. ' is out of stock',
type = 'error'
})
return
end
-- Calculate dynamic price
local basePrice = item.price
local stockRatio = stock / item.stock
local dynamicPrice = math.ceil(basePrice * (1 + (1 - stockRatio) * 0.5))
-- Deduct money
local paid = exports['framework']:RemoveMoney(src, dynamicPrice, 'cash')
if not paid then
TriggerClientEvent('ox_lib:notify', src, {
title = 'No Cash',
description = 'You need $' .. dynamicPrice,
type = 'error'
})
return
end
-- Add item to inventory
local added = exports['ox_inventory']:AddItem(src, item.name, 1)
if not added then
exports['framework']:AddMoney(src, dynamicPrice, 'cash')
return
end
-- Decrease stock
machineStock[machineKey] = machineStock[machineKey] or {}
machineStock[machineKey][itemIndex] = stock - 1
TriggerClientEvent('ox_lib:notify', src, {
title = 'Purchased',
description = item.label .. ' - $' .. dynamicPrice,
type = 'success'
})
end)
آليات السرقة للعب الأدوار الإجرامية
تمثل آلات البيع هدف سرقة طبيعي للشخصيات الإجرامية. نفذ ميكانيكية فتح الأقفال أو أداة فك لتمكين المجرمين من اقتحام الآلات لسرقة النقود بداخلها. يجب أن تستغرق السرقة وقتًا، وتتطلب لعبة صغيرة مثل لغز فتح القفل، تولد ضوضاء تنبه اللاعبين القريبين وقد تؤدي إلى إرسال الشرطة، وتنتج مكافأة متغيرة بناءً على عدد المشتريات التي عالجتها الآلة منذ آخر إعادة تزويد أو سرقة. تتبع تراكم النقود لكل آلة بناءً على مشتريات اللاعبين الفعلية بحيث تكون مكافأة السرقة واقعية ومرتبطة بالنشاط الاقتصادي الحقيقي. بعد سرقة ناجحة، تدخل الآلة حالة تالفة حيث لا يمكنها معالجة المشتريات حتى يتم إصلاحها بواسطة ميكانيكي أو تلقائيًا بعد فترة تبريد. يخلق هذا عواقب للنشاط الإجرامي تؤثر على المجتمع الأوسع وتولد أعمال إصلاح للميكانيكيين، مما يثري النظام الاقتصادي بشكل أكبر.
محلات مملوكة للاعبين وواجهات متاجر مخصصة
قم بتوسيع إطار عمل آلة البيع إلى نظام نقاط بيع كامل للأعمال التجارية المملوكة للاعبين. يمكن لأصحاب الأعمال وضع عدادات متجر مخصصة في ممتلكاتهم، وتكوين العناصر التي يبيعونها، وتحديد أسعارهم الخاصة، وإدارة مخزونهم من خلال واجهة إدارة NUI. يستخدم نظام المتجر نفس آليات الشراء والمخزون الأساسية لآلات البيع لكنه يضيف طبقة مالك مع تتبع الإيرادات، إدارة النفقات، أذونات الموظفين، وتقارير الأرباح. يمكن للموظفين المسجلين في العمل الوصول إلى السجل لمعالجة المبيعات يدويًا لتفاعلات التمثيل، بينما يتعامل النظام الآلي مع المبيعات عند عدم وجود موظف. تذهب الإيرادات من المبيعات الآلية إلى حساب البنك التجاري مطروحًا منه نسبة ضريبة قابلة للتكوين تتدفق إلى خزينة حكومة الخادم. هذا يخلق اقتصاد تجارة تجزئة كامل حيث يصنع اللاعبون أو يحصلون على السلع، يملؤون متاجرهم، يحددون الأسعار تنافسياً ضد الأعمال الأخرى، ويكسبون دخلًا سلبيًا من استثماراتهم.
تحسين الأداء والتكامل المستهدف
فحص نماذج الدعائم في كل إطار سيدمر أداء السيرفر. بدلاً من ذلك، استخدم نهج كشف متعدد المستويات. قم بإجراء مسح واسع كل ثانيتين في دائرة نصف قطرها 15 متراً للعثور على نماذج آلات البيع القريبة. خزّن مواقع الآلات المكتشفة ولا تقم إلا بفحوصات دقيقة للمسافة مقابل الذاكرة المؤقتة في الإطارات التالية. عندما يتحرك اللاعب أكثر من 20 متراً من أي آلة مخزنة، امسح الذاكرة المؤقتة وابدأ مسحاً جديداً. للسيرفرات التي تستخدم أنظمة استهداف مثل ox_target أو qb-target، سجل مناطق الاستهداف على الدعائم المكتشفة بدلاً من استخدام مطالبات القرب. مناطق الاستهداف أكثر كفاءة لأنها تنشط فقط عندما يهدف اللاعب إلى الجسم، مما يلغي الحاجة للفحص المستمر للمسافة تماماً. كما يوفر نهج الاستهداف تجربة واجهة مستخدم أنظف مع خيارات تفاعل حساسة للسياق تظهر مباشرة على نموذج الآلة بدلاً من نص عائم.
-- client/target.lua (ox_target integration)
local registeredMachines = {}
CreateThread(function()
for machineType, config in pairs(Config.Machines) do
for _, model in ipairs(config.models) do
exports.ox_target:addModel(model, {
{
name = 'vending_' .. machineType,
icon = 'fas fa-shopping-cart',
label = 'Use ' .. config.label,
onSelect = function(data)
OpenMachineMenu(machineType, data.entity)
end,
distance = Config.InteractDistance,
},
})
end
end
end)