├── item.sql ├── html ├── fonts │ ├── Dosis-Medium.ttf │ └── SquadaOne-Regular.ttf ├── ui.html ├── js │ └── script.js └── css │ └── style.css ├── README.md ├── fxmanifest.lua ├── config.lua ├── client └── plate.lua └── server └── plate.lua /item.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `items` (`name`, `label`, `weight`) VALUES 2 | ('licenseplate', 'License Plate', 1) 3 | ; -------------------------------------------------------------------------------- /html/fonts/Dosis-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EntityEvolution/ev-platechanger/HEAD/html/fonts/Dosis-Medium.ttf -------------------------------------------------------------------------------- /html/fonts/SquadaOne-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EntityEvolution/ev-platechanger/HEAD/html/fonts/SquadaOne-Regular.ttf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ev-platechanger 2 | A simple NUI plate changer. 3 | 4 | # LICENSE 5 | Feel free to do whatever you want. 6 | 7 | ### [Forum Post](https://forum.cfx.re/t/release-ev-platechanger/4749084) 8 | ### [Discord](https://discord.com/invite/u4zk4tVTkG) 9 | ### [Donations](https://www.buymeacoffee.com/bombayV) 10 | 11 | 12 | ### [Preview](https://c.fileglass.com/59bd7.png) 13 | ### [Video](https://youtu.be/yaS4xfmHQ1Y) 14 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | 3 | game 'gta5' 4 | 5 | lua54 'yes' 6 | 7 | description 'A simple NUI plate creator by Entity Evolution' 8 | 9 | version '2.0.0' 10 | 11 | shared_scripts { 12 | 'config.lua', 13 | '@es_extended/imports.lua' 14 | --'@qb-core/import.lua' 15 | } 16 | 17 | client_scripts { 18 | 'client/*.lua' 19 | } 20 | 21 | server_scripts { 22 | '@mysql-async/lib/MySQL.lua', -- Remove if using QBCore 23 | 'server/*.lua' 24 | } 25 | 26 | ui_page 'html/ui.html' 27 | 28 | files { 29 | 'html/ui.html', 30 | 'html/fonts/*.ttf', 31 | 'html/css/style.css', 32 | 'html/js/script.js' 33 | } 34 | -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | Config.JsKey = "Insert" 3 | Config.PlateHeader = "SAN ANDREAS" 4 | Config.EightChars = true 5 | Config.useButtons = true 6 | Config.MaxChars = 8 -- Max is 8 7 | 8 | Config.Locales = { 9 | Error = 'Something weird occured', 10 | ErrorVehicle = 'You somehow left your vehicle!', 11 | ErrorDriver = 'You need to be the driver', 12 | ErrorWalking = 'You are not in a vehicle', 13 | ErrorOwner = 'This is not your vehicle', 14 | ErrorPlate = 'Plate already exists', 15 | ErrorPlateReal = 'Your vehicle appears to be missing a real plate', 16 | ErrorCharsMin = 'Plate needs at least 1 character', 17 | ErrorCharsMax = 'Somehow you passed more than 6 chars', 18 | NewPlate = 'Your new plate has been set!' 19 | } 20 | 21 | Config.Blacklist = { 22 | 'NIGGER', 23 | 'NIGGA', 24 | 'NEGRO' 25 | } -------------------------------------------------------------------------------- /html/ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Plate Changer 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | SAN ANDREAS 20 | 21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /html/js/script.js: -------------------------------------------------------------------------------- 1 | const doc = document; 2 | const plate = doc.getElementById('wrapper'); 3 | let currentKey = ""; 4 | let usingButtons = false; 5 | 6 | this.window.addEventListener('load', e => { 7 | window.addEventListener('message', e => { 8 | switch (e.data.action) { 9 | case 'show': 10 | plate.style.display = 'flex'; 11 | plate.style.opacity = '1'; 12 | if (!usingButtons) { 13 | doc.getElementById('btns').style.display = 'none'; 14 | } 15 | break; 16 | 17 | case 'hide': 18 | plate.style.display = 'none'; 19 | plate.style.opacity = '0'; 20 | if (!usingButtons) { 21 | doc.getElementById('btns').style.display = 'none'; 22 | } 23 | break; 24 | 25 | case 'key': 26 | usingButtons = e.data.buttons; 27 | currentKey = e.data.key; 28 | doc.getElementById('title').textContent = e.data.title; 29 | if (e.data.chars) { 30 | document.getElementById("text").maxLength = '8' 31 | document.getElementsByName('text')[0].placeholder='ABCD1234'; 32 | } 33 | break; 34 | } 35 | }) 36 | }) 37 | 38 | doc.getElementById('one').addEventListener('click', () => fetchNUI('getPlateText', doc.getElementById('text').value.toUpperCase())); 39 | 40 | doc.getElementById('two').addEventListener('click', () => fetchNUI('close')); 41 | 42 | const fetchNUI = async (cbname, data) => { 43 | const options = { 44 | method: 'POST', 45 | headers: { 46 | 'Content-Type': 'application/json; charset=UTF-8' 47 | }, 48 | body: JSON.stringify(data) 49 | }; 50 | const resp = await fetch(`https://ev-platechanger/${cbname}`, options); 51 | return await resp.json(); 52 | } 53 | 54 | doc.onkeyup = e => { 55 | if (e.key == currentKey) { 56 | const inp = doc.getElementById('text'); 57 | fetchNUI('getPlateText', inp.value.toUpperCase()); 58 | } else if (e.key == 'Escape') { 59 | plate.style.opacity = '0'; 60 | fetchNUI('close'); 61 | } 62 | } -------------------------------------------------------------------------------- /html/css/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: def; 3 | src: url(../fonts/SquadaOne-Regular.ttf); 4 | font-display: swap; 5 | } 6 | 7 | @font-face { 8 | font-family: def2; 9 | src: url(../fonts/Dosis-Medium.ttf); 10 | font-display: swap; 11 | } 12 | 13 | *:focus { 14 | outline: none; 15 | } 16 | 17 | body { 18 | overflow: hidden; 19 | } 20 | 21 | .wrapper { 22 | position: absolute; 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | flex-direction: column; 27 | top: 0; 28 | left: 0; 29 | width: 100%; 30 | height: 100%; 31 | opacity: 0; 32 | } 33 | 34 | .container { 35 | position: relative; 36 | display: flex; 37 | justify-content: space-between; 38 | align-items: center; 39 | flex-direction: column; 40 | width: 28%; 41 | height: 22%; 42 | border-radius: 1.0vh; 43 | border: 1.2vh solid #373737; 44 | 45 | background-color: #dddddd; 46 | } 47 | 48 | .container span { 49 | position: relative; 50 | display: flex; 51 | justify-content: space-around; 52 | align-items: center; 53 | font-family: def; 54 | text-transform: uppercase; 55 | font-size: 5.0vh; 56 | margin-top: 1.0%; 57 | width: 100%; 58 | height: 15%; 59 | } 60 | 61 | .input-text { 62 | display: flex; 63 | justify-content: center; 64 | align-content: center; 65 | text-align: center; 66 | font-size: 10vh; 67 | width: 90%; 68 | height: 50%; 69 | border: none; 70 | background-color: #dddddd; 71 | margin-bottom: 5%; 72 | font-family: def2; 73 | text-transform: uppercase; 74 | } 75 | 76 | .btn-container { 77 | display: flex; 78 | justify-content: space-evenly; 79 | align-items: center; 80 | width: 28%; 81 | height: 8%; 82 | 83 | margin-top: 0.5%; 84 | } 85 | 86 | .btn-container button { 87 | width: 40%; 88 | height: 70%; 89 | 90 | border: none; 91 | border-radius: 0.5vh; 92 | box-shadow:0 0.3vh 0.4vh #00000058, 0 0 20vh #0000001a; 93 | 94 | font-family: def2; 95 | font-size: 2.5vh; 96 | font-weight: 700; 97 | background-color: #DBDBDB; 98 | transition: 0.2s; 99 | } 100 | 101 | .btn-container button:nth-of-type(1):hover { 102 | background-color: rgb(33, 131, 221); 103 | } 104 | 105 | .btn-container button:nth-of-type(2):hover { 106 | background-color: rgb(233, 29, 56); 107 | } -------------------------------------------------------------------------------- /client/plate.lua: -------------------------------------------------------------------------------- 1 | local stateEsx = GetResourceState('es_extended') == 'started' or GetResourceState('extendedmode') == 'started' 2 | local stateQbus = GetResourceState('qb-core') == 'started' 3 | local isOpen = false 4 | 5 | local function showNoti(message) 6 | if stateEsx then 7 | ESX.ShowNotification(message) 8 | elseif stateQbus then 9 | QBCore.Functions.Notify(message) 10 | end 11 | end 12 | 13 | -- NUI Callback 14 | RegisterNUICallback('getPlateText', function(data, cb) 15 | if isOpen then 16 | local ped = PlayerPedId() 17 | if IsPedInAnyVehicle(ped, false) then 18 | if GetPedInVehicleSeat(GetVehiclePedIsIn(ped, false), -1) == ped then 19 | if data then 20 | for i=0, #Config.Blacklist, 1 do 21 | if Config.Blacklist[i] == data then 22 | return showNoti('You tried to set a plate with a bad word: ' .. data) 23 | end 24 | end 25 | if data:len() > 0 then 26 | SendNUIMessage({action = 'hide'}) 27 | SetNuiFocus(0, 0) 28 | TriggerServerEvent('ev:getPlate', data, GetVehicleNumberPlateText(GetVehiclePedIsIn(ped, false)):match( "^%s*(.-)%s*$" )) 29 | isOpen = false 30 | else 31 | showNoti(Config.Locales.ErrorCharsMin) 32 | end 33 | else 34 | showNoti(Config.Locales.Error) 35 | end 36 | else 37 | showNoti(Config.Locales.ErrorDriver) 38 | end 39 | else 40 | showNoti(Config.Locales.ErrorVehicle) 41 | end 42 | end 43 | cb({}) 44 | end) 45 | 46 | RegisterNUICallback('close', function(_, cb) 47 | if isOpen then 48 | isOpen = false 49 | SendNUIMessage({action = 'hide'}) 50 | SetNuiFocus(0, 0) 51 | end 52 | cb({}) 53 | end) 54 | 55 | -- Events 56 | RegisterNetEvent('ev:getPlateNui', function() 57 | if not isOpen then 58 | local ped = PlayerPedId() 59 | if IsPedInAnyVehicle(ped, false) then 60 | if GetPedInVehicleSeat(GetVehiclePedIsIn(ped, false), -1) == ped then 61 | isOpen = true 62 | SendNUIMessage({action = 'show'}) 63 | SetNuiFocus(1, 1) 64 | else 65 | showNoti(Config.Locales.ErrorDriver) 66 | end 67 | else 68 | showNoti(Config.Locales.ErrorWalking) 69 | end 70 | end 71 | end) 72 | 73 | --Handlers 74 | AddEventHandler('playerSpawned', function() 75 | Wait(3000) 76 | SendNUIMessage({ 77 | action = 'key', 78 | key = Config.JsKey, 79 | title = Config.PlateHeader, 80 | chars = Config.EightChars, 81 | buttons = Config.useButtons 82 | }) 83 | end) 84 | 85 | AddEventHandler('onClientResourceStart', function(resourceName) 86 | if resourceName == GetCurrentResourceName() then 87 | Wait(3000) 88 | SendNUIMessage({ 89 | action = 'key', 90 | key = Config.JsKey, 91 | title = Config.PlateHeader, 92 | chars = Config.EightChars, 93 | buttons = Config.useButtons 94 | }) 95 | end 96 | end) -------------------------------------------------------------------------------- /server/plate.lua: -------------------------------------------------------------------------------- 1 | local stateEsx = GetResourceState('es_extended') == 'started' or GetResourceState('extendedmode') == 'started' 2 | local stateQbus = GetResourceState('qb-core') == 'started' 3 | 4 | if stateEsx then 5 | RegisterNetEvent('ev:getPlate', function(plate, currentPlate) 6 | local source = source 7 | local xPlayer = ESX.GetPlayerFromId(source) 8 | if xPlayer then 9 | if GetVehiclePedIsIn(GetPlayerPed(source)) == 0 or not currentPlate then 10 | return xPlayer.showNotification(Config.Locales.ErrorVehicle) 11 | elseif plate:len() > 6 then 12 | return xPlayer.showNotification(Config.Locales.ErrorCharsMax) 13 | end 14 | for i=0, #Config.Blacklist, 1 do 15 | if Config.Blacklist[i] == plate then 16 | return xPlayer.showNotification('You tried to set a plate with a bad word: ' .. plate) 17 | end 18 | end 19 | MySQL.Async.fetchScalar('SELECT plate FROM owned_vehicles WHERE owner = @owner AND plate = @plate', { 20 | ['owner'] = xPlayer.identifier, 21 | ['plate'] = currentPlate 22 | }, function(resultPlate) 23 | if not resultPlate then 24 | return xPlayer.showNotification(Config.Locales.ErrorOwner) 25 | else 26 | MySQL.Async.fetchScalar('SELECT * FROM owned_vehicles WHERE plate = @plate', { 27 | ['plate'] = plate 28 | }, function(result) 29 | if not result then 30 | MySQL.Async.fetchAll('SELECT plate, vehicle FROM owned_vehicles WHERE plate = @plate', { 31 | ['plate'] = currentPlate 32 | },function(result) 33 | if result[1] then 34 | local vehicle = json.decode(result[1].vehicle) 35 | if not vehicle.plate then 36 | return xPlayer.showNotification(Config.Locales.ErrorPlateReal) 37 | end 38 | local oldPlate = vehicle.plate 39 | vehicle.plate = plate 40 | MySQL.Async.execute('UPDATE owned_vehicles SET plate = @newplate, vehicle = @vehicle WHERE plate = @oldplate AND owner=@identifier', { 41 | ['newplate'] = plate, 42 | ['oldplate'] = oldPlate, 43 | ['identifier'] = xPlayer.identifier, 44 | ['vehicle'] = json.encode(vehicle) 45 | }) 46 | SetVehicleNumberPlateText(GetVehiclePedIsIn(GetPlayerPed(source)), plate) 47 | xPlayer.removeInventoryItem('licenseplate', 1) 48 | xPlayer.showNotification(Config.Locales.NewPlate) 49 | return 50 | end 51 | end) 52 | else 53 | xPlayer.showNotification(Config.Locales.ErrorPlate) 54 | end 55 | end) 56 | end 57 | end) 58 | end 59 | end) 60 | 61 | ESX.RegisterUsableItem('licenseplate', function(source) 62 | local source = source 63 | local xPlayer = ESX.GetPlayerFromId(source) 64 | local vehicle = GetVehiclePedIsIn(GetPlayerPed(source)) 65 | if vehicle ~= 0 then 66 | MySQL.Async.fetchScalar('SELECT plate FROM owned_vehicles WHERE owner = @owner AND plate = @plate', { 67 | ['owner'] = xPlayer.identifier, 68 | ['plate'] = GetVehicleNumberPlateText(vehicle):match( "^%s*(.-)%s*$" ) 69 | }, function(plate) 70 | if plate then 71 | TriggerClientEvent('ev:getPlateNui', source) 72 | else 73 | xPlayer.showNotification(Config.Locales.ErrorOwner) 74 | end 75 | end) 76 | else 77 | xPlayer.showNotification(Config.Locales.ErrorWalking) 78 | end 79 | end) 80 | elseif stateQbus then 81 | RegisterNetEvent('ev:getPlate', function(plate, currentPlate) 82 | local source = source 83 | local xPlayer = QBCore.Functions.GetPlayer(source) 84 | if xPlayer then 85 | if GetVehiclePedIsIn(GetPlayerPed(source)) == 0 or not currentPlate then 86 | return TriggerClientEvent('QBCore:Notify', source, Config.Locales.ErrorVehicle) 87 | elseif plate:len() > Config.MaxChars then 88 | return TriggerClientEvent('QBCore:Notify', source, Config.Locales.ErrorCharsMax) 89 | end 90 | for i=0, #Config.Blacklist, 1 do 91 | if Config.Blacklist[i] == plate then 92 | return TriggerClientEvent('QBCore:Notify', source, 'You tried to set a plate with a bad word: ' .. plate) 93 | end 94 | end 95 | exports.oxmysql:fetch('SELECT plate FROM player_vehicles WHERE plate = @plate AND citizenid = @citizenid', { 96 | ['plate'] = currentPlate, 97 | ['citizenid'] = xPlayer.PlayerData.citizenid 98 | }, function(result) 99 | if result[1] then 100 | exports.oxmysql:fetch('SELECT * FROM player_vehicles WHERE plate = @plate', { 101 | ['plate'] = plate 102 | }, function(exist) 103 | if not exist[1] then 104 | exports.oxmysql:fetch('SELECT plate, mods FROM player_vehicles WHERE plate = @plate', { 105 | ['plate'] = currentPlate 106 | },function(currentVehicle) 107 | if currentVehicle[1] then 108 | local vehicle = json.decode(currentVehicle[1].mods) 109 | if not vehicle.plate then 110 | return TriggerClientEvent('QBCore:Notify', source, Config.Locales.ErrorPlateReal) 111 | end 112 | local oldPlate = vehicle.plate 113 | vehicle.plate = plate 114 | exports.oxmysql:execute('UPDATE player_vehicles SET plate = @newplate, mods = @vehicle WHERE plate = @oldplate AND citizenid=@citizenid', { 115 | ['newplate'] = plate, 116 | ['oldplate'] = oldPlate, 117 | ['citizenid'] = xPlayer.PlayerData.citizenid, 118 | ['vehicle'] = json.encode(vehicle) 119 | }) 120 | SetVehicleNumberPlateText(GetVehiclePedIsIn(GetPlayerPed(source)), plate) 121 | xPlayer.Functions.RemoveItem('licenseplate', 1) 122 | TriggerClientEvent('QBCore:Notify', source, Config.Locales.NewPlate) 123 | return 124 | end 125 | end) 126 | else 127 | TriggerClientEvent('QBCore:Notify', source, Config.Locales.ErrorPlate) 128 | end 129 | end) 130 | else 131 | TriggerClientEvent('QBCore:Notify', source, Config.Locales.ErrorOwner) 132 | end 133 | end) 134 | end 135 | end) 136 | 137 | QBCore.Functions.CreateUseableItem('licenseplate', function(source) 138 | local source = source 139 | local xPlayer = QBCore.Functions.GetPlayer(source) 140 | local vehicle = GetVehiclePedIsIn(GetPlayerPed(source)) 141 | if vehicle ~= 0 then 142 | exports.oxmysql:fetch('SELECT plate FROM player_vehicles WHERE plate = @plate AND citizenid = @citizenid', { 143 | ['plate'] = GetVehicleNumberPlateText(vehicle):match( "^%s*(.-)%s*$" ), 144 | ['citizenid'] = xPlayer.PlayerData.citizenid 145 | }, function(result) 146 | if result[1] then 147 | TriggerClientEvent('ev:getPlateNui', source) 148 | else 149 | TriggerClientEvent('QBCore:Notify', source, Config.Locales.ErrorOwner) 150 | end 151 | end) 152 | else 153 | TriggerClientEvent('QBCore:Notify', source, Config.Locales.ErrorWalking) 154 | end 155 | end) 156 | end --------------------------------------------------------------------------------