Pourquoi les distributeurs automatiques ajoutent de la profondeur à ton serveur
Les distributeurs automatiques et les systèmes de magasins automatisés jouent un rôle crucial dans toute économie de jeu de rôle. Ils offrent un accès 24 heures sur 24 et 7 jours sur 7 à des articles essentiels comme de la nourriture, de l'eau, des cigarettes et des fournitures de base, sans qu'il soit nécessaire qu'un employé de magasin soit en ligne. Au-delà de la commodité, ils créent des points de rassemblement naturels où les joueurs se rassemblent, conduisant à des rencontres de jeu de rôle spontanées dans les stations-service, les hôpitaux et les immeubles de bureaux. Un système de distributeur automatique bien mis en œuvre va bien au-delà d’un simple menu d’achat. Il comprend des animations d'achat réalistes, une gestion des stocks qui crée de la rareté, une tarification dynamique qui répond à l'offre et à la demande, différents types de machines qui vendent différentes catégories de produits, des mécanismes de vol pour un jeu de rôle criminel et des tâches de réapprovisionnement qui donnent aux joueurs un emploi significatif. Ce didacticiel couvre la création d'un système complet de vente et de magasin, depuis la détection des accessoires jusqu'à l'intégration des stocks et la gestion de l'économie.
Détection et configuration des accessoires de distributeurs automatiques
Le monde de GTA V regorge d'accessoires de distributeurs automatiques que tu peux transformer en objets interactifs. Les principaux modèles d'accessoires incluent prop_vend_soda_01 et prop_vend_soda_02 pour les machines à soda, prop_vend_water_01 pour les distributeurs d'eau, prop_vend_coffe_01 pour les machines à café, et prop_vend_snak_01 pour distributeurs de snacks. Plutôt que de placer manuellement des points d'interaction à chaque emplacement de machine sur la carte, utilisez un système de détection basé sur le rayon qui recherche ces modèles d'accessoires à proximité du joueur. Lorsqu'un joueur s'approche d'un accessoire correspondant dans la plage d'interaction, le système active une invite ou une zone cible. Cette approche fonctionne automatiquement avec chaque instance de ces accessoires sur l'ensemble de la carte sans configuration manuelle, et elle sélectionne même les intérieurs MLO personnalisés qui incluent ces modèles. Définissez différents catalogues de produits pour chaque type d'accessoire afin que les machines à soda ne vendent que des boissons, les machines à snacks vendent des produits alimentaires et les machines à café vendent des variantes de café.
-- 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},
},
},
}
Système d'interaction et d'animation côté client
Lorsqu'un joueur s'approche d'un distributeur automatique et appuie sur la touche d'interaction, plusieurs choses se produisent en séquence pour créer une expérience d'achat immersive. Premièrement, les mouvements du joueur sont restreints pour l'empêcher de s'éloigner au milieu de l'animation. Ensuite, le personnage du joueur se tourne vers la machine en utilisant TaskTurnPedToFaceEntity. Une animation d'achat est diffusée à l'aide du MINI@SPRUNK dictionnaire d'animation pour machines à soda ou ANIM@AM_HOLD_UP@MALE pour les interactions générales. La machine elle-même joue un effet sonore en utilisant PlaySoundFromEntity pour simuler le bruit mécanique de la distribution. Une fois l'animation terminée, le client envoie une demande d'achat au serveur, qui valide la transaction et ajoute l'objet à l'inventaire du joueur. Si la machine a une interaction avec une fente à pièces, ajoutez un petit accessoire à la main du joueur pendant l'animation en utilisant AttachEntityToEntity pour une fidélité visuelle supplémentaire. La séquence entière devrait durer environ 3 secondes, suffisamment longue pour paraître réaliste mais suffisamment courte pour ne pas frustrer les joueurs.
-- 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
Gestion des stocks et économie côté serveur
La gestion des stocks transforme les distributeurs automatiques de distributeurs d'articles illimités en éléments économiques dynamiques. Chaque emplacement de machine suit son propre inventaire de manière indépendante. Lorsqu'une machine manque d'un article particulier, les joueurs le voient marqué comme épuisé dans le menu d'achat. Les niveaux de stock persistent dans la base de données lors des redémarrages du serveur. Un travail de réapprovisionnement permet aux joueurs de travailler en tant que chauffeurs-livreurs qui récupèrent des fournitures dans un entrepôt et les conduisent vers des machines vides dans la ville. Cela crée toute une boucle économique : les fournisseurs réapprovisionnent les machines, les machines vendent aux consommateurs, les revenus reviennent au propriétaire de la machine et le chauffeur-livreur gagne un salaire. La tarification dynamique ajoute une autre couche dans laquelle le prix des articles populaires augmente progressivement à mesure que le stock diminue, et les prix sont réinitialisés lorsque la machine est réapprovisionnée. Cela équilibre naturellement les modes de consommation et crée une urgence lorsqu’une machine manque d’un article désirable.
-- 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)
Mécanismes de vol pour le jeu de rôle criminel
Les distributeurs automatiques constituent une cible naturelle de vol pour les personnages criminels. Implémentez un mécanisme de crochetage ou de levier qui permet aux criminels de s'introduire dans les machines pour voler l'argent qu'elles contiennent. Le vol devrait prendre du temps, nécessiter un mini-jeu comme un puzzle de crochetage, générer du bruit qui alerte les joueurs à proximité et déclencher potentiellement une dépêche de police, et rapporter une récompense variable en fonction du nombre d'achats traités par la machine depuis son dernier réapprovisionnement ou vol. Suivez l'accumulation d'argent par machine en fonction des achats réels des joueurs afin que la récompense du vol soit réaliste et liée à une véritable activité économique. Après un vol réussi, la machine entre dans un état endommagé où elle ne peut pas traiter les achats jusqu'à ce qu'elle soit réparée par un mécanicien ou automatiquement après une période de refroidissement. Cela crée des conséquences en matière d'activités criminelles qui affectent la communauté au sens large et génère des travaux de réparation pour les mécaniciens, enrichissant ainsi davantage l'écosystème économique.
Boutiques appartenant aux joueurs et vitrines personnalisées
Étendez le cadre des distributeurs automatiques à un système de point de vente complet pour les entreprises appartenant aux joueurs. Les propriétaires d'entreprise peuvent placer des comptoirs de boutique personnalisés dans leurs propriétés, configurer les articles qu'ils vendent, fixer leurs propres prix et gérer leur inventaire via un système de gestion NUI. Le système de magasin utilise les mêmes mécanismes d'achat et de stock sous-jacents que les distributeurs automatiques, mais ajoute une couche de propriétaire avec le suivi des revenus, la gestion des dépenses, les autorisations des employés et les rapports sur les bénéfices. Les employés pointés dans l'entreprise peuvent accéder au registre pour traiter les ventes manuellement pour des interactions de jeu de rôle, tandis que le système automatisé gère les ventes lorsqu'aucun employé n'est présent. Les revenus des ventes automatisées sont versés sur le compte bancaire de l'entreprise, moins un pourcentage de taxe configurable qui est versé au trésor public du serveur. Cela crée une économie de vente au détail complète dans laquelle les acteurs fabriquent ou s'approvisionnent en biens, approvisionnent leurs magasins, établissent des prix compétitifs par rapport à d'autres entreprises et tirent un revenu passif de leur investissement.
Optimisation des performances et intégration cible
La recherche de modèles d'accessoires à chaque image détruirait les performances du serveur. Utilisez plutôt une approche de détection à plusieurs niveaux. Exécutez une analyse approfondie toutes les 2 secondes dans un rayon de 15 mètres pour trouver les accessoires des distributeurs automatiques à proximité. Le cache découvre les emplacements des machines et effectue uniquement des vérifications précises de la distance par rapport au cache dans les images suivantes. Lorsque le joueur s'éloigne de plus de 20 mètres d'une machine mise en cache, videz le cache et déclenchez une nouvelle analyse. Pour les serveurs utilisant des systèmes cibles tels que ox_target ou qb-target, enregistrez les zones cibles sur les accessoires découverts plutôt que d'utiliser des invites de proximité. Les zones cibles sont plus performantes car elles ne s'activent que lorsque le joueur vise l'objet, éliminant ainsi complètement le besoin d'une vérification continue de la distance. L'approche cible offre également une expérience d'interface utilisateur plus claire avec des options d'interaction contextuelles qui apparaissent directement sur le modèle de machine plutôt que sous forme d'invites de texte flottantes.
-- 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)
