From 5389ae09624f31baf5f45136b8c842091e6b638a Mon Sep 17 00:00:00 2001 From: Andyyy7666 <86536434+Andyyy7666@users.noreply.github.com> Date: Mon, 10 Jul 2023 02:40:39 +0200 Subject: [PATCH] revert, 2.0.0 is in new branch --- client/events.lua | 18 +- client/functions.lua | 65 +++- client/main.lua | 84 +++-- config_client.lua | 74 +++++ config_server.lua | 6 + fxmanifest.lua | 24 +- init.lua | 16 - server/commands.lua | 117 +++++++ server/events.lua | 117 +++++++ server/functions.lua | 766 +++++++++++++++++++++++++++++++++++++++++++ server/main.lua | 25 +- shared/import.lua | 1 + shared/main.lua | 30 ++ 13 files changed, 1265 insertions(+), 78 deletions(-) create mode 100644 config_client.lua create mode 100644 config_server.lua delete mode 100644 init.lua create mode 100644 server/commands.lua create mode 100644 server/events.lua create mode 100644 server/functions.lua create mode 100644 shared/import.lua create mode 100644 shared/main.lua diff --git a/client/events.lua b/client/events.lua index 791dc64..d2a034f 100644 --- a/client/events.lua +++ b/client/events.lua @@ -4,29 +4,21 @@ end) -- updates the money on the client. RegisterNetEvent("ND:updateMoney", function(cash, bank) - NDCore.player.cash = cash - NDCore.player.bank = bank + NDCore.SelectedCharacter.cash = cash + NDCore.SelectedCharacter.bank = bank end) -- Sets main character. RegisterNetEvent("ND:setCharacter", function(character) - NDCore.player = character + NDCore.SelectedCharacter = character end) -- Update main character info. RegisterNetEvent("ND:updateCharacter", function(character) - NDCore.player = character + NDCore.SelectedCharacter = character end) -- Updates last lcoation. RegisterNetEvent("ND:updateLastLocation", function(location) - if not NDCore.player then return end - NDCore.player.lastLocation = location + NDCore.SelectedCharacter.lastLocation = location end) - --- Enable pvp for players. -AddEventHandler("playerSpawned", function() - print("^0ND Framework support discord: ^5https://discord.gg/Z9Mxu72zZ6") - SetCanAttackFriendly(PlayerPedId(), true, false) - NetworkSetFriendlyFireOption(true) -end) \ No newline at end of file diff --git a/client/functions.lua b/client/functions.lua index 3fa06a3..6e2b4a4 100644 --- a/client/functions.lua +++ b/client/functions.lua @@ -1,6 +1,19 @@ -function NDCore.GetPlayersFromCoords(distance, coords) +function GetCoreObject() + return NDCore +end + +function NDCore.Functions.GetSelectedCharacter() + return NDCore.SelectedCharacter +end + +function NDCore.Functions.GetCharacters() + return NDCore.Characters +end + + +function NDCore.Functions.GetPlayersFromCoords(distance, coords) if coords then - coords = type(coords) == "table" and vec3(coords.x, coords.y, coords.z) or coords + coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords else coords = GetEntityCoords(PlayerPedId()) end @@ -17,3 +30,51 @@ function NDCore.GetPlayersFromCoords(distance, coords) end return closePlayers end + +-- Callbacks are licensed under LGPL v3.0 +-- +NDCore.callback = {} +local events = {} + +RegisterNetEvent("ND:callbacks", function(key, ...) + local cb = events[key] + return cb and cb(...) +end) + +local function triggerCallback(_, name, cb, ...) + local key = ("%s:%s"):format(name, math.random(0, 100000)) + TriggerServerEvent(("ND:%s_cb"):format(name), key, ...) + + local promise = not cb and promise.new() + + events[key] = function(response, ...) + response = { response, ... } + events[key] = nil + + if promise then + return promise:resolve(response) + end + + if cb then + cb(table.unpack(response)) + end + end + + if promise then + return table.unpack(Citizen.Await(promise)) + end +end + +setmetatable(NDCore.callback, { + __call = triggerCallback +}) + +function NDCore.callback.await(name, ...) + return triggerCallback(nil, name, false, ...) +end + +function NDCore.callback.register(name, callback) + RegisterNetEvent(("ND:%s_cb"):format(name), function(key, ...) + TriggerServerEvent("ND:callbacks", key, callback(...)) + end) +end diff --git a/client/main.lua b/client/main.lua index 1cef364..56d2691 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,36 +1,54 @@ -CreateThread(function() - SetDiscordAppId(Config.discordAppId) - SetDiscordRichPresenceAsset(Config.discordAsset) - SetDiscordRichPresenceAssetSmall(Config.discordAssetSmall) - SetDiscordRichPresenceAction(0, Config.discordActionText, Config.discordActionLink) - SetDiscordRichPresenceAction(1, Config.discordActionText2, Config.discordActionLink2) - local presenceText = ("Playing: %s"):format(Config.serverName) - while true do - if NDCore.player then - local presence = ("Playing: %s as % %"):format(Config.serverName, NDCore.player.firstname, NDCore.player.lastname) - local presenceTextSmall = ("Playing as: %s %s"):format(NDCore.player.firstname, NDCore.player.lastname) - SetRichPresence(presence) - SetDiscordRichPresenceAssetText(presenceText) - SetDiscordRichPresenceAssetSmallText(presenceTextSmall) - end - Wait(60000) - end -end) +-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 -CreateThread(function() - AddTextEntry("FE_THDR_GTAO", Config.serverName) - local sleep = 500 - while true do - Wait(sleep) - if NDCore.player and IsPauseMenuActive() then - sleep = 0 - BeginScaleformMovieMethodOnFrontendHeader("SET_HEADING_DETAILS") - ScaleformMovieMethodAddParamPlayerNameString(("%s %s"):format(NDCore.player.firstname, NDCore.player.lastname)) - PushScaleformMovieFunctionParameterString(("Cash: $%d"):format(NDCore.player.cash)) - PushScaleformMovieFunctionParameterString(("Bank: $%d"):format(NDCore.player.bank)) - EndScaleformMovieMethod() - elseif sleep == 0 then - sleep = 500 +NDCore = {} +NDCore.SelectedCharacter = nil +NDCore.Characters = {} +NDCore.Functions = {} +NDCore.Config = config + +-- discord rich precense will show on a users profile. +if config.enableRichPresence then + Citizen.CreateThread(function() + while true do + if NDCore.SelectedCharacter then + SetDiscordAppId(config.appId) + SetRichPresence(" Playing : " .. config.serverName .. " as " .. NDCore.SelectedCharacter.firstName .. " " .. NDCore.SelectedCharacter.lastName) + SetDiscordRichPresenceAsset(config.largeLogo) + SetDiscordRichPresenceAssetText("Playing: " .. config.serverName) + SetDiscordRichPresenceAssetSmall(config.smallLogo) + SetDiscordRichPresenceAssetSmallText("Playing as: " .. NDCore.SelectedCharacter.firstName .. " " .. NDCore.SelectedCharacter.lastName) + SetDiscordRichPresenceAction(0, config.firstButtonName, config.firstButtonLink) + SetDiscordRichPresenceAction(1, config.secondButtonName, config.secondButtonLink) + end + Citizen.Wait(config.updateIntervall * 1000) end + end) +end + +-- show server name, first name, last name, and the amount of money the character has in the pause menu. +if config.customPauseMenu then + Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + if NDCore.SelectedCharacter then + if IsPauseMenuActive() then + BeginScaleformMovieMethodOnFrontendHeader("SET_HEADING_DETAILS") + AddTextEntry("FE_THDR_GTAO", config.serverName) + ScaleformMovieMethodAddParamPlayerNameString(NDCore.SelectedCharacter.firstName .. " " .. NDCore.SelectedCharacter.lastName) + PushScaleformMovieFunctionParameterString("Cash: $" .. tostring(NDCore.SelectedCharacter.cash)) + PushScaleformMovieFunctionParameterString("Bank: $" .. tostring(NDCore.SelectedCharacter.bank)) + EndScaleformMovieMethod() + end + end + end + end) +end + +-- Enables pvp if it's selected in the config. +AddEventHandler("playerSpawned", function() + if config.enablePVP then + SetCanAttackFriendly(PlayerPedId(), true, false) + NetworkSetFriendlyFireOption(true) end -end) + print("^0This framework is created by ^5Andyyy#7666 ^0for support you can join the ^5discord: ^0https://discord.gg/Z9Mxu72zZ6") +end) \ No newline at end of file diff --git a/config_client.lua b/config_client.lua new file mode 100644 index 0000000..a6e5390 --- /dev/null +++ b/config_client.lua @@ -0,0 +1,74 @@ +-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 + +config = { + serverName = "Andy's Development", + characterLimit = 15, -- How many characters can a player create. + customPauseMenu = true, -- A custom pause menu will display your money, characters name and the server name in the pause menu. + enablePVP = true, -- pvp allows doing damage to other players. + + -- Money related + startingCash = 2500, -- default cash the character will start with if the character creator doesn't specify it. + startingBank = 8000,-- default money in the bank account the character will start with if the character creator doesn't specify it. + + -- If you'd like to whitelist certain roles on discord then set this to true and add role ids. + enableDiscordWhitelist = false, + notWhitelistedMessage = "You're not allowlisted in this server please join our discord to apply for a allowlist: https://discord.gg/Z9Mxu72zZ6", + whitelistRoles = { + "872921520719142932" + }, + + -- These are admins roles that will give a user permission to admin commands and more. + adminRoles = { + "872921520719142932" + }, + + -- Discord Rich presence + enableRichPresence = true, + updateIntervall = 60, -- how many seconds of delay until it updates status. + appId = 858146067018416128, + largeLogo = "andyyy", + smallLogo = "andyyy", + firstButtonName = "DISCORD", + firstButtonLink = "https://discord.gg/Z9Mxu72zZ6", + secondButtonName = "TEBEX", + secondButtonLink = "https://andyyy.tebex.io/category/fivem-scripts", + + + -- Groups can be gangs, jobs, subdivisions, etc. + groups = { + ["Ballas"] = { + "Member", -- rank 1 + "Boss" -- rank 2 + }, + ["SWAT"] = { + "Member", -- rank 1 + "Sniper", -- rank 2 + "Team lead", -- rank 3 + "Commander" -- rank 4 + }, + ["SAHP"] = { + "Trooper", + "Senior Trooper", + "Corporal", + "Sergeant", + "Lieutenant", + "Cheif" + }, + ["LSPD"] = { + "Officer", + "Senior officer", + "Corporal", + "Sergeant", + "Lieutenant", + "Cheif" + }, + ["BCSO"] = { + "Deputy", + "Senior Deputy", + "Corporal", + "Sergeant", + "Lieutenant", + "Cheif" + } + }, +} \ No newline at end of file diff --git a/config_server.lua b/config_server.lua new file mode 100644 index 0000000..7847de3 --- /dev/null +++ b/config_server.lua @@ -0,0 +1,6 @@ +-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 + +server_config = { + discordServerToken = "OTAwODg1MDMxMDQ2Nzc0ODQ1.GU1TSC.Ed8D2_wcU-UQ1vZhe1khahgV8J3hLahOp4N1D8", -- discord bot token for permissions + guildId = "872496972454592523", -- discord guild id +} diff --git a/fxmanifest.lua b/fxmanifest.lua index e084ad9..0069149 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,25 +1,39 @@ -- For support join my discord: https://discord.gg/Z9Mxu72zZ6 -author "Andyyy#7666" +author "Andyyy#7666, N1K0#0001" description "ND Framework Core" -version "2.0.0" +version "1.0.3" fx_version "cerulean" game "gta5" lua54 "yes" shared_scripts { - "init.lua" + "config_client.lua", + "shared/main.lua" } client_scripts { + "client/main.lua", "client/functions.lua", "client/events.lua", - "client/main.lua" + "shared/import.lua" } server_scripts { "@oxmysql/lib/MySQL.lua", + "config_server.lua", "server/main.lua", - "server/player.lua" + "server/functions.lua", + "server/events.lua", + "server/commands.lua", + "shared/import.lua" +} + +exports { + "GetCoreObject" +} + +server_exports { + "GetCoreObject" } dependency "oxmysql" diff --git a/init.lua b/init.lua deleted file mode 100644 index b4ded23..0000000 --- a/init.lua +++ /dev/null @@ -1,16 +0,0 @@ -Config = { - serverName = GetConvar("core:serverName", "Unconfigured ND-Core Server"), - discordInvite = GetConvar("core:discordInvite", "https://discord.gg/Z9Mxu72zZ6"), - discordAppId = GetConvar("core:discordAppId", "858146067018416128"), - discordAsset = GetConvar("core:discordAsset", "andyyy"), - discordAssetSmall = GetConvar("core:discordAssetSmall", "andyyy"), - discordActionText = GetConvar("core:discordActionText", "DISCORD"), - discordActionLink = GetConvar("discordActionLink", "https://discord.gg/Z9Mxu72zZ6"), - discordActionText2 = GetConvar("core:discordActionText2", "STORE"), - discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"), - -} - -NDCore = { - config = Config -} diff --git a/server/commands.lua b/server/commands.lua new file mode 100644 index 0000000..e98e0d5 --- /dev/null +++ b/server/commands.lua @@ -0,0 +1,117 @@ +NDCore.Functions.AddCommand("setmoney", "Admin command, manage player money.", function(source, args, rawCommand) + if not NDCore.Functions.IsPlayerAdmin(source) then return end + + local target = tonumber(args[1]) + local action = args[2] + local moneyType = args[3]:lower() + local amount = tonumber(args[4]) + + if not target or GetPlayerPing(target) == 0 then return end + if action ~= "remove" and action ~= "add" and action ~= "set" then return end + if moneyType ~= "bank" and moneyType ~= "cash" then return end + + if action == "remove" then + if not amount or amount < 1 then return end + NDCore.Functions.DeductMoney(amount, target, moneyType, "Server staff action.") + elseif action == "add" then + if not amount or amount < 1 then return end + NDCore.Functions.AddMoney(amount, target, moneyType, "Server staff action.") + elseif action == "set" then + local character = NDCore.Functions.GetPlayer(target) + NDCore.Functions.SetPlayerData(character.id, moneyType, amount, "Server staff action.") + end +end, true, { + { name="player", help="Player server id" }, + { name="action", help="remove/add/set" }, + { name="type", help="bank/cash" }, + { name="amount" } +}) + +NDCore.Functions.AddCommand("setjob", "Admin command, set player job.", function(source, args, rawCommand) + if not NDCore.Functions.IsPlayerAdmin(source) then + return { + color = {255, 0, 0}, + args = {"Error", "you don't have access to this command."} + } + end + + local target = tonumber(args[1]) + if not target or GetPlayerPing(target) == 0 then + return { + color = {255, 0, 0}, + args = {"Error", "target player not found."} + } + end + + local job = args[2] + if not job then + return { + color = {255, 0, 0}, + args = {"Error", "job required."} + } + end + + local character = NDCore.Functions.GetPlayer(target) + NDCore.Functions.SetPlayerJob(character.id, job, args[3]) + return { + color = {0, 255, 0}, + args = {"Success", GetPlayerName(target) .. " job set to " .. job .. (args[3] and " rank " .. args[3] or " rank 1.")} + } +end, false, { + { name="player", help="Player server id" }, + { name="job name" }, + { name="rank", help="This should be a number, default value is 1." } +}) + +NDCore.Functions.AddCommand("setgroup", "Admin command, set player group.", function(source, args, rawCommand) + if not NDCore.Functions.IsPlayerAdmin(source) then return end + + local target = tonumber(args[1]) + if not target or GetPlayerPing(target) == 0 then + return { + color = {255, 0, 0}, + args = {"Error", "target player not found."} + } + end + + local group = args[3] + if not group then + return { + color = {255, 0, 0}, + args = {"Error", "group required."} + } + end + + local character = NDCore.Functions.GetPlayer(target) + if args[2] == "remove" then + NDCore.Functions.RemovePlayerFromGroup(character.id, group) + return { + color = {0, 255, 0}, + args = {"Success", GetPlayerName(target) .. " removed from " .. group} + } + elseif args[2] == "add" then + local rank = args[4] + NDCore.Functions.SetPlayerToGroup(character.id, group, rank) + return { + color = {0, 255, 0}, + args = {"Success", GetPlayerName(target) .. " added to " .. group .. (rank and " rank " .. rank or " rank 1.")} + } + end +end, false, { + { name="player", help="Player server id" }, + { name="action", help="remove/add"}, + { name="group", help="Group name, make sure it's correct or it won't work."}, + { name="rank", help="This should be a number, default value is 1 (not required if removing)." } +}) + +NDCore.Functions.AddCommand("pay", "give cash to a nearby player", function(source, args, rawCommand) + local amount = args[1] + if not amount or amount == 0 then return end + NDCore.Functions.GiveCashToNearbyPlayer(source, amount) + return { + color = {0, 255, 0}, + args = {"Success", amount .. " paid."} + } +end, true, { + { name="Amount" }, +}) \ No newline at end of file diff --git a/server/events.lua b/server/events.lua new file mode 100644 index 0000000..e8f6251 --- /dev/null +++ b/server/events.lua @@ -0,0 +1,117 @@ +-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 + +-- Check if discord is connected, and if whitelist is enabled then it will only allow you to join if you have the roles. +AddEventHandler("playerConnecting", function(name, setKickReason, deferrals) + local player = source + local discordIdentifier = NDCore.Functions.GetPlayerIdentifierFromType("discord", player) + + deferrals.defer() + Wait(0) + deferrals.update("Connecting to discord.") + Wait(0) + + if not discordIdentifier then + deferrals.done("Your discord isn't connected to FiveM, make sure discord is open and restart FiveM.") + else + if config.enableDiscordWhitelist then + local discordUserId = discordIdentifier:gsub("discord:", "") + local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId) + for _, whitelistRole in pairs(config.whitelistRoles) do + if whitelistRole == 0 or whitelistRole == "0" or (discordInfo and discordInfo.roles[whitelistRole]) then + deferrals.done() + break + end + end + deferrals.done(config.notWhitelistedMessage) + else + deferrals.done() + end + end +end) + +-- Getting all the characters the player has and returning them to the client. +RegisterNetEvent("ND:GetCharacters", function() + local player = source + TriggerClientEvent("ND:returnCharacters", player, NDCore.Functions.GetPlayerCharacters(player)) +end) + +-- Creating a new character. +RegisterNetEvent("ND:newCharacter", function(newCharacter) + local player = source + NDCore.Functions.CreateCharacter(player, newCharacter.firstName, newCharacter.lastName, newCharacter.dob, newCharacter.gender, newCharacter.cash, newCharacter.bank) +end) + +-- Update the character info when edited. +RegisterNetEvent("ND:editCharacter", function(newCharacter) + local player = source + local characters = NDCore.Functions.GetPlayerCharacters(player) + if not characters[newCharacter.id] then return end + NDCore.Functions.UpdateCharacterData(newCharacter.id, newCharacter.firstName, newCharacter.lastName, newCharacter.dob, newCharacter.gender) +end) + +-- Delete character from database. +RegisterNetEvent("ND:deleteCharacter", function(characterId) + local player = source + local characters = NDCore.Functions.GetPlayerCharacters(player) + if not characters[characterId] then return end + NDCore.Functions.DeleteCharacter(characterId) +end) + +-- add a player to the table. +RegisterNetEvent("ND:setCharacterOnline", function(id) + local player = source + local characters = NDCore.Functions.GetPlayerCharacters(player) + if not characters[id] then return end + NDCore.Functions.SetActiveCharacter(player, id) +end) + +-- Update the characters clothes. +RegisterNetEvent("ND:updateClothes", function(clothing) + local player = source + local character = NDCore.Players[player] + NDCore.Functions.SetPlayerData(character.id, "clothing", clothing) +end) + +-- Disconnecting a player +RegisterNetEvent("ND:exitGame", function() + local player = source + DropPlayer(player, "Disconnected.") +end) + +-- Remove player from NDCore.Players table when they leave. +AddEventHandler("playerDropped", function() + local player = source + local character = NDCore.Players[player] + if character then + local ped = GetPlayerPed(player) + local lastLocation = GetEntityCoords(ped) + NDCore.Functions.UpdateLastLocation(character.id, {x = lastLocation.x, y = lastLocation.y, z = lastLocation.z}) + end + TriggerEvent("ND:characterUnloaded", player, character) + character = nil +end) + +-- Get player discord info on join. +AddEventHandler("playerJoining", function() + local src = source + + local discordUserId = NDCore.Functions.GetPlayerIdentifierFromType("discord", src):gsub("discord:", "") + local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId) + + NDCore.PlayersDiscordInfo[src] = discordInfo +end) + +AddEventHandler("onResourceStart", function(resourceName) + if (GetCurrentResourceName() ~= resourceName) then + return + end + Wait(1000) + + if not next(NDCore.PlayersDiscordInfo) then + for _, playerId in ipairs(GetPlayers()) do + local discordUserId = NDCore.Functions.GetPlayerIdentifierFromType("discord", playerId):gsub("discord:", "") + local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId) + NDCore.PlayersDiscordInfo[tonumber(playerId)] = discordInfo + end + end +end) diff --git a/server/functions.lua b/server/functions.lua new file mode 100644 index 0000000..e42e7c9 --- /dev/null +++ b/server/functions.lua @@ -0,0 +1,766 @@ +-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 + +-- Get an active players character data. +function NDCore.Functions.GetPlayer(player) + return NDCore.Players[player] +end + +-- Get all active players character data. +function NDCore.Functions.GetPlayers(getBy, value) + if not getBy or not value then + return NDCore.Players + end + local players = {} + + if getBy == "groups" then + for player, playerInfo in pairs(NDCore.Players) do + if playerInfo.data.groups then + local valueGroup = value:lower() + for group, _ in pairs(playerInfo.data.groups) do + if group and group:lower() == valueGroup then + players[player] = playerInfo + end + end + end + end + else + for player, playerInfo in pairs(NDCore.Players) do + if playerInfo[getBy] == value then + players[player] = playerInfo + end + end + end + + return players +end + +local discordErrors = { + [400] = "improper http request", + [401] = "Discord bot token might be missing or incorrect", + [404] = "user might not be in server.", + [429] = "Discord bot rate limited." +} +-- Used to retrive the players discord server nickname, discord name and tag, and the roles. +function NDCore.Functions.GetUserDiscordInfo(discordUserId) + local data + local timeout = 0 + PerformHttpRequest("https://discordapp.com/api/guilds/" .. server_config.guildId .. "/members/" .. discordUserId, function(errorCode, resultData, resultHeaders) + if errorCode ~= 200 then + print("Error: " .. errorCode .. " " .. discordErrors[errorCode]) + end + local result = json.decode(resultData) + local roles = {} + local nickname = "" + local tag = "" + if result and result.roles then + for _, roleId in pairs(result.roles) do + roles[roleId] = roleId + end + if result.nick then + nickname = result.nick + end + if result.user and result.user.username and result.user.discriminator then + tag = tostring(result.user.username) .. "#" .. tostring(result.user.discriminator) + end + data = { + nickname = nickname, + discordTag = tag, + roles = roles + } + return + end + data = { + nickname = nickname, + discordTag = tag, + roles = roles + } + end, "GET", "", {["Content-Type"] = "application/json", ["Authorization"] = "Bot " .. server_config.discordServerToken}) + while not data do + Wait(1000) + timeout = timeout + 1 + if timeout > 5 then + break + end + end + return data +end + +-- Get player any identifier, available types: steam, license, xbl, ip, discord, live. +function NDCore.Functions.GetPlayerIdentifierFromType(type, player) + local identifierCount = GetNumPlayerIdentifiers(player) + for count = 0, identifierCount do + local identifier = GetPlayerIdentifier(player, count) + if identifier and string.find(identifier, type) then + return identifier + end + end + return nil +end + +-- This will return the server id and ND player data of a nearby player. +function NDCore.Functions.GetNearbyPedToPlayer(player) + local pedCoords = GetEntityCoords(GetPlayerPed(player)) + for targetId, targetInfo in pairs(NDCore.Players) do + local targetCoords = GetEntityCoords(GetPlayerPed(targetId)) + if #(pedCoords - targetCoords) < 2.0 and targetId ~= player then + return targetId, targetInfo + end + end +end + +-- update the players money on the client kinda like a refresh. +function NDCore.Functions.UpdateMoney(player) + local player = tonumber(player) + local result = MySQL.query.await("SELECT cash, bank FROM characters WHERE character_id = ? LIMIT 1", {NDCore.Players[player].id}) + if result then + local cash = result[1].cash + local bank = result[1].bank + NDCore.Players[player].cash = cash + NDCore.Players[player].bank = bank + TriggerClientEvent("ND:updateMoney", player, cash, bank) + end +end + +-- Transfer money from one players bank account to another. +function NDCore.Functions.TransferBank(amount, player, target, descriptionSender, descriptionReceiver) + local amount = tonumber(amount) + local player = tonumber(player) + local target = tonumber(target) + if player == target then + TriggerClientEvent("chat:addMessage", player, { + color = {255, 0, 0}, + args = {"Error", "You can't send money to yourself."} + }) + return false + elseif GetPlayerPing(target) == 0 then + TriggerClientEvent("chat:addMessage", player, { + color = {255, 0, 0}, + args = {"Error", "That player does not exist."} + }) + return false + elseif amount <= 0 then + TriggerClientEvent("chat:addMessage", player, { + color = {255, 0, 0}, + args = {"Error", "You can't send that amount."} + }) + return false + elseif NDCore.Players[player].bank < amount then + TriggerClientEvent("chat:addMessage", player, { + color = {255, 0, 0}, + args = {"Error", "You don't have enough money."} + }) + return false + else + MySQL.query.await("UPDATE characters SET bank = bank - ? WHERE character_id = ?", {amount, NDCore.Players[player].id}) + NDCore.Functions.UpdateMoney(player) + TriggerEvent("ND:moneyChange", player, "bank", amount, "remove", descriptionSender or "Transfer") + TriggerClientEvent("chat:addMessage", player, { + color = {0, 255, 0}, + args = {"Success", "You paid " .. NDCore.Players[target].firstName .. " " .. NDCore.Players[target].lastName .. " $" .. amount .. "."} + }) + + MySQL.query.await("UPDATE characters SET bank = bank + ? WHERE character_id = ?", {amount, NDCore.Players[target].id}) + NDCore.Functions.UpdateMoney(target) + TriggerEvent("ND:moneyChange", target, "bank", amount, "add", descriptionReceiver or "Transfer") + TriggerClientEvent("chat:addMessage", target, { + color = {0, 255, 0}, + args = {"Success", NDCore.Players[player].firstName .. " " .. NDCore.Players[player].lastName .. " sent you $" .. amount .. "."} + }) + return true + end +end + +-- Give cash from one players wallet to another. +function NDCore.Functions.GiveCash(amount, player, target) + local amount = tonumber(amount) + local player = tonumber(player) + local target = tonumber(target) + if player == target then + TriggerClientEvent("chat:addMessage", player, { + color = {255, 0, 0}, + args = {"Error", "You can't give money to yourself."} + }) + return false + elseif GetPlayerPing(target) == 0 then + TriggerClientEvent("chat:addMessage", player, { + color = {255, 0, 0}, + args = {"Error", "That player does not exist."} + }) + return false + elseif amount <= 0 then + TriggerClientEvent("chat:addMessage", player, { + color = {255, 0, 0}, + args = {"Error", "You can't give that amount."} + }) + return false + elseif NDCore.Players[player].cash < amount then + TriggerClientEvent("chat:addMessage", player, { + color = {255, 0, 0}, + args = {"Error", "You don't have enough money."} + }) + return false + else + MySQL.query.await("UPDATE characters SET cash = cash - ? WHERE character_id = ?", {amount, NDCore.Players[player].id}) + NDCore.Functions.UpdateMoney(player) + TriggerEvent("ND:moneyChange", player, "cash", amount, "remove") + TriggerClientEvent("chat:addMessage", player, { + color = {0, 255, 0}, + args = {"Success", "You gave " .. NDCore.Players[target].firstName .. " " .. NDCore.Players[target].lastName .. " $" .. amount .. "."} + }) + + MySQL.query.await("UPDATE characters SET cash = cash + ? WHERE character_id = ?", {amount, NDCore.Players[target].id}) + NDCore.Functions.UpdateMoney(target) + TriggerEvent("ND:moneyChange", target, "cash", amount, "add") + TriggerClientEvent("chat:addMessage", target, { + color = {0, 255, 0}, + args = {"Success", " Received $" .. amount .. "."} + }) + return true + end +end + +-- Give money from a players wallet to a nearby player. +function NDCore.Functions.GiveCashToNearbyPlayer(player, amount) + local targetId = NDCore.Functions.GetNearbyPedToPlayer(player) + if targetId then + NDCore.Functions.GiveCash(amount, player, targetId) + return true + end + TriggerClientEvent("chat:addMessage", player, { + color = {255, 0, 0}, + args = {"Error", "No players nearby."} + }) + return false +end + +-- withdraws money from a players bank account to their wallet/cash. +function NDCore.Functions.WithdrawMoney(amount, player) + local amount = tonumber(amount) + local player = tonumber(player) + if amount <= 0 then return false end + if NDCore.Players[player].bank < amount then return false end + MySQL.query.await("UPDATE characters SET bank = bank - ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id}) + MySQL.query.await("UPDATE characters SET cash = cash + ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id}) + NDCore.Functions.UpdateMoney(player) + TriggerEvent("ND:moneyChange", player, "bank", amount, "remove", "Withdraw") + TriggerEvent("ND:moneyChange", player, "cash", amount, "add", "Withdraw") + return true +end + +-- deposits money from a players wallet/cash to their bank account. +function NDCore.Functions.DepositMoney(amount, player) + local amount = tonumber(amount) + local player = tonumber(player) + if amount <= 0 then return false end + if NDCore.Players[player].cash < amount then return false end + MySQL.query.await("UPDATE characters SET cash = cash - ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id}) + MySQL.query.await("UPDATE characters SET bank = bank + ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id}) + NDCore.Functions.UpdateMoney(player) + TriggerEvent("ND:moneyChange", player, "cash", amount, "remove", "Deposit") + TriggerEvent("ND:moneyChange", player, "bank", amount, "add", "Deposit") + return true +end + +-- Deducts money from the player, "bank" or "cash" needs to be specified. +function NDCore.Functions.DeductMoney(amount, player, from, description) + local amount = tonumber(amount) + local player = tonumber(player) + if from == "bank" then + MySQL.query.await("UPDATE characters SET bank = bank - ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id}) + elseif from == "cash" then + MySQL.query.await("UPDATE characters SET cash = cash - ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id}) + end + NDCore.Functions.UpdateMoney(player) + TriggerEvent("ND:moneyChange", player, from, amount, "remove", description) +end + +-- Adds money from the player, "bank" or "cash" needs to be specified. +function NDCore.Functions.AddMoney(amount, player, to, description) + local amount = tonumber(amount) + local player = tonumber(player) + if to == "bank" then + MySQL.query.await("UPDATE characters SET bank = bank + ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id}) + elseif to == "cash" then + MySQL.query.await("UPDATE characters SET cash = cash + ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id}) + end + NDCore.Functions.UpdateMoney(player) + TriggerEvent("ND:moneyChange", player, to, amount, "add", description) +end + +-- Adds the players character to the NDCore.Players table, this table consists of every players selected character. +function NDCore.Functions.SetActiveCharacter(player, characterId) + if NDCore.Players[player] then + TriggerEvent("ND:characterUnloaded", player, NDCore.Players[player]) + end + local result = MySQL.query.await("SELECT * FROM characters WHERE character_id = ? LIMIT 1", {characterId}) + if result then + local i = result[1] + NDCore.Players[player] = { + source = player, + id = characterId, + firstName = i.first_name, + lastName = i.last_name, + dob = i.dob, + gender = i.gender, + cash = i.cash, + bank = i.bank, + phoneNumber = i.phone_number, + lastLocation = json.decode(i.last_location), + inventory = json.decode(i.inventory), + discordInfo = NDCore.PlayersDiscordInfo[player], + data = json.decode(i.data), + job = i.job + } + end + NDCore.Functions.RefreshCommands(player) + TriggerEvent("ND:characterLoaded", NDCore.Players[player]) + TriggerClientEvent("ND:setCharacter", player, NDCore.Players[player]) +end + +-- This returns all the characters the player has. +function NDCore.Functions.GetPlayerCharacters(player) + local characters = {} + local result = MySQL.query.await("SELECT * FROM characters WHERE license = ?", {NDCore.Functions.GetPlayerIdentifierFromType("license", player)}) + for i = 1, #result do + local temp = result[i] + characters[temp.character_id] = { + id = temp.character_id, + firstName = temp.first_name, + lastName = temp.last_name, + dob = temp.dob, + gender = temp.gender, + cash = temp.cash, + bank = temp.bank, + phoneNumber = temp.phone_number, + lastLocation = json.decode(temp.last_location), + inventory = json.decode(temp.inventory), + discordInfo = NDCore.PlayersDiscordInfo[player], + data = json.decode(temp.data), + job = temp.job + } + end + return characters +end + +-- Creates a new character for the player and returns all their characters to the client. +function NDCore.Functions.CreateCharacter(player, firstName, lastName, dob, gender, cb) + local characterId = false + local license = NDCore.Functions.GetPlayerIdentifierFromType("license", player) + local result = MySQL.query.await("SELECT character_id FROM characters WHERE license = ?", {license}) + if result and config.characterLimit > #result then + characterId = MySQL.insert.await("INSERT INTO characters (license, first_name, last_name, dob, gender, cash, bank, data) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", {license, firstName, lastName, dob, gender, config.startingCash, config.startingBank, json.encode({groups={}})}) + if cb then cb(characterId) end + TriggerClientEvent("ND:returnCharacters", player, NDCore.Functions.GetPlayerCharacters(player)) + end + return characterId +end + +-- Update/edit a character info by character id. +function NDCore.Functions.UpdateCharacter(characterId, firstName, lastName, dob, gender) + local result = MySQL.query.await("UPDATE characters SET first_name = ?, last_name = ?, dob = ?, gender = ? WHERE character_id = ? LIMIT 1", {firstName, lastName, dob, gender, characterId}) + return result +end + +-- Delete a character by character id. +function NDCore.Functions.DeleteCharacter(characterId) + local result = MySQL.query.await("DELETE FROM characters WHERE character_id = ? LIMIT 1", {characterId}) + return result +end + +-- Updates the player's data +function NDCore.Functions.SetPlayerData(characterId, key, value, description) + if not key then return end + + local player = nil + for id, character in pairs(NDCore.Players) do + if character.id == characterId then + player = id + break + end + end + + if key == "cash" then + if player then + NDCore.Players[player][key] = value + TriggerEvent("ND:moneyChange", player, "cash", tonumber(value), "set", description) + end + MySQL.query.await("UPDATE characters SET cash = ? WHERE character_id = ?", {tonumber(value), characterId}) + elseif key == "bank" then + if player then + NDCore.Players[player][key] = value + TriggerEvent("ND:moneyChange", player, "bank", tonumber(value), "set", description) + end + MySQL.query.await("UPDATE characters SET bank = ? WHERE character_id = ?", {tonumber(value), characterId}) + elseif key == "job" then + if player then + NDCore.Players[player].job = value + end + MySQL.query.await("UPDATE characters SET job = ? WHERE character_id = ?", {value, characterId}) + else + if player then + NDCore.Players[player].data[key] = value + MySQL.query.await("UPDATE characters SET `data` = ? WHERE character_id = ?", {json.encode(NDCore.Players[player].data), characterId}) + else + local result = MySQL.query.await("SELECT `data` FROM characters WHERE character_id = ?", {characterId}) + if not result or not result[1] then return end + + local data = json.decode(result[1].data) + data[key] = value + MySQL.query.await("UPDATE characters SET `data` = ? WHERE character_id = ?", {json.encode(data), characterId}) + end + end + + if not player then return end + TriggerClientEvent("ND:updateCharacter", player, NDCore.Players[player]) +end + +-- Get a character by the character id. +function NDCore.Functions.GetPlayerByCharacterId(id) + for _, character in pairs(NDCore.Players) do + if character.id == id then + return character + end + end +end + +-- Generate a random string with letters and numbers. +function randomString(length) + local number = {} + for i = 1, length do + number[i] = math.random(0, 1) == 1 and string.char(math.random(65, 90)) or math.random(0, 9) + end + return table.concat(number) +end + +-- Give a player a license. +function NDCore.Functions.CreatePlayerLicense(characterId, licenseType, expire) + local expireIn = tonumber(expire) + if not expireIn then + expireIn = 2592000 + end + + local time = os.time() + local license = { + type = licenseType, + status = "valid", + issued = time, + expires = time+expireIn, + identifier = randomString(16) + } + + local character = NDCore.Functions.GetPlayerByCharacterId(characterId) + if character then + local data = character.data + if not data.licences then + data.licences = {} + end + character.data.licences[#character.data.licences+1] = license + NDCore.Functions.SetPlayerData(character.id, "licences", character.data.licences) + return true + end + + local result = MySQL.query.await("SELECT data FROM characters WHERE character_id = ?", {characterId}) + if result and result[1] then + local data = result[1].data + if not data.licences then + data.licences = {} + end + data.licences[#data.licences+1] = license + NDCore.Functions.SetPlayerData(character.id, "licences", data.licences) + return true + end +end + +-- find a players license by it's identifier. +function NDCore.Functions.FindLicenseByIdentifier(licences, identifier) + for key, license in pairs(licences) do + if license.identifier == identifier then + return license + end + end + return {} +end + +-- Edit a license by the license identifier. +function NDCore.Functions.EditPlayerLicense(characterId, identifier, newData) + local licences = {} + local character = NDCore.Functions.GetPlayerByCharacterId(characterId) + if character then + licences = character.data.licences + else + local result = MySQL.query.await("SELECT data FROM characters WHERE character_id = ?", {characterId}) + if result and result[1] then + local data = result[1].data + if not data.licences then + data.licences = {} + end + licences = data.licences + end + end + + local license = NDCore.Functions.FindLicenseByIdentifier(licences, identifier) + for k, v in pairs(newData) do + license[k] = v + end + NDCore.Functions.SetPlayerData(characterId, "licences", licences) + return licences +end + +-- Set the players job and job rank. +function NDCore.Functions.SetPlayerJob(characterId, job, rank) + if not job then return end + + local jobRank = tonumber(rank) + if not jobRank then + jobRank = 1 + end + + local result = MySQL.query.await("SELECT job FROM characters WHERE character_id = ?", {characterId}) + if result and result[1] then + local character = NDCore.Functions.GetPlayerByCharacterId(characterId) + if character then + local oldRank = 1 + if character.data.groups and character.data.groups[character.job] then + oldRank = character.data.groups[character.job].rank + end + TriggerEvent("ND:jobChanged", character.source, {name = job, rank = jobRank}, {name = character.job, rank = oldRank}) + TriggerClientEvent("ND:jobChanged", character.source, {name = job, rank = jobRank}, {name = character.job, rank = oldRank}) + end + NDCore.Functions.RemovePlayerFromGroup(characterId, result[1].job) + end + + NDCore.Functions.SetPlayerData(characterId, "job", job) + NDCore.Functions.SetPlayerToGroup(characterId, job, jobRank) +end + +-- Set a player to a group. +function NDCore.Functions.SetPlayerToGroup(characterId, group, rank) + local groupRank = tonumber(rank) + if not groupRank then + groupRank = 1 + end + + local group = group:lower() + for groupName, groupRanks in pairs(config.groups) do + if groupName:lower() == group then + group = groupName + break + end + end + + local character = NDCore.Functions.GetPlayerByCharacterId(characterId) + if character then + local data = character.data + if not data.groups then + data.groups = {} + end + local rankName = tostring(groupRank) + if config.groups[group] and config.groups[group][groupRank] then + rankName = config.groups[group][groupRank] + end + data.groups[group] = { + rank = groupRank, + rankName = rankName + } + NDCore.Functions.SetPlayerData(characterId, "groups", data.groups) + return true + end + + local result = MySQL.query.await("SELECT data FROM characters WHERE character_id = ?", {characterId}) + if not result or not result[1] then return end + local data = json.decode(result[1].data) + if not data then + data = {} + end + if not data.groups then + data.groups = {} + end + local rankName = tostring(groupRank) + if config.groups[group] and config.groups[group][groupRank] then + rankName = config.groups[group][groupRank] + end + data.groups[group] = { + rank = groupRank, + rankName = rankName + } + NDCore.Functions.SetPlayerData(characterId, "groups", data.groups) + return true +end + +-- Remove a player from a group. +function NDCore.Functions.RemovePlayerFromGroup(characterId, group) + if not group then return end + local group = group:lower() + for groupName, groupRanks in pairs(config.groups) do + if groupName:lower() == group then + group = groupName + break + end + end + + local character = NDCore.Functions.GetPlayerByCharacterId(characterId) + if character then + local data = character.data + if not data.groups then + data.groups = {} + end + data.groups[group] = nil + NDCore.Functions.SetPlayerData(characterId, "groups", data.groups) + return true + end + + local result = MySQL.query.await("SELECT data FROM characters WHERE character_id = ?", {characterId}) + if result and result[1] then + local data = result[1].data + if not data.groups then + data.groups = {} + end + data.groups[group] = nil + NDCore.Functions.SetPlayerData(characterId, "groups", data.groups) + return true + end +end + +-- Update the characters last location into the database. +function NDCore.Functions.UpdateLastLocation(characterId, location) + local result = MySQL.query.await("UPDATE characters SET last_location = ? WHERE character_id = ? LIMIT 1", {json.encode(location), characterId}) + return result +end + +function NDCore.Functions.AddCommand(name, help, callback, argsrequired, arguments) + local commandName = name:lower() + if NDCore.Commands[commandName] then print("/" .. commandName .. " has already been registered.") return end + + local arguments = arguments or {} + + RegisterCommand(commandName, function(source, args, rawCommand) + if argsrequired and #args < #arguments then + return TriggerClientEvent("chat:addMessage", source, { + color = {255, 0, 0}, + multiline = true, + args = {"Error", "all arguments required."} + }) + end + local message = callback(source, args, rawCommand) + if not message then return end + TriggerClientEvent("chat:addMessage", source, message) + end, false) + + NDCore.Commands[commandName] = { + name = commandName, + help = help, + callback = callback, + argsrequired = argsrequired, + arguments = arguments + } +end + +function NDCore.Functions.RefreshCommands(source) + local suggestions = {} + for command, info in pairs(NDCore.Commands) do + suggestions[#suggestions + 1] = { + name = "/" .. command, + help = info.help, + params = info.arguments + } + end + TriggerClientEvent("chat:addSuggestions", source, suggestions) +end + +function NDCore.Functions.IsPlayerAdmin(src) + local discordInfo = NDCore.PlayersDiscordInfo[src] + if not discordInfo or not discordInfo.roles then return end + for _, adminRole in pairs(config.adminRoles) do + for _, role in pairs(discordInfo.roles) do + if role == adminRole then return true end + end + end +end + +function NDCore.Functions.VersionChecker(expectedResourceName, resourceName, downloadLink, rawGithubLink) + if expectedResourceName ~= resourceName then + print(("^4%s ^1WARNING^0"):format(expectedResourceName)) + print(("Change the resource name to ^4%s ^0or else it won't work properly!"):format(expectedResourceName)) + StopResource(resourceName) + return + end + PerformHttpRequest(rawGithubLink, function(errorCode, resultData, resultHeaders) + local i, j = tostring(resultData):find("version") + if not i or not j then return end + local resultData = tostring(resultData):sub(i, j + 12) + local resultData = resultData:gsub("version \"", "") + local i, j = resultData:find("\"") + local resultData = resultData:sub(1, i - 1) + local githubVersion = resultData:gsub("%.", "") + local fileVersion = GetResourceMetadata(expectedResourceName, "version", 0):gsub("%.", "") + + if not githubVersion and not fileVersion then + print(("^4%s ^1WARNING^0"):format(expectedResourceName)) + print(("You may not have the latest version of ^4%s^0. A newer, improved version may be present at ^5%s^0"):format(expectedResourceName, downloadLink)) + elseif githubVersion > fileVersion then + local oldVersion = ("%s.%s.%s"):format(fileVersion:sub(1, 1), fileVersion:sub(2, 2), fileVersion:sub(3, 3)) + local newVersion = ("%s.%s.%s"):format(githubVersion:sub(1, 1), githubVersion:sub(2, 2), githubVersion:sub(3, 3)) + print(("^4%s ^1WARNING^0"):format(expectedResourceName)) + print(("^4%s ^0is outdated. Please update it from ^5%s ^0| Current Version: ^1%s ^0| New Version: ^2%s ^0|"):format(expectedResourceName, downloadLink, oldVersion, newVersion)) + elseif githubVersion < fileVersion then + local oldVersion = ("%s.%s.%s"):format(fileVersion:sub(1, 1), fileVersion:sub(2, 2), fileVersion:sub(3, 3)) + local newVersion = ("%s.%s.%s"):format(githubVersion:sub(1, 1), githubVersion:sub(2, 2), githubVersion:sub(3, 3)) + print(("^4%s ^1WARNING^0"):format(expectedResourceName)) + print(("^4%s ^0version number is higher than expected | Current Version: ^3%s ^0| Expected Version: ^2%s ^0|"):format(expectedResourceName, oldVersion, newVersion)) + else + local newVersion = ("%s.%s.%s"):format(githubVersion:sub(1, 1), githubVersion:sub(2, 2), githubVersion:sub(3, 3)) + print(("^4%s ^0is up to date | Current Version: ^2%s ^0|"):format(expectedResourceName, newVersion)) + end + end) +end +NDCore.Functions.VersionChecker("ND_Core", GetCurrentResourceName(), "https://github.com/ND-Framework/ND_Core", "https://raw.githubusercontent.com/ND-Framework/ND_Core/main/fxmanifest.lua") + + +-- Callbacks are licensed under LGPL v3.0 +-- +NDCore.callback = {} +local events = {} + +RegisterNetEvent("ND:callbacks", function(key, ...) + local cb = events[key] + return cb and cb(...) +end) + +function triggerCallback(_, name, playerId, cb, ...) + local key = ("%s:%s:%s"):format(name, math.random(0, 100000), playerId) + TriggerClientEvent(("ND:%s_cb"):format(name), playerId, key, ...) + + local promise = not cb and promise.new() + + events[key] = function(response, ...) + response = { response, ... } + events[key] = nil + + if promise then + return promise:resolve(response) + end + + if cb then + cb(table.unpack(response)) + end + end + + if promise then + return table.unpack(Citizen.Await(promise)) + end +end + +setmetatable(NDCore.callback, { + __call = triggerCallback +}) + +function NDCore.callback.await(name, playerId, ...) + return triggerCallback(nil, name, playerId, false, ...) +end + +function NDCore.callback.register(name, callback) + RegisterNetEvent(("ND:%s_cb"):format(name), function(key, ...) + local src = source + TriggerClientEvent("ND:callbacks", src, key, callback(src, ...)) + end) +end diff --git a/server/main.lua b/server/main.lua index d13951d..f84ab27 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,14 +1,21 @@ -ActivePlayers = {} +-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 -function NDCore.getPlayer(src) - return ActivePlayers[src] +NDCore = {} +NDCore.Players = {} +NDCore.Functions = {} +NDCore.Commands = {} +NDCore.PlayersDiscordInfo = {} +NDCore.Config = config + +function GetCoreObject() + return NDCore end -AddEventHandler("playerDropped", function() - local src = source - local char = ActivePlayers[src] - if not char then return end - char:unload() +isResourceStarted("ox_inventory", function(started) + if not started then return end + SetConvarReplicated("inventory:framework", "nd") end) -SetConvarServerInfo("ND_Core", GetResourceMetadata(GetCurrentResourceName(), "version", 0) or "invalid") +for _, roleid in pairs(config.adminRoles) do + ExecuteCommand("add_principal identifier.discord:" .. roleid .. " group.admin") +end \ No newline at end of file diff --git a/shared/import.lua b/shared/import.lua new file mode 100644 index 0000000..138e3c9 --- /dev/null +++ b/shared/import.lua @@ -0,0 +1 @@ +NDCore = exports["ND_Core"]:GetCoreObject() \ No newline at end of file diff --git a/shared/main.lua b/shared/main.lua new file mode 100644 index 0000000..948dd79 --- /dev/null +++ b/shared/main.lua @@ -0,0 +1,30 @@ +local startedResources = {} +local callbacks = {} + +AddEventHandler("onResourceStart", function(resourceName) + startedResources[resourceName] = true + local callback = callbacks[resourceName] + if not callback then return end + callback(true) +end) + +AddEventHandler("onResourceStop", function(resourceName) + startedResources[resourceName] = nil + local callback = callbacks[resourceName] + if not callback then return end + callback(false) +end) + +function isResourceStarted(resourceName, cb) + local started = GetResourceState(resourceName) == "started" + startedResources[resourceName] = started + + if cb then + callbacks[resourceName] = cb + cb(started) + end + + return started +end + +exports("isResourceStarted", isResourceStarted) \ No newline at end of file