diff --git a/client/events.lua b/client/events.lua index 13eb73e..ae7daaa 100644 --- a/client/events.lua +++ b/client/events.lua @@ -57,12 +57,12 @@ RegisterNetEvent("ND:characterUnloaded") RegisterNetEvent("ND:clothingMenu", function() if GetResourceState("fivem-appearance") ~= "started" then return end - + local function customize(appearance) if not appearance then return end TriggerServerEvent("ND:updateClothing", appearance) end - + exports["fivem-appearance"]:startPlayerCustomization(customize, { ped = false, headBlend = true, @@ -73,3 +73,74 @@ RegisterNetEvent("ND:clothingMenu", function() tattoos = true }) end) + +RegisterNetEvent("ND:teleportToMarker", function() + local waypoint = GetFirstBlipInfoId(8) + if not DoesBlipExist(waypoint) then + NDCore.notify({ + title = "Error", + description = "There is no waypoint set!", + type = "error" + }) + return + end + + -- Fade out the screen for teleporting + DoScreenFadeOut(500) + Wait(600) -- Wait a bit longer than the fade out time to ensure it's fully faded + + local ped = PlayerPedId() + local coords = GetBlipInfoIdCoord(waypoint) + local x, y, z = coords.x, coords.y, 0 + local ground + local groundFound = false + local groundCheckHeight = 1000.0 + + for i = 0, 1000 do + SetPedCoordsKeepVehicle(ped, x, y, groundCheckHeight, false, false, false, false) + ground, z = GetGroundZFor_3dCoord(x, y, groundCheckHeight, false) + + if ground then + z = z + 1.0 + groundFound = true + break + end + + groundCheckHeight = groundCheckHeight - 5.0 + if groundCheckHeight < 0 then break end + + Wait(0) + end + + if not groundFound then + return NDCore.notify({ + title = "Error", + description = "Could not find ground, please try again!", + type = "error" + }) + end + + local pedHeading = GetEntityHeading(ped) + SetPedCoordsKeepVehicle(ped, x, y, z, false, false, false, false) + SetEntityHeading(ped, pedHeading) + + -- Fade the screen back in after teleportation + Wait(200) -- Small wait to ensure entity is positioned properly + DoScreenFadeIn(800) + + NDCore.notify({ + title = "Success", + description = "Successfully teleported to waypoint!", + type = "success" + }) +end) + +RegisterNetEvent("ND:changeWeather", function(weather) + SetWeatherTypeOverTime(weather, 2.0) + Wait(2000) + SetWeatherTypeNowPersist(weather) +end) + +RegisterNetEvent("ND:changeTime", function(hours, minutes) + NetworkOverrideClockTime(hours, minutes, 0) +end) \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua index 614a137..2685e49 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -24,6 +24,7 @@ server_scripts { "@oxmysql/lib/MySQL.lua", "server/main.lua", "shared/functions.lua", + "shared/items.lua", "server/player.lua", "server/vehicle.lua", "server/functions.lua", diff --git a/server/commands.lua b/server/commands.lua index 495f6a4..9e6bc74 100644 --- a/server/commands.lua +++ b/server/commands.lua @@ -13,6 +13,9 @@ local moneyActions = { return ("Set %s's (%s) to $%d"):format(player.name, account, amount), ("set %s to $%d"):format(account, amount) end } +local validWeather = { + "clear", "extrasunny", "clouds", "overcast", "rain", "clearing", "thunder", "smog", "foggy", "xmas", "snow", "snowlight", "blizzard", "halloween", "neutral" +} lib.addCommand("setmoney", { help = "Admin command, set a players money.", @@ -85,7 +88,7 @@ lib.addCommand("setjob", { }, function(source, args, raw) local player = NDCore.getPlayer(args.target) if not player then return end - + local job = args.job:lower() local jobInfo = player.setJob(job, args.rank) if not player or not jobInfo then return end @@ -245,14 +248,14 @@ lib.addCommand("pay", { duration = 5000 }) end - + targetPlayer.notify({ title = "Money received", description = ("Received $%d in cash"):format(args.amount), type = "inform", duration = 5000 }) - + player.notify({ title = "Money given", description = ("You gave $%d in cash"):format(args.amount), @@ -471,7 +474,7 @@ lib.addCommand("claim-veh", { type = "error" }) end - + NDCore.setVehicleOwned(targetPlayer.id, properties, true) player.notify({ @@ -480,3 +483,141 @@ lib.addCommand("claim-veh", { type = "success" }) end) + +lib.addCommand("tpm", { + help = "Admin command, teleport to marked location.", + restricted = "group.admin" +}, function(source, args, raw) + TriggerClientEvent("ND:teleportToMarker", source) +end) + +lib.addCommand("tp", { + help = "Admin command, teleport to coordinates.", + restricted = "group.admin", + params = { + { + name = "x", + type = "number", + help = "X coordinate" + }, + { + name = "y", + type = "number", + help = "Y coordinate" + }, + { + name = "z", + type = "number", + help = "Z coordinate" + } + } +}, function(source, args, raw) + if not args or not args.x or not args.y or not args.z then + local player = NDCore.getPlayer(source) + return player.notify({ + title = "Error", + description = "No coordinates provided! /tp ", + type = "error" + }) + end + local ped = GetPlayerPed(source) + SetEntityCoords(ped, tonumber(args.x) + 0.0, tonumber(args.y) + 0.0, tonumber(args.z) + 0.0) +end) + +lib.addCommand("weather", { + help = "Admin command, change the weather.", + restricted = "group.admin", + params = { + { + name = "weather", + type = "string", + help = ("Weather Type (%s)"):format(table.concat(validWeather, ", ")) + } + } +}, function(source, args, raw) + if not args or not args.weather then + local player = NDCore.getPlayer(source) + return player.notify({ + title = "Error", + description = "No weather type provided! /weather ", + type = "error" + }) + end + local weather = args.weather:lower() + + if not lib.table.contains(validWeather, weather) then + local player = NDCore.getPlayer(source) + return player.notify({ + title = "Invalid Weather", + description = ("Format: %s"):format(table.concat(validWeather, ", ")), + type = "error" + }) + end + + TriggerClientEvent("ND:changeWeather", -1, weather) +end) + +lib.addCommand("time", { + help = "Admin command, change the time.", + restricted = "group.admin", + params = { + { + name = "hours", + type = "number", + help = "Hours" + }, + { + name = "minutes", + type = "number", + help = "Minutes" + }, + { + name = "seconds", + type = "number", + help = "Seconds", + optional = true + } + } +}, function(source, args, raw) + if not args or not args.hours or not args.minutes then + local player = NDCore.getPlayer(source) + return player.notify({ + title = "Error", + description = "No time provided! /time ", + type = "error" + }) + else + local hours = tonumber(args.hours) + local minutes = tonumber(args.minutes) + local seconds = args.seconds and tonumber(args.seconds) or 0 + + if not hours or hours < 0 or hours > 23 or + not minutes or minutes < 0 or minutes > 59 or + seconds < 0 or seconds > 59 then + local player = NDCore.getPlayer(source) + return player.notify({ + title = "Invalid Time", + description = "Format: /time ", + type = "error" + }) + end + + TriggerClientEvent("ND:changeTime", -1, hours, minutes, seconds) + end +end) + +lib.addCommand("coords", { + help = "Admin command, get your current coordinates.", + restricted = "group.admin" +}, function(source, args, raw) + local ped = GetPlayerPed(source) + local coords = GetEntityCoords(ped) + local heading = GetEntityHeading(ped) + local message = ("Your current coordinates are: x: %.2f, y: %.2f, z: %.2f, w: %.2f"):format(coords.x, coords.y, coords.z, heading) + + TriggerClientEvent("chat:addMessage", source, { + color = {65, 105, 225}, + multiline = true, + args = {"Coordinates", message} + }) +end) \ No newline at end of file diff --git a/server/functions.lua b/server/functions.lua index 8c585de..31b9d78 100644 --- a/server/functions.lua +++ b/server/functions.lua @@ -5,6 +5,43 @@ function NDCore.getPlayerIdentifierByType(src, indentifierType) return GetPlayerIdentifierByType(src, indentifierType) end +NDCore.UsableItems = {} + +-- Function to register usable items +---@param item string The name of the item to register +---@param cb function The callback function to execute when the item is used +function NDCore.registerUsableItem(item, cb) + if not item or type(item) ~= "string" then return end + + NDCore.UsableItems[item] = { + cb = cb, + resource = GetInvokingResource() or GetCurrentResourceName() + } +end + +-- Function to check if an item is usable +---@param item string The name of the item to check +---@return boolean|table Returns the item data if usable, false otherwise +function NDCore.isItemUsable(item) + return NDCore.UsableItems[item] or false +end + +-- Function to use an item +---@param src number The source player ID +---@param item string|table The item name or item data +function NDCore.useItem(src, item) + if not src or not item then return end + + local itemName = type(item) == "table" and item.name or item + local itemData = NDCore.UsableItems[itemName] + + if itemData and itemData.cb then + if type(itemData.cb) == "function" then + itemData.cb(src, item) + end + end +end + ---@param src number ---@return table function NDCore.getPlayer(src) @@ -15,8 +52,8 @@ end ---@param data any ---@return table function NDCore.getPlayers(key, value, returnArray) - if not key or not value then return NDCore.players end - + if not key or not value then return NDCore.players end + local players = {} local keyTypes = {id = "id", firstname = "firstname", lastname = "lastname", gender = "gender", groups = "groups"} local findBy = keyTypes[key] or "metadata" @@ -59,7 +96,7 @@ function NDCore.loadSQL(fileLocation, resource) MySQL.query(file) return true end - + for i=1, #fileLocation do local file = LoadResourceFile(resourceName, fileLocation[i]) if file then @@ -106,6 +143,51 @@ function NDCore.enableMultiCharacter(enable) Config.multiCharacter = enable end +-- Track if we've already integrated +local hasIntegratedWithOxInventory = false + +-- Provides a direct hook into ox_inventory's server.UseItem function +function NDCore.integrateWithOxInventory() + if GetResourceState("ox_inventory") ~= "started" or hasIntegratedWithOxInventory then + return false + end + + -- Mark as integrated to prevent duplicate event handlers + hasIntegratedWithOxInventory = true + + -- Use a more reliable method through event handling + -- Create a hook for the ox_inventory event + AddEventHandler("ox_inventory:usedItem", function(playerId, itemName, slot, metadata) + exports["ND_Core"]:UseItem(playerId, itemName, { + name = itemName, + slot = slot, + metadata = metadata + }) + end) + + return true +end + +-- Try to integrate with ox_inventory when the resource starts +AddEventHandler("onResourceStart", function(resourceName) + if resourceName ~= GetCurrentResourceName() then return end + + -- Wait a bit to ensure everything is loaded + SetTimeout(2000, function() + NDCore.integrateWithOxInventory() + end) +end) + +-- Also try to integrate when ox_inventory starts +AddEventHandler("onResourceStart", function(resourceName) + if resourceName ~= "ox_inventory" then return end + + -- Wait a bit to ensure ox_inventory is fully loaded + SetTimeout(2000, function() + NDCore.integrateWithOxInventory() + end) +end) + for name, func in pairs(NDCore) do if type(func) == "function" then exports(name, func) diff --git a/shared/items.lua b/shared/items.lua new file mode 100644 index 0000000..3bf80ca --- /dev/null +++ b/shared/items.lua @@ -0,0 +1,97 @@ +-- Initialize the items table to store item callbacks +local items = {} + +-- Flag to prevent multiple event handling +local isProcessingItemUse = {} + +-- Function to register a usable item +---@param name string The name of the item to register +---@param cb function The callback function to execute when the item is used +local function RegisterUsableItem(name, cb) + if not name or type(name) ~= "string" then return end + + -- Store the callback in the items table + items[name] = cb + + -- Register the item with ND_Core's usable items system + exports["ND_Core"]:registerUsableItem(name, cb) +end + +-- Create a function that ox_inventory can call to use items +local function UseItem(source, itemName, data) + -- Prevent duplicate handling + local playerId = tonumber(source) + if not playerId or isProcessingItemUse[playerId .. itemName] then return false end + + -- Set flag to prevent duplicate handling + isProcessingItemUse[playerId .. itemName] = true + + -- Check if the item has a registered callback + local item = items[itemName] + local result = false + + if item then + -- Call the item's callback with the source and item data + result = item(source, data) + elseif exports["ND_Core"]:isItemUsable(itemName) then + -- Call ND_Core's useItem function if registered directly with ND_Core + result = exports["ND_Core"]:useItem(source, data) + end + + -- Clear the processing flag after a short delay + SetTimeout(500, function() + isProcessingItemUse[playerId .. itemName] = nil + end) + + return result +end + +-- Make the RegisterUsableItem function available as an export +exports("RegisterUsableItem", RegisterUsableItem) + +-- Make the UseItem function available as an export +exports("UseItem", UseItem) + +-- Listen for ox_inventory usedItem event +-- Only register this once when the resource starts +local hasRegisteredEventHandler = false +AddEventHandler("onResourceStart", function(resourceName) + if resourceName ~= GetCurrentResourceName() or hasRegisteredEventHandler then return end + + hasRegisteredEventHandler = true + + AddEventHandler("ox_inventory:usedItem", function(playerId, itemName, slot, metadata) + -- Call our UseItem function which has duplicate prevention + exports["ND_Core"]:UseItem(playerId, itemName, { + name = itemName, + slot = slot, + metadata = metadata + }) + end) + + -- Wait a bit to ensure everything is loaded + SetTimeout(1000, function() + if GetResourceState("ox_inventory") ~= "started" then + print("WARNING: ox_inventory is not running!") + return + end + end) +end) + +-- Example usage: +--[[ + exports["ND_Core"]:RegisterUsableItem("bandage", function(source, item) + local player = exports["ND_Core"]:getPlayer(source) + if not player then return end + + TriggerClientEvent("some:healing:event", source) + + TriggerClientEvent("ox_lib:notify", source, { + title = "Used Bandage", + description = "You have used a bandage and feel better", + type = "success" + }) + + return true + end) +--]] \ No newline at end of file