├── fxmanifest.lua ├── server ├── logs.settings.lua └── main.lua ├── LICENSE ├── readme.md ├── shared └── main.lua └── client └── main.lua /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | 4 | name "Mycroft's Job Forms" 5 | description "FREE in-game Job Applications using ox lib" 6 | author "Mycroft" 7 | lua54 'yes' 8 | version "1.0.0" 9 | 10 | shared_scripts { 11 | '@ox_lib/init.lua', 12 | 'shared/*.lua' 13 | } 14 | 15 | client_scripts { 16 | 'client/*.lua' 17 | } 18 | 19 | server_scripts { 20 | 'server/logs.settings.lua', 21 | 'server/main.lua' 22 | } 23 | -------------------------------------------------------------------------------- /server/logs.settings.lua: -------------------------------------------------------------------------------- 1 | DiscordLogs = { 2 | Webhooks = { 3 | default = '', 4 | police = "", 5 | }, 6 | 7 | Colors = { -- https://www.spycolor.com/ 8 | default = 14423100, 9 | blue = 255, 10 | red = 16711680, 11 | green = 65280, 12 | white = 16777215, 13 | black = 0, 14 | orange = 16744192, 15 | yellow = 16776960, 16 | pink = 16761035, 17 | lightgreen = 65309 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright 2023 Mycroft (Kasey Fitton) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
Highly Configurable Job Forms with ox lib
2 |
3 | > This Script Was Made Because someone was selling a version of this with less features and Server Owners shouldn't have to spend money for such basic and simplistic code.
4 |
5 | > I've also decided to MIT license this code, do whatever you want with it.
6 |
7 | This Script is a fantastic way for players to apply for in-game jobs within their FiveM servers. This script allows players to fill out job applications in-game using a custom form that collects all the necessary information. Once the form is submitted, all the data is transferred via a webhook to a designated Discord channel, where it can
8 | be reviewed and processed by the server staff.
9 |
10 | Overall, the job application script for FiveM is a powerful and essential tool for servers that want to streamline their hiring process and provide a better user experience for job applicants.
11 |
12 | ## Features
13 |
14 | - Optional State-bag-based Cooldowns
15 | - Option to change `true/false` to `Yes/No`
16 | - Change **every** detail about the embed
17 | - Add As many questions as you want!
18 | - Supports loads of differant input types (text, number, checkbox, slider + more)
19 | - optional OX Target support
20 | - Completely customise EVERYTHING todo with the marker and target.
21 | - Input Santisation before data is sent to Discord
22 | - Cool embed formatting
23 | - Each Job can have their own webhook
24 | - Each Job can have their own Form
25 |
26 | ### Line Count
27 |
28 | - 335 (Including Config & fxmanifest & ReadMe)
29 | - 289 (Including Config & fxmanifest)
30 | - 266 (Including Config)
31 | - 157 (Excluding config/fxmanfiest/readme)
32 |
33 | ## Preview
34 |
35 | [](https://streamable.com/gex5u0)
36 |
37 | ## Notice
38 |
39 | Copyright 2023 Mycroft (Kasey Fitton)
40 |
41 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
42 |
43 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
44 |
45 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
46 |
--------------------------------------------------------------------------------
/server/main.lua:
--------------------------------------------------------------------------------
1 | function Discord(name, title, color, fields)
2 | local webHook = DiscordLogs.Webhooks[name] or DiscordLogs.Webhooks.default
3 | local embedData = {{
4 | ['title'] = title,
5 | ['color'] = DiscordLogs.Colors[color] or DiscordLogs.Colors.default,
6 | ['footer'] = {
7 | ['text'] = Config.ShowTime and Config.FooterText .. " " .. os.date() or Config.FooterText,
8 | ['icon_url'] = Config.FooterIconURL
9 | },
10 | ['fields'] = fields,
11 | ['description'] = "",
12 | ['author'] = {
13 | ['name'] = Config.ServerName,
14 | ['icon_url'] = Config.AuthorAvatar
15 | }
16 | }}
17 | PerformHttpRequest(webHook, nil, 'POST', json.encode({
18 | username = Config.BotUsername,
19 | avatar_url = Config.BotAvatar,
20 | embeds = embedData
21 | }), {['Content-Type'] = 'application/json'})
22 | end
23 |
24 | function Sanitize(str)
25 | local replacements = {
26 | ['&' ] = '&',
27 | ['<' ] = '<',
28 | ['>' ] = '>',
29 | ['\n'] = '
'
30 | }
31 |
32 | return str
33 | :gsub('[&<>\n]', replacements)
34 | :gsub(' +', function(s)
35 | return ' '..(' '):rep(#s-1)
36 | end)
37 | end
38 |
39 | function TableToString(table)
40 | local string = ""
41 | for k,v in pairs(table) do
42 | local answer = Sanitize(tostring(v))
43 | if not answer or answer == "" then answer = "N/A" end
44 | if Config.ChangeBoolsToStrings and answer == "true" then answer = "Yes" end
45 | if Config.ChangeBoolsToStrings and answer == "false" then answer = "No" end
46 | string = string .. (Config.MakeAnswersBold and ("**%s**: **%s** \n"):format(k, answer) or ("%s: %s"):format(k, answer))
47 | end
48 | return string
49 | end
50 |
51 | function ConvertAnswers(Questions, Answers)
52 | for i=1, #(Answers) do
53 | if type(Answers[i]) == "table" then
54 | Questions[i].Answer = TableToString(Answers[i])
55 | else
56 | local answer = Sanitize(tostring(Answers[i]))
57 | if not answer or answer == "" then answer = "N/A" end
58 | if Config.ChangeBoolsToStrings and answer == "true" then answer = "Yes" end
59 | if Config.ChangeBoolsToStrings and answer == "false" then answer = "No" end
60 | Questions[i].Answer = Config.MakeAnswersBold and ("**%s**"):format(answer) or answer
61 | end
62 | end
63 | return Questions
64 | end
65 |
66 | RegisterNetEvent("jobforms:apply", function(AreaIndex, Answers)
67 | local source = source
68 | if Player(source).state.ApplicationCooldown then
69 | return
70 | end
71 | local Ped = GetPlayerPed(source)
72 | local ped_pos = GetEntityCoords(Ped)
73 | local dist = #(Config.Areas[AreaIndex].Coords - ped_pos)
74 | if dist > 10.0 then
75 | return
76 | end
77 | local Questions = ConvertAnswers(Config.Areas[AreaIndex].Questions, Answers)
78 | local Fields = {}
79 | for i=1, #(Questions) do
80 | Fields[#Fields + 1] = {name = Questions[i].label, value = Questions[i].Answer, inline = false}
81 | end
82 | Discord(Config.Areas[AreaIndex].webhook, Config.Areas[AreaIndex].label, "green", Fields)
83 | Player(source).state:set("ApplicationCooldown", true, true)
84 | SetTimeout(Config.ApplicationSettings.Cooldown.time, function()
85 | Player(source).state:set("ApplicationCooldown", false, true)
86 | end)
87 | end)
--------------------------------------------------------------------------------
/shared/main.lua:
--------------------------------------------------------------------------------
1 | Config = {}
2 |
3 | Config.UseTarget = false -- ox target support
4 |
5 | -- Discord Log Settings
6 | Config.ServerName = "Example Roleplay"
7 | Config.FooterText = "Mycroft | Job Forms | "
8 | Config.FooterIconURL = ""
9 | Config.ShowTime = true -- shows date in the footer
10 | Config.ChangeBoolsToStrings = true -- changes True/False to Yes/No
11 | Config.MakeAnswersBold = true
12 | Config.BotUsername = "Job Form Bot"
13 | Config.BotAvatar = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless"
14 | Config.AuthorAvatar = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless"
15 |
16 | --[[
17 | Question Types:
18 | input,
19 | number,
20 | checkbox,
21 | select,
22 | slider
23 | color
24 | multi-select
25 | date
26 | date-range
27 | time
28 | textarea
29 |
30 | for more details, visit: https://overextended.github.io/docs/ox_lib/Interface/Client/input
31 | ]]
32 |
33 | -- Main Settings
34 |
35 | Config.Areas = {
36 | {
37 | label = "Police Application",
38 | webhook = "police", -- note if not present, it will send to "default"
39 | Coords = vector3(441.5298, -981.1339, 30.6896),
40 | Blip = {
41 | enabled = false,
42 | sprite = 1,
43 | size = 0.7,
44 | colour = 1,
45 | },
46 | TargetSettings = { -- For when using OX-target
47 | radius = 2,
48 | debug = false,
49 | icon = "fa-solid fa-list-check",
50 | label = "Police Applications"
51 | },
52 | MarkerSettings = { -- if not using OX-Target
53 | DrawMarker = true,
54 | size = vec3(1, 1, 1),
55 | rotation = vec3(1, 1, 1),
56 | type = 21,
57 | Distance = 10.0,
58 | colour = {r = 50, g = 200, b = 50, a = 200},
59 | TextUI = "[E] -> Police Applications"
60 | },
61 | Questions = {
62 | {
63 | type = "input",
64 | label = "Question 1",
65 | description = "This is a Required Question",
66 | placeholder = "Answer",
67 | required = true
68 | },
69 | {
70 | type = "input",
71 | label = "Question 2",
72 | description = "This is a Non-Required Question",
73 | placeholder = "Answer",
74 | required = false
75 | },
76 | {
77 | type = "checkbox",
78 | label = "Do you like Chicken?",
79 | description = "Very Important Yes/No Question.",
80 | required = false
81 | },
82 | {
83 | type = "slider",
84 | label = "How Much Do you like Noodles?",
85 | default = 5,
86 | min = 1,
87 | max = 10,
88 | step = 1,
89 | required = true
90 | },
91 | {
92 | type = "textarea",
93 | label = "Is the Sky Blue?",
94 | description = "This is a Very Long Question",
95 | placeholder = "Who Knows?",
96 | max = 50, -- Max Lines
97 | required = true
98 | },
99 | }
100 | }
101 | }
102 |
103 | Config.ApplicationSettings = {
104 | Cooldown = {
105 | enabled = true,
106 | time = 5 * 60000 -- time in milliseconds (default: 5 mins)
107 | },
108 | }
109 |
--------------------------------------------------------------------------------
/client/main.lua:
--------------------------------------------------------------------------------
1 | function DoApplication(AreaIndex)
2 | local area = Config.Areas[AreaIndex]
3 | local Questions = area.Questions
4 | local input = lib.inputDialog(area.label, Questions)
5 | if not input then return end
6 | lib.notify({
7 | title = 'Job Application',
8 | description = 'Successfully Sent your Application.',
9 | type = 'success'
10 | })
11 | TriggerServerEvent("jobforms:apply", AreaIndex, input)
12 | end
13 |
14 | CreateThread(function()
15 | for i=1, #(Config.Areas) do
16 | if Config.UseTarget then
17 | local area = Config.Areas[i]
18 | exports.ox_target:addSphereZone({
19 | coords = area.Coords,
20 | radius = area.TargetSettings.radius,
21 | debug = area.TargetSettings.debug,
22 | options = {
23 | {
24 | name = area.label,
25 | icon = area.TargetSettings.icon,
26 | label = area.TargetSettings.label,
27 | onSelect = function()
28 | DoApplication(i)
29 | end,
30 | canInteract = function()
31 | if not Config.ApplicationSettings.Cooldown.enabled then
32 | return true
33 | end
34 | if not LocalPlayer.state.ApplicationCooldown then
35 | return true
36 | end
37 | return false
38 | end
39 | }
40 | }
41 | })
42 | else
43 | local point = lib.points.new(Config.Areas[i].Coords, Config.Areas[i].MarkerSettings.Distance, {
44 | area = Config.Areas[i], DrawingTextUI = false, areaIndex = i
45 | })
46 | function point:nearby()
47 | if self.area.MarkerSettings.DrawMarker then
48 | local m_set = self.area.MarkerSettings
49 | DrawMarker(m_set.type, self.coords.x, self.coords.y, self.coords.z, 0, 0, 0, m_set.rotation.x, m_set.rotation.y, m_set.rotation.z, m_set.size.x, m_set.size.y, m_set.size.z, m_set.colour.r, m_set.colour.g, m_set.colour.b, m_set.colour.a, false, true, 2, nil, nil, false)
50 | end
51 |
52 | if self.currentDistance < 1 then
53 | if not self.DrawingTextUI then
54 | self.DrawingTextUI = true
55 | lib.showTextUI(self.area.MarkerSettings.TextUI)
56 | end
57 | if IsControlJustPressed(0, 38) then
58 | if Config.ApplicationSettings.Cooldown.enabled then
59 | if LocalPlayer.state.ApplicationCooldown then
60 | return lib.notify({
61 | title = 'Job Application',
62 | description = 'Please Wait for your Cooldown to finish.',
63 | icon = 'ban',
64 | iconColor = '#C53030'
65 | })
66 | end
67 | end
68 | DoApplication(self.areaIndex)
69 | end
70 | else
71 | if self.DrawingTextUI then
72 | self.DrawingTextUI = false
73 | lib.hideTextUI()
74 | end
75 | end
76 | end
77 | end
78 | if Config.Areas[i].Blip.enabled then
79 | local blip_set = Config.Areas[i].Blip
80 | local blip = AddBlipForCoord(Config.Areas[i].Coords)
81 | SetBlipSprite (blip, blip_set.sprite)
82 | SetBlipAsShortRange(blip, true)
83 | SetBlipColour(blip, blip_set.colour)
84 | SetBlipScale (blip, blip_set.size)
85 | BeginTextCommandSetBlipName("STRING")
86 | AddTextComponentSubstringPlayerName(Config.Areas[i].label)
87 | EndTextCommandSetBlipName(blip)
88 | end
89 | end
90 | end)
--------------------------------------------------------------------------------