diff --git a/shared/callback.lua b/shared/callback.lua index 03a882d..0c1ac2c 100644 --- a/shared/callback.lua +++ b/shared/callback.lua @@ -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) diff --git a/shared/contextmenus.lua b/shared/contextmenus.lua index 4bc0778..7f7685c 100644 --- a/shared/contextmenus.lua +++ b/shared/contextmenus.lua @@ -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' diff --git a/shared/coreloader.lua b/shared/coreloader.lua index e7ebe54..5b1d086 100644 --- a/shared/coreloader.lua +++ b/shared/coreloader.lua @@ -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) diff --git a/shared/crafting.lua b/shared/crafting.lua index 3c76c9a..74cc73a 100644 --- a/shared/crafting.lua +++ b/shared/crafting.lua @@ -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 \ No newline at end of file +end) \ No newline at end of file diff --git a/shared/helpers.lua b/shared/helpers.lua index 41c30e9..ade1bd7 100644 --- a/shared/helpers.lua +++ b/shared/helpers.lua @@ -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 ------------------------------------------------------------- diff --git a/shared/input.lua b/shared/input.lua index efb84b5..84b921a 100644 --- a/shared/input.lua +++ b/shared/input.lua @@ -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, diff --git a/shared/inventories.lua b/shared/inventories.lua index e094f1c..a42a4e6 100644 --- a/shared/inventories.lua +++ b/shared/inventories.lua @@ -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 diff --git a/shared/itemcontrol.lua b/shared/itemcontrol.lua index c8d850a..7707b31 100644 --- a/shared/itemcontrol.lua +++ b/shared/itemcontrol.lua @@ -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 \ No newline at end of file +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 diff --git a/shared/make/cameras.lua b/shared/make/cameras.lua index b08cd04..ae6b34b 100644 --- a/shared/make/cameras.lua +++ b/shared/make/cameras.lua @@ -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 diff --git a/shared/make/loaders.lua b/shared/make/loaders.lua index 1b97373..e3605f9 100644 --- a/shared/make/loaders.lua +++ b/shared/make/loaders.lua @@ -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 \ No newline at end of file diff --git a/shared/make/makeVeh.lua b/shared/make/makeVeh.lua index 9c65af3..e08c04f 100644 --- a/shared/make/makeVeh.lua +++ b/shared/make/makeVeh.lua @@ -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 diff --git a/shared/playerfunctions.lua b/shared/playerfunctions.lua index aa4454d..b968701 100644 --- a/shared/playerfunctions.lua +++ b/shared/playerfunctions.lua @@ -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() diff --git a/shared/scaleforms/debugScaleform.lua b/shared/scaleforms/debugScaleform.lua index 52f69f3..641f17e 100644 --- a/shared/scaleforms/debugScaleform.lua +++ b/shared/scaleforms/debugScaleform.lua @@ -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 diff --git a/shared/shops.lua b/shared/shops.lua new file mode 100644 index 0000000..5f726ac --- /dev/null +++ b/shared/shops.lua @@ -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 \ No newline at end of file diff --git a/shared/skillcheck.lua b/shared/skillcheck.lua new file mode 100644 index 0000000..d50621b --- /dev/null +++ b/shared/skillcheck.lua @@ -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 \ No newline at end of file diff --git a/shared/stashcontrol.lua b/shared/stashcontrol.lua index 550c7d0..c79f255 100644 --- a/shared/stashcontrol.lua +++ b/shared/stashcontrol.lua @@ -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) diff --git a/shared/targets.lua b/shared/targets.lua index 0f85ff1..ba9781b 100644 --- a/shared/targets.lua +++ b/shared/targets.lua @@ -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) diff --git a/shared/vehicles.lua b/shared/vehicles.lua index 49c068e..1476d1b 100644 --- a/shared/vehicles.lua +++ b/shared/vehicles.lua @@ -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) diff --git a/shared/wrapperfunctions.lua b/shared/wrapperfunctions.lua index 8eef846..26bcc28 100644 --- a/shared/wrapperfunctions.lua +++ b/shared/wrapperfunctions.lua @@ -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 diff --git a/starter.lua b/starter.lua index b1d38ed..12f7552 100644 --- a/starter.lua +++ b/starter.lua @@ -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" diff --git a/version.txt b/version.txt index 6609db5..415b19f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2 +2.0 \ No newline at end of file