From 49cdbddd6633ee291edf4e3f613d3f446ddd81c1 Mon Sep 17 00:00:00 2001 From: Giana Date: Wed, 15 Feb 2023 19:39:19 -0600 Subject: [PATCH 01/17] Base --- .idea/discord.xml | 2 +- .idea/vcs.xml | 6 ++++++ README.md | 3 +++ client/main.lua | 0 config.lua | 1 + fxmanifest.lua | 25 +++++++++++++++++++++++++ server/main.lua | 0 7 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 client/main.lua create mode 100644 config.lua create mode 100644 fxmanifest.lua create mode 100644 server/main.lua diff --git a/.idea/discord.xml b/.idea/discord.xml index 30bab2a..d8e9561 100644 --- a/.idea/discord.xml +++ b/.idea/discord.xml @@ -1,7 +1,7 @@ - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ecc2971 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# G-DrugSelling + +G-DrugSelling is a script for FiveM QBCore for selling drugs (or any items) via vehicle and/or walk up, or target, at configurable coordinates. \ No newline at end of file diff --git a/client/main.lua b/client/main.lua new file mode 100644 index 0000000..e69de29 diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..58ce239 --- /dev/null +++ b/config.lua @@ -0,0 +1 @@ +Config = {} \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua new file mode 100644 index 0000000..0301575 --- /dev/null +++ b/fxmanifest.lua @@ -0,0 +1,25 @@ +fx_version 'cerulean' + +game 'gta5' + +author 'Giana - github.com/Giana' +description 'g-drugselling' + +shared_scripts { + 'config.lua' +} + +client_scripts { + 'client/main.lua' +} + +server_scripts { + 'server/main.lua' +} + +dependencies { + 'qb-core', + 'qb-target' +} + +lua54 'yes' \ No newline at end of file diff --git a/server/main.lua b/server/main.lua new file mode 100644 index 0000000..e69de29 From 4b2cdb52c6a7d6a1d4f7bf95d78efb15e854f19c Mon Sep 17 00:00:00 2001 From: Giana Date: Wed, 15 Feb 2023 22:27:53 -0600 Subject: [PATCH 02/17] Fill config --- config.lua | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 245 insertions(+), 1 deletion(-) diff --git a/config.lua b/config.lua index 58ce239..642c443 100644 --- a/config.lua +++ b/config.lua @@ -1 +1,245 @@ -Config = {} \ No newline at end of file +Config = {} + +--[[ + For each Config.SellLocations[x]: + - active: If the sell location is active + - Set to false to disable selling at this location (blip and marker, if enabled, will not appear on map or to players at all) + - policeAlertChance: % chance police are alerted of selling + - type: How location is approached by player to sell + - Options: + - 'walkup' + - Player can sell if they walk up to coords + - 'driveup' + - Player can sell if they drive up to coords + - 'any' + - Player can sell if they walk or drive up to coords + - 'target' + - Player can sell if they target at coords + - coords: Coordinated of the selling point + - blip: + - enabled: If blip is enabled + - label: Blip label + - color: Blip color + - sprite: Blip sprite + - scale: Blip scale + - display: Blip display + - shortRange: If blip is short range + - marker: + - enabled: If marker is enabled + - type: Marker type + - scaleX: Marker scale on X axis + - scaleY: Marker scale on Y axis + - scaleZ: Marker scale on Z axis + - red: Marker red component + - green: Marker green component + - blue: Marker blue component + - alpha: Marker alpha component: + - bob: If marker bobs up and down + - faceCamera: If marker constantly faces camera + - npc: + - enabled: If NPC is enabled + - ped: NPC ped model + - heading: NPC ped heading + - scenario: NPC ped scenario + - sellable_items: + - 'itemName': Name of item + - quantity: Amount to sell + - reward_type: Reward type + - reward_amount: Amount of reward +]] +Config.SellLocations = { + [1] = { + active = true, + policeAlertChance = 15, + type = 'walkup', + coords = vector3(292.65, -1072.83, 28.41), + blip = { + enabled = false, + label = 'Weed Selling', + color = 0, + sprite = 41, + scale = 0.6, + display = 4, + shortRange = true + }, + marker = { + enabled = false, + type = 2, + scaleX = 0.2, + scaleY = 0.2, + scaleZ = 0.1, + red = 109, + green = 255, + blue = 0, + alpha = 0.64, + bob = false, + faceCamera = false + }, + npc = { + enabled = false, + ped = 's_m_y_xmech_02_mp', + heading = 240.00, + scenario = 'WORLD_HUMAN_AA_SMOKE', + }, + sellable_items = { + ['weed_white-widow'] = { + minimum_quantity = 5, + reward_type = 'cash', + reward_amount = math.random(75, 120) + }, + ['weed_skunk'] = { + minimum_quantity = 5, + reward_type = 'cash', + reward_amount = math.random(75, 120) + }, + ['weed_purple-haze'] = { + minimum_quantity = 5, + reward_type = 'cash', + reward_amount = math.random(75, 120) + }, + ['weed_og-kush'] = { + minimum_quantity = 5, + reward_type = 'cash', + reward_amount = math.random(75, 120) + }, + ['weed_amnesia'] = { + minimum_quantity = 5, + reward_type = 'cash', + reward_amount = math.random(75, 120) + }, + ['weed_brick'] = { + minimum_quantity = 10, + reward_type = 'cash', + reward_amount = math.random(2000, 3000) + } + } + }, + [2] = { + active = true, + policeAlertChance = 15, + type = 'walkup', + coords = vector3(292.65, -1072.83, 28.41), + blip = { + enabled = false, + label = 'Meth Selling', + color = 0, + sprite = 41, + scale = 0.6, + display = 4, + shortRange = true + }, + marker = { + enabled = false, + type = 2, + scaleX = 0.2, + scaleY = 0.2, + scaleZ = 0.1, + red = 109, + green = 255, + blue = 0, + alpha = 0.64, + bob = false, + faceCamera = false + }, + npc = { + enabled = false, + ped = 's_m_y_xmech_02_mp', + heading = 240.00, + scenario = 'WORLD_HUMAN_AA_SMOKE', + }, + sellable_items = { + ['meth'] = { + minimum_quantity = 5, + reward_type = 'cash', + reward_amount = math.random(75, 120) + } + } + }, + [3] = { + active = true, + policeAlertChance = 15, + type = 'walkup', + coords = vector3(292.65, -1072.83, 28.41), + blip = { + enabled = false, + label = 'Crack Selling', + color = 0, + sprite = 41, + scale = 0.6, + display = 4, + shortRange = true + }, + marker = { + enabled = false, + type = 2, + scaleX = 0.2, + scaleY = 0.2, + scaleZ = 0.1, + red = 109, + green = 255, + blue = 0, + alpha = 0.64, + bob = false, + faceCamera = false + }, + npc = { + enabled = false, + ped = 's_m_y_xmech_02_mp', + heading = 240.00, + scenario = 'WORLD_HUMAN_AA_SMOKE', + }, + sellable_items = { + ['crack_baggy'] = { + minimum_quantity = 5, + reward_type = 'cash', + reward_amount = math.random(90, 170) + } + } + }, + [4] = { + active = true, + policeAlertChance = 15, + type = 'walkup', + coords = vector3(292.65, -1072.83, 28.41), + blip = { + enabled = false, + label = 'Coke Selling', + color = 0, + sprite = 41, + scale = 0.6, + display = 4, + shortRange = true + }, + marker = { + enabled = false, + type = 2, + scaleX = 0.2, + scaleY = 0.2, + scaleZ = 0.1, + red = 109, + green = 255, + blue = 0, + alpha = 0.64, + bob = false, + faceCamera = false + }, + npc = { + enabled = false, + ped = 's_m_y_xmech_02_mp', + heading = 240.00, + scenario = 'WORLD_HUMAN_AA_SMOKE', + }, + sellable_items = { + ['cokebaggy'] = { + minimum_quantity = 5, + reward_type = 'cash', + reward_amount = math.random(90, 185) + }, + ['coke_brick'] = { + minimum_quantity = 10, + reward_type = 'cash', + reward_amount = math.random(8500, 10000) + } + } + } +} \ No newline at end of file From 4c4a77171efe28bb8156305a98cf4cde4b68201a Mon Sep 17 00:00:00 2001 From: Giana Date: Thu, 16 Feb 2023 20:07:02 -0600 Subject: [PATCH 03/17] Refactor config --- config.lua | 86 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/config.lua b/config.lua index 642c443..a166e9c 100644 --- a/config.lua +++ b/config.lua @@ -5,6 +5,8 @@ Config = {} - active: If the sell location is active - Set to false to disable selling at this location (blip and marker, if enabled, will not appear on map or to players at all) - policeAlertChance: % chance police are alerted of selling + - notificationsEnabled: If notifications regarding selling are enabled + - itemNotificationsEnabled: If notifications regarding items being added/removed to/from inventory are enabled - type: How location is approached by player to sell - Options: - 'walkup' @@ -43,14 +45,16 @@ Config = {} - scenario: NPC ped scenario - sellable_items: - 'itemName': Name of item - - quantity: Amount to sell - - reward_type: Reward type - - reward_amount: Amount of reward + - sell_quantity: Amount to sell in increments of + - rewards: + - 'rewardItemName' (name of money type or reward item) = rewardAmount (# amount of money type or reward item) ]] Config.SellLocations = { [1] = { active = true, policeAlertChance = 15, + notificationsEnabled = false, + itemNotificationsEnabled = false, type = 'walkup', coords = vector3(292.65, -1072.83, 28.41), blip = { @@ -83,40 +87,48 @@ Config.SellLocations = { }, sellable_items = { ['weed_white-widow'] = { - minimum_quantity = 5, - reward_type = 'cash', - reward_amount = math.random(75, 120) + sell_quantity = 5, + rewards = { + ['cash'] = math.random(75, 120) + } }, ['weed_skunk'] = { - minimum_quantity = 5, - reward_type = 'cash', - reward_amount = math.random(75, 120) + sell_quantity = 5, + rewards = { + ['cash'] = math.random(75, 120) + } }, ['weed_purple-haze'] = { - minimum_quantity = 5, - reward_type = 'cash', - reward_amount = math.random(75, 120) + sell_quantity = 5, + rewards = { + ['cash'] = math.random(75, 120) + } }, ['weed_og-kush'] = { - minimum_quantity = 5, - reward_type = 'cash', - reward_amount = math.random(75, 120) + sell_quantity = 5, + rewards = { + ['cash'] = math.random(75, 120) + } }, ['weed_amnesia'] = { - minimum_quantity = 5, - reward_type = 'cash', - reward_amount = math.random(75, 120) + sell_quantity = 5, + rewards = { + ['cash'] = math.random(75, 120) + } }, ['weed_brick'] = { - minimum_quantity = 10, - reward_type = 'cash', - reward_amount = math.random(2000, 3000) + sell_quantity = 10, + rewards = { + ['cash'] = math.random(2000, 3000) + } } } }, [2] = { active = true, policeAlertChance = 15, + notificationsEnabled = false, + itemNotificationsEnabled = false, type = 'walkup', coords = vector3(292.65, -1072.83, 28.41), blip = { @@ -149,15 +161,18 @@ Config.SellLocations = { }, sellable_items = { ['meth'] = { - minimum_quantity = 5, - reward_type = 'cash', - reward_amount = math.random(75, 120) + sell_quantity = 5, + rewards = { + ['cash'] = math.random(75, 120) + } } } }, [3] = { active = true, policeAlertChance = 15, + notificationsEnabled = false, + itemNotificationsEnabled = false, type = 'walkup', coords = vector3(292.65, -1072.83, 28.41), blip = { @@ -190,15 +205,18 @@ Config.SellLocations = { }, sellable_items = { ['crack_baggy'] = { - minimum_quantity = 5, - reward_type = 'cash', - reward_amount = math.random(90, 170) + sell_quantity = 5, + rewards = { + ['cash'] = math.random(90, 170) + } } } }, [4] = { active = true, policeAlertChance = 15, + notificationsEnabled = false, + itemNotificationsEnabled = false, type = 'walkup', coords = vector3(292.65, -1072.83, 28.41), blip = { @@ -231,14 +249,16 @@ Config.SellLocations = { }, sellable_items = { ['cokebaggy'] = { - minimum_quantity = 5, - reward_type = 'cash', - reward_amount = math.random(90, 185) + sell_quantity = 5, + rewards = { + ['cash'] = math.random(90, 185) + } }, ['coke_brick'] = { - minimum_quantity = 10, - reward_type = 'cash', - reward_amount = math.random(8500, 10000) + sell_quantity = 10, + rewards = { + ['cash'] = math.random(8500, 10000) + } } } } From a5fe9ef87f37d2a7bb2622ec792483c95f08e691 Mon Sep 17 00:00:00 2001 From: Giana Date: Thu, 16 Feb 2023 20:07:13 -0600 Subject: [PATCH 04/17] Add dependency --- fxmanifest.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/fxmanifest.lua b/fxmanifest.lua index 0301575..339da14 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -19,6 +19,7 @@ server_scripts { dependencies { 'qb-core', + 'qb-inventory', 'qb-target' } From 5cdbe40e6279736ef0717ebb4c01404e381e4b5b Mon Sep 17 00:00:00 2001 From: Giana Date: Thu, 16 Feb 2023 20:49:53 -0600 Subject: [PATCH 05/17] Add language support --- fxmanifest.lua | 2 ++ locales/en.lua | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 locales/en.lua diff --git a/fxmanifest.lua b/fxmanifest.lua index 339da14..95b3c5a 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -6,6 +6,8 @@ author 'Giana - github.com/Giana' description 'g-drugselling' shared_scripts { + '@qb-core/shared/locale.lua', + 'locales/en.lua', 'config.lua' } diff --git a/locales/en.lua b/locales/en.lua new file mode 100644 index 0000000..d0c74c5 --- /dev/null +++ b/locales/en.lua @@ -0,0 +1,22 @@ +local Translations = { + error = { + + }, + success = { + + }, + menu = { + + }, + button = { + + }, + other = { + + } +} + +Lang = Locale:new({ + phrases = Translations, + warnOnMissing = true +}) \ No newline at end of file From 5f4a51ac0999b9d18d56f70f3dd650a6fb2ce86f Mon Sep 17 00:00:00 2001 From: Giana Date: Thu, 16 Feb 2023 21:09:16 -0600 Subject: [PATCH 06/17] Implement walk up and drive up --- client/main.lua | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/client/main.lua b/client/main.lua index e69de29..504a84d 100644 --- a/client/main.lua +++ b/client/main.lua @@ -0,0 +1,34 @@ +-- Threads -- + +-- Walk up & drive up +Citizen.CreateThread(function() + while true do + if LocalPlayer.state.isLoggedIn then + local ped = PlayerPedId() + local pos = GetEntityCoords(ped) + local inRange = false + for k, v in pairs(Config.SellLocations) do + if v.active then + local dist = #(pos - v.coords) + if dist < 10 then + if v.marker.enabled then + if (v.type == 'walkup' and not IsPedInAnyVehicle(ped, false)) or (v.type == 'driveup' and IsPedInAnyVehicle(ped, false)) or v.type == 'any' or v.type == 'target' then + DrawMarker(v.marker.type, v.coords.x, v.coords.y, v.coords.z + 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, v.marker.scaleX, v.marker.scaleY, v.marker.scaleZ, v.marker.red, v.marker.green, v.marker.blue, v.marker.alpha, v.marker.bob, v.marker.faceCamera, 0, 1, 0, 0, 0) + end + end + if dist < 1.5 then + if (v.type == 'walkup' and not IsPedInAnyVehicle(ped, false)) or (v.type == 'driveup' and IsPedInAnyVehicle(ped, false)) or v.type == 'any' then + TriggerServerEvent('g-drugselling:server:sell', v) + end + end + inRange = true + end + end + end + if not inRange then + Citizen.Wait(2000) + end + end + Citizen.Wait(3) + end +end) \ No newline at end of file From 91bae9e952c3f2b15a974e32b9da1eddb3b48c09 Mon Sep 17 00:00:00 2001 From: Giana Date: Thu, 16 Feb 2023 21:09:33 -0600 Subject: [PATCH 07/17] Implement selling --- locales/en.lua | 15 +++------------ server/main.lua | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/locales/en.lua b/locales/en.lua index d0c74c5..0643a9f 100644 --- a/locales/en.lua +++ b/locales/en.lua @@ -1,18 +1,9 @@ local Translations = { error = { - - }, - success = { - - }, - menu = { - - }, - button = { - + not_enough = 'You do not have enough of: %{itemLabel}' }, - other = { - + info = { + police_alert = 'Drug sale in progress' } } diff --git a/server/main.lua b/server/main.lua index e69de29..95b0896 100644 --- a/server/main.lua +++ b/server/main.lua @@ -0,0 +1,49 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +-- Events -- + +RegisterNetEvent('g-drugselling:server:sell', function(sellLocation) + local src = source + local player = QBCore.Functions.GetPlayer(src) + for k, v in pairs(sellLocation.sellable_items) do + local hasItem = player.Functions.GetItemByName(k) + if hasItem then + if hasItem.amount >= v.sell_quantity then + local bundlesToSell = math.floor(hasItem.amount / v.sell_quantity) + local quantityToSell = bundlesToSell * v.sell_quantity + if player.Functions.RemoveItem(k, quantityToSell) then + for k2, v2 in pairs(v.rewards) do + local isMoneyType = false + for k3, v3 in pairs(QBConfig.Money.MoneyTypes) do + if tostring(k3) == k2 then + isMoneyType = true + end + end + if isMoneyType then + if not player.Functions.AddMoney(k2, v2) then + player.Functions.AddItem(k, quantityToSell) + return + end + else + if not player.Functions.AddItem(k2, v2) then + player.Functions.AddItem(k, quantityToSell) + return + end + if sellLocation.itemNotificationsEnabled then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[k2], 'add', v2) + end + end + end + local notifyPoliceRoll = math.random(1, 100) + if notifyPoliceRoll <= sellLocation.policeAlertChance then + TriggerServerEvent('police:server:policeAlert', Lang:t('info.police_alert')) + end + end + else + if sellLocation.notificationsEnabled then + TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_enough', { itemLabel = QBCore.Shared.Items[k]['label'] }), 'error') + end + end + end + end +end) \ No newline at end of file From 4a56f5de6cb749264860bc27e23955c9838239b3 Mon Sep 17 00:00:00 2001 From: Giana Date: Thu, 16 Feb 2023 21:20:14 -0600 Subject: [PATCH 08/17] Implement blip setup --- client/main.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/client/main.lua b/client/main.lua index 504a84d..bb57d59 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,5 +1,22 @@ -- Threads -- +-- Blip setup +Citizen.CreateThread(function() + for k, v in pairs(Config.SellLocations) do + if v.active and v.blip.enabled then + local locationBlip = AddBlipForCoord(v.coords.x, v.coords.y, v.coords.z) + SetBlipColour(locationBlip, v.blip.color) + SetBlipSprite(locationBlip, v.blip.sprite) + SetBlipScale(locationBlip, v.blip.scale) + SetBlipDisplay(locationBlip, v.blip.display) + SetBlipAsShortRange(locationBlip, v.blip.shortRange) + BeginTextCommandSetBlipName('STRING') + AddTextComponentSubstringPlayerName(v.blip.label) + EndTextCommandSetBlipName(locationBlip) + end + end +end) + -- Walk up & drive up Citizen.CreateThread(function() while true do From c0f8853bd79c76321ac4f6a1e40890a7891e6945 Mon Sep 17 00:00:00 2001 From: Giana Date: Thu, 16 Feb 2023 21:31:38 -0600 Subject: [PATCH 09/17] Implement NPC setup --- client/main.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/client/main.lua b/client/main.lua index bb57d59..3f19aa6 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,3 +1,26 @@ +-- Events -- + +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + TriggerEvent('g-drugselling:client:spawnNpcs') +end) + +RegisterNetEvent('g-drugselling:client:spawnNpcs', function() + for k, v in pairs(Config.SellLocations) do + if v.npc.enabled then + local npc = v.npc + RequestModel(GetHashKey(npc.ped)) + while not HasModelLoaded(GetHashKey(npc.ped)) do + Citizen.Wait(100) + end + local sellPed = CreatePed(7, GetHashKey(npc.Ped), v.coords.x, v.coords.y, v.coords.z - 0.97, npc.heading, 0, true, true) + FreezeEntityPosition(sellPed, true) + SetBlockingOfNonTemporaryEvents(sellPed, true) + TaskStartScenarioInPlace(sellPed, npc.scenario, 0, false) + SetEntityInvincible(sellPed, true) + end + end +end) + -- Threads -- -- Blip setup From 2449d7867931c60bef505fe628bda0f8235240bc Mon Sep 17 00:00:00 2001 From: Giana Date: Thu, 16 Feb 2023 23:58:13 -0600 Subject: [PATCH 10/17] Fix incorrect trigger --- server/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/main.lua b/server/main.lua index 95b0896..eddfaba 100644 --- a/server/main.lua +++ b/server/main.lua @@ -36,7 +36,7 @@ RegisterNetEvent('g-drugselling:server:sell', function(sellLocation) end local notifyPoliceRoll = math.random(1, 100) if notifyPoliceRoll <= sellLocation.policeAlertChance then - TriggerServerEvent('police:server:policeAlert', Lang:t('info.police_alert')) + TriggerEvent('police:server:policeAlert', Lang:t('info.police_alert')) end end else From e37fc150935ce0b948b09aac2b0498fabfb12f86 Mon Sep 17 00:00:00 2001 From: Giana Date: Thu, 16 Feb 2023 23:58:39 -0600 Subject: [PATCH 11/17] Split rewards into item and money & fix incorrect reward amount --- server/main.lua | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/server/main.lua b/server/main.lua index eddfaba..134f4c7 100644 --- a/server/main.lua +++ b/server/main.lua @@ -12,26 +12,21 @@ RegisterNetEvent('g-drugselling:server:sell', function(sellLocation) local bundlesToSell = math.floor(hasItem.amount / v.sell_quantity) local quantityToSell = bundlesToSell * v.sell_quantity if player.Functions.RemoveItem(k, quantityToSell) then - for k2, v2 in pairs(v.rewards) do - local isMoneyType = false - for k3, v3 in pairs(QBConfig.Money.MoneyTypes) do - if tostring(k3) == k2 then - isMoneyType = true - end + for k2, v2 in pairs(v.money_rewards) do + local amountOwed = v2 * bundlesToSell + if not player.Functions.AddMoney(k2, amountOwed) then + player.Functions.AddItem(k, quantityToSell) + return end - if isMoneyType then - if not player.Functions.AddMoney(k2, v2) then - player.Functions.AddItem(k, quantityToSell) - return - end - else - if not player.Functions.AddItem(k2, v2) then - player.Functions.AddItem(k, quantityToSell) - return - end - if sellLocation.itemNotificationsEnabled then - TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[k2], 'add', v2) - end + end + for k2, v2 in pairs(v.item_rewards) do + local amountOwed = v2 * bundlesToSell + if not player.Functions.AddItem(k2, amountOwed) then + player.Functions.AddItem(k, quantityToSell) + return + end + if sellLocation.itemNotificationsEnabled then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[k2], 'add', amountOwed) end end local notifyPoliceRoll = math.random(1, 100) From 2d56b7d20331a8a3cfc5d533677ceb1d2bc36c3f Mon Sep 17 00:00:00 2001 From: Giana Date: Sun, 19 Feb 2023 02:03:04 -0600 Subject: [PATCH 12/17] Add target support --- client/target.lua | 21 +++++++++++++++++++++ fxmanifest.lua | 3 ++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 client/target.lua diff --git a/client/target.lua b/client/target.lua new file mode 100644 index 0000000..04bb4e5 --- /dev/null +++ b/client/target.lua @@ -0,0 +1,21 @@ +for k, v in pairs(Config.SellLocations) do + if Config.SellLocations[k].active and Config.SellLocations[k].type == 'target' then + exports['qb-target']:AddBoxZone(Config.SellLocations[k].blip.label, Config.SellLocations[k].coords, 2.5, 2.5, { + name = Config.SellLocations[k].blip.label, + heading = 0, + debugPoly = false, + minZ = Config.SellLocations[k].coords.z - 1, + maxZ = Config.SellLocations[k].coords + 1, + }, { + options = { + { + type = 'client', + event = 'g-drugselling:client:registerTarget', + icon = 'fas fa-pills', + label = Lang:t('other.target_label'), + }, + }, + distance = 2 + }) + end +end \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua index 95b3c5a..3a9584b 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -12,7 +12,8 @@ shared_scripts { } client_scripts { - 'client/main.lua' + 'client/main.lua', + 'client/target.lua' } server_scripts { From ea0c460f88a6c2b3642b79587ed0495429c7185b Mon Sep 17 00:00:00 2001 From: Giana Date: Sun, 19 Feb 2023 02:06:26 -0600 Subject: [PATCH 13/17] Refactor config --- config.lua | 104 ++++++++++++++++++++++++++--------------------------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/config.lua b/config.lua index a166e9c..b1d0079 100644 --- a/config.lua +++ b/config.lua @@ -1,12 +1,17 @@ Config = {} +Config.CopCallCooldown = 60 -- Minimum seconds between cop calls for drug sales in server + +Config.NpcRenderDistance = 60 -- Render distance for NPCs (if NPC enabled for any location) + --[[ For each Config.SellLocations[x]: - active: If the sell location is active - Set to false to disable selling at this location (blip and marker, if enabled, will not appear on map or to players at all) - - policeAlertChance: % chance police are alerted of selling + - policeAlertChance: Percent chance police are alerted of selling + - policeRequired: Amount of online police required to sell - notificationsEnabled: If notifications regarding selling are enabled - - itemNotificationsEnabled: If notifications regarding items being added/removed to/from inventory are enabled + - itemNotificationsEnabled: If notifications regarding items being removed from inventory are enabled - type: How location is approached by player to sell - Options: - 'walkup' @@ -17,7 +22,7 @@ Config = {} - Player can sell if they walk or drive up to coords - 'target' - Player can sell if they target at coords - - coords: Coordinated of the selling point + - coords: Coordinates of selling point - blip: - enabled: If blip is enabled - label: Blip label @@ -35,7 +40,7 @@ Config = {} - red: Marker red component - green: Marker green component - blue: Marker blue component - - alpha: Marker alpha component: + - alpha: Marker alpha component - bob: If marker bobs up and down - faceCamera: If marker constantly faces camera - npc: @@ -43,25 +48,27 @@ Config = {} - ped: NPC ped model - heading: NPC ped heading - scenario: NPC ped scenario + - money_reward_type: Name of money type to reward + - sellAll: If all sellable items are sold at once, otherwise just the first sellable item - sellable_items: - 'itemName': Name of item - sell_quantity: Amount to sell in increments of - - rewards: - - 'rewardItemName' (name of money type or reward item) = rewardAmount (# amount of money type or reward item) + - money_amount: Amount of money to reward per sell_quantity ]] Config.SellLocations = { [1] = { active = true, policeAlertChance = 15, + policeRequired = 2, notificationsEnabled = false, - itemNotificationsEnabled = false, + itemNotificationsEnabled = true, type = 'walkup', - coords = vector3(292.65, -1072.83, 28.41), + coords = vector3(-352.83, 6231.16, 31.49), blip = { enabled = false, label = 'Weed Selling', color = 0, - sprite = 41, + sprite = 51, scale = 0.6, display = 4, shortRange = true @@ -82,60 +89,51 @@ Config.SellLocations = { npc = { enabled = false, ped = 's_m_y_xmech_02_mp', - heading = 240.00, + heading = 135.93, scenario = 'WORLD_HUMAN_AA_SMOKE', }, + money_reward_type = 'cash', + sellAll = true, sellable_items = { ['weed_white-widow'] = { sell_quantity = 5, - rewards = { - ['cash'] = math.random(75, 120) - } + money_amount = math.random(75, 120) }, ['weed_skunk'] = { sell_quantity = 5, - rewards = { - ['cash'] = math.random(75, 120) - } + money_amount = math.random(75, 120) }, ['weed_purple-haze'] = { sell_quantity = 5, - rewards = { - ['cash'] = math.random(75, 120) - } + money_amount = math.random(75, 120) }, ['weed_og-kush'] = { sell_quantity = 5, - rewards = { - ['cash'] = math.random(75, 120) - } + money_amount = math.random(75, 120) }, ['weed_amnesia'] = { sell_quantity = 5, - rewards = { - ['cash'] = math.random(75, 120) - } + money_amount = math.random(75, 120) }, ['weed_brick'] = { sell_quantity = 10, - rewards = { - ['cash'] = math.random(2000, 3000) - } + money_amount = math.random(2000, 3000) } } }, [2] = { active = true, policeAlertChance = 15, + policeRequired = 2, notificationsEnabled = false, - itemNotificationsEnabled = false, + itemNotificationsEnabled = true, type = 'walkup', - coords = vector3(292.65, -1072.83, 28.41), + coords = vector3(1748.58, 3706.22, 34.2), blip = { enabled = false, label = 'Meth Selling', color = 0, - sprite = 41, + sprite = 51, scale = 0.6, display = 4, shortRange = true @@ -156,30 +154,31 @@ Config.SellLocations = { npc = { enabled = false, ped = 's_m_y_xmech_02_mp', - heading = 240.00, + heading = 286.2, scenario = 'WORLD_HUMAN_AA_SMOKE', }, + money_reward_type = 'cash', + sellAll = true, sellable_items = { ['meth'] = { sell_quantity = 5, - rewards = { - ['cash'] = math.random(75, 120) - } + money_amount = math.random(75, 120) } } }, [3] = { active = true, policeAlertChance = 15, + policeRequired = 2, notificationsEnabled = false, - itemNotificationsEnabled = false, + itemNotificationsEnabled = true, type = 'walkup', - coords = vector3(292.65, -1072.83, 28.41), + coords = vector3(906.92, -1932.72, 30.62), blip = { enabled = false, label = 'Crack Selling', color = 0, - sprite = 41, + sprite = 51, scale = 0.6, display = 4, shortRange = true @@ -200,30 +199,31 @@ Config.SellLocations = { npc = { enabled = false, ped = 's_m_y_xmech_02_mp', - heading = 240.00, + heading = 129.33, scenario = 'WORLD_HUMAN_AA_SMOKE', }, + money_reward_type = 'cash', + sellAll = true, sellable_items = { ['crack_baggy'] = { sell_quantity = 5, - rewards = { - ['cash'] = math.random(90, 170) - } + money_amount = math.random(90, 170) } } }, [4] = { active = true, policeAlertChance = 15, + policeRequired = 2, notificationsEnabled = false, - itemNotificationsEnabled = false, + itemNotificationsEnabled = true, type = 'walkup', - coords = vector3(292.65, -1072.83, 28.41), + coords = vector3(1144.53, -299.44, 68.81), blip = { enabled = false, label = 'Coke Selling', color = 0, - sprite = 41, + sprite = 51, scale = 0.6, display = 4, shortRange = true @@ -242,23 +242,21 @@ Config.SellLocations = { faceCamera = false }, npc = { - enabled = false, + enabled = true, ped = 's_m_y_xmech_02_mp', - heading = 240.00, + heading = 97.09, scenario = 'WORLD_HUMAN_AA_SMOKE', }, + money_reward_type = 'cash', + sellAll = true, sellable_items = { ['cokebaggy'] = { sell_quantity = 5, - rewards = { - ['cash'] = math.random(90, 185) - } + money_amount = math.random(90, 185) }, ['coke_brick'] = { sell_quantity = 10, - rewards = { - ['cash'] = math.random(8500, 10000) - } + money_amount = math.random(8500, 10000) } } } From eefc62a2e8944b1bbd45de343bab362b1df414fb Mon Sep 17 00:00:00 2001 From: Giana Date: Sun, 19 Feb 2023 02:06:51 -0600 Subject: [PATCH 14/17] Add messages --- locales/en.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/locales/en.lua b/locales/en.lua index 0643a9f..259c1fc 100644 --- a/locales/en.lua +++ b/locales/en.lua @@ -1,9 +1,14 @@ local Translations = { error = { - not_enough = 'You do not have enough of: %{itemLabel}' + not_enough = 'You do not have enough to sell.', + cannot_sell = 'You cannot sell right now.' }, info = { - police_alert = 'Drug sale in progress' + police_alert = 'Drug sale in progress', + selling = 'Selling...' + }, + other = { + target_label = 'Sell' } } From 4b8d877677b68ca2ac08ccf4f41a74936b31771a Mon Sep 17 00:00:00 2001 From: Giana Date: Sun, 19 Feb 2023 02:09:58 -0600 Subject: [PATCH 15/17] Implement NPC spawning/despawning --- client/main.lua | 100 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/client/main.lua b/client/main.lua index 3f19aa6..e7e4db3 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,3 +1,75 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +local IsSelling = false +local Peds = {} + +-- Functions -- + +function createPed(hash, ...) + RequestModel(hash) + while not HasModelLoaded(hash) do + Citizen.Wait(100) + end + local ped = CreatePed(26, hash, ...) + SetModelAsNoLongerNeeded(hash) + return ped +end + +function createClientNpc(index) + if (Peds[index]) then + deleteClientNpc(index) + end + Peds[index] = {} + local location = Config.SellLocations[index] + local ped = createPed(location.npc.ped, location.coords.x, location.coords.y, location.coords.z - 0.97, location.npc.heading, false, true) + FreezeEntityPosition(ped, true) + SetEntityHeading(ped, location.npc.heading) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + Peds[index].npc = ped +end + +function deleteClientNpc(index) + if (Peds[index]) then + DeleteEntity(Peds[index].npc) + Peds[index] = nil + end +end + +function attemptSell(sellLocationIndex) + local notificationsEnabled = Config.SellLocations[sellLocationIndex].notificationsEnabled + if IsSelling then + return + end + IsSelling = true + QBCore.Functions.TriggerCallback('g-drugselling:server:getCopCount', function(copCount) + if copCount >= Config.SellLocations[sellLocationIndex].policeRequired then + QBCore.Functions.TriggerCallback('g-drugselling:server:getSellableItems', function(sellableItems) + if sellableItems and #sellableItems > 0 then + if notificationsEnabled then + QBCore.Functions.Notify(Lang:t('info.selling')) + end + TriggerServerEvent('g-drugselling:server:sellItems', sellLocationIndex, sellableItems, Config.SellLocations[sellLocationIndex].money_reward_type) + Citizen.Wait(10000) + IsSelling = false + else + if notificationsEnabled then + QBCore.Functions.Notify(Lang:t('error.not_enough')) + end + Citizen.Wait(10000) + IsSelling = false + end + end, sellLocationIndex) + else + if notificationsEnabled then + QBCore.Functions.Notify(Lang:t('error.cannot_sell')) + end + Citizen.Wait(20000) + IsSelling = false + end + end) +end + -- Events -- RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() @@ -40,6 +112,34 @@ Citizen.CreateThread(function() end end) +-- NPC ped setup +Citizen.CreateThread(function() + while true do + if LocalPlayer.state.isLoggedIn then + local ped = PlayerPedId() + local pos = GetEntityCoords(ped) + local inRange = false + for k, v in pairs(Config.SellLocations) do + if v.active and v.npc.enabled then + local dist = #(pos - v.coords) + if dist < Config.NpcRenderDistance then + if not Peds[k] then + createClientNpc(k) + end + inRange = true + elseif Peds[k] then + deleteClientNpc(k) + end + end + end + if not inRange then + Citizen.Wait(2000) + end + end + Citizen.Wait(3) + end +end) + -- Walk up & drive up Citizen.CreateThread(function() while true do From 202ab4c554e569fb13240ffacb57088af4e13019 Mon Sep 17 00:00:00 2001 From: Giana Date: Sun, 19 Feb 2023 02:11:05 -0600 Subject: [PATCH 16/17] Refactor and implement police alert --- client/main.lua | 47 +++++++++++++-------- server/main.lua | 109 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 106 insertions(+), 50 deletions(-) diff --git a/client/main.lua b/client/main.lua index e7e4db3..6c52cc9 100644 --- a/client/main.lua +++ b/client/main.lua @@ -72,27 +72,36 @@ end -- Events -- -RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() - TriggerEvent('g-drugselling:client:spawnNpcs') +AddEventHandler('onResourceStart', function(resourceName) + if resourceName == GetCurrentResourceName() then + IsSelling = false + end +end) + +AddEventHandler('onResourceStop', function(resourceName) + if resourceName == GetCurrentResourceName() then + for k, v in pairs(Peds) do + deleteClientNpc(k) + end + end end) -RegisterNetEvent('g-drugselling:client:spawnNpcs', function() +RegisterNetEvent('g-drugselling:client:registerTarget', function() + local ped = PlayerPedId() + local pos = GetEntityCoords(ped) for k, v in pairs(Config.SellLocations) do - if v.npc.enabled then - local npc = v.npc - RequestModel(GetHashKey(npc.ped)) - while not HasModelLoaded(GetHashKey(npc.ped)) do - Citizen.Wait(100) - end - local sellPed = CreatePed(7, GetHashKey(npc.Ped), v.coords.x, v.coords.y, v.coords.z - 0.97, npc.heading, 0, true, true) - FreezeEntityPosition(sellPed, true) - SetBlockingOfNonTemporaryEvents(sellPed, true) - TaskStartScenarioInPlace(sellPed, npc.scenario, 0, false) - SetEntityInvincible(sellPed, true) + local dist = #(pos - v.coords) + if dist < 2 then + attemptSell(k) + break end end end) +RegisterNetEvent('g-drugselling:client:policeAlert', function(message) + TriggerServerEvent('police:server:policeAlert', message) +end) + -- Threads -- -- Blip setup @@ -150,15 +159,17 @@ Citizen.CreateThread(function() for k, v in pairs(Config.SellLocations) do if v.active then local dist = #(pos - v.coords) - if dist < 10 then + if dist < 20 then if v.marker.enabled then if (v.type == 'walkup' and not IsPedInAnyVehicle(ped, false)) or (v.type == 'driveup' and IsPedInAnyVehicle(ped, false)) or v.type == 'any' or v.type == 'target' then - DrawMarker(v.marker.type, v.coords.x, v.coords.y, v.coords.z + 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, v.marker.scaleX, v.marker.scaleY, v.marker.scaleZ, v.marker.red, v.marker.green, v.marker.blue, v.marker.alpha, v.marker.bob, v.marker.faceCamera, 0, 1, 0, 0, 0) + DrawMarker(v.marker.type, v.coords.x, v.coords.y, v.coords.z - 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, v.marker.scaleX, v.marker.scaleY, v.marker.scaleZ, v.marker.red, v.marker.green, v.marker.blue, v.marker.alpha, v.marker.bob, v.marker.faceCamera, 0, 1, 0, 0, 0) end end - if dist < 1.5 then + if dist < 2 then if (v.type == 'walkup' and not IsPedInAnyVehicle(ped, false)) or (v.type == 'driveup' and IsPedInAnyVehicle(ped, false)) or v.type == 'any' then - TriggerServerEvent('g-drugselling:server:sell', v) + if not IsSelling then + attemptSell(k) + end end end inRange = true diff --git a/server/main.lua b/server/main.lua index 134f4c7..553ac2f 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,44 +1,89 @@ local QBCore = exports['qb-core']:GetCoreObject() +local CopsCalled = false +local CachedPolice = {} + +-- Functions -- + +function rollForNotifyingPolice(source, policeAlertChance) + local src = source + local notifyPoliceRoll = math.random(1, 100) + if not CopsCalled and notifyPoliceRoll <= policeAlertChance then + CopsCalled = true + TriggerClientEvent('g-drugselling:client:policeAlert', src, Lang:t('info.police_alert')) + Citizen.Wait(Config.CopCallCooldown * 1000) + CopsCalled = false + end +end + -- Events -- -RegisterNetEvent('g-drugselling:server:sell', function(sellLocation) +RegisterNetEvent('g-drugselling:server:sellItems', function(sellLocationIndex, items, moneyType) local src = source local player = QBCore.Functions.GetPlayer(src) - for k, v in pairs(sellLocation.sellable_items) do - local hasItem = player.Functions.GetItemByName(k) - if hasItem then - if hasItem.amount >= v.sell_quantity then - local bundlesToSell = math.floor(hasItem.amount / v.sell_quantity) - local quantityToSell = bundlesToSell * v.sell_quantity - if player.Functions.RemoveItem(k, quantityToSell) then - for k2, v2 in pairs(v.money_rewards) do - local amountOwed = v2 * bundlesToSell - if not player.Functions.AddMoney(k2, amountOwed) then - player.Functions.AddItem(k, quantityToSell) - return - end - end - for k2, v2 in pairs(v.item_rewards) do - local amountOwed = v2 * bundlesToSell - if not player.Functions.AddItem(k2, amountOwed) then - player.Functions.AddItem(k, quantityToSell) - return - end - if sellLocation.itemNotificationsEnabled then - TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[k2], 'add', amountOwed) - end - end - local notifyPoliceRoll = math.random(1, 100) - if notifyPoliceRoll <= sellLocation.policeAlertChance then - TriggerEvent('police:server:policeAlert', Lang:t('info.police_alert')) - end + if CachedPolice[src] == nil then + DropPlayer(src, "Exploiting") + return + end + if Config.SellLocations[sellLocationIndex].sellAll then + for k, v in pairs(items) do + if player.Functions.RemoveItem(v.name, v.sellableQuantity) then + Citizen.Wait(900) + if not player.Functions.AddMoney(moneyType, v.price) then + player.Functions.AddItem(v.name, v.sellableQuantity) + return end - else - if sellLocation.notificationsEnabled then - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_enough', { itemLabel = QBCore.Shared.Items[k]['label'] }), 'error') + if Config.SellLocations[sellLocationIndex].itemNotificationsEnabled then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[v.name], 'remove', v.sellableQuantity) end end end + rollForNotifyingPolice(src, Config.SellLocations[sellLocationIndex].policeAlertChance) + else + if player.Functions.RemoveItem(items[1].name, items[1].sellableQuantity) then + Citizen.Wait(800) + if not player.Functions.AddMoney(moneyType, items[1].price) then + player.Functions.AddItem(items[1].name, items[1].sellableQuantity) + return + end + if Config.SellLocations[sellLocationIndex].itemNotificationsEnabled then + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[items[1].name], 'remove', items[1].sellableQuantity) + end + end + rollForNotifyingPolice(src, Config.SellLocations[sellLocationIndex].policeAlertChance) + end +end) + +-- Callbacks -- + +QBCore.Functions.CreateCallback('g-drugselling:server:getSellableItems', function(source, cb, sellLocationIndex) + local src = source + local player = QBCore.Functions.GetPlayer(src) + local items = {} + for k, v in pairs(Config.SellLocations[sellLocationIndex].sellable_items) do + local hasItem = player.Functions.GetItemByName(k) + if hasItem and hasItem.amount >= v.sell_quantity then + local bundlesToSell = math.floor(hasItem.amount / v.sell_quantity) + local sellableQuantity = bundlesToSell * v.sell_quantity + local price = v.money_amount * bundlesToSell + local item = {} + item.name = k + item.sellableQuantity = sellableQuantity + item.price = price + table.insert(items, item) + end + end + cb(items) +end) + +QBCore.Functions.CreateCallback('g-drugselling:server:getCopCount', function(source, cb) + local src = source + local policeCount = 0 + for _, v in pairs(QBCore.Functions.GetQBPlayers()) do + if v.PlayerData.job.name == "police" and v.PlayerData.job.onduty then + policeCount = policeCount + 1 + end end + CachedPolice[src] = policeCount + cb(policeCount) end) \ No newline at end of file From 3edc36c5348ae0777d057ff7a65887b048be1acb Mon Sep 17 00:00:00 2001 From: Giana Date: Sun, 19 Feb 2023 02:48:47 -0600 Subject: [PATCH 17/17] Update README --- README.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ecc2971..a371d94 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ # G-DrugSelling -G-DrugSelling is a script for FiveM QBCore for selling drugs (or any items) via vehicle and/or walk up, or target, at configurable coordinates. \ No newline at end of file +G-DrugSelling is a script for FiveM QBCore for selling drugs (or any items) from the player's inventory via vehicle and/or walk up, or target, at configurable coordinates. + +

INSTALLATION GUIDE

+ +1. Drop the g-drugselling folder into your [standalone] folder (or whichever other ensured folder you want to use) + +

FEATURES

+ +- Create locations anywhere for selling items and choose sellable items and rewards for each location +- Enable/disable locations, blips, markers, NPCs, and notifications +- Configure means of selling by location + - Examples include walk up, drive up, both, or target +- Configure sellable quantities and reward amounts per item +- Configure police required to sell and chance of police alert per location + +**IMAGES** +----- +Coming Soon + +**DEPENDENCIES** +----- +- [QBCore](https://github.com/qbcore-framework) + - [qb-core](https://github.com/qbcore-framework/qb-core) + - [qb-inventory](https://github.com/qbcore-framework/qb-inventory) + - [qb-target](https://github.com/qbcore-framework/qb-target) + +**CREDIT** +----- +Code excerpts for NPC spawning and the concept of NPC render distance were repurposed and refactored from [pickle_farming](https://github.com/PickleModifications/pickle_farming). +Code excerpts for getting and caching current police count were repurposed from [qb-jewelery](https://github.com/qbcore-framework/qb-jewelery).