diff --git a/client/events.lua b/client/events.lua index d2a034f..791dc64 100644 --- a/client/events.lua +++ b/client/events.lua @@ -4,21 +4,29 @@ end) -- updates the money on the client. RegisterNetEvent("ND:updateMoney", function(cash, bank) - NDCore.SelectedCharacter.cash = cash - NDCore.SelectedCharacter.bank = bank + NDCore.player.cash = cash + NDCore.player.bank = bank end) -- Sets main character. RegisterNetEvent("ND:setCharacter", function(character) - NDCore.SelectedCharacter = character + NDCore.player = character end) -- Update main character info. RegisterNetEvent("ND:updateCharacter", function(character) - NDCore.SelectedCharacter = character + NDCore.player = character end) -- Updates last lcoation. RegisterNetEvent("ND:updateLastLocation", function(location) - NDCore.SelectedCharacter.lastLocation = location + if not NDCore.player then return end + NDCore.player.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 6e2b4a4..3fa06a3 100644 --- a/client/functions.lua +++ b/client/functions.lua @@ -1,19 +1,6 @@ -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) +function NDCore.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 @@ -30,51 +17,3 @@ function NDCore.Functions.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 56d2691..1cef364 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,54 +1,36 @@ --- For support join my discord: https://discord.gg/Z9Mxu72zZ6 - -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) +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 - 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) + Wait(60000) 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 +end) + +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 + end + end +end) diff --git a/config_client.lua b/config_client.lua deleted file mode 100644 index e911801..0000000 --- a/config_client.lua +++ /dev/null @@ -1,74 +0,0 @@ --- 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 deleted file mode 100644 index f252796..0000000 --- a/config_server.lua +++ /dev/null @@ -1,6 +0,0 @@ --- 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 0069149..e084ad9 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,39 +1,25 @@ -- For support join my discord: https://discord.gg/Z9Mxu72zZ6 -author "Andyyy#7666, N1K0#0001" +author "Andyyy#7666" description "ND Framework Core" -version "1.0.3" +version "2.0.0" fx_version "cerulean" game "gta5" lua54 "yes" shared_scripts { - "config_client.lua", - "shared/main.lua" + "init.lua" } client_scripts { - "client/main.lua", "client/functions.lua", "client/events.lua", - "shared/import.lua" + "client/main.lua" } server_scripts { "@oxmysql/lib/MySQL.lua", - "config_server.lua", "server/main.lua", - "server/functions.lua", - "server/events.lua", - "server/commands.lua", - "shared/import.lua" -} - -exports { - "GetCoreObject" -} - -server_exports { - "GetCoreObject" + "server/player.lua" } dependency "oxmysql" diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..b4ded23 --- /dev/null +++ b/init.lua @@ -0,0 +1,16 @@ +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 deleted file mode 100644 index e98e0d5..0000000 --- a/server/commands.lua +++ /dev/null @@ -1,117 +0,0 @@ -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 deleted file mode 100644 index e8f6251..0000000 --- a/server/events.lua +++ /dev/null @@ -1,117 +0,0 @@ --- 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 deleted file mode 100644 index e42e7c9..0000000 --- a/server/functions.lua +++ /dev/null @@ -1,766 +0,0 @@ --- 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 f84ab27..d13951d 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,21 +1,14 @@ --- For support join my discord: https://discord.gg/Z9Mxu72zZ6 +ActivePlayers = {} -NDCore = {} -NDCore.Players = {} -NDCore.Functions = {} -NDCore.Commands = {} -NDCore.PlayersDiscordInfo = {} -NDCore.Config = config - -function GetCoreObject() - return NDCore +function NDCore.getPlayer(src) + return ActivePlayers[src] end -isResourceStarted("ox_inventory", function(started) - if not started then return end - SetConvarReplicated("inventory:framework", "nd") +AddEventHandler("playerDropped", function() + local src = source + local char = ActivePlayers[src] + if not char then return end + char:unload() end) -for _, roleid in pairs(config.adminRoles) do - ExecuteCommand("add_principal identifier.discord:" .. roleid .. " group.admin") -end \ No newline at end of file +SetConvarServerInfo("ND_Core", GetResourceMetadata(GetCurrentResourceName(), "version", 0) or "invalid") diff --git a/server/player.lua b/server/player.lua new file mode 100644 index 0000000..d1a1daf --- /dev/null +++ b/server/player.lua @@ -0,0 +1,192 @@ +local function charDelete(self) + local result = MySQL.query.await("DELETE FROM nd_characters WHERE charid = ?", {self.id}) + return result +end + +local function charDeductMoney(self, account, amount, reason) + local amount = tonumber(amount) + if not amount or account ~= "bank" and account ~= "cash" then return end + self[account] -= amount + if self.source then + TriggerEvent("ND:moneyChange", self.source, account, amount, "remove", reason) + end + return true +end + +local function charAddMoney(self, account, amount, reason) + local amount = tonumber(amount) + if not amount or account ~= "bank" and account ~= "cash" then return end + self[account] += amount + if self.source then + TriggerEvent("ND:moneyChange", self.source, account, amount, "add", reason) + end + return true +end + +local function charDepositMoney(self, amount) + local amount = tonumber(amount) + if not amount or self.cash < amount or amount <= 0 then return end + return self:deductMoney("cash", amount, "Deposit") and self:AddMoney("bank", amount, "Deposit") +end + +local function charWithdrawMoney(self, amount) + local amount = tonumber(amount) + if not amount or self.bank < amount or amount <= 0 then return end + return self:deductMoney("bank", amount, "Withdraw") and self:AddMoney("cash", amount, "Withdraw") +end + +local function charSetMetadata(self, key, value) + self.metadata[key] = value +end + +local function charUnload(self) + TriggerEvent("ND:characterUnloaded", self.source, self) + local ped = GetPlayerPed(self.source) + self:setMetadata("location", GetEntityCoords(ped)) + self:save() + ActivePlayers[self.source] = nil +end + +local function charSave(self) + MySQL.update.await("UPDATE nd_characters SET name = ?, firstname = ?, lastname = ?, dob = ?, gender = ?, cash = ?, bank = ?, metadata = ?, inventory = ? WHERE charid = ?", { + self.name, + self.firstname, + self.lastname, + self.dob, + self.gender, + self.cash, + self.bank, + json.encode(self.metadata), + json.encode(self.inventory), + self.id, + }) +end + +local function charCreateLicense(self, licenseType, expire) + local expireIn = tonumber(expire) or 2592000 + local time = os.time() + local licenses = self.metadata.licenses + local identifier = {} + + for i=1, 16 do + identifier[i] = math.random(0, 1) == 1 and string.char(math.random(65, 90)) or math.random(0, 9) + end + + local license = { + type = licenseType, + status = "valid", + issued = time, + expires = time+expireIn, + identifier = table.concat(identifier) + } + + if licenses then + self.metadata.licenses[#licenses+1] = license + return + end + self.metadata.licenses = {license} +end + +local function initPlayerTable(playerTable) + playerTable.delete = charDelete, + playerTable.deductMoney = charDeductMoney, + playerTable.addMoney = charAddMoney + playerTable.depositMoney = charDepositMoney, + playerTable.withdrawMoney = charWithdrawMoney, + playerTable.setMetadata = charSetMetadata, + playerTable.save = charSave, + playerTable.unload = charUnload, + playerTable.createLicense = charCreateLicense + return playerTable +end + +function NDCore.newCharacter(src, info) + local license = GetPlayerIdentifierByType(src, "license") + if not license then return end + + local charInfo = initPlayerTable({ + source = src, + name = GetPlayerName(src) or "", + firstname = info.firstname or "", + lastname = info.lastname or "", + dob = info.dob or "", + gender = info.gender or "", + cash = info.cash or 0, + bank = info.bank or 0, + license = license, + metadata = info.metadata or {}, + inventory = info.inventory or {}, + }) + + charInfo.id = MySQL.insert.await("INSERT INTO nd_characters (license, name, firstname, lastname, dob, gender, cash, bank, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", { + license, + charInfo.name, + charInfo.firstname, + charInfo.lastname, + charInfo.dob, + charInfo.gender, + charInfo.cash, + charInfo.bank, + charInfo.metadata + ) + + return charInfo +end + +function NDCore.fetchCharacter(id) + local result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ?", {id}) + if not result then return end + + local info = result[1] + return initPlayerTable({ + id = id, + name = info.name, + firstname = info.firstname, + lastname = info.lastname, + dob = info.dob, + gender = info.gender, + cash = info.cash, + bank = info.bank, + license = info.license, + metadata = json.decode(info.metadata), + inventory = json.decode(i.inventory) + }) +end + +function NDCore.getPlayerCharacters(src) + local characters = {} + local result = MySQL.query.await("SELECT * FROM nd_characters WHERE license = ?", {GetPlayerIdentifierByType(src, "license")}) + local amount = #result + + for i=1, amount do + local info = result[i] + characters[info.charid] = initPlayerTable({ + id = info.charid, + name = info.name, + firstname = info.firstname, + lastname = info.lastname, + dob = info.dob, + gender = info.gender, + cash = info.cash, + bank = info.bank, + license = info.license, + metadata = json.decode(info.metadata), + inventory = json.decode(info.inventory), + }) + end + return characters +end + +function NDCore.setActiveCharacter(src, id) + local char = ActivePlayers[src] + if char then char:unload() end + + local character = NDCore.fetchCharacter(id) + character.source = src, + character.name = GetPlayerName(src) + ActivePlayers[src] = character + + TriggerEvent("ND:characterLoaded", character) + TriggerClientEvent("ND:characterLoaded", src, character) + return ActivePlayers[src] +end diff --git a/shared/import.lua b/shared/import.lua deleted file mode 100644 index 138e3c9..0000000 --- a/shared/import.lua +++ /dev/null @@ -1 +0,0 @@ -NDCore = exports["ND_Core"]:GetCoreObject() \ No newline at end of file diff --git a/shared/main.lua b/shared/main.lua deleted file mode 100644 index 948dd79..0000000 --- a/shared/main.lua +++ /dev/null @@ -1,30 +0,0 @@ -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