edi
239
resources/[Developer]/[Nordi]/nordi_vending/client/main.lua
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
local QBCore = exports['qb-core']:GetCoreObject()
|
||||||
|
local Config = require 'config'
|
||||||
|
local inZone = false
|
||||||
|
local currentMachine = nil
|
||||||
|
local PlayerData = {}
|
||||||
|
|
||||||
|
local function SetupTarget()
|
||||||
|
if not Config.UseQBTarget then return end
|
||||||
|
|
||||||
|
-- Default 24/7 machines
|
||||||
|
for modelHash, machineData in pairs(Config.DefaultMachines.Models) do
|
||||||
|
exports['qb-target']:AddTargetModel(modelHash, {
|
||||||
|
options = {
|
||||||
|
{
|
||||||
|
type = 'client',
|
||||||
|
event = 'vendingmachines:client:openMenu',
|
||||||
|
icon = 'fas fa-cash-register',
|
||||||
|
label = 'Use Vending Machine',
|
||||||
|
machineModel = modelHash,
|
||||||
|
isPlayerOwned = false
|
||||||
|
},
|
||||||
|
Config.EnableRobbery and {
|
||||||
|
type = 'client',
|
||||||
|
event = 'vendingmachines:client:startRobbery',
|
||||||
|
icon = 'fas fa-lock',
|
||||||
|
label = 'Rob Machine',
|
||||||
|
item = Config.RobberyItem,
|
||||||
|
machineModel = modelHash,
|
||||||
|
isPlayerOwned = false
|
||||||
|
} or nil
|
||||||
|
},
|
||||||
|
distance = Config.TargetOptions.distance
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Player owned machines will be added dynamically as they're loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
local function RefreshPlayerMachines()
|
||||||
|
local playerMachines = QBCore.Functions.TriggerRpc('vendingmachines:server:getPlayerMachines', PlayerData.citizenid)
|
||||||
|
|
||||||
|
for _, machine in pairs(playerMachines) do
|
||||||
|
exports['qb-target']:AddBoxZone('vending_' .. machine.id, json.decode(machine.location), 0.5, 0.5, {
|
||||||
|
name = 'vending_' .. machine.id,
|
||||||
|
heading = 0,
|
||||||
|
debugPoly = false,
|
||||||
|
minZ = json.decode(machine.location).z - 0.5,
|
||||||
|
maxZ = json.decode(machine.location).z + 0.5
|
||||||
|
}, {
|
||||||
|
options = {
|
||||||
|
{
|
||||||
|
type = 'client',
|
||||||
|
event = 'vendingmachines:client:openMenu',
|
||||||
|
icon = 'fas fa-cash-register',
|
||||||
|
label = 'Use Vending Machine',
|
||||||
|
machineId = machine.id,
|
||||||
|
isPlayerOwned = true,
|
||||||
|
machineModel = machine.model_hash
|
||||||
|
},
|
||||||
|
Config.EnableRobbery and {
|
||||||
|
type = 'client',
|
||||||
|
event = 'vendingmachines:client:startRobbery',
|
||||||
|
icon = 'fas fa-lock',
|
||||||
|
label = 'Rob Machine',
|
||||||
|
item = Config.RobberyItem,
|
||||||
|
machineId = machine.id,
|
||||||
|
isPlayerOwned = true
|
||||||
|
} or nil,
|
||||||
|
table.contains(Config.PlayerOwnedMachines.AllowedJobs, PlayerData.job.name) and {
|
||||||
|
type = 'client',
|
||||||
|
event = 'vendingmachines:client:restockMenu',
|
||||||
|
icon = 'fas fa-boxes',
|
||||||
|
label = 'Restock Machine',
|
||||||
|
machineId = machine.id
|
||||||
|
} or nil,
|
||||||
|
table.contains(Config.PlayerOwnedMachines.AllowedJobs, PlayerData.job.name) and {
|
||||||
|
type = 'client',
|
||||||
|
event = 'vendingmachines:client:collectEarnings',
|
||||||
|
icon = 'fas fa-money-bill-wave',
|
||||||
|
label = 'Collect Earnings',
|
||||||
|
machineId = machine.id
|
||||||
|
} or nil
|
||||||
|
},
|
||||||
|
distance = Config.TargetOptions.distance
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function OpenVendingMenu(machineInfo)
|
||||||
|
local menu = {
|
||||||
|
header = 'Vending Machine',
|
||||||
|
items = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if machineInfo.isPlayerOwned then
|
||||||
|
menu.header = 'Player Vending Machine'
|
||||||
|
menu.items[#menu.items + 1] = {
|
||||||
|
title = 'Stock: ' .. (currentMachine and currentMachine.stock or '?'),
|
||||||
|
description = 'Available items for purchase',
|
||||||
|
disabled = true
|
||||||
|
}
|
||||||
|
else
|
||||||
|
menu.header = '24/7 Vending Machine'
|
||||||
|
end
|
||||||
|
|
||||||
|
local items = Config.DefaultMachines.Models[machineInfo.machineModel].items
|
||||||
|
for _, item in pairs(items) do
|
||||||
|
menu.items[#menu.items + 1] = {
|
||||||
|
title = item.label,
|
||||||
|
description = 'Price: $' .. item.price,
|
||||||
|
onSelect = function()
|
||||||
|
local input = lib.inputDialog('Purchase Amount', {
|
||||||
|
{ type = 'number', label = 'Amount', min = 1, max = 10, default = 1 }
|
||||||
|
})
|
||||||
|
|
||||||
|
if input and input[1] then
|
||||||
|
TriggerServerEvent('vendingmachines:server:processPurchase',
|
||||||
|
machineInfo.isPlayerOwned,
|
||||||
|
machineInfo.isPlayerOwned and machineInfo.machineId or machineInfo.machineModel,
|
||||||
|
item.name,
|
||||||
|
input[1])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
lib.registerContext(menu)
|
||||||
|
lib.showContext('vending_menu')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function StartRobbery(machineInfo)
|
||||||
|
QBCore.Functions.TriggerCallback('vendingmachines:server:hasItem', function(hasItem)
|
||||||
|
if hasItem then
|
||||||
|
local success = lib.skillCheck({ 'easy', 'easy', { areaSize = 60, speedMultiplier = 1 } }, { 'w', 'a', 's', 'd' })
|
||||||
|
|
||||||
|
if success then
|
||||||
|
QBCore.Functions.Progressbar('robbing_machine', 'Robbing vending machine...',
|
||||||
|
Config.RobberyTime * 1000, false, true, {
|
||||||
|
disableMovement = true,
|
||||||
|
disableCarMovement = true,
|
||||||
|
disableMouse = false,
|
||||||
|
disableCombat = true,
|
||||||
|
}, {
|
||||||
|
animDict = 'mini@safe_cracking',
|
||||||
|
anim = 'idle_base',
|
||||||
|
flags = 16,
|
||||||
|
}, {}, {}, function()
|
||||||
|
TriggerServerEvent('vendingmachines:server:completeRobbery',
|
||||||
|
machineInfo.isPlayerOwned,
|
||||||
|
machineInfo.isPlayerOwned and machineInfo.machineId or nil)
|
||||||
|
end, function()
|
||||||
|
TriggerServerEvent('vendingmachines:server:robberyFailed',
|
||||||
|
machineInfo.isPlayerOwned,
|
||||||
|
machineInfo.isPlayerOwned and machineInfo.machineId or nil)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
QBCore.Functions.Notify('Robbery failed', 'error')
|
||||||
|
end
|
||||||
|
else
|
||||||
|
QBCore.Functions.Notify('You don\'t have the required item', 'error')
|
||||||
|
end
|
||||||
|
end, Config.RobberyItem)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Events
|
||||||
|
RegisterNetEvent('vendingmachines:client:openMenu', function(data)
|
||||||
|
OpenVendingMenu(data)
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('vendingmachines:client:registerMachine', function()
|
||||||
|
local playerPed = PlayerPedId()
|
||||||
|
local coords = GetEntityCoords(playerPed)
|
||||||
|
local obj = GetClosestObjectOfType(coords, 3.0, GetHashKey('prop_vend_soda_01'), false, false, false)
|
||||||
|
|
||||||
|
if DoesEntityExist(obj) then
|
||||||
|
local modelHash = GetEntityModel(obj)
|
||||||
|
local validModel = false
|
||||||
|
|
||||||
|
for _, model in pairs(Config.PlayerOwnedMachines.Models) do
|
||||||
|
if model == modelHash then
|
||||||
|
validModel = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not validModel then
|
||||||
|
QBCore.Functions.Notify('This vending machine cannot be owned', 'error')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local objCoords = GetEntityCoords(obj)
|
||||||
|
TriggerServerEvent('vendingmachines:server:registerMachine', modelHash, objCoords)
|
||||||
|
else
|
||||||
|
QBCore.Functions.Notify('No vending machine nearby', 'error')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('vendingmachines:client:startRobbery', function(data)
|
||||||
|
StartRobbery(data)
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('vendingmachines:client:refreshPlayerMachines', function()
|
||||||
|
RefreshPlayerMachines()
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('vendingmachines:client:restockMenu', function(data)
|
||||||
|
local input = lib.inputDialog('Restock Amount', {
|
||||||
|
{ type = 'number', label = 'Amount', min = 1, default = 1 }
|
||||||
|
})
|
||||||
|
|
||||||
|
if input and input[1] then
|
||||||
|
TriggerServerEvent('vendingmachines:server:restockMachine', data.machineId)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('vendingmachines:client:collectEarnings', function(data)
|
||||||
|
TriggerServerEvent('vendingmachines:server:collectEarnings', data.machineId)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Initialization
|
||||||
|
AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
|
||||||
|
PlayerData = QBCore.Functions.GetPlayerData()
|
||||||
|
SetupTarget()
|
||||||
|
RefreshPlayerMachines()
|
||||||
|
end)
|
||||||
|
|
||||||
|
AddEventHandler('QBCore:Client:OnJobUpdate', function(job)
|
||||||
|
PlayerData.job = job
|
||||||
|
end)
|
||||||
|
|
||||||
|
AddEventHandler('onResourceStart', function(resource)
|
||||||
|
if GetCurrentResourceName() == resource then
|
||||||
|
PlayerData = QBCore.Functions.GetPlayerData()
|
||||||
|
SetupTarget()
|
||||||
|
Citizen.SetTimeout(5000, function()
|
||||||
|
RefreshPlayerMachines()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
70
resources/[Developer]/[Nordi]/nordi_vending/config.lua
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
local Config = {}
|
||||||
|
|
||||||
|
-- General Settings
|
||||||
|
Config.UseQBTarget = true
|
||||||
|
Config.UseOxLib = true
|
||||||
|
Config.Inventory = 'qs-inventory' -- or 'qb-inventory'
|
||||||
|
|
||||||
|
-- Robbery Settings
|
||||||
|
Config.EnableRobbery = true
|
||||||
|
Config.RobberyItem = 'lockpick' -- item needed to rob
|
||||||
|
Config.MinRobberyCash = 50
|
||||||
|
Config.MaxRobberyCash = 250
|
||||||
|
Config.RobberyTime = 15 -- seconds
|
||||||
|
Config.PoliceJob = 'police'
|
||||||
|
Config.Dispatch = 'ps-dispatch' -- or 'qb-dispatch'
|
||||||
|
Config.RobberyAlertChance = 70 -- percentage chance police get alerted
|
||||||
|
|
||||||
|
-- Player-owned Vending Machines
|
||||||
|
Config.PlayerOwnedMachines = {
|
||||||
|
AllowedJobs = { -- jobs that can restock/collect money
|
||||||
|
'vending',
|
||||||
|
'delivery'
|
||||||
|
},
|
||||||
|
Models = {
|
||||||
|
`prop_vend_soda_01`,
|
||||||
|
`prop_vend_soda_02`,
|
||||||
|
`prop_vend_water_01`
|
||||||
|
},
|
||||||
|
RestockItem = 'sodasyrup', -- item needed to restock
|
||||||
|
StockAmount = 10, -- how much stock restock item provides
|
||||||
|
PricePerItem = 10, -- how much player earns per sale
|
||||||
|
MaxStock = 50,
|
||||||
|
OwnerCut = 0.7 -- percentage owner gets (30% goes to business)
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 24/7 Vending Machines
|
||||||
|
Config.DefaultMachines = {
|
||||||
|
Models = {
|
||||||
|
[`prop_vend_soda_01`] = {
|
||||||
|
items = {
|
||||||
|
{ name = 'water_bottle', price = 5, label = 'Water' },
|
||||||
|
{ name = 'kurkakola', price = 7, label = 'Cola' },
|
||||||
|
{ name = 'sprunk', price = 7, label = 'Sprunk' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`prop_vend_soda_02`] = {
|
||||||
|
items = {
|
||||||
|
{ name = 'water_bottle', price = 5, label = 'Water' },
|
||||||
|
{ name = 'ecola', price = 8, label = 'eCola' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`prop_vend_coffe_01`] = {
|
||||||
|
items = {
|
||||||
|
{ name = 'coffee', price = 10, label = 'Coffee' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Target Options
|
||||||
|
Config.TargetOptions = {
|
||||||
|
useZones = true, -- create target zones for machines
|
||||||
|
distance = 2.0
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Notification Settings
|
||||||
|
Config.Notify = 'qb' -- or 'ox', 'okok', 'esx'
|
||||||
|
Config.Locale = 'en' -- if using locale files
|
||||||
|
|
||||||
|
return Config
|
30
resources/[Developer]/[Nordi]/nordi_vending/fxmanifest.lua
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
fx_version 'cerulean'
|
||||||
|
game 'gta5'
|
||||||
|
|
||||||
|
name 'QB-VendingMachines'
|
||||||
|
author 'Nordi'
|
||||||
|
description 'Advanced Vending Machine System for QB-Core'
|
||||||
|
version '1.0.0'
|
||||||
|
|
||||||
|
shared_scripts {
|
||||||
|
'config.lua',
|
||||||
|
'@qb-core/shared/locale.lua',
|
||||||
|
'locales/en.lua' -- add other locales as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
client_scripts {
|
||||||
|
'@qb-target/client.lua',
|
||||||
|
'client/main.lua'
|
||||||
|
}
|
||||||
|
|
||||||
|
server_scripts {
|
||||||
|
'@oxmysql/lib/MySQL.lua',
|
||||||
|
'server/main.lua'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
'qb-core',
|
||||||
|
'qb-target',
|
||||||
|
'qs-inventory',
|
||||||
|
'ox_lib' -- or 'ox_lib' if using newer version
|
||||||
|
}
|
316
resources/[Developer]/[Nordi]/nordi_vending/server/main.lua
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
local QBCore = exports['qb-core']:GetCoreObject()
|
||||||
|
local Config = require 'config'
|
||||||
|
|
||||||
|
-- SQL Setup
|
||||||
|
local function SetupDatabase()
|
||||||
|
local query = [[
|
||||||
|
CREATE TABLE IF NOT EXISTS player_vending_machines (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
owner VARCHAR(50) NOT NULL,
|
||||||
|
model_hash BIGINT NOT NULL,
|
||||||
|
location LONGTEXT NOT NULL,
|
||||||
|
stock INT DEFAULT 0,
|
||||||
|
earnings DECIMAL(10,2) DEFAULT 0,
|
||||||
|
last_collected BIGINT DEFAULT 0,
|
||||||
|
UNIQUE KEY unique_machine (owner, model_hash, location(255))
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
]]
|
||||||
|
MySQL.ready(function()
|
||||||
|
MySQL.query(query, {}, function(result)
|
||||||
|
print('^2[Vending Machines]^7 Database table initialized')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Global state for 24/7 machine robberies
|
||||||
|
local robberyCooldown = {}
|
||||||
|
|
||||||
|
-- Register usable items
|
||||||
|
QBCore.Functions.CreateUseableItem(Config.RobberyItem, function(source)
|
||||||
|
TriggerClientEvent('vendingmachines:client:startRobbery', source)
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Config.PlayerOwnedMachines.RestockItem then
|
||||||
|
QBCore.Functions.CreateUseableItem(Config.PlayerOwnedMachines.RestockItem, function(source)
|
||||||
|
TriggerClientEvent('vendingmachines:client:startRestock', source)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Player-owned machine management
|
||||||
|
local function GetPlayerMachines(citizenid)
|
||||||
|
local result = MySQL.query.await('SELECT * FROM player_vending_machines WHERE owner = ?', { citizenid })
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetMachineById(id)
|
||||||
|
local result = MySQL.query.await('SELECT * FROM player_vending_machines WHERE id = ?', { id })
|
||||||
|
return result[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function RegisterPlayerMachine(source, modelHash, coords)
|
||||||
|
local Player = QBCore.Functions.GetPlayer(source)
|
||||||
|
if not Player then return false end
|
||||||
|
|
||||||
|
local location = json.encode({
|
||||||
|
x = coords.x,
|
||||||
|
y = coords.y,
|
||||||
|
z = coords.z
|
||||||
|
})
|
||||||
|
|
||||||
|
local result = MySQL.insert.await([[
|
||||||
|
INSERT INTO player_vending_machines (owner, model_hash, location)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
]], { Player.PlayerData.citizenid, modelHash, location })
|
||||||
|
|
||||||
|
if result then
|
||||||
|
TriggerClientEvent('vendingmachines:client:refreshPlayerMachines', -1)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function RestockMachine(machineId, amount)
|
||||||
|
local machine = GetMachineById(machineId)
|
||||||
|
if not machine then return false end
|
||||||
|
|
||||||
|
local newStock = math.min(machine.stock + amount, Config.PlayerOwnedMachines.MaxStock)
|
||||||
|
MySQL.update.await('UPDATE player_vending_machines SET stock = ? WHERE id = ?', { newStock, machineId })
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CollectEarnings(machineId)
|
||||||
|
local machine = GetMachineById(machineId)
|
||||||
|
if not machine or machine.earnings <= 0 then return 0 end
|
||||||
|
|
||||||
|
local amount = machine.earnings
|
||||||
|
MySQL.update.await('UPDATE player_vending_machines SET earnings = 0, last_collected = ? WHERE id = ?',
|
||||||
|
{ os.time(), machineId })
|
||||||
|
return amount
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle vending machine purchases
|
||||||
|
local function ProcessPurchase(source, isPlayerOwned, machineId, itemName, amount)
|
||||||
|
local Player = QBCore.Functions.GetPlayer(source)
|
||||||
|
if not Player then return false end
|
||||||
|
|
||||||
|
if isPlayerOwned then
|
||||||
|
-- Player-owned machine purchase
|
||||||
|
local machine = GetMachineById(machineId)
|
||||||
|
if not machine or machine.stock < amount then return false end
|
||||||
|
|
||||||
|
local itemData = nil
|
||||||
|
for _, item in pairs(Config.DefaultMachines.Models[machine.model_hash].items) do
|
||||||
|
if item.name == itemName then
|
||||||
|
itemData = item
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not itemData then return false end
|
||||||
|
|
||||||
|
local totalPrice = itemData.price * amount
|
||||||
|
if Player.Functions.RemoveMoney('cash', totalPrice) then
|
||||||
|
local earnings = totalPrice
|
||||||
|
local ownerCut = math.floor(earnings * Config.PlayerOwnedMachines.OwnerCut)
|
||||||
|
local businessCut = earnings - ownerCut
|
||||||
|
|
||||||
|
MySQL.update.await('UPDATE player_vending_machines SET stock = stock - ?, earnings = earnings + ? WHERE id = ?',
|
||||||
|
{ amount, businessCut, machineId })
|
||||||
|
|
||||||
|
-- Give item to player
|
||||||
|
Player.Functions.AddItem(itemName, amount)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- 24/7 machine purchase
|
||||||
|
local modelHash = tonumber(machineId)
|
||||||
|
local machineItems = Config.DefaultMachines.Models[modelHash]
|
||||||
|
if not machineItems then return false end
|
||||||
|
|
||||||
|
local itemData = nil
|
||||||
|
for _, item in pairs(machineItems.items) do
|
||||||
|
if item.name == itemName then
|
||||||
|
itemData = item
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not itemData then return false end
|
||||||
|
|
||||||
|
local totalPrice = itemData.price * amount
|
||||||
|
if Player.Functions.RemoveMoney('cash', totalPrice) then
|
||||||
|
Player.Functions.AddItem(itemName, amount)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Robbery functions
|
||||||
|
local function AlertPolice(machineCoords)
|
||||||
|
if Config.Dispatch == 'ps-dispatch' then
|
||||||
|
exports['ps-dispatch']:VendingMachineRobbery(machineCoords)
|
||||||
|
elseif Config.Dispatch == 'qb-dispatch' then
|
||||||
|
TriggerEvent('qb-dispatch:officerDistress', {
|
||||||
|
dispatchCode = '10-31',
|
||||||
|
firstStreet = GetStreetAndZone(machineCoords),
|
||||||
|
priority = 2,
|
||||||
|
origin = {
|
||||||
|
x = machineCoords.x,
|
||||||
|
y = machineCoords.y,
|
||||||
|
z = machineCoords.z
|
||||||
|
},
|
||||||
|
blip = {
|
||||||
|
sprite = 52,
|
||||||
|
scale = 1.5,
|
||||||
|
color = 1,
|
||||||
|
flashes = true,
|
||||||
|
text = 'Vending Machine Robbery',
|
||||||
|
time = 5
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CompleteRobbery(source, isPlayerOwned, machineId)
|
||||||
|
local Player = QBCore.Functions.GetPlayer(source)
|
||||||
|
if not Player then return false end
|
||||||
|
|
||||||
|
if robberyCooldown[source] and os.time() - robberyCooldown[source] < 300 then
|
||||||
|
TriggerClientEvent('QBCore:Notify', source, 'You need to wait before robbing again', 'error')
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if Player.Functions.RemoveItem(Config.RobberyItem, 1) then
|
||||||
|
local reward = math.random(Config.MinRobberyCash, Config.MaxRobberyCash)
|
||||||
|
Player.Functions.AddMoney('cash', reward)
|
||||||
|
|
||||||
|
if math.random(1, 100) <= Config.RobberyAlertChance then
|
||||||
|
local coords = GetEntityCoords(GetPlayerPed(source))
|
||||||
|
AlertPolice(coords)
|
||||||
|
end
|
||||||
|
|
||||||
|
robberyCooldown[source] = os.time()
|
||||||
|
|
||||||
|
if isPlayerOwned then
|
||||||
|
local machine = GetMachineById(machineId)
|
||||||
|
if machine then
|
||||||
|
MySQL.update.await('UPDATE player_vending_machines SET stock = GREATEST(0, stock - ?) WHERE id = ?',
|
||||||
|
{ math.random(5, 15), machineId })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Server Events
|
||||||
|
RegisterNetEvent('vendingmachines:server:registerMachine', function(modelHash, coords)
|
||||||
|
local src = source
|
||||||
|
if RegisterPlayerMachine(src, modelHash, coords) then
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'Vending machine registered successfully', 'success')
|
||||||
|
else
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'Failed to register vending machine', 'error')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('vendingmachines:server:processPurchase', function(isPlayerOwned, machineId, itemName, amount)
|
||||||
|
local src = source
|
||||||
|
if ProcessPurchase(src, isPlayerOwned, machineId, itemName, amount) then
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'Purchase successful', 'success')
|
||||||
|
else
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'Purchase failed', 'error')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('vendingmachines:server:restockMachine', function(machineId)
|
||||||
|
local src = source
|
||||||
|
local Player = QBCore.Functions.GetPlayer(src)
|
||||||
|
if not Player then return end
|
||||||
|
|
||||||
|
local jobName = Player.PlayerData.job.name
|
||||||
|
local allowed = false
|
||||||
|
for _, job in pairs(Config.PlayerOwnedMachines.AllowedJobs) do
|
||||||
|
if job == jobName then
|
||||||
|
allowed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not allowed then
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'You are not authorized to restock machines', 'error')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Player.Functions.RemoveItem(Config.PlayerOwnedMachines.RestockItem, 1) then
|
||||||
|
if RestockMachine(machineId, Config.PlayerOwnedMachines.StockAmount) then
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'Machine restocked successfully', 'success')
|
||||||
|
else
|
||||||
|
Player.Functions.AddItem(Config.PlayerOwnedMachines.RestockItem, 1)
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'Failed to restock machine', 'error')
|
||||||
|
end
|
||||||
|
else
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'You don\'t have the restock item', 'error')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('vendingmachines:server:collectEarnings', function(machineId)
|
||||||
|
local src = source
|
||||||
|
local Player = QBCore.Functions.GetPlayer(src)
|
||||||
|
if not Player then return end
|
||||||
|
|
||||||
|
local jobName = Player.PlayerData.job.name
|
||||||
|
local allowed = false
|
||||||
|
for _, job in pairs(Config.PlayerOwnedMachines.AllowedJobs) do
|
||||||
|
if job == jobName then
|
||||||
|
allowed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not allowed then
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'You are not authorized to collect earnings', 'error')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local amount = CollectEarnings(machineId)
|
||||||
|
if amount > 0 then
|
||||||
|
Player.Functions.AddMoney('cash', amount)
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, string.format('Collected $%d from the machine', amount), 'success')
|
||||||
|
else
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'No earnings to collect', 'error')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('vendingmachines:server:completeRobbery', function(isPlayerOwned, machineId)
|
||||||
|
local src = source
|
||||||
|
if CompleteRobbery(src, isPlayerOwned, machineId) then
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'Robbery successful', 'success')
|
||||||
|
else
|
||||||
|
TriggerClientEvent('QBCore:Notify', src, 'Robbery failed', 'error')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Commands
|
||||||
|
QBCore.Commands.Add('registermachine', 'Register a player-owned vending machine', {}, false, function(source)
|
||||||
|
TriggerClientEvent('vendingmachines:client:registerMachine', source)
|
||||||
|
end, 'admin')
|
||||||
|
|
||||||
|
QBCore.Commands.Add('mymachines', 'View your owned vending machines', {}, false, function(source)
|
||||||
|
local Player = QBCore.Functions.GetPlayer(source)
|
||||||
|
if not Player then return end
|
||||||
|
|
||||||
|
local machines = GetPlayerMachines(Player.PlayerData.citizenid)
|
||||||
|
if #machines > 0 then
|
||||||
|
local msg = "Your machines:\n"
|
||||||
|
for _, machine in pairs(machines) do
|
||||||
|
msg = msg .. string.format("ID: %d | Stock: %d | Earnings: $%.2f\n", machine.id, machine.stock, machine.earnings)
|
||||||
|
end
|
||||||
|
TriggerClientEvent('chat:addMessage', source, { args = { 'SYSTEM', msg } })
|
||||||
|
else
|
||||||
|
TriggerClientEvent('QBCore:Notify', source, 'You don\'t own any machines', 'error')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Initialize
|
||||||
|
SetupDatabase()
|
BIN
resources/[Developer]/[Nordi]/qb-vehiclesales-main.zip
Normal file
|
@ -671,23 +671,27 @@ local vehicles = { -- Vehicle list, credits to qb-core!
|
||||||
|
|
||||||
|
|
||||||
-- Import Trucks
|
-- Import Trucks
|
||||||
{ model = 'brickadeb', name = 'Brickader Boxtruck', brand = 'MTL', price = 80000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'brickadeb', name = 'Brickader Boxtruck', brand = 'MTL', price = 80000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'brickadef', name = 'Brickader Flatbed', brand = 'MTL', price = 80000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'brickadef', name = 'Brickader Flatbed', brand = 'MTL', price = 80000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'nmule', name = 'Mule 4x4', brand = 'Maibatsu', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'nmule', name = 'Mule 4x4', brand = 'Maibatsu', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'gbvoyagerb2', name = 'Voyager Boxtruck', brand = 'MTL', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'gbvoyagerb2', name = 'Voyager Boxtruck', brand = 'MTL', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'mulef', name = 'Mule Flatbed', brand = 'Maibatsu', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'mulef', name = 'Mule Flatbed', brand = 'Maibatsu', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'brickaderv', name = 'Brickader Camper', brand = 'MTL', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'brickaderv', name = 'Brickader Camper', brand = 'MTL', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'sandroamer', name = 'Sandroamer Camper', brand = 'Vapid', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'sandroamer', name = 'Sandroamer Camper', brand = 'Vapid', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'sandkingrv', name = 'Sandking Camper', brand = 'Vapid', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'sandkingrv', name = 'Sandking Camper', brand = 'Vapid', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'guardianrv', name = 'Guardian Camper', brand = 'Vapid', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'guardianrv', name = 'Guardian Camper', brand = 'Vapid', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'cararv', name = 'Caracara Camper', brand = 'Vapid', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'cararv', name = 'Caracara Camper', brand = 'Vapid', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
{ model = 'galaxyroamer', name = 'Galaxy Roamer', brand = 'Brute', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
{ model = 'galaxyroamer', name = 'Galaxy Roamer', brand = 'Brute', price = 55000, category = 'importtrucks', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
|
|
||||||
|
|
||||||
|
-- Trailer
|
||||||
|
{ model = ' boattrailer', name = 'Bootsanhänger', brand = 'HVY', price = 8000, category = 'trailer', shop = { 'cardealer', 'fmpdm', 'laryscars' } },
|
||||||
|
|
||||||
-- Import Cars
|
-- Import Cars
|
||||||
{ model = 'gb811s2', name = '811 S2', brand = 'Pfister', price = 80000, category = 'importcars', shop = { 'fussion' } },
|
{ model = 'gb811s2', name = '811 S2', brand = 'Pfister', price = 80000, category = 'importcars', shop = { 'fussion' } },
|
||||||
{ model = 'gbadmiral', name = 'Admiral', brand = 'Declasse', price = 80000, category = 'importcars', shop = { 'fussion' } },
|
{ model = 'gbadmiral', name = 'Admiral', brand = 'Declasse', price = 80000, category = 'importcars', shop = { 'fussion' } },
|
||||||
{ model = 'gbargento7f', name = 'Argento 7F', brand = 'Obey', price = 80000, category = 'importcars', shop = { 'fussion' } },
|
{ model = 'gbargento7f', name = 'Argento 7F', brand = 'Obey', price = 80000, category = 'importcars', shop = { 'fussion' } },
|
||||||
{ model = 'gbbanshees', name = 'Banshee ', brand = 'Bravado', price = 80000, category = 'importcars', shop = { 'fussion' } },
|
{ model = 'gbbanshees', name = 'Banshee ', brand = 'Bravado', price = 80000, category = 'importcars', shop = { 'fussion' } },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,8 @@ Config.dealerships.cardealer = { -- Dealership ID, NEEDS TO BE THE SAME AS THE J
|
||||||
{ label = 'LKWS', id = 'trucks' },
|
{ label = 'LKWS', id = 'trucks' },
|
||||||
{ label = 'Import LKWS', id = 'importtrucks' },
|
{ label = 'Import LKWS', id = 'importtrucks' },
|
||||||
{ label = 'Vans', id = 'vans' },
|
{ label = 'Vans', id = 'vans' },
|
||||||
|
{ label = 'Anhänger', id = 'trailer' },
|
||||||
|
|
||||||
},
|
},
|
||||||
pickups = {
|
pickups = {
|
||||||
vec4(1201.35, -3187.03, 5.98, 175.35)
|
vec4(1201.35, -3187.03, 5.98, 175.35)
|
||||||
|
|
|
@ -2452,7 +2452,7 @@ CodeStudio.Shops = {
|
||||||
[1] = 'medic_items',
|
[1] = 'medic_items',
|
||||||
},
|
},
|
||||||
requiredJob = {
|
requiredJob = {
|
||||||
['ambulance'] = {11,10,9}
|
['ambulance'] = {11,10,9,8}
|
||||||
},
|
},
|
||||||
Locations = {
|
Locations = {
|
||||||
vector4(-1830.3236, -389.1446, 49.3909, 3)
|
vector4(-1830.3236, -389.1446, 49.3909, 3)
|
||||||
|
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 354 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
@ -97,7 +97,8 @@ Config.RagdollTime = {
|
||||||
Config.rubberBulletWeapons = {
|
Config.rubberBulletWeapons = {
|
||||||
[94989220],
|
[94989220],
|
||||||
[-2009644972],
|
[-2009644972],
|
||||||
[-86904375]
|
[-86904375],
|
||||||
|
[911657153],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -196,7 +197,7 @@ Config.WeaponRecoil = {
|
||||||
[-771403250] = 3.0, --WEAPON_HEAVYPISTOL
|
[-771403250] = 3.0, --WEAPON_HEAVYPISTOL
|
||||||
[137902532] = 2.5, --WEAPON_VINTAGEPISTOL
|
[137902532] = 2.5, --WEAPON_VINTAGEPISTOL
|
||||||
[-598887786] = 4.5, --WEAPON_MARKSMANPISTOL
|
[-598887786] = 4.5, --WEAPON_MARKSMANPISTOL
|
||||||
[584646201] = 2.4, --WEAPON_APPISTOL
|
[584646201] = 1.8, --WEAPON_APPISTOL
|
||||||
[911657153] = 0.02, --WEAPON_STUNGUN
|
[911657153] = 0.02, --WEAPON_STUNGUN
|
||||||
[1198879012] = 1.3, --WEAPON_FLAREGUN
|
[1198879012] = 1.3, --WEAPON_FLAREGUN
|
||||||
[-1746263880] = 3.5, --WEAPON_DOUBLEACTION
|
[-1746263880] = 3.5, --WEAPON_DOUBLEACTION
|
||||||
|
@ -216,13 +217,13 @@ Config.WeaponRecoil = {
|
||||||
[-1121678507] = 2.4, --WEAPON_MINISMG
|
[-1121678507] = 2.4, --WEAPON_MINISMG
|
||||||
|
|
||||||
--ASSAULT RIFLES
|
--ASSAULT RIFLES
|
||||||
[-1074790547] = 2.5, --WEAPON_ASSAULTRIFLE
|
[-1074790547] = 2.2, --WEAPON_ASSAULTRIFLE
|
||||||
[961495388] = 2.5, --WEAPON_ASSAULTRIFLEMK2
|
[961495388] = 2.5, --WEAPON_ASSAULTRIFLEMK2
|
||||||
[-2084633992] = 2.3, --WEAPON_CARBINERIFLE
|
[-2084633992] = 2.0, --WEAPON_CARBINERIFLE
|
||||||
[-86904375] = 2.3, --WEAPON_CARBINERIFLEMK2
|
[-86904375] = 2.3, --WEAPON_CARBINERIFLEMK2
|
||||||
[-1357824103] = 2.5, --WEAPON_ADVANCEDRIFLE
|
[-1357824103] = 2.5, --WEAPON_ADVANCEDRIFLE
|
||||||
[-1063057011] = 2.4, --WEAPON_SPECIALCARBINE
|
[-1063057011] = 2.0, --WEAPON_SPECIALCARBINE
|
||||||
[2132975508] = 2.4, --WEAPON_BULLPUPRIFLE
|
[2132975508] = 2.2, --WEAPON_BULLPUPRIFLE
|
||||||
[1649403952] = 2.0, --WEAPON_COMPACTRIFLE
|
[1649403952] = 2.0, --WEAPON_COMPACTRIFLE
|
||||||
|
|
||||||
--SNIPER RIFLES
|
--SNIPER RIFLES
|
||||||
|
|
|
@ -128,7 +128,27 @@ JobCalls.Positions = {
|
||||||
display = 4
|
display = 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name = "Santoro",
|
||||||
|
coords = vector3(162.3750, 268.6166, 109.9737),
|
||||||
|
number = 3131,
|
||||||
|
needjob = "sud",
|
||||||
|
useMarker = true,
|
||||||
|
marker = {
|
||||||
|
drawDistance = 10.0,
|
||||||
|
type = 25,
|
||||||
|
size = {x = 1.0, y = 1.0, z = 1.0},
|
||||||
|
color = {r = 50, g = 50, b = 204}
|
||||||
|
},
|
||||||
|
blip = {
|
||||||
|
active = false,
|
||||||
|
name = "Santoro Control Center",
|
||||||
|
sprite = 60,
|
||||||
|
color = 38,
|
||||||
|
scale = 0.8,
|
||||||
|
display = 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
BIN
resources/[phone]/roadphone/public/img/service/sud.png
Normal file
After Width: | Height: | Size: 293 KiB |
|
@ -70,21 +70,26 @@
|
||||||
"name": "Petrol Head Performance",
|
"name": "Petrol Head Performance",
|
||||||
"number": 5556969,
|
"number": 5556969,
|
||||||
"img": "/public/img/service/php.png",
|
"img": "/public/img/service/php.png",
|
||||||
"acceptMessage": false
|
"acceptMessage": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Kayas Restaurant",
|
"name": "Kayas Restaurant",
|
||||||
"number": 5553022,
|
"number": 5553022,
|
||||||
"img": "/public/img/service/kayas.png",
|
"img": "/public/img/service/kayas.png",
|
||||||
"acceptMessage": false
|
"acceptMessage": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Weazel News",
|
"name": "Weazel News",
|
||||||
"number": 404,
|
"number": 404,
|
||||||
"img": "/public/img/service/wn.png",
|
"img": "/public/img/service/wn.png",
|
||||||
"acceptMessage": false
|
"acceptMessage": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Santoro Unlimited",
|
||||||
|
"number": 3131,
|
||||||
|
"img": "/public/img/service/sud.png",
|
||||||
|
"acceptMessage": true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
"Ringtones": {
|
"Ringtones": {
|
||||||
|
|