├── config.lua ├── README.md ├── fxmanifest.lua ├── ui ├── reset.css ├── index.html ├── style.css └── app.js └── client └── main.lua /config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | Config.Metric = true -- false if imperial 4 | Config.ESX = true -- false if qbcore 5 | 6 | --seatbelt toggle export: 7 | --exports['east_hud']:seatbelt(true/false) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # east_hud 2 | 3 | Simple hud for esx/qbcore frameworks 4 | 5 | Framework and units are configurable in `config.lua` 6 | 7 | To toggle seatbelt icon use export: 8 | 9 | `exports['east_hud']:seatbelt(true/false)` 10 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | description 'east_hud' 4 | 5 | shared_script 'config.lua' 6 | 7 | client_script 'client/main.lua' 8 | 9 | export 'seatbelt' 10 | 11 | ui_page 'ui/index.html' 12 | 13 | files { 14 | 'ui/index.html', 15 | 'ui/app.js', 16 | 'ui/*.css', 17 | } -------------------------------------------------------------------------------- /ui/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, 7 | body, 8 | div, 9 | span, 10 | applet, 11 | object, 12 | iframe, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | blockquote, 21 | pre, 22 | a, 23 | abbr, 24 | acronym, 25 | address, 26 | big, 27 | cite, 28 | code, 29 | del, 30 | dfn, 31 | em, 32 | img, 33 | ins, 34 | kbd, 35 | q, 36 | s, 37 | samp, 38 | small, 39 | strike, 40 | strong, 41 | sub, 42 | sup, 43 | tt, 44 | var, 45 | b, 46 | u, 47 | i, 48 | center, 49 | dl, 50 | dt, 51 | dd, 52 | ol, 53 | ul, 54 | li, 55 | fieldset, 56 | form, 57 | label, 58 | legend, 59 | table, 60 | caption, 61 | tbody, 62 | tfoot, 63 | thead, 64 | tr, 65 | th, 66 | td, 67 | article, 68 | aside, 69 | canvas, 70 | details, 71 | embed, 72 | figure, 73 | figcaption, 74 | footer, 75 | header, 76 | hgroup, 77 | menu, 78 | nav, 79 | output, 80 | ruby, 81 | section, 82 | summary, 83 | time, 84 | mark, 85 | audio, 86 | video { 87 | margin: 0; 88 | padding: 0; 89 | border: 0; 90 | font-size: 100%; 91 | font: inherit; 92 | vertical-align: baseline; 93 | } 94 | 95 | /* HTML5 display-role reset for older browsers */ 96 | article, 97 | aside, 98 | details, 99 | figcaption, 100 | figure, 101 | footer, 102 | header, 103 | hgroup, 104 | menu, 105 | nav, 106 | section { 107 | display: block; 108 | } 109 | 110 | body { 111 | line-height: 1; 112 | } 113 | 114 | ol, 115 | ul { 116 | list-style: none; 117 | } 118 | 119 | blockquote, 120 | q { 121 | quotes: none; 122 | } 123 | 124 | blockquote:before, 125 | blockquote:after, 126 | q:before, 127 | q:after { 128 | content: ''; 129 | content: none; 130 | } 131 | 132 | table { 133 | border-collapse: collapse; 134 | border-spacing: 0; 135 | } -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | east hud 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 |

0

34 |

km/h

35 |
36 |
37 | 38 |
39 |
40 |
41 | 42 | 43 |
44 | 45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ui/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100vh; 3 | display: flex; 4 | justify-content: center; 5 | align-items: flex-end; 6 | font-size: 0.9vmin; 7 | font-family: 'Poppins', sans-serif; 8 | } 9 | 10 | .status { 11 | display: none; 12 | justify-content: center; 13 | align-items: flex-end; 14 | margin-bottom: 1.8em; 15 | transition: 200ms ease-out; 16 | } 17 | 18 | .circle { 19 | display: flex; 20 | align-items: flex-end; 21 | justify-content: center; 22 | width: 4em; 23 | height: 4em; 24 | border-radius: 1em; 25 | overflow: hidden; 26 | margin: 0 1em; 27 | box-shadow: 0 0 1.0em 0.1em rgb(0 0 0 / 50%); 28 | } 29 | 30 | .fill { 31 | width: 100%; 32 | height: 0%; 33 | transition: 200ms ease-out; 34 | } 35 | 36 | i { 37 | position: absolute; 38 | align-self: center; 39 | color: white; 40 | font-size: 2.2em; 41 | opacity: 0.502; 42 | transition: 200ms ease-out; 43 | } 44 | 45 | .health { 46 | background-color: #ef9a9a80; 47 | } 48 | 49 | .health .fill { 50 | background-color: #E53935; 51 | } 52 | 53 | .armour { 54 | background-color: #B0BEC580; 55 | } 56 | 57 | .armour .fill { 58 | background-color: #546E7A; 59 | } 60 | 61 | .food { 62 | background-color: #FFCC8080; 63 | } 64 | 65 | .food .fill { 66 | background-color: #FB8C00; 67 | } 68 | 69 | .water { 70 | background-color: #90CAF980; 71 | } 72 | 73 | .water .fill { 74 | background-color: #1E88E5; 75 | } 76 | 77 | .carhud { 78 | display: none; 79 | justify-content: center; 80 | align-items: flex-end; 81 | text-shadow: 0 0 1.0em rgba(0, 0, 0, 0.5); 82 | } 83 | 84 | .speedometer { 85 | display: flex; 86 | flex-direction: column; 87 | justify-content: center; 88 | align-items: center; 89 | text-align: center; 90 | color: white; 91 | margin: 0 0.5em; 92 | margin-bottom: 1em; 93 | width: 5em; 94 | } 95 | 96 | .speedometer #speed { 97 | font-weight: 700; 98 | font-size: 3em; 99 | margin-bottom: 0.2em; 100 | opacity: 0.502; 101 | transition: 200ms ease-out; 102 | } 103 | 104 | .speedometer #unit { 105 | font-weight: 400; 106 | font-size: 1em; 107 | } 108 | 109 | .seatbelt { 110 | background-color: #e53935; 111 | width: 3em; 112 | height: 3em; 113 | } 114 | 115 | .seatbelt i { 116 | font-size: 1.65em; 117 | } 118 | 119 | .seatbelt .fill { 120 | background-color: #fb8c00; 121 | } 122 | 123 | .fuel { 124 | background-color: #ffcc8080; 125 | width: 3em; 126 | height: 3em; 127 | } 128 | 129 | .fuel i { 130 | font-size: 1.65em; 131 | } 132 | 133 | .fuel .fill { 134 | background-color: #FB8C00; 135 | } -------------------------------------------------------------------------------- /client/main.lua: -------------------------------------------------------------------------------- 1 | local hud = false 2 | local speedometer = false 3 | 4 | RegisterNUICallback('ready', function(data, cb) 5 | if data.show then 6 | Wait(500) 7 | SendNUIMessage({ 8 | action = 'show' 9 | }) 10 | hud = true 11 | end 12 | end) 13 | 14 | local last = { 15 | health = -1, 16 | armour = -1, 17 | food = -1, 18 | water = -1, 19 | fuel = -1, 20 | speed = -1, 21 | pause = false 22 | } 23 | 24 | if not Config.ESX then 25 | RegisterNetEvent('hud:client:UpdateNeeds', function(newHunger, newThirst) 26 | food = newHunger 27 | water = newThirst 28 | end) 29 | end 30 | 31 | Citizen.CreateThread(function() 32 | while true do 33 | if hud then 34 | local pause = IsPauseMenuActive() 35 | if pause ~= last.pause then 36 | if pause then 37 | SendNUIMessage({action = 'hide', opacity = 0}) 38 | else 39 | SendNUIMessage({action = 'hide', opacity = 1}) 40 | end 41 | last.pause = pause 42 | end 43 | local player = PlayerPedId() 44 | local health = GetEntityHealth(player) - 100 45 | local armour = GetPedArmour(player) 46 | if Config.ESX then 47 | TriggerEvent('esx_status:getStatus', 'hunger', function(status) food = status.val / 10000 end) 48 | TriggerEvent('esx_status:getStatus', 'thirst', function(status) water = status.val / 10000 end) 49 | end 50 | if health < 0 then health = 0 end 51 | if health ~= last.health then SendNUIMessage({action = 'health', health = health}) last.health = health end 52 | if armour ~= last.armour then SendNUIMessage({action = 'armour', armour = armour}) last.armour = armour end 53 | if food ~= last.food then SendNUIMessage({action = 'food', food = food}) last.food = food end 54 | if water ~= last.water then SendNUIMessage({action = 'water', water = water}) last.water = water end 55 | end 56 | Citizen.Wait(1000) 57 | end 58 | end) 59 | 60 | Citizen.CreateThread(function() 61 | while true do 62 | local wait = 1000 63 | if hud then 64 | local player = PlayerPedId() 65 | if IsPedInAnyVehicle(player) then 66 | local vehicle = GetVehiclePedIsIn(player) 67 | if GetPedInVehicleSeat(vehicle, -1) == player then 68 | wait = 200 69 | if not speedometer then 70 | SendNUIMessage({action = 'speedometer', speedometer = 'show', metric = Config.Metric}) 71 | speedometer = true 72 | else 73 | local fuel = GetVehicleFuelLevel(vehicle) 74 | local speed = GetEntitySpeed(vehicle) 75 | if fuel ~= last.fuel then SendNUIMessage({action = 'fuel', fuel = fuel}) last.fuel = fuel end 76 | if speed ~= last.speed then SendNUIMessage({action = 'speed', speed = speed}) last.speed = speed end 77 | end 78 | elseif speedometer then 79 | SendNUIMessage({action = 'speedometer', speedometer = 'hide', metric = Config.Metric}) 80 | speedometer = false 81 | end 82 | elseif speedometer then 83 | SendNUIMessage({action = 'speedometer', speedometer = 'hide', metric = Config.Metric}) 84 | speedometer = false 85 | end 86 | elseif speedometer then 87 | SendNUIMessage({action = 'speedometer', speedometer = 'hide', metric = Config.Metric}) 88 | speedometer = false 89 | end 90 | Citizen.Wait(wait) 91 | end 92 | end) 93 | 94 | function seatbelt(toggle) 95 | SendNUIMessage({action = 'seatbelt', seatbelt = toggle}) 96 | end 97 | -------------------------------------------------------------------------------- /ui/app.js: -------------------------------------------------------------------------------- 1 | let speedometer = false; 2 | let unit = 'km/h'; 3 | let multiply = 3.6; 4 | let oldSpeed = 0; 5 | 6 | fetch('https://east_hud/ready', { 7 | method: 'POST', 8 | headers: { 9 | 'Content-Type': 'application/json; charset=UTF-8', 10 | }, 11 | body: JSON.stringify({ 12 | show: true 13 | }) 14 | }).then(resp => resp.json()).then(resp => console.log(resp)); 15 | 16 | window.addEventListener('message', (event) => { 17 | if ((event.data.action) === 'show') { 18 | document.querySelector('.status').style.display = 'flex'; 19 | } else if ((event.data.action) === 'hide' && event.data.opacity || event.data.opacity === 0) { 20 | document.querySelector('.status').style.opacity = event.data.opacity; 21 | } else if (event.data.action === 'health' && event.data.health || event.data.health === 0) { 22 | document.querySelector('.health .fill').style.height = event.data.health + '%'; 23 | if (event.data.health <= 0) { 24 | document.querySelector('.health i').style.opacity = 0.502; 25 | } else { 26 | document.querySelector('.health i').style.opacity = 1; 27 | } 28 | } else if (event.data.action === 'armour' && event.data.armour || event.data.armour === 0) { 29 | document.querySelector('.armour .fill').style.height = event.data.armour + '%'; 30 | if (event.data.armour <= 0) { 31 | document.querySelector('.armour i').style.opacity = 0.502; 32 | } else { 33 | document.querySelector('.armour i').style.opacity = 1; 34 | } 35 | } else if (event.data.action === 'food' && event.data.food || event.data.food === 0) { 36 | document.querySelector('.food .fill').style.height = event.data.food + '%'; 37 | if (event.data.food <= 0) { 38 | document.querySelector('.food i').style.opacity = 0.502; 39 | } else { 40 | document.querySelector('.food i').style.opacity = 1; 41 | } 42 | } else if (event.data.action === 'water' && event.data.water || event.data.water === 0) { 43 | document.querySelector('.water .fill').style.height = event.data.water + '%'; 44 | if (event.data.water <= 0) { 45 | document.querySelector('.water i').style.opacity = 0.502; 46 | } else { 47 | document.querySelector('.water i').style.opacity = 1; 48 | } 49 | } else if (event.data.action === 'speedometer' && event.data.speedometer) { 50 | if (event.data.speedometer === 'show') { 51 | document.querySelector('.carhud').style.display = 'flex'; 52 | speedometer = true; 53 | if (!event.data.metric) { 54 | unit = 'mph'; 55 | multiply = 2.236936; 56 | } 57 | document.querySelector('#unit').innerText = unit; 58 | } else if (event.data.speedometer === 'hide') { 59 | document.querySelector('.carhud').style.display = 'none'; 60 | speedometer = false; 61 | } 62 | } else if (event.data.action === 'fuel' && speedometer && event.data.fuel || event.data.fuel === 0) { 63 | document.querySelector('.fuel .fill').style.height = event.data.fuel + '%'; 64 | if (event.data.fuel <= 0) { 65 | document.querySelector('.fuel i').style.opacity = 0.502; 66 | } else { 67 | document.querySelector('.fuel i').style.opacity = 1; 68 | } 69 | } else if (event.data.action === 'speed' && event.data.speed && speedometer) { 70 | animateValue('#speed', oldSpeed, Math.round(event.data.speed * multiply), 200) 71 | oldSpeed = Math.round(event.data.speed * multiply); 72 | if (oldSpeed <= 0) { 73 | document.querySelector('#speed').style.opacity = 0.502; 74 | } else { 75 | document.querySelector('#speed').style.opacity = 1; 76 | } 77 | } else if (event.data.action === 'seatbelt' && speedometer) { 78 | if (event.data.seatbelt) { 79 | document.querySelector('.seatbelt i').setAttribute('class', 'fa-solid fa-user'); 80 | document.querySelector('.seatbelt').style.backgroundColor = '#43A047' 81 | document.querySelector('.seatbelt i').style.opacity = 1 82 | } else if (!event.data.seatbelt) { 83 | document.querySelector('.seatbelt i').setAttribute('class', 'fa-solid fa-user-slash'); 84 | document.querySelector('.seatbelt').style.backgroundColor = '#E53935' 85 | document.querySelector('.seatbelt i').style.opacity = 0.502 86 | } 87 | } 88 | }); 89 | 90 | function animateValue(id, start, end, duration) { 91 | if (start === end) return; 92 | let range = end - start; 93 | let current = start; 94 | let increment = end > start ? 1 : -1; 95 | let stepTime = Math.abs(Math.floor(duration / range)); 96 | let obj = document.querySelector(id); 97 | let timer = setInterval(function () { 98 | current += increment; 99 | obj.innerText = current; 100 | if (current == end) { 101 | clearInterval(timer); 102 | } 103 | }, stepTime); 104 | } --------------------------------------------------------------------------------