This commit is contained in:
Nordi98 2025-06-12 03:59:16 +02:00
parent 453b281a4b
commit 46b895aff2
25 changed files with 716 additions and 0 deletions

View file

@ -0,0 +1,2 @@
# pickle_throwables
A multi-framework and standalone throwing script, great for football, soccer and other sports.

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View file

@ -0,0 +1,6 @@
INSERT INTO `items` (`name`, `label`, `limit`) VALUES
('football', 'Football', 100),
('basketball', 'Basketball', 100),
('baseball', 'Baseball', 100),
('soccer', 'Soccer Ball', 100),
;

View file

@ -0,0 +1,6 @@
INSERT INTO `items` (`name`, `label`, `weight`) VALUES
('football', 'Football', 100),
('basketball', 'Basketball', 100),
('baseball', 'Baseball', 100),
('soccer', 'Soccer Ball', 100),
;

View file

@ -0,0 +1,24 @@
["football"] = {
label = 'Football',
weight = 1,
stack = true,
description = ""
},
["basketball"] = {
label = 'Basketball',
weight = 1,
stack = true,
description = ""
},
["baseball"] = {
label = 'Baseball',
weight = 1,
stack = true,
description = ""
},
["soccer"] = {
label = 'Soccer Ball',
weight = 1,
stack = true,
description = ""
},

View file

@ -0,0 +1,4 @@
["football"] = {["name"] = "football", ["label"] = "Football", ["weight"] = 1, ["type"] = "item", ["image"] = "football.png", ["unique"] = true, ["useable"] = true, ["shouldClose"] = true, ["combinable"] = nil, ["description"] = ""},
["basketball"] = {["name"] = "basketball", ["label"] = "Basketball", ["weight"] = 1, ["type"] = "item", ["image"] = "basketball.png", ["unique"] = true, ["useable"] = true, ["shouldClose"] = true, ["combinable"] = nil, ["description"] = ""},
["baseball"] = {["name"] = "baseball", ["label"] = "Baseball", ["weight"] = 1, ["type"] = "item", ["image"] = "baseball.png", ["unique"] = true, ["useable"] = true, ["shouldClose"] = true, ["combinable"] = nil, ["description"] = ""},
["soccer"] = {["name"] = "soccer", ["label"] = "Soccer Ball", ["weight"] = 1, ["type"] = "item", ["image"] = "soccer.png", ["unique"] = true, ["useable"] = true, ["shouldClose"] = true, ["combinable"] = nil, ["description"] = ""},

View file

@ -0,0 +1,32 @@
if GetResourceState('es_extended') ~= 'started' then return end
ESX = exports.es_extended:getSharedObject()
function ShowNotification(text)
ESX.ShowNotification(text)
end
function ShowHelpNotification(text)
ESX.ShowHelpNotification(text)
end
function ServerCallback(name, cb, ...)
ESX.TriggerServerCallback(name, cb, ...)
end
function GetPlayersInArea(coords, maxDistance)
return ESX.Game.GetPlayersInArea(coords, maxDistance)
end
function CanAccessGroup(data)
if not data then return true end
local pdata = ESX.GetPlayerData()
for k,v in pairs(data) do
if (pdata.job.name == k and pdata.job.grade >= v) then return true end
end
return false
end
RegisterNetEvent(GetCurrentResourceName()..":showNotification", function(text)
ShowNotification(text)
end)

View file

@ -0,0 +1,67 @@
if GetResourceState('es_extended') ~= 'started' then return end
ESX = exports.es_extended:getSharedObject()
function RegisterCallback(name, cb)
ESX.RegisterServerCallback(name, cb)
end
function RegisterUsableItem(...)
ESX.RegisterUsableItem(...)
end
function ShowNotification(target, text)
TriggerClientEvent(GetCurrentResourceName()..":showNotification", target, text)
end
function Search(source, name)
local xPlayer = ESX.GetPlayerFromId(source)
if (name == "money") then
return xPlayer.getMoney()
elseif (name == "bank") then
return xPlayer.getAccount('bank').money
else
local item = xPlayer.getInventoryItem(name)
if item ~= nil then
return item.count
else
return 0
end
end
end
function AddItem(source, name, amount)
local xPlayer = ESX.GetPlayerFromId(source)
if (name == "money") then
return xPlayer.addMoney(amount)
elseif (name == "bank") then
return xPlayer.addAccountMoney('bank', amount)
else
return xPlayer.addInventoryItem(name, amount)
end
end
function RemoveItem(source, name, amount)
local xPlayer = ESX.GetPlayerFromId(source)
if (name == "money") then
return xPlayer.removeMoney(amount)
elseif (name == "bank") then
return xPlayer.removeAccountMoney('bank', amount)
else
return xPlayer.removeInventoryItem(name, amount)
end
end
function CanAccessGroup(source, data)
if not data then return true end
local pdata = ESX.GetPlayerFromId(source)
for k,v in pairs(data) do
if (pdata.job.name == k and pdata.job.grade >= v) then return true end
end
return false
end
function GetIdentifier(source)
local xPlayer = ESX.GetPlayerFromId(source)
return xPlayer.identifier
end

View file

@ -0,0 +1,34 @@
if GetResourceState('qb-core') ~= 'started' then return end
QBCore = exports['qb-core']:GetCoreObject()
function ServerCallback(name, cb, ...)
QBCore.Functions.TriggerCallback(name, cb, ...)
end
function ShowNotification(text)
QBCore.Functions.Notify(text)
end
function ShowHelpNotification(text)
AddTextEntry('qbHelpNotification', text)
BeginTextCommandDisplayHelp('qbHelpNotification')
EndTextCommandDisplayHelp(0, false, false, -1)
end
function GetPlayersInArea(coords, maxDistance)
return QBCore.Functions.GetPlayersFromCoords(coords, maxDistance)
end
function CanAccessGroup(data)
if not data then return true end
local pdata = QBCore.Functions.GetPlayerData()
for k,v in pairs(data) do
if (pdata.job.name == k and pdata.job.grade.level >= v) then return true end
end
return false
end
RegisterNetEvent(GetCurrentResourceName()..":showNotification", function(text)
ShowNotification(text)
end)

View file

@ -0,0 +1,67 @@
if GetResourceState('qb-core') ~= 'started' then return end
QBCore = exports['qb-core']:GetCoreObject()
function RegisterCallback(name, cb)
QBCore.Functions.CreateCallback(name, cb)
end
function RegisterUsableItem(...)
QBCore.Functions.CreateUseableItem(...)
end
function ShowNotification(target, text)
TriggerClientEvent(GetCurrentResourceName()..":showNotification", target, text)
end
function Search(source, name)
local xPlayer = QBCore.Functions.GetPlayer(source)
if (name == "money") then
return xPlayer.PlayerData.money['cash']
elseif (name == "bank") then
return xPlayer.PlayerData.money['cash'] -- If anyone knows how to get bank balance for QBCore, let me know.
else
local item = xPlayer.Functions.GetItemByName(name)
if item ~= nil then
return item.amount
else
return 0
end
end
end
function AddItem(source, name, amount)
local xPlayer = QBCore.Functions.GetPlayer(source)
if (name == "money") then
return xPlayer.Functions.AddMoney("cash", amount)
elseif (name == "bank") then
return xPlayer.Functions.AddMoney("cash", amount) -- If anyone knows how to add to bank balance for QBCore, let me know.
else
return xPlayer.Functions.AddItem(name, amount)
end
end
function RemoveItem(source, name, amount)
local xPlayer = QBCore.Functions.GetPlayer(source)
if (name == "money") then
return xPlayer.Functions.RemoveMoney("cash", amount)
elseif (name == "bank") then
return xPlayer.Functions.RemoveMoney("cash", amount) -- If anyone knows how to remove from bank balance for QBCore, let me know.
else
return xPlayer.Functions.RemoveItem(name, amount)
end
end
function CanAccessGroup(source, data)
if not data then return true end
local pdata = QBCore.Functions.GetPlayer(source).PlayerData
for k,v in pairs(data) do
if (pdata.job.name == k and pdata.job.grade.level >= v) then return true end
end
return false
end
function GetIdentifier(source)
local xPlayer = QBCore.Functions.GetPlayer(source).PlayerData
return xPlayer.citizenid
end

View file

@ -0,0 +1,4 @@
if GetResourceState('es_extended') == 'started' then return end
if GetResourceState('qb-core') == 'started' then return end
print("You are not using a supported framework, it will be required to make edits to the bridge files.")

View file

@ -0,0 +1,4 @@
if GetResourceState('es_extended') == 'started' then return end
if GetResourceState('qb-core') == 'started' then return end
print("You are not using a supported framework, it will be required to make edits to the bridge files.")

View file

@ -0,0 +1,41 @@
Config = {}
Config.Debug = true
Config.Language = "en"
Config.RenderDistance = 20.0
Config.CatchRadius = 2.5
Config.CommandSpawning = false -- Set this to true if you want to be able to get throwables without using items.
Config.CommandSpawnCheck = function()
return true
end
Config.Throwables = {
["football"] = {
item = "football",
entityType = "object", -- "object", "vehicle", "ped"
model = `p_ld_am_ball_01`,
maxThrowingPower = 200
},
["basketball"] = {
item = "basketball",
entityType = "object", -- "object", "vehicle", "ped"
model = `prop_bskball_01`,
maxThrowingPower = 200
},
["baseball"] = {
item = "baseball",
entityType = "object", -- "object", "vehicle", "ped"
model = `w_am_baseball`,
maxThrowingPower = 200
},
["soccer"] = {
item = "soccer",
entityType = "object", -- "object", "vehicle", "ped"
model = `p_ld_soc_ball_01`,
maxThrowingPower = 200
},
}

View file

@ -0,0 +1,70 @@
function ModelRequest(modelHash)
if not IsModelInCdimage(modelHash) then return end
RequestModel(modelHash)
local loaded
for i=1, 100 do
if HasModelLoaded(modelHash) then
loaded = true
break
end
Wait(100)
end
return loaded
end
function CreateVeh(modelHash, ...)
if not ModelRequest(modelHash) then
print("Couldn't load model: " .. modelHash)
return
end
local veh = CreateVehicle(modelHash, ...)
SetModelAsNoLongerNeeded(modelHash)
return veh
end
function CreateNPC(modelHash, ...)
if not ModelRequest(modelHash) then
print("Couldn't load model: " .. modelHash)
return
end
local ped = CreatePed(26, modelHash, ...)
SetModelAsNoLongerNeeded(modelHash)
return ped
end
function CreateProp(modelHash, ...)
if not ModelRequest(modelHash) then
print("Couldn't load model: " .. modelHash)
return
end
local obj = CreateObject(modelHash, ...)
SetModelAsNoLongerNeeded(modelHash)
return obj
end
function PlayAnim(ped, dict, ...)
RequestAnimDict(dict)
while not HasAnimDictLoaded(dict) do Wait(0) end
TaskPlayAnim(ped, dict, ...)
end
local interactTick = 0
local interactThread = false
local interactText = nil
function ShowInteractText(text)
interactTick = GetGameTimer()
lib.showTextUI(text)
if interactThread then return end
interactThread = true
CreateThread(function()
while interactThread do
if GetGameTimer() - interactTick > 20 then
interactThread = false
break
end
Citizen.Wait(150)
end
lib.hideTextUI()
end)
end

View file

@ -0,0 +1,10 @@
function v3(coords) return vec3(coords.x, coords.y, coords.z), coords.w end
function GetRandomInt(min, max, exclude)
for i=1, 1000 do
local int = math.random(min, max)
if exclude == nil or exclude ~= int then
return int
end
end
end

View file

@ -0,0 +1,28 @@
fx_version 'cerulean'
lua54 'yes'
game 'gta5'
name 'pickle_throwables'
version '1.0.0'
description 'A multi-framework and standalone throwing script, great for football, soccer and other sports.'
author 'Pickle Mods'
shared_scripts {
'@ox_lib/init.lua',
'config.lua',
'core/shared.lua',
"locales/locale.lua",
"locales/translations/*.lua",
'modules/**/shared.lua',
}
server_scripts {
'bridge/**/server.lua',
'modules/**/server.lua',
}
client_scripts {
'core/client.lua',
'bridge/**/client.lua',
'modules/**/client.lua',
}

View file

@ -0,0 +1,14 @@
Language = {}
function _L(name, ...)
if name then
local str = Language[Config.Language][name]
if str then
return string.format(str, ...)
else
return "ERR_TRANSLATE_"..(name).."_404"
end
else
return "ERR_TRANSLATE_404"
end
end

View file

@ -0,0 +1,5 @@
Language["en"] = {
throwable_interact = "[E] - Take Object \n [G] - Kick \n Power: %s",
throwable_list = "[E] - Throw \n [G] - Drop \n [H] - Handoff \n Power: %s",
throwable_list_alt = "[E] - Throw \n [G] - Drop \n [H] - Inventory \n Power: %s",
}

View file

@ -0,0 +1,225 @@
local ThrowingPower = 1
local Throwables = {}
local canInteract = true
local attemptingCatch = false
local holdingBall = nil
function GetClosestPlayer(coords, radius)
local closest
local coords = coords or GetEntityCoords(PlayerPedId())
local radius = radius or 2.0
for _, player in ipairs(GetActivePlayers()) do
local ped = GetPlayerPed(player)
if PlayerPedId() ~= ped then
local pedCoords = GetEntityCoords(ped)
local distance = #(coords - pedCoords)
if distance < radius and (not closest or closest.distance > distance) then
closest = {player = player, distance = distance}
end
end
end
return closest?.player, closest?.distance
end
function GetDirectionFromRotation(rotation)
local dm = (math.pi / 180)
return vector3(-math.sin(dm * rotation.z) * math.abs(math.cos(dm * rotation.x)), math.cos(dm * rotation.z) * math.abs(math.cos(dm * rotation.x)), math.sin(dm * rotation.x))
end
function PerformPhysics(throwType, entity, action)
local cfg = Config.Throwables[throwType]
local power = (ThrowingPower / 10) * cfg.maxThrowingPower
FreezeEntityPosition(entity, false)
local rot = GetGameplayCamRot(2)
local dir = GetDirectionFromRotation(rot)
SetEntityHeading(entity, rot.z + 90.0)
if not action or action == "throw" then
SetEntityVelocity(entity, dir.x * power, dir.y * power, dir.z * power)
else
SetEntityVelocity(entity, dir.x * power, dir.y * power, (dir.z * 1.75) * power)
end
end
function CreateThrowable(throwType, attach)
local cfg = Config.Throwables[throwType]
local ped = PlayerPedId()
local model = cfg.model
local heading = GetEntityHeading(ped)
local coords = GetOffsetFromEntityInWorldCoords(ped, 0.0, 1.0, 0.5)
local prop
if cfg.entityType == "object" then
prop = CreateProp(model, coords.x, coords.y, coords.z, true, true, true)
elseif cfg.entityType == "vehicle" then
prop = CreateVeh(model, coords.x, coords.y, coords.z, true, true, true)
elseif cfg.entityType == "ped" then
prop = CreateNPC(model, coords.x, coords.y, coords.z, true, true, true)
end
if not prop then return end
if attach then
local off, rot = vector3(0.05, 0.0, -0.085), vector3(90.0, 90.0, 0.0)
AttachEntityToEntity(prop, ped, GetPedBoneIndex(ped, 28422), off.x, off.y, off.z, rot.x, rot.y, rot.z, false, false, false, true, 2, true)
else
local coords = GetOffsetFromEntityInWorldCoords(ped, 0.0, 1.0, -0.9)
SetEntityCoords(prop, coords.x, coords.y, coords.z)
end
return prop
end
function HoldThrowable(throwType)
local ped = PlayerPedId()
if holdingBall then return end
local prop = CreateThrowable(throwType, true)
holdingBall = prop
CreateThread(function()
while holdingBall do
local player, dist = GetClosestPlayer()
if player then
ShowInteractText(_L("throwable_list", ThrowingPower .. "/" .. 10))
else
ShowInteractText(_L("throwable_list_alt", ThrowingPower .. "/" .. 10))
end
if IsControlJustPressed(1, 51) then
CreateThread(function()
PlayAnim(ped, "melee@thrown@streamed_core", "plyr_takedown_front", -8.0, 8.0, -1, 49)
Wait(600)
ClearPedTasks(ped)
end)
Wait(550)
DetachEntity(prop, false, true)
SetEntityCollision(prop, true, true)
SetEntityRecordsCollisions(prop, true)
TriggerServerEvent("pickle_throwables:throwObject", {throwType = throwType, net_id = ObjToNet(prop)})
local coords = GetOffsetFromEntityInWorldCoords(ped, 0.0, 0.0, 1.0)
SetEntityCoords(prop, coords.x, coords.y, coords.z)
SetEntityHeading(prop, GetEntityHeading(ped) + 90.0)
PerformPhysics(throwType, prop)
holdingBall = nil
elseif IsControlJustPressed(1, 47) then
PlayAnim(ped, "pickup_object", "pickup_low", -8.0, 8.0, -1, 49, 1.0)
Wait(800)
DetachEntity(prop, true, true)
SetEntityCollision(prop, true, true)
SetEntityRecordsCollisions(prop, true)
ActivatePhysics(prop)
TriggerServerEvent("pickle_throwables:throwObject", {throwType = throwType, net_id = ObjToNet(prop)})
Wait(800)
ClearPedTasks(ped)
holdingBall = nil
elseif IsControlJustPressed(1, 74) then
if player then
ServerCallback("pickle_throwables:giveObject", function(result)
if not result then return end
DeleteEntity(prop)
holdingBall = nil
PlayAnim(PlayerPedId(), "mp_common", "givetake1_b", -8.0, 8.0, -1, 49, 1.0)
Wait(1600)
ClearPedTasks(ped)
end, GetPlayerServerId(player))
else
ServerCallback("pickle_throwables:storeObject", function(result)
if not result then return end
PlayAnim(PlayerPedId(), "pickup_object", "putdown_low", -8.0, 8.0, -1, 49, 1.0)
Wait(1600)
ClearPedTasks(ped)
DeleteEntity(prop)
holdingBall = nil
end)
end
end
PowerControls()
Wait(0)
end
end)
end
function CatchObject(index, cb)
if attemptingCatch then return end
attemptingCatch = true
local data = Throwables[index]
local entity = NetToObj(data.net_id)
SetEntityCollision(entity, false, false)
DeleteEntity(entity)
ServerCallback("pickle_throwables:catchObject", cb, index)
Wait(100)
attemptingCatch = false
end
function PowerControls()
if IsControlJustPressed(1, 181) then
ThrowingPower = (ThrowingPower + 1 > 10 and 10 or ThrowingPower + 1)
elseif IsControlJustPressed(1, 180) then
ThrowingPower = (ThrowingPower - 1 < 1 and 1 or ThrowingPower - 1)
end
end
function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
CreateThread(function()
while true do
local wait = 1000
local ped = PlayerPedId()
local throwables = deepcopy(Throwables)
for k,v in pairs(throwables) do
if NetworkDoesNetworkIdExist(v.net_id) then
local entity = NetToObj(v.net_id)
local dist = #(GetEntityCoords(ped) - GetEntityCoords(entity))
if dist < Config.RenderDistance then
wait = 0
if not holdingBall and canInteract and dist < Config.CatchRadius and not ShowInteractText(_L("throwable_interact", ThrowingPower .. "/" .. 10)) then
if IsControlJustPressed(1, 51) then
CatchObject(k, function(result)
if not result then return end
HoldThrowable(v.throwType)
end)
elseif IsControlJustPressed(1, 47) then
CatchObject(k, function(result)
if not result then return end
canInteract = false
local prop = CreateThrowable(v.throwType, false)
TriggerServerEvent("pickle_throwables:throwObject", {throwType = v.throwType, net_id = ObjToNet(prop)})
--FreezeEntityPosition(ped, true)
--PlayAnim(ped, "melee@unarmed@streamed_core", "ground_attack_0", -8.0, 8.0, -1, 33, 1.0)
--Wait(1000)
PerformPhysics(v.throwType, prop, "kick")
--Wait(600)
--ClearPedTasks(ped)
--FreezeEntityPosition(ped, false)
canInteract = true
end)
end
PowerControls()
end
end
end
end
Wait(wait)
end
end)
RegisterNetEvent("pickle_throwables:giveObject", function(data)
HoldThrowable(data.throwType)
end)
RegisterNetEvent("pickle_throwables:setObjectData", function(throwID, data)
Throwables[throwID] = data
end)
AddEventHandler("onResourceStop", function(name)
if (GetCurrentResourceName() ~= name) then return end
for k,v in pairs(Throwables) do
DeleteEntity(NetToObj(v.net_id))
end
end)

View file

@ -0,0 +1,73 @@
local Throwables = {}
local Carrying = {}
function GiveObject(source, data, timeout)
Carrying[source] = data
if timeout then
SetTimeout(1600, function()
TriggerClientEvent("pickle_throwables:giveObject", source, data)
end)
else
TriggerClientEvent("pickle_throwables:giveObject", source, data)
end
end
RegisterNetEvent("pickle_throwables:throwObject", function(data)
local source = source
if not Carrying[source] then return end
Carrying[source] = nil
local throwID = nil
repeat
throwID = os.time() .. "_" .. math.random(1000, 9999)
until not Throwables[throwID]
Throwables[throwID] = data
TriggerClientEvent("pickle_throwables:setObjectData", -1, throwID, data)
end)
RegisterCallback("pickle_throwables:catchObject", function(source, cb, throwID)
if Carrying[source] then return cb(false) end
if not Throwables[throwID] then return cb(false) end
local entity = NetworkGetEntityFromNetworkId(Throwables[throwID].net_id)
Carrying[source] = {throwType = Throwables[throwID].throwType}
DeleteEntity(entity)
Throwables[throwID] = nil
TriggerClientEvent("pickle_throwables:setObjectData", -1, throwID, nil)
cb(true)
end)
RegisterCallback("pickle_throwables:storeObject", function(source, cb)
if not Carrying[source] then return cb(false) end
local data = Carrying[source]
local cfg = Config.Throwables[data.throwType]
Carrying[source] = nil
if cfg.item and not Config.CommandSpawning then
AddItem(source, cfg.item, 1)
end
cb(true)
end)
RegisterCallback("pickle_throwables:giveObject", function(source, cb, target)
if not Carrying[source] or Carrying[target] then return cb(false) end
local data = Carrying[source]
GiveObject(target, {throwType = data.throwType}, true)
Carrying[source] = nil
cb(true)
end)
if Config.CommandSpawning then
RegisterCommand("spawnthrowable", function(source, args, raw)
if not args[1] or not Config.Throwables[args[1]] then return end
if not Config.CommandSpawnCheck(source, args[1]) then return end
GiveObject(source, {throwType = args[1]})
end)
else
for k,v in pairs(Config.Throwables) do
if v.item then
RegisterUsableItem(v.item, function(source)
if Carrying[source] then return end
RemoveItem(source, v.item, 1)
GiveObject(source, {throwType = k})
end)
end
end
end