I am still alive

This commit is contained in:
Jim Shield
2025-03-26 21:43:48 +00:00
committed by GitHub
parent 2572f86030
commit 8611bc6d3c
21 changed files with 607 additions and 292 deletions

View File

@@ -57,6 +57,7 @@ function triggerCallback(callbackName, ...)
p:resolve(cbResult)
end, ...)
result = Citizen.Await(p)
Wait(10)
elseif isStarted(ESXExport) then
local p = promise.new()
ESX.TriggerServerCallback(callbackName, function(cbResult)

View File

@@ -95,7 +95,7 @@ function openMenu(Menu, data)
Menu[k].args = Menu[k].params.args or {}
end
if Menu[k].isMenuHeader then
Menu[k].disabled = true
Menu[k].readOnly = true
end
end
local menuID = 'Menu'

View File

@@ -140,7 +140,7 @@ if isStarted(QBXExport) or isStarted(QBExport) then
elseif isStarted(OXCoreExport) then
Vehicles = {}
for k, v in pairs(Ox.GetVehicleData()) do
Vehicles[k] = { model = k, price = v.price, name = v.name, brand = v.make }
Vehicles[k] = { model = k, hash = GetHashKey(k), price = v.price, name = v.name, brand = v.make }
end
vehResource = OXCoreExport
@@ -159,6 +159,7 @@ elseif isStarted(ESXExport) then
Vehicles = Vehicles or {}
Vehicles[v.model] = {
model = v.model,
hash = GetHashKey(v.model),
price = v.price,
name = v.name,
brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper)

View File

@@ -12,6 +12,14 @@
-------------------------------------------------------------
CraftLock = false
-- helper filter table for crafting menus
local excludeKeys = {
amount = true, metadata = true, description = true, info = true,
job = true, gang = true, oneUse = true, slot = true,
blueprintRef = true, craftingLevel = true, craftedItems = true,
hasCrafted = true, exp = true, anim = true, time = true,
}
-------------------------------------------------------------
-- Crafting Menu
-------------------------------------------------------------
@@ -73,6 +81,7 @@ function craftingMenu(data)
for i = 1, #Recipes do
for k in pairs(Recipes[i]) do
if k == "hasCrafted" and not data.craftable.craftedItems then
-- Retreive list of already crafted items from playermetadata to see if we should class this recipe as "new"
craftedItems = GetMetadata(nil, "craftedItems") or {}
data.craftable.craftedItems = craftedItems
end
@@ -88,12 +97,6 @@ function craftingMenu(data)
for i = 1, #Recipes do
if not Recipes[i]["amount"] then Recipes[i]["amount"] = 1 end
for k, _ in pairs(Recipes[i]) do
local excludeKeys = {
amount = true, metadata = true, description = true, info = true,
job = true, gang = true, oneUse = true, slot = true,
blueprintRef = true, craftingLevel = true, craftedItems = true,
hasCrafted = true, exp = true, anim = true, time = true,
}
if not excludeKeys[k] then
local hasjob = true
if Recipes[i].job then
@@ -203,48 +206,79 @@ end
--- })
--- ```
function multiCraft(data)
local Menu = {}
local amounts = Config.Crafting.MultiCraftAmounts
local metadata = data.metadata or nil
-- Header for the multi-craft menu.
Menu[#Menu + 1] = {
isMenuHeader = true,
icon = invImg(metadata and metadata.image or data.item),
header = metadata and metadata.label or Items[data.item].label,
}
for k in pairsByKeys(amounts) do
local settext = ""
local max = 0
local stashName = nil
for i = 1, 100 do
local itemTable = {}
for l, b in pairs(data.craft[data.item]) do
itemTable[l] = (b * k)
settext = settext..(settext ~= "" and br or "")..Items[l].label..(b * k > 1 and " x"..b * k or "")
Wait(0)
debugPrint("")
itemTable[l] = (b * i)
end
local disable, stashname = checkHasItem(data.stashName, itemTable)
Menu[#Menu + 1] = {
isMenuHeader = not disable,
arrow = disable,
header = "Craft - x"..(k * data.craft.amount),
txt = settext,
onSelect = function()
makeItem({
item = data.item,
craft = data.craft,
craftable = data.craftable,
amount = k,
coords = data.coords,
stashName = stashname,
stashTable = data.stashName,
onBack = data.onBack,
metadata = data.metadata,
})
end,
}
if data.stashName then
debugPrint("")
local hasItems, stashname = checkHasItem(data.stashName, itemTable)
if hasItems == true then
max += 1
stashName = stashname
else
break
end
else
debugPrint("")
local has, _ = hasItem(itemTable, nil, nil)
if has then
max += 1
else
break
end
end
Wait(10)
end
openMenu(Menu, { header = data.craftable.Header, onBack = function() craftingMenu(data) end })
local dialog = createInput(data.craftable.Header, {
((Config.System.Menu == "ox") and {
type = "slider",
label = "How many to craft?",
required = true,
default = 1,
min = 1,
max = max
}) or nil,
((Config.System.Menu == "qb") and {
type = "number",
label = "How many to craft?"..br.."Max: "..max,
name = "amount",
isRecuired = true,
default = 1,
}) or nil,
})
if dialog then
if Config.System.Menu == "ox" then
end
if Config.System.Menu == "qb" then
if dialog["amount"] > max or dialog["amount"] < 1 or dialog["amount"] == nil or dialog["amount"] == "" then
triggerNotify(nil, "Invalid Amount", "error")
craftingMenu(data)
return
end
end
makeItem({
item = data.item,
craft = data.craft,
craftable = data.craftable,
amount = dialog["amount"] or dialog[1],
coords = data.coords,
stashName = stashName or nil,
--stashTable = data.stashName,
onBack = data.onBack,
metadata = data.metadata,
})
end
end
-------------------------------------------------------------
@@ -300,12 +334,6 @@ function makeItem(data)
for i = 1, craftAmount do
for k, v in pairs(data.craft) do
local excludeKeys = {
amount = true, info = true, metadata = true, description = true,
job = true, gang = true, oneUse = true, slot = true,
blueprintRef = true, craftingLevel = true, craftedItems = true,
hasCrafted = true, exp = true, anim = true, time = true,
}
if not excludeKeys[k] then
if type(v) == "table" then
for l, b in pairs(v) do
@@ -392,6 +420,7 @@ end
--- @param craftable table The crafting recipe and details.
--- @param stashName string|table The stash name(s) to remove ingredients from.
--- @param metadata table (optional) Metadata for the crafted item.
--- @usage
RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata)
local src = source
local hasItems, hasTable = hasItem(ItemMake, 1, src)
@@ -424,188 +453,6 @@ RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable,
end
addItem(ItemMake, craftable.amount or 1, metadata, src)
-- Optionally, add experience here:
-- for example:
-- if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
end)
-------------------------------------------------------------
-- Selling Menu and Animation
-------------------------------------------------------------
--- Opens a selling menu with available items and prices.
---
--- @param data table Contains selling menu data:
--- - sellTable (`table`) Table with Header and Items (item names and prices).
--- - ped (optional) (`number`) Ped entity involved.
--- - onBack (optional) (`function`) Callback for returning.
--- @usage
--- ```lua
--- sellMenu({
--- sellTable = {
--- Header = "Sell Items",
--- Items = {
--- ["gold_ring"] = 100,
--- ["diamond"] = 500,
--- },
--- },
--- ped = pedEntity,
--- onBack = function() print("Returning to previous menu") end,
--- })
--- ```
function sellMenu(data)
local origData = data
local Menu = {}
if data.sellTable.Items then
local itemList = {}
for k, v in pairs(data.sellTable.Items) do itemList[k] = 1 end
local _, hasTable = hasItem(itemList)
for k, v in pairsByKeys(data.sellTable.Items) do
Menu[#Menu + 1] = {
isMenuHeader = not hasTable[k].hasItem,
icon = invImg(k),
header = Items[k].label..(hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""),
txt = Loc[Config.Lan].info["sell_all"]..v.." "..Loc[Config.Lan].info["sell_each"],
onSelect = function()
sellAnim({ item = k, price = v, ped = data.ped, onBack = function() sellMenu(data) end })
end,
}
end
else
for k, v in pairsByKeys(data.sellTable) do
if type(v) == "table" then
Menu[#Menu + 1] = {
arrow = true,
header = k,
txt = "Amount of items: "..countTable(v.Items),
onSelect = function()
v.onBack = function() sellMenu(origData) end
v.sellTable = data.sellTable[k]
sellMenu(v)
end,
}
end
end
end
openMenu(Menu, {
header = data.sellTable.Header or "Amount of items: "..countTable(data.sellTable.Items),
headertxt = data.sellTable.Header and "Amount of items: "..countTable(data.sellTable.Items) or "",
canClose = true,
onBack = data.onBack,
})
end
--- Plays the selling animation and processes the sale transaction.
---
--- Checks if the player has the item, plays animations, triggers the server event for selling,
--- and then calls the onBack callback if provided.
---
--- @param data table Contains:
--- `- item: The item to sell.
--- `- price: Price per item.
--- `- ped (optional): Ped entity involved.
--- `- onBack (optional): Callback to call on completion.
---@usage
--- ```lua
--- sellAnim({
--- item = "gold_ring",
--- price = 100,
--- ped = pedEntity,
--- onBack = function() sellMenu(data) end,
--- })
--- ```
function sellAnim(data)
if not hasItem(data.item, 1) then
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error")
return
end
-- Remove any attached clipboard objects.
for _, obj in pairs(GetGamePool('CObject')) do
for _, model in pairs({ `p_cs_clipboard` }) do
if GetEntityModel(obj) == model and IsEntityAttachedToEntity(data.ped, obj) then
DeleteObject(obj)
DetachEntity(obj, 0, 0)
SetEntityAsMissionEntity(obj, true, true)
Wait(100)
DeleteEntity(obj)
end
end
end
TriggerServerEvent(getScript().."Sellitems", data)
lookEnt(data.ped)
local dict = "mp_common"
playAnim(dict, "givetake2_a", 0.3, 2)
playAnim(dict, "givetake2_b", 0.3, 2, data.ped)
Wait(2000)
StopAnimTask(PlayerPedId(), dict, "givetake2_a", 0.5)
StopAnimTask(data.ped, dict, "givetake2_b", 0.5)
if data.onBack then data.onBack() end
end
--- Server event handler for processing item sales.
--- Removes sold items from inventory and funds the player based on the sale.
RegisterNetEvent(getScript().."Sellitems", function(data)
local src = source
local hasItems, hasTable = hasItem(data.item, 1, src)
if hasItems then
removeItem(data.item, hasTable[data.item].count, src)
TriggerEvent(getScript()..":server:FundPlayer", (hasTable[data.item].count * data.price), "cash", src)
else
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error", src)
end
end)
-------------------------------------------------------------
-- Shop Interface
-------------------------------------------------------------
--- Opens a shop interface for the player.
---
--- Checks job/gang restrictions, then uses the active inventory system to open the shop.
--- @param data table Contains:
--- - shop (`string`) The shop identifier.
--- - items (`table`) The items available in the shop.
--- - coords (`vector3`) where the shop is located.
--- - job/gang (optional) (`string`) Job or gang requirements.
---@usage
--- ```lua
--- openShop({
--- shop = "weapon_shop",
--- items = weaponShopItems,
--- coords = vector3(100.0, 200.0, 300.0),
--- job = "police",
--- })
--- ```
function openShop(data)
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
if isStarted(OXInv) then
exports[OXInv]:openInventory('shop', { type = data.shop })
elseif isStarted(QBInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:OpenShopNewQB', data.shop)
else
TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
--elseif isStarted(OrigenInv) then -- Needs testing, not sure if i did this right
-- exports[OrigenInv]:openInventory('shop', data.shop, data.items)
else
TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
lookEnt(data.coords)
end
--- Server event handler for opening a shop using the new QB inventory system.
RegisterNetEvent(getScript()..':server:OpenShopNewQB', function(data)
exports[QBInv]:OpenShop(source, data)
end)
-------------------------------------------------------------
-- Server Callback Registration
-------------------------------------------------------------
if isServer() then
createCallback(getScript()..':server:canCarry', function(source, itemTable) local result = canCarry(itemTable, source) return result end)
end
end)

View File

@@ -515,6 +515,37 @@ function ensureNetToEnt(entNetID)
return entity
end
function sendLog(text)
local Player = getPlayer()
local coords = GetEntityCoords(PlayerPedId())
local _, _, _, hour, min, sec = GetLocalTime()
local data = {
script = debug.getinfo(2, "nSl"),
coords = coords,
localTime = { hour = hour, min = min, sec = sec },
firstname = Player.firstname,
lastname = Player.lastname,
source = Player.source,
id = Player.citizenId,
text = text,
}
debugPrint("^5Log Message^7: "..getScript().." - "..Player.firstname.." "..Player.lastname.."("..Player.source..") ["..Player.citizenId.."]", text)
TriggerServerEvent(getScript()..":server:sendlog", data)
end
function sendServerLog(data)
local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S')
data.serverTime = { house = hour, min = min, sec = sec }
--jsonPrint(data)
debugPrint("^5Log Message^7: "..getScript().." - "..data.firstname.." "..data.lastname.."("..data.source..") ["..data.id.."]", data.text)
-- Add your logger here
end
RegisterNetEvent(getScript()..":server:sendlog", sendServerLog)
-------------------------------------------------------------
-- Material and Prop Functions
-------------------------------------------------------------

View File

@@ -97,13 +97,34 @@ function createInput(title, opts)
default = opts[i].default,
}
end
if opts[i].type == "slider" then
options[currentNum] = {
type = opts[i].type,
label = opts[i].label,
isRequired = opts[i].required,
min = opts[i].min,
max = opts[i].max,
default = opts[i].default,
}
end
::skip::
end
dialog = exports[OXLibExport]:inputDialog(title, options)
return dialog
elseif Config.System.Menu == "qb" then
dialog = exports['qb-input']:ShowInput({ header = title, submitText = "Accept", inputs = opts })
for k, v in pairs(opts) do
currentNum += 1
if opts[k] == nil then
currentNum -= 1
else
options[currentNum] = opts[k]
end
end
dialog = exports['qb-input']:ShowInput(
{ header = title, submitText = "Accept", inputs = options }
)
return dialog
elseif Config.System.Menu == "gta" then
WarMenu.CreateMenu(tostring(opts),
title,

View File

@@ -59,7 +59,11 @@ function hasItem(items, amount, src)
debugPrint(foundMessage)
hasTable[item] = { hasItem = count >= amt, count = count }
end
for k, v in pairs(hasTable) do if not v.hasItem then return false, hasTable end end
for k, v in pairs(hasTable) do
if not v.hasItem then
return false, hasTable
end
end
return true, hasTable
end
end

View File

@@ -12,6 +12,7 @@
• Granting random rewards from a reward pool.
• Checking if a player can carry specific items based on weight.
]]
validTokens = {}
-------------------------------------------------------------
-- Registering Usable Items
@@ -74,7 +75,7 @@ function invImg(item)
elseif isStarted(QBInv) then
imgLink = "nui://"..QBInv.."/html/images/"..(Items[item].image or "")
else
print("^4ERROR^7: ^2No Inventory detected for invImg - Check exports.lua")
print("^4ERROR^7: ^2No Inventory detected for invImg - Check starter.lua")
end
end
return imgLink
@@ -103,7 +104,8 @@ function addItem(item, amount, info, src)
if src then
TriggerEvent(getScript()..":server:toggleItem", true, item, amount, src, info)
else
TriggerServerEvent(getScript()..":server:toggleItem", true, item, amount, nil, info)
TriggerServerEvent(getScript()..":server:toggleItem", true, item, amount, currentToken, info)
currentToken = nil -- clear client cached token
end
end
@@ -159,7 +161,25 @@ RegisterNetEvent(getScript()..":server:toggleItem", function(give, item, amount,
return
end
local src = newsrc or source
local src = source or newsrc
if (give == true or give == 1) then
if newsrc == nil then -- must be coming from client this would be blank
debugPrint("^1Auth^7: ^1No token recieved^7")
dupeWarn(src, item, "Auth: Player "..src.." attempted to spawn "..item.." without an auth token")
else
if type(newsrc) ~= "number" then -- checks if the newsrc is a source or token, if number its coming form the server itself
debugPrint("^1Auth^7: ^2Auth token received^7, ^2checking against server cache^7..")
if newsrc ~= validTokens[src] then
debugPrint("^1Auth^7: ^1Tokens don't match! ^7", newsrc, validTokens[src])
dupeWarn(src, item, "Auth: "..src.." attempted to spawn "..item.." with an incorrect auth token")
else
debugPrint("^1Auth^7: ^2Client and Server Auth tokens match^7!", newsrc, validTokens[src])
validTokens[src] = nil
end
end
end
end
local action = (tostring(give) == "true" and "addItem" or "removeItem")
local remamount = amount or 1
if item == nil then return end
@@ -599,4 +619,62 @@ function canCarry(itemTable, src)
end
end
return resultTable
end
end
-------------------------------------------------------------
-- Server Callback Registration
-------------------------------------------------------------
currentToken = nil
if isServer() then
createCallback(getScript()..":server:canCarry", function(source, itemTable)
local result = canCarry(itemTable, source)
return result
end)
local AuthEvent = getScript()..":"..keyGen()..keyGen()..keyGen()..keyGen()..":"..keyGen()..keyGen()..keyGen()..keyGen()
validTokens = validTokens or {}
createCallback(AuthEvent, function(source)
local src = source
local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here
debugPrint("^1Auth^7:^2 Player Source^7: "..src.." ^2requested new token^7:", token)
validTokens[src] = token
timeOutAuth(validTokens[src], src) -- Give script 10 seconds, then clear token
return token
end)
function timeOutAuth(token, src)
local token = token
SetTimeout(10000, function()
if token == validTokens[src] then
print("^1--------------------------------------------^7")
print("^7Clearing token for player ^1"..src.."^7", token)
print("^7This shouldn't happen unless a token has been called by a player or script and it hasn't been used")
print("^1--------------------------------------------^7")
end
end)
end
RegisterNetEvent(getScript()..":clearAuthToken", function()
local src = source
debugPrint("^1Auth^7: ^2Manually removing token for Player Source^7:", src, validTokens[src])
validTokens[src] = nil
end)
receivedEvent = {}
createCallback(getScript()..":callback:GetAuthEvent", function(source)
local src = source
debugPrint("^1Auth^7: ^2Player Source^7: "..src.." ^2requested ^3AuthEvent^7", AuthEvent)
if not receivedEvent[src] then receivedEvent[src] = true
return AuthEvent
else
print("^1Auth^7: ^1Player ^7"..src.." ^1tried to request auth token more than once^7")
return ""
end
end)
else
onResourceStart(function()
debugPrint("^1Auth^7: ^2Requesting ^3Auth Event^7")
AuthEvent = triggerCallback(getScript()..":callback:GetAuthEvent")
end, true)
end

View File

@@ -22,15 +22,21 @@ function createTempCam(ent, coords)
triggerNotify(nil, "ModCam Created", "success")
end
local camCoords = nil
local pointCoords = nil
if type(ent) ~= "vector3" then
camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8)
else
camCoords = ent
end
-- Create the camera with specified parameters
cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
-- Point the camera at the target coordinates
PointCamAtCoord(cam, coords)
if type(coords) == "number" then
SetCamCoord(cam, GetCamCoord(cam) + vec3(0, 0, 1.0))
PointCamAtEntity(cam, coords)
else
PointCamAtCoord(cam, coords)
end
end
return cam
end

View File

@@ -137,11 +137,11 @@ end
--- ```
function loadScriptBank(bank)
local timeout = 2000
debugPrint("^6Debug^7: ^2Loading ^3Script ^2AudioBank^7...")
while not RequestScriptAudioBank(bank, 0) do Wait(10) timeout -= 10 if timeout <= 0 then break end end
debugPrint("^6Bridge^7: ^2Loading ^3Script ^2AudioBank^7...")
while not RequestScriptAudioBank(bank, false) do Wait(10) timeout -= 10 if timeout <= 0 then break end end
local success = RequestScriptAudioBank(bank, 0)
debugPrint("^6Debug^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
local success = RequestScriptAudioBank(bank, false)
debugPrint("^6Bridge^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
return success
end
@@ -159,14 +159,14 @@ end
--- ```
function loadAmbientBank(bank)
local timeout = 2000
debugPrint("^6Debug^7: ^2Loading ^3Ambient ^2AudioBank^7...")
debugPrint("^6Bridge^7: ^2Loading ^3Ambient ^2AudioBank^7...")
while not RequestAmbientAudioBank(bank, 0) do
Wait(10)
timeout -= 10
if timeout <= 0 then break end
end
local success = RequestAmbientAudioBank(bank, 0)
debugPrint("^6Debug^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
debugPrint("^6Bridge^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
return success
end
@@ -224,16 +224,18 @@ end
--- ```lua
--- playGameSound('DLC_HEIST_HACKING_SNAKE_SOUNDS', 'Beep', vector3(0, 0, 0), false, 15.0)
--- ```
function playGameSound(bank, sound, coords, synced, range)
debugPrint("^6Debug^7: ^2Attempting to play: ^3"..sound.." ^7(^4"..bank.."^7')")
function playGameSound(audioBank, soundSet, soundRef, coords, synced, range)
debugPrint("^6Bridge^7: ^2Attempting to play: ^3"..soundRef.." ^7('^4"..audioBank.."^7')")
loadScriptBank(audioBank)
local range = range or 10.0
local soundId = GetSoundId()
while not soundId do Wait(10) end
if type(coords) == "vector3" or type(coords) == "vector4" then
debugPrint("^6Debug^7: ^2Playing sound from Coord^7: "..formatCoord(coords.xyz))
PlaySoundFromCoord(soundId, sound, coords.x, coords.y, coords.z, bank, synced, range, 0)
debugPrint("^6Bridge^7: ^2Playing sound from Coord^7: "..formatCoord(coords.xyz))
PlaySoundFromCoord(soundId, soundRef, coords.x, coords.y, coords.z, soundSet, synced, range, 0)
else
debugPrint("^6Debug^7: ^2Playing sound from Entity^7: ^4"..coords.."^7")
PlaySoundFromEntity(soundId, sound, coords, bank, synced, 0)
debugPrint("^6Bridge^7: ^2Playing sound from Entity^7: ^4"..coords.."^7")
PlaySoundFromEntity(soundId, soundRef, coords, soundSet, synced, 1.0)
end
ReleaseScriptAudioBank(audioBank)
end

View File

@@ -31,6 +31,60 @@ function makeVeh(model, coords)
return veh
end
local distanceVehicles = {}
--- Creates a vehicle that spawns when the player enters a designated polyzone area.
---
--- This function sets up a circular polyzone; when the player enters the zone, the vehicle is spawned,
--- and when the player exits, the vehicle is deleted.
---
---@param data table A table containing vehicle data.
--- - **vehicle** `string`: The model name or hash of the vehicle to spawn.
--- - **coords** `vector4`: The coordinates where the vehicle will be placed. Should include x, y, z, and w (heading).
---@param freeze boolean (optional) Whether to freeze the vehicle in place. Defaults to `false`.
---@param synced boolean (optional) Whether the vehicle should be synced across clients. Defaults to `false`.
function makeDistVehicle(data, radius, onEnter, onExit)
local vehicle = nil
local zoneId = keyGen() .. keyGen()
local zone = createCirclePoly({
name = zoneId,
coords = vec3(data.coords.x, data.coords.y, data.coords.z),
radius = radius,
onEnter = function()
vehicle = makeVeh(data.model, data.coords)
if onEnter then
debugPrint("makeDistVehicle onEnter running")
onEnter(vehicle)
end
end,
onExit = function()
deleteVehicle(vehicle)
if onExit then
debugPrint("makeDistVehicle onExit running")
onExit(vehicle)
end
end,
debug = debugMode,
})
distanceVehicles[zoneId] = { zone = zone, vehicle = vehicle }
return zoneId
end
--- Removes a specific distance-based vehicle spawning zone.
---
---@param zoneId string The unique identifier of the zone to remove.
function removeDistVehicleZone(zoneId)
if distanceVehicles[zoneId].zone then
removePolyZone(distanceVehicles[zoneId].zone) -- Adjust this if your polyzone library uses a different removal method.
if distanceVehicles[zoneId].vehicle then
deleteVehicle(distanceVehicles[zoneId].vehicle)
end
distanceVehicles[zoneId] = nil
print("Removed polyzone for zoneId: " .. zoneId)
else
print("No zone found with zoneId: " .. zoneId)
end
end
--- Attempts to gain network control of a vehicle and set it as a mission entity.
---
--- This function forces synchronization of a vehicle with other players by requesting network control and setting the vehicle as a mission entity.
@@ -67,6 +121,20 @@ function pushVehicle(entity)
end
end
--- Deletes a spawned vehicle.
---
---@param vehicle number The handle of the vehicle entity to delete.
function deleteVehicle(vehicle)
if vehicle then
debugPrint("^6Bridge^7: ^2Destroying Vehicle^7: '^6" .. vehicle .. "^7'")
if IsEntityAttachedToEntity(vehicle, PlayerPedId()) then
SetEntityAsMissionEntity(vehicle)
DetachEntity(vehicle, true, true)
end
DeleteVehicle(vehicle)
end
end
--- Cleans up all created vehicles when the resource stops.
onResourceStop(function(r)
for i = 1, #Vehicles do DeleteVehicle(Vehicles[i]) end

View File

@@ -220,7 +220,6 @@ function fundPlayer(fund, moneyType, newsrc)
debugPrint("^6Bridge^7: ^2Funding Player: '^2"..fund.."^7'", moneyType, fundResource)
end
end
RegisterNetEvent(getScript()..":server:FundPlayer", fundPlayer)
-------------------------------------------------------------
-- Item Consumption & Effects
@@ -458,7 +457,7 @@ function getPlayer(source)
--gangBoss = info.gang.isboss,
onDuty = info.job.onDuty,
--account = info.charinfo.account,
--citizenId = info.citizenid,
citizenId = info.citizenid,
}
elseif isStarted(OXCoreExport) then
@@ -468,9 +467,20 @@ function getPlayer(source)
chunk()
local player = Ox.GetPlayer(src)
Player = {
firstname = player.firstName,
lastname = player.lastName ,
name = ('%s %s'):format(player.firstName, player.lastName),
cash = exports[OXInv]:Search(src, 'count', "money"),
bank = 0,
source = src,
--job = OxPlayer.getGroups(),
--jobBoss = info.job.isboss,
--gang = OxPlayer.getGroups(),
--gangBoss = info.gang.isboss,
--onDuty = info.job.onduty,
--account = info.charinfo.account,
citizenId = player.stateId,
}
elseif isStarted(QBXExport) then
local info = exports[QBXExport]:GetPlayer(src)
@@ -513,7 +523,7 @@ function getPlayer(source)
}
end
else
print("^4ERROR^7: ^2No Core detected for getPlayer() - Check exports.lua")
print("^4ERROR^7: ^2No Core detected for getPlayer() - Check starter.lua")
end
else
-- Client-side: Get current player info.
@@ -527,7 +537,9 @@ function getPlayer(source)
Player = {
firstname = info.firstName,
lastname = info.lastName,
name = info.firstName.." "..info.lastName,
cash = cash,
bank = bank,
source = GetPlayerServerId(PlayerId()),
job = info.job.name,
--jobBoss = info.job.isboss,
@@ -535,30 +547,23 @@ function getPlayer(source)
--gangBoss = info.gang.isboss,
onDuty = info.job.onDuty,
--account = info.charinfo.account,
--citizenId = info.citizenid,
name = info.firstName.." "..info.lastName,
cash = cash,
bank = bank,
citizenId = info.identifier,
}
elseif isStarted(OXCoreExport) then
--local info = exports[OXCoreExport]:GetPlayerData()
Player = {
firstname = OxPlayer.get("firstName"),
lastname = OxPlayer.get("lastName"),
name = OxPlayer.get("firstName").." "..OxPlayer.get("lastName"),
cash = exports[OXInv]:Search('count', "money"),
bank = 0,
--source = info.source,
source = GetPlayerServerId(PlayerId()),
job = OxPlayer.getGroups(),
--jobBoss = info.job.isboss,
gang = OxPlayer.getGroups(),
--gangBoss = info.gang.isboss,
--onDuty = info.job.onduty,
--account = info.charinfo.account,
citizenId = OxPlayer.get("stateId"),
citizenId = OxPlayer.userId,
}
elseif isStarted(QBXExport) then
local info = exports[QBXExport]:GetPlayerData()

View File

@@ -31,7 +31,7 @@ function debugScaleForm(textTable, loc)
local size = vec2(0.18, totalHeight + boxPadding * 2)
-- Draw background rectangle.
DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 255)
DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 200)
-- Render each line of text.
for i = 1, #textTable do

206
shared/shops.lua Normal file
View File

@@ -0,0 +1,206 @@
-------------------------------------------------------------
-- Selling Menu and Animation
-------------------------------------------------------------
--- Opens a selling menu with available items and prices.
---
--- @param data table Contains selling menu data:
--- - sellTable (`table`) Table with Header and Items (item names and prices).
--- - ped (optional) (`number`) Ped entity involved.
--- - onBack (optional) (`function`) Callback for returning.
--- @usage
--- ```lua
--- sellMenu({
--- sellTable = {
--- Header = "Sell Items",
--- Items = {
--- ["gold_ring"] = 100,
--- ["diamond"] = 500,
--- },
--- },
--- ped = pedEntity,
--- onBack = function() print("Returning to previous menu") end,
--- })
--- ```
function sellMenu(data)
local origData = data
local Menu = {}
if data.sellTable.Items then
local itemList = {}
for k, v in pairs(data.sellTable.Items) do itemList[k] = 1 end
local _, hasTable = hasItem(itemList)
for k, v in pairsByKeys(data.sellTable.Items) do
Menu[#Menu + 1] = {
isMenuHeader = not hasTable[k].hasItem,
icon = invImg(k),
header = Items[k].label..(hasTable[k].hasItem and "💰 (x"..hasTable[k].count..")" or ""),
txt = Loc[Config.Lan].info["sell_all"]..v.." "..Loc[Config.Lan].info["sell_each"],
onSelect = function()
sellAnim({ item = k, price = v, ped = data.ped, onBack = function() sellMenu(data) end })
end,
}
end
else
for k, v in pairsByKeys(data.sellTable) do
if type(v) == "table" then
Menu[#Menu + 1] = {
arrow = true,
header = k,
txt = "Amount of items: "..countTable(v.Items),
onSelect = function()
v.onBack = function() sellMenu(origData) end
v.sellTable = data.sellTable[k]
sellMenu(v)
end,
}
end
end
end
openMenu(Menu, {
header = data.sellTable.Header or "Amount of items: "..countTable(data.sellTable.Items),
headertxt = data.sellTable.Header and "Amount of items: "..countTable(data.sellTable.Items) or "",
canClose = true,
onBack = data.onBack,
})
end
--- Plays the selling animation and processes the sale transaction.
---
--- Checks if the player has the item, plays animations, triggers the server event for selling,
--- and then calls the onBack callback if provided.
---
--- @param data table Contains:
--- `- item: The item to sell.
--- `- price: Price per item.
--- `- ped (optional): Ped entity involved.
--- `- onBack (optional): Callback to call on completion.
---@usage
--- ```lua
--- sellAnim({
--- item = "gold_ring",
--- price = 100,
--- ped = pedEntity,
--- onBack = function() sellMenu(data) end,
--- })
--- ```
function sellAnim(data)
if not hasItem(data.item, 1) then
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error")
return
end
-- Remove any attached clipboard objects.
for _, obj in pairs(GetGamePool('CObject')) do
for _, model in pairs({ `p_cs_clipboard` }) do
if GetEntityModel(obj) == model and IsEntityAttachedToEntity(data.ped, obj) then
DeleteObject(obj)
DetachEntity(obj, 0, 0)
SetEntityAsMissionEntity(obj, true, true)
Wait(100)
DeleteEntity(obj)
end
end
end
TriggerServerEvent(getScript().."Sellitems", data)
lookEnt(data.ped)
local dict = "mp_common"
playAnim(dict, "givetake2_a", 0.3, 2)
playAnim(dict, "givetake2_b", 0.3, 2, data.ped)
Wait(2000)
StopAnimTask(PlayerPedId(), dict, "givetake2_a", 0.5)
StopAnimTask(data.ped, dict, "givetake2_b", 0.5)
if data.onBack then data.onBack() end
end
--- Server event handler for processing item sales.
--- Removes sold items from inventory and funds the player based on the sale.
RegisterNetEvent(getScript().."Sellitems", function(data)
local src = source
local hasItems, hasTable = hasItem(data.item, 1, src)
if hasItems then
removeItem(data.item, hasTable[data.item].count, src)
fundPlayer((hasTable[data.item].count * data.price), "cash", src)
else
triggerNotify(nil, Loc[Config.Lan].error["dont_have"].." "..Items[data.item].label, "error", src)
end
end)
-------------------------------------------------------------
-- Shop Interface
-------------------------------------------------------------
--- Opens a shop interface for the player.
---
--- Checks job/gang restrictions, then uses the active inventory system to open the shop.
--- @param data table Contains:
--- - shop (`string`) The shop identifier.
--- - items (`table`) The items available in the shop.
--- - coords (`vector3`) where the shop is located.
--- - job/gang (optional) (`string`) Job or gang requirements.
---@usage
--- ```lua
--- openShop({
--- shop = "weapon_shop",
--- items = weaponShopItems,
--- coords = vector3(100.0, 200.0, 300.0),
--- job = "police",
--- })
--- ```
function openShop(data)
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
if isStarted(OXInv) then
exports[OXInv]:openInventory('shop', { type = data.shop })
elseif isStarted(QBInv) then
if QBInvNew then
TriggerServerEvent(getScript()..':server:OpenShopNewQB', data.shop)
else
TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
--elseif isStarted(OrigenInv) then -- Needs testing, not sure if i did this right
-- exports[OrigenInv]:openInventory('shop', data.shop, data.items)
else
TriggerServerEvent(Config.General.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.items.label, data.items)
end
lookEnt(data.coords)
end
--- Server event handler for opening a shop using the new QB inventory system.
RegisterNetEvent(getScript()..':server:OpenShopNewQB', function(data)
exports[QBInv]:OpenShop(source, data)
end)
--- Registers a shop with the active inventory system.
--- Supports either OXInv or QBInv (with QBInvNew flag).
---
--- @param name string Unique shop identifier.
--- @param label string Display name for the shop.
--- @param items table List of available shop items.
--- @param society string|nil (Optional) Society identifier for shared shops.
--- @usage
--- ```lua
--- registerShop("weaponShop", "Weapon Shop", weaponItems, "society_weapons")
--- ```
function registerShop(name, label, items, society)
if isStarted(OXInv) then
debugPrint("^6Bridge^7: ^2Registering ^3OX ^2Store^7:", name, label)
exports[OXInv]:RegisterShop(name, {
name = label,
inventory = items,
society = society,
})
elseif isStarted(QBInv) and QBInvNew then
debugPrint("^6Bridge^7: ^2Registering ^3QB ^2Store^7:", name, label)
exports[QBInv]:CreateShop({
name = name,
label = label,
slots = #items,
items = items,
society = society,
})
end
end

36
shared/skillcheck.lua Normal file
View File

@@ -0,0 +1,36 @@
function skillCheck(data)
local result = false
if Config.System.skillCheck == "qb" then
local Skillbar = exports["qb-minigames"]:Skillbar()
if Skillbar then
result = true
else
result = false
end
elseif Config.System.skillCheck == "ox" then
local Skillbar = exports[OXLibExport]:skillCheck(
{
"easy",
"easy",
"easy"
},
{
"1",
"2",
"3",
"4"
})
if Skillbar then
result = true
else
result = false
end
else
result = true
end
return result
end

View File

@@ -99,7 +99,7 @@ function checkHasItem(stashes, itemTable)
for item, amount in pairs(itemTable) do
debugPrint("^6Bridge^7: ^2Checking "..(name and " '^3"..name.."^7'" or "").." ingredients - ^6"..item.."^7")
if stashhasItem(stashCache[name].items, item, amount) then
successes = successes + 1
successes += 1
if successes == itemCount then
return true, name
end
@@ -271,7 +271,6 @@ function stashRemoveItem(stashItems, stashName, items)
if isStarted(OXInv) then
for k, v in pairs(items) do
debugPrint("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v)
if type(stashName) == "table" then
for _, name in pairs(stashName) do
local success = exports[OXInv]:RemoveItem(name, k, v)

View File

@@ -119,9 +119,8 @@ function createEntityTarget(entity, opts, dist)
item = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].action,
canInteract = function(_, distance)
return distance < dist and true or false
end
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
exports[OXTargetExport]:addLocalEntity(entity, options)
@@ -223,9 +222,8 @@ function createBoxTarget(data, opts, dist)
item = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].onSelect or opts[i].action,
canInteract = function(_, distance)
return distance < dist and true or false
end
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
if not data[5].useZ then
@@ -324,9 +322,8 @@ function createCircleTarget(data, opts, dist)
item = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].onSelect or opts[i].action,
canInteract = function(_, distance)
return distance < dist
end
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
local target = exports[OXTargetExport]:addSphereZone({
@@ -385,9 +382,8 @@ function createModelTarget(models, opts, dist)
item = opts[i].item or nil,
groups = opts[i].job or opts[i].gang,
onSelect = opts[i].action,
canInteract = function(_, distance)
return distance < dist and true or false
end
distance = dist,
canInteract = opts[i].canInteract or nil,
}
end
exports[OXTargetExport]:addModel(models, options)

View File

@@ -128,14 +128,15 @@ end
function setVehicleProperties(vehicle, props)
if checkDifferences(vehicle, props) then
if not DoesEntityExist(vehicle) then
print("Unable to set vehicle properties for '"..vehicle.."' (entity does not exist)")
print("Unable to set vehicle properties for '"..vehicle.."' (^1entity does not exist^7)")
end
if isStarted(QBExport) and not isStarted(QBXExport) then
Core.Functions.SetVehicleProperties(vehicle, props)
debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
else
TriggerServerEvent(getScript()..":ox:setVehicleProperties", VehToNet(vehicle), props)
elseif isStarted(OXLibExport) then
lib.setVehicleProperties(vehicle, props, false)
debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
end
else
debugPrint("^6Bridge^7: ^2No Changes Found ^7 [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
@@ -158,7 +159,7 @@ end
function checkDifferences(vehicle, newProps)
local oldProps = getVehicleProperties(vehicle)
debugPrint("^6Bridge^7: ^2Finding differences in ^3Vehicle Properties^7")
local differencesFound = false
local differencesFound = true
for k in pairs(oldProps) do
if json.encode(oldProps[k]) ~= json.encode(newProps[k]) then
@@ -199,7 +200,7 @@ AddStateBagChangeHandler(getScript()..':setVehicleProperties', '', function(bagN
local networked = not bagName:find('localEntity')
debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..entity.."^7] - [^3"..GetEntityModel(entity).."^7] - [^3"..value.plate.."^7]")
if networked and NetworkGetEntityOwner(entity) ~= cache.playerId then return end
if networked then return end
if lib.setVehicleProperties(entity, value) then
Entity(entity).state:set('setVehicleProperties', nil, true)

View File

@@ -119,7 +119,18 @@ if isServer() then
--- ```lua
--- TriggerEvent(getScript()..":server:makeOXStash", name, label, slots, weight, owner, coords)
--- ```
RegisterNetEvent(getScript()..":server:makeOXStash", function(name, label, slots, weight, owner, coords)
RegisterNetEvent(getScript()..":server:makeOXStash", function(name, label, slots, weight, owner, coords, token)
local src = source or nil
if src then
debugPrint("^1Auth^7: ^2Auth token received^7, ^2checking against server cache^7..")
if token ~= validTokens[src] then
debugPrint("^1Auth^7: ^1Tokens don't match! ^7", token, validTokens[src])
else
debugPrint("^1Auth^7: ^2Client and Server Auth tokens match^7!", token, validTokens[src])
validTokens[src] = nil
end
end
registerStash(name, label, slots, weight, owner, coords)
end)
end

View File

@@ -71,9 +71,11 @@ for _, v in pairs({ -- This is a specific load order
'input.lua',
'notify.lua',
'drawText.lua',
'skillcheck.lua',
-- Crafting / Shops / Stashes
'crafting.lua',
'shops.lua',
'stashcontrol.lua',
-- Kind of "other"

View File

@@ -1 +1 @@
1.2
2.0