├── img └── driving_test_permit.png ├── fxmanifest.lua ├── readme.md ├── html ├── debounce.min.js ├── questions.js ├── styles.css ├── scripts.js └── ui.html ├── server └── main.lua ├── config.lua └── client ├── gui.lua └── main.lua /img/driving_test_permit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QBLes/qb-drivingschool/HEAD/img/driving_test_permit.png -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'adamant' 2 | game 'gta5' 3 | 4 | description 'QB Driving School' 5 | 6 | version '1.0.4' 7 | 8 | server_scripts { 9 | 'config.lua', 10 | 'server/main.lua' 11 | } 12 | 13 | client_scripts { 14 | 15 | 'config.lua', 16 | 'client/main.lua', 17 | 'client/gui.lua', 18 | } 19 | 20 | ui_page 'html/ui.html' 21 | 22 | files { 23 | 'html/ui.html', 24 | 'html/logo.png', 25 | 'html/dmv.png', 26 | 'html/styles.css', 27 | 'html/questions.js', 28 | 'html/scripts.js', 29 | 'html/debounce.min.js' 30 | } 31 | 32 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | qb-core/shaed/main.lua 2 | 3 | remove line 76: ['driver_license'] = { amount = 1, item = 'driver_license' }, 4 | 5 | 6 | 7 | qb-core/server/player.lua 8 | 9 | change line 106 from ['driver] = true to false 10 | 11 | 12 | 13 | Add the following to qb-core/shaed/items.lua 14 | 15 | ['driving_test_permit'] = {['name'] = 'driving_test_permit', ['label'] = 'Driving Test Permit', ['weight'] = 0, ['type'] = 'item', ['image'] = 'driving_test_permit.png', ['unique'] = true, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Permite for Driving Test'}, 16 | 17 | Add the image from img folder into qb-inventory/html/images 18 | 19 | 20 | Once images added can remove the folder. 21 | -------------------------------------------------------------------------------- /html/debounce.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery throttle / debounce - v1.1 - 3/7/2010 3 | * http://benalman.com/projects/jquery-throttle-debounce-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this); 10 | -------------------------------------------------------------------------------- /server/main.lua: -------------------------------------------------------------------------------- 1 | QBCore = exports['qb-core']:GetCoreObject() 2 | 3 | 4 | QBCore.Functions.CreateCallback('qb-drivingschool:server:hasfunds', function(source, cb) 5 | local src = source 6 | local xPlayer = QBCore.Functions.GetPlayer(src) 7 | local bankamount = xPlayer.PlayerData.money["bank"] 8 | if bankamount >= Config.Price then 9 | xPlayer.Functions.RemoveMoney('bank', 500) 10 | cb(true) 11 | else 12 | cb(false) 13 | end 14 | end) 15 | 16 | 17 | 18 | RegisterServerEvent('qb-drivingschool:server:GetLicense', function() 19 | local src = source 20 | local Player = QBCore.Functions.GetPlayer(src) 21 | 22 | 23 | local info = {} 24 | info.firstname = Player.PlayerData.charinfo.firstname 25 | info.lastname = Player.PlayerData.charinfo.lastname 26 | info.birthdate = Player.PlayerData.charinfo.birthdate 27 | info.type = "Class C Driver License" 28 | Player.Functions.RemoveItem('driving_test_permit', 1) 29 | Player.Functions.AddItem('driver_license', 1, nil, info) 30 | -- add the meta to add license to true 31 | local licenses = {["driver"] = true, ['weapon'] = Player.PlayerData.metadata["licences"].weapon, ["business"] = Player.PlayerData.metadata["licences"].business} 32 | Player.Functions.SetMetaData("licences", licenses) 33 | TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items['driver_license'], 'add') 34 | end) 35 | 36 | QBCore.Functions.CreateCallback('qb-drivingschool:server:HasLicense', function(source, cb) 37 | local src = source 38 | local Player = QBCore.Functions.GetPlayer(src) 39 | local license = Player.PlayerData.metadata["licences"].driver 40 | if license then 41 | cb(true) 42 | else 43 | cb(false) 44 | end 45 | end) 46 | 47 | QBCore.Functions.CreateCallback('qb-drivingschool:server:HasPermit', function(source, cb) 48 | local src = source 49 | local Player = QBCore.Functions.GetPlayer(src) 50 | local permit = Player.Functions.GetItemByName("driving_test_permit") 51 | if permit ~= nil then 52 | cb(true) 53 | else 54 | cb(false) 55 | end 56 | end) 57 | 58 | RegisterNetEvent('qb-drivingschool:server:givepermit', function() 59 | Player = QBCore.Functions.GetPlayer(source) 60 | Player.Functions.AddItem('driving_test_permit', 1, nil, info) 61 | end) -------------------------------------------------------------------------------- /html/questions.js: -------------------------------------------------------------------------------- 1 | var tableauQuestion = [ 2 | { 3 | question: "If you're going 80 km/h, and you're approaching a residential area you must:", 4 | propositionA: "You accelerate", 5 | propositionB: "You keep your speed, if you do not pass other vehicles", 6 | propositionC: "You slow down", 7 | propositionD: "You keep your speed", 8 | reponse: "C" 9 | }, 10 | 11 | { 12 | question: "If you're turning right at a traffic light, but see a pedestrian crossing what do you do:", 13 | propositionA: "You pass the pedestrian", 14 | propositionB: "You check that there is no other vehicles around", 15 | propositionC: "You wait until the pedestrian has crossed", 16 | propositionD: "You shoot the pedestrian and continue to drive", 17 | reponse: "C" 18 | }, 19 | 20 | { 21 | question: "Without any prior indication, the speed in a residential area is: __ km/h", 22 | propositionA: "30 km/h", 23 | propositionB: "50 km/h", 24 | propositionC: "40 km/h", 25 | propositionD: "60 km/h", 26 | reponse: "B" 27 | }, 28 | 29 | { 30 | question: "Before every lane change you must:", 31 | propositionA: "Check your mirrors", 32 | propositionB: "Check your blind spots", 33 | propositionC: "Signal your intentions", 34 | propositionD: "All of the above", 35 | reponse: "D" 36 | }, 37 | 38 | { 39 | question: "What blood alcohol level is classified as driving while intoxicated?", 40 | propositionA: "0.05%", 41 | propositionB: "0.18%", 42 | propositionC: "0.08%", 43 | propositionD: "0.06%", 44 | reponse: "C" 45 | }, 46 | 47 | { 48 | question: "When can you continue to drive at a traffic light?", 49 | propositionA: "When it is green", 50 | propositionB: "When there is nobody in the intersection", 51 | propositionC: "You are in a school zone", 52 | propositionD: "When it is green and / or red and you're turning right", 53 | reponse: "D" 54 | }, 55 | 56 | { 57 | question: "A pedestrian has a do not cross signal, what do you do?", 58 | propositionA: "You let them pass", 59 | propositionB: "You observe before continuing", 60 | propositionC: "You wave to tell them to cross", 61 | propositionD: "You continue because your traffic light is green", 62 | reponse: "D" 63 | }, 64 | 65 | { 66 | question: "What is allowed when passing another vehicle", 67 | propositionA: "You follow it closely to pass it faster", 68 | propositionB: "You pass it without leaving the roadway", 69 | propositionC: "You drive on the opposite side of the road to pass", 70 | propositionD: "You exceed the speed limit to pass them", 71 | reponse: "C" 72 | }, 73 | 74 | { 75 | question: "You are driving on a highway which indicates a maximum speed of 120 km/h. But most trafficers drive at 125 km/h, so you should not drive faster than:", 76 | propositionA: "120 km/h", 77 | propositionB: "125 km/h", 78 | propositionC: "130 km/h", 79 | propositionD: "110 km/h", 80 | reponse: "A" 81 | }, 82 | 83 | { 84 | question: "When you are overtaken by another vehicle it is important NOT to:", 85 | propositionA: "Slow Down", 86 | propositionB: "Check your mirrors", 87 | propositionC: "Watch other drivers", 88 | propositionD: "Increase your speed", 89 | reponse: "D" 90 | }, 91 | ] 92 | -------------------------------------------------------------------------------- /html/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | overflow: hidden; 3 | font-family: 'Open Sans', sans-serif; 4 | } 5 | 6 | .full-screen { 7 | width: 100%; 8 | height: 100%; 9 | display: flex; 10 | align-items: center; 11 | } 12 | 13 | .question-container { 14 | width: 70%; 15 | height: 700px; 16 | background-color: rgba(0, 0, 0, 0.9); 17 | color: #fff; 18 | border: 1px solid #222; 19 | border-radius: 1px; 20 | margin-left: auto; 21 | margin-right: auto; 22 | overflow: hidden; 23 | z-index: 9999999; 24 | display: none; 25 | } 26 | 27 | .header { 28 | width: 100%; 29 | height: 20%; 30 | background-color: rgba(0, 0, 0, 0.8); 31 | color: #fff; 32 | display: flex; 33 | flex-direction: row; 34 | flex-wrap: wrap; 35 | justify-content: center; 36 | } 37 | 38 | .header .logo { 39 | width: 80%; 40 | max-height: 50%; 41 | margin: auto; 42 | margin-bottom: 0; 43 | } 44 | 45 | .header h1 { 46 | font-size: 2em; 47 | text-align: center; 48 | margin: auto; 49 | } 50 | 51 | .body { 52 | width: 100%; 53 | height: 80%; 54 | display: flex; 55 | flex-direction: row; 56 | flex-wrap: wrap; 57 | display: none; 58 | color: #fff; 59 | } 60 | 61 | .content { 62 | display: flex; 63 | flex-direction: column; 64 | align-self: center; 65 | width: 90%; 66 | height: 70%; 67 | margin: auto; 68 | border: none; 69 | } 70 | 71 | .content .logo { 72 | width: auto; 73 | max-height: auto; 74 | margin: auto; 75 | margin-bottom: 0; 76 | } 77 | 78 | .content h2, p { 79 | margin: 5px; 80 | } 81 | 82 | .buttonspot { 83 | display: flex; 84 | flex-direction: column; 85 | align-self: center; 86 | width: auto; 87 | height: 15%; 88 | margin: auto; 89 | border: none; 90 | } 91 | 92 | .buttonspot h2, p { 93 | margin: 5px; 94 | } 95 | 96 | .button { 97 | width: 30%; 98 | height: 40px; 99 | align-self: center; 100 | text-align: center; 101 | margin-left: 40px; 102 | margin-right: 40px; 103 | background: #ffffff; 104 | border-radius: 4px; 105 | color: #000000; 106 | font-size: 1.5em; 107 | text-decoration: none; 108 | } 109 | 110 | .form { 111 | display: flex; 112 | flex-direction: row; 113 | justify-content: center; 114 | flex-wrap: wrap; 115 | width: 100%; 116 | margin-top: 5px; 117 | } 118 | 119 | .form div { 120 | margin: auto; 121 | margin-top: 5px; 122 | margin-bottom: 5px; 123 | width: 100%; 124 | } 125 | 126 | .submit { 127 | align-items: center; 128 | height: 30px; 129 | text-align: center; 130 | width: 30%; 131 | background: #ffffff; 132 | border-radius: 4px; 133 | color: rgb(0, 0, 0); 134 | font-size: 1.2em; 135 | margin-top: 10px; 136 | } 137 | 138 | .barre-progression { 139 | width: 50%; 140 | align-self: center; 141 | height: 10%; 142 | margin: auto; 143 | margin-top: 0; 144 | display: flex; 145 | flex-wrap: wrap; 146 | justify-content: center; 147 | padding: 0; 148 | } 149 | 150 | .barre-progression h2 { 151 | width: 100%; 152 | height: 60%; 153 | text-align: center; 154 | display: flex; 155 | justify-content: center; 156 | align-items: center; 157 | margin: 0; 158 | color: #ffffff; 159 | } 160 | 161 | .progression { 162 | width: 80%; 163 | border: 0.2px solid white; 164 | background: rgba(0, 0, 0, 0.712); 165 | border-radius: 14px; 166 | box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.5), 0px 1px 0px rgba(255, 255, 255, 0.2); 167 | } 168 | 169 | .progression::-moz-progress-bar { 170 | background: rgb(0, 0, 0); 171 | border-radius: 14px; 172 | box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.4), 0 2px 5px 0px rgba(0, 0, 0, 0.3); 173 | } 174 | 175 | .progression::-webkit-progress-bar { 176 | background: transparent; 177 | } 178 | 179 | .progression::-webkit-progress-value { 180 | background: #ffffff; 181 | border-radius: 14px; 182 | box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.4), 0 2px 5px 0px rgba(0, 0, 0, 0.3); 183 | } 184 | 185 | .bold-text { 186 | font-weight: bold; 187 | font-size: 1.3em; 188 | } -------------------------------------------------------------------------------- /html/scripts.js: -------------------------------------------------------------------------------- 1 | // question variables 2 | var questionNumber = 1; 3 | var userAnswer = []; 4 | var goodAnswer = []; 5 | var questionUsed = []; 6 | var nbQuestionToAnswer = 10; // don't forget to change the progress bar max value in html 7 | var nbAnswerNeeded = 5; // out of nbQuestionToAnswer 8 | var nbPossibleQuestions = 10; // number of questions in database questions.js 9 | var lastClick = 0; 10 | 11 | function getRandomQuestion() { 12 | var random = Math.floor(Math.random() * nbPossibleQuestions); 13 | 14 | while (true) { 15 | if (questionUsed.indexOf(random) === -1) { 16 | break; 17 | } 18 | 19 | random = Math.floor(Math.random() * nbPossibleQuestions); 20 | } 21 | 22 | questionUsed.push(random); 23 | 24 | return random; 25 | } 26 | 27 | // Partial Functions 28 | function closeMain() { 29 | $(".home").css("display", "none"); 30 | } 31 | function openMain() { 32 | $(".home").css("display", "block"); 33 | } 34 | function closeAll() { 35 | $(".body").css("display", "none"); 36 | } 37 | function openQuestionnaire() { 38 | $(".questionnaire-container").css("display", "block"); 39 | var randomQuestion = getRandomQuestion(); 40 | 41 | $("#questionNumero").html("Question: " + questionNumber); 42 | $("#question").html(tableauQuestion[randomQuestion].question); 43 | $(".answerA").html(tableauQuestion[randomQuestion].propositionA); 44 | $(".answerB").html(tableauQuestion[randomQuestion].propositionB); 45 | $(".answerC").html(tableauQuestion[randomQuestion].propositionC); 46 | $(".answerD").html(tableauQuestion[randomQuestion].propositionD); 47 | $('input[name=question]').attr('checked', false); 48 | 49 | goodAnswer.push(tableauQuestion[randomQuestion].reponse); 50 | $(".questionnaire-container .progression").val(questionNumber - 1); 51 | } 52 | function openResultGood() { 53 | $(".resultGood").css("display", "block"); 54 | } 55 | function openResultBad() { 56 | $(".resultBad").css("display", "block"); 57 | } 58 | function openContainer() { 59 | $(".question-container").css("display", "block"); 60 | } 61 | function closeContainer() { 62 | $(".question-container").css("display", "none"); 63 | } 64 | 65 | // Listen for NUI Events 66 | window.addEventListener('message', function (event) { 67 | var item = event.data; 68 | 69 | // Open & Close main window 70 | if (item.openQuestion == true) { 71 | openContainer(); 72 | openMain(); 73 | } 74 | 75 | if (item.openQuestion == false) { 76 | closeContainer(); 77 | closeMain(); 78 | } 79 | 80 | // Open sub-windows / partials 81 | if (item.openSection == "question") { 82 | closeAll(); 83 | openQuestionnaire(); 84 | } 85 | }); 86 | 87 | // Handle Button Presses 88 | $(".btnQuestion").click(function () { 89 | $.post('http://qb-drivingschool/question', JSON.stringify({})); 90 | }); 91 | 92 | $(".btnClose").click(function () { 93 | $.post('http://qb-drivingschool/close', JSON.stringify({})); 94 | userAnswer = []; 95 | goodAnswer = []; 96 | questionUsed = []; 97 | questionNumber = 1; 98 | }); 99 | 100 | $(".btnKick").click(function () { 101 | $.post('http://qb-drivingschool/kick', JSON.stringify({})); 102 | userAnswer = []; 103 | goodAnswer = []; 104 | questionUsed = []; 105 | questionNumber = 1; 106 | }); 107 | 108 | // Handle Form Submits 109 | $("#question-form").submit(function (e) { 110 | e.preventDefault(); 111 | 112 | if (questionNumber != nbQuestionToAnswer) { 113 | //question 1 to 9: pushing answer in array 114 | closeAll(); 115 | userAnswer.push($('input[name="question"]:checked').val()); 116 | questionNumber++; 117 | openQuestionnaire(); 118 | } else { 119 | // question 10: comparing arrays and sending number of good answers 120 | userAnswer.push($('input[name="question"]:checked').val()); 121 | var nbGoodAnswer = 0; 122 | for (i = 0; i < nbQuestionToAnswer; i++) { 123 | if (userAnswer[i] == goodAnswer[i]) { 124 | nbGoodAnswer++; 125 | } 126 | } 127 | 128 | closeAll(); 129 | if (nbGoodAnswer >= nbAnswerNeeded) { 130 | openResultGood(); 131 | } else { 132 | openResultBad(); 133 | } 134 | } 135 | 136 | return false; 137 | }); 138 | -------------------------------------------------------------------------------- /html/ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 |
11 |

DRIVING SCHOOL

12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 |

Welcome to Driving School 20 |

21 |
22 |
All citizens of MUST pass their exam before they can drive. 23 |
Take your time, answer with common sense, and do not answer randomly. 24 |
25 |
Theory Test 26 |
- Don't be afraid, the driving school accepts credit, but be careful not to get into debt. 27 |
- If you fail your test the first time, you can retake it. 28 |
29 |
Driving Test 30 |
- Make sure you stay alert whilst driving, and avoid accidents and the speed limits!

31 |
32 |
Total Cost of Theory Test is $500 its the same for price for the Driving Test, this payment will not be refunded if you fail. 33 |
34 |
35 |
36 | Start 37 |
38 |
39 |

Progress

40 | 41 |
42 |
43 | 44 |
45 |
46 |

47 |

48 |
49 |
50 | 51 | 52 |
53 |
54 | 55 | 56 |
57 |
58 | 59 | 60 |
61 |
62 | 63 | 64 |
65 | 66 |
67 |
68 |
69 |

Progress

70 | 71 |
72 |
73 | 74 |
75 |
76 |
77 |

Good work!

78 |
79 |
You did well during the examination. 80 |
81 |
You can close this window, and go take your road test. 82 |
83 |
84 |
85 | Close 86 |
87 |
88 |

Progress

89 | 90 |
91 |
92 |
93 |
94 |
95 |

You failed

96 |
97 |
You weren't ready for this test, try again later... 98 |
99 |
100 |
101 |
102 |
103 | Close 104 |
105 |
106 |

Progress

107 | 108 |
109 |
110 |
111 |
112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | Config.DrawDistance = 100.0 3 | Config.MaxErrors = 5 4 | Config.SpeedMultiplier = 3.6 -- change this to 2.236936 for MPH 5 | Config.Price = 500 6 | 7 | Config.SpeedLimits = { 8 | residence = 50, 9 | town = 80, 10 | freeway = 120 11 | } 12 | 13 | 14 | Config.Vehicles = { 15 | ["blista"] = "blista", 16 | } 17 | 18 | Config.Zones = { 19 | DMVSchool = { 20 | Pos = {x = 239.62, y = -1381.08, z = 33.74}, 21 | Size = {x = 0.5, y = 0.3, z = 0.3}, 22 | Color = {r = 255, g = 255, b = 255}, 23 | Type = 20 24 | }, 25 | 26 | } 27 | 28 | Config.CheckPoints = { 29 | 30 | { 31 | Pos = {x = 255.139, y = -1400.731, z = 29.537}, 32 | Action = function(playerPed, vehicle, setCurrentZoneType) 33 | QBCore.Functions.Notify("Next Point Speed - " .. Config.SpeedLimits['residence'] .. " ", "success", 5000) 34 | 35 | end 36 | }, 37 | 38 | { 39 | Pos = {x = 271.874, y = -1370.574, z = 30.932}, 40 | Action = function(playerPed, vehicle, setCurrentZoneType) 41 | QBCore.Functions.Notify('Go to Next Point', "success", 5000) 42 | end 43 | }, 44 | 45 | { 46 | Pos = {x = 217.821, y = -1410.520, z = 28.292}, 47 | Action = function(playerPed, vehicle, setCurrentZoneType) 48 | setCurrentZoneType('town') 49 | 50 | Citizen.CreateThread(function() 51 | QBCore.Functions.Notify("Stop Look Left - " .. Config.SpeedLimits['town'] .. " ", "error", 5000) 52 | PlaySound(-1, 'RACE_PLACED', 'HUD_AWARDS', false, 0, true) 53 | FreezeEntityPosition(vehicle, true) 54 | Citizen.Wait(6000) 55 | 56 | FreezeEntityPosition(vehicle, false) 57 | QBCore.Functions.Notify('Good Turn Right', "success", 5000) 58 | end) 59 | end 60 | }, 61 | 62 | { 63 | Pos = {x = 178.550, y = -1401.755, z = 27.725}, 64 | Action = function(playerPed, vehicle, setCurrentZoneType) 65 | QBCore.Functions.Notify('Watch Traffic Light', "error", 5000) 66 | end 67 | }, 68 | 69 | { 70 | Pos = {x = 113.160, y = -1365.276, z = 27.725}, 71 | Action = function(playerPed, vehicle, setCurrentZoneType) 72 | QBCore.Functions.Notify('Go To Next Point', "success", 5000) 73 | end 74 | }, 75 | 76 | { 77 | Pos = {x = -73.542, y = -1364.335, z = 27.789}, 78 | Action = function(playerPed, vehicle, setCurrentZoneType) 79 | QBCore.Functions.Notify('Stop For Passing', "error", 5000) 80 | PlaySound(-1, 'RACE_PLACED', 'HUD_AWARDS', false, 0, true) 81 | end 82 | }, 83 | 84 | { 85 | Pos = {x = -355.143, y = -1420.282, z = 27.868}, 86 | Action = function(playerPed, vehicle, setCurrentZoneType) 87 | QBCore.Functions.Notify('Go To Next Point', "success", 5000) 88 | end 89 | }, 90 | 91 | { 92 | Pos = {x = -439.148, y = -1417.100, z = 27.704}, 93 | Action = function(playerPed, vehicle, setCurrentZoneType) 94 | QBCore.Functions.Notify('Go To Next Point', "success", 5000) 95 | end 96 | }, 97 | 98 | { 99 | Pos = {x = -453.790, y = -1444.726, z = 27.665}, 100 | Action = function(playerPed, vehicle, setCurrentZoneType) 101 | setCurrentZoneType('freeway') 102 | 103 | 104 | QBCore.Functions.Notify("Free way time - " .. Config.SpeedLimits['freeway'] .. " ", "error", 5000) 105 | PlaySound(-1, 'RACE_PLACED', 'HUD_AWARDS', false, 0, true) 106 | end 107 | }, 108 | 109 | { 110 | Pos = {x = -463.237, y = -1592.178, z = 37.519}, 111 | Action = function(playerPed, vehicle, setCurrentZoneType) 112 | QBCore.Functions.Notify('Go To Next Point', "success", 5000) 113 | end 114 | }, 115 | 116 | { 117 | Pos = {x = -900.647, y = -1986.28, z = 26.109}, 118 | Action = function(playerPed, vehicle, setCurrentZoneType) 119 | QBCore.Functions.Notify('Go To Next Point', "success", 5000) 120 | end 121 | }, 122 | 123 | { 124 | Pos = {x = 1225.759, y = -1948.792, z = 38.718}, 125 | Action = function(playerPed, vehicle, setCurrentZoneType) 126 | QBCore.Functions.Notify('Go To Next Point', "success", 5000) 127 | end 128 | }, 129 | 130 | { 131 | Pos = {x = 1225.759, y = -1948.792, z = 38.718}, 132 | Action = function(playerPed, vehicle, setCurrentZoneType) 133 | setCurrentZoneType('town') 134 | QBCore.Functions.Notify("In Town Speed - " .. Config.SpeedLimits['town'] .. " ", "error", 5000) 135 | end 136 | }, 137 | 138 | { 139 | Pos = {x = 1163.603, y = -1841.771, z = 35.679}, 140 | Action = function(playerPed, vehicle, setCurrentZoneType) 141 | QBCore.Functions.Notify('Stay Alert', "error", 5000) 142 | PlaySound(-1, 'RACE_PLACED', 'HUD_AWARDS', false, 0, true) 143 | end 144 | }, 145 | 146 | { 147 | Pos = {x = 235.283, y = -1398.329, z = 28.921}, 148 | Action = function(playerPed, vehicle, setCurrentZoneType) 149 | DeleteVehicle(vehicle) 150 | end 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /client/gui.lua: -------------------------------------------------------------------------------- 1 | Keys = { 2 | ["ESC"] = 322, ["F1"] = 288, ["F2"] = 289, ["F3"] = 170, ["F5"] = 166, ["F6"] = 167, ["F7"] = 168, ["F8"] = 169, ["F9"] = 56, ["F10"] = 57, 3 | ["~"] = 243, ["1"] = 157, ["2"] = 158, ["3"] = 160, ["4"] = 164, ["5"] = 165, ["6"] = 159, ["7"] = 161, ["8"] = 162, ["9"] = 163, ["-"] = 84, ["="] = 83, ["BACKSPACE"] = 177, 4 | ["TAB"] = 37, ["Q"] = 44, ["W"] = 32, ["E"] = 38, ["R"] = 45, ["T"] = 245, ["Y"] = 246, ["U"] = 303, ["P"] = 199, ["["] = 39, ["]"] = 40, ["ENTER"] = 18, 5 | ["CAPS"] = 137, ["A"] = 34, ["S"] = 8, ["D"] = 9, ["F"] = 23, ["G"] = 47, ["H"] = 74, ["K"] = 311, ["L"] = 182, 6 | ["LEFTSHIFT"] = 21, ["Z"] = 20, ["X"] = 73, ["C"] = 26, ["V"] = 0, ["B"] = 29, ["N"] = 249, ["M"] = 244, [","] = 82, ["."] = 81, 7 | ["LEFTCTRL"] = 36, ["LEFTALT"] = 19, ["SPACE"] = 22, ["RIGHTCTRL"] = 70, 8 | ["HOME"] = 213, ["PAGEUP"] = 10, ["PAGEDOWN"] = 11, ["DELETE"] = 178, 9 | ["LEFT"] = 174, ["RIGHT"] = 175, ["TOP"] = 27, ["DOWN"] = 173, 10 | ["NENTER"] = 201, ["N4"] = 108, ["N5"] = 60, ["N6"] = 107, ["N+"] = 96, ["N-"] = 97, ["N7"] = 117, ["N8"] = 61, ["N9"] = 118 11 | } 12 | 13 | Menu = {} 14 | Menu.GUI = {} 15 | Menu.buttonCount = 0 16 | Menu.selection = 0 17 | Menu.hidden = true 18 | MenuTitle = "Menu" 19 | 20 | function Menu.addButton(name, func,args,extra,damages,bodydamages,fuel) 21 | 22 | local yoffset = 0.25 23 | local xoffset = 0.3 24 | local xmin = 0.0 25 | local xmax = 0.15 26 | local ymin = 0.03 27 | local ymax = 0.03 28 | Menu.GUI[Menu.buttonCount+1] = {} 29 | if extra ~= nil then 30 | Menu.GUI[Menu.buttonCount+1]["extra"] = extra 31 | end 32 | if damages ~= nil then 33 | Menu.GUI[Menu.buttonCount+1]["damages"] = damages 34 | Menu.GUI[Menu.buttonCount+1]["bodydamages"] = bodydamages 35 | Menu.GUI[Menu.buttonCount+1]["fuel"] = fuel 36 | end 37 | 38 | 39 | Menu.GUI[Menu.buttonCount+1]["name"] = name 40 | Menu.GUI[Menu.buttonCount+1]["func"] = func 41 | Menu.GUI[Menu.buttonCount+1]["args"] = args 42 | Menu.GUI[Menu.buttonCount+1]["active"] = false 43 | Menu.GUI[Menu.buttonCount+1]["xmin"] = xmin 44 | Menu.GUI[Menu.buttonCount+1]["ymin"] = ymin * (Menu.buttonCount + 0.01) +yoffset 45 | Menu.GUI[Menu.buttonCount+1]["xmax"] = xmax 46 | Menu.GUI[Menu.buttonCount+1]["ymax"] = ymax 47 | Menu.buttonCount = Menu.buttonCount+1 48 | end 49 | 50 | 51 | function Menu.updateSelection() 52 | if IsControlJustPressed(1, Keys["DOWN"]) then 53 | if(Menu.selection < Menu.buttonCount -1 ) then 54 | Menu.selection = Menu.selection +1 55 | else 56 | Menu.selection = 0 57 | end 58 | PlaySound(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0, 0, 1) 59 | elseif IsControlJustPressed(1, Keys["TOP"]) then 60 | if(Menu.selection > 0)then 61 | Menu.selection = Menu.selection -1 62 | else 63 | Menu.selection = Menu.buttonCount-1 64 | end 65 | PlaySound(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0, 0, 1) 66 | elseif IsControlJustPressed(1, 215) then 67 | MenuCallFunction(Menu.GUI[Menu.selection +1]["func"], Menu.GUI[Menu.selection +1]["args"]) 68 | PlaySound(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0, 0, 1) 69 | end 70 | local iterator = 0 71 | for id, settings in ipairs(Menu.GUI) do 72 | Menu.GUI[id]["active"] = false 73 | if(iterator == Menu.selection ) then 74 | Menu.GUI[iterator +1]["active"] = true 75 | end 76 | iterator = iterator +1 77 | end 78 | end 79 | 80 | function Menu.renderGUI() 81 | if not Menu.hidden then 82 | Menu.renderButtons() 83 | Menu.updateSelection() 84 | end 85 | end 86 | 87 | function Menu.renderBox(xMin,xMax,yMin,yMax,color1,color2,color3,color4) 88 | DrawRect(0.7, yMin,0.15, yMax-0.002, color1, color2, color3, color4); 89 | end 90 | 91 | function Menu.renderButtons() 92 | 93 | local yoffset = 0.5 94 | local xoffset = 0 95 | 96 | 97 | 98 | for id, settings in pairs(Menu.GUI) do 99 | local screen_w = 0 100 | local screen_h = 0 101 | screen_w, screen_h = GetScreenResolution(0, 0) 102 | 103 | boxColor = {0, 0, 0, 200} 104 | local movetext = 0.0 105 | if(settings["extra"] == "Garage") then 106 | boxColor = {0, 0, 0, 200} 107 | elseif (settings["extra"] == "In Seizure") then 108 | boxColor = {0, 0, 0, 200} 109 | end 110 | 111 | if(settings["active"]) then 112 | boxColor = {216, 8, 36, 155} 113 | end 114 | 115 | 116 | if settings["extra"] ~= nil then 117 | 118 | SetTextFont(4) 119 | 120 | SetTextScale(0.34, 0.34) 121 | SetTextColour(255, 255, 255, 255) 122 | SetTextEntry("STRING") 123 | AddTextComponentString(settings["name"]) 124 | DrawText(0.63, (settings["ymin"] - 0.012 )) 125 | 126 | SetTextFont(4) 127 | SetTextScale(0.26, 0.26) 128 | SetTextColour(255, 255, 255, 255) 129 | SetTextEntry("STRING") 130 | AddTextComponentString(settings["extra"]) 131 | DrawText(0.730 + movetext, (settings["ymin"] - 0.011 )) 132 | 133 | 134 | SetTextFont(4) 135 | SetTextScale(0.28, 0.28) 136 | SetTextColour(11, 11, 11, 255) 137 | SetTextEntry("STRING") 138 | AddTextComponentString(settings["damages"]) 139 | DrawText(0.778, (settings["ymin"] - 0.012 )) 140 | 141 | SetTextFont(4) 142 | SetTextScale(0.28, 0.28) 143 | SetTextColour(11, 11, 11, 255) 144 | SetTextEntry("STRING") 145 | AddTextComponentString(settings["bodydamages"]) 146 | DrawText(0.815, (settings["ymin"] - 0.012 )) 147 | 148 | SetTextFont(4) 149 | SetTextScale(0.28, 0.28) 150 | SetTextColour(11, 11, 11, 255) 151 | SetTextEntry("STRING") 152 | AddTextComponentString(settings["fuel"]) 153 | DrawText(0.854, (settings["ymin"] - 0.012 )) 154 | 155 | 156 | 157 | DrawRect(0.832, settings["ymin"], 0.11, settings["ymax"]-0.002, 255,255,255,199) 158 | --Global.DrawRect(x, y, width, height, r, g, b, a) 159 | else 160 | SetTextFont(4) 161 | SetTextScale(0.31, 0.31) 162 | SetTextColour(255, 255, 255, 255) 163 | SetTextCentre(true) 164 | SetTextEntry("STRING") 165 | AddTextComponentString(settings["name"]) 166 | DrawText(0.7, (settings["ymin"] - 0.012 )) 167 | 168 | end 169 | 170 | Menu.renderBox(settings["xmin"] ,settings["xmax"], settings["ymin"], settings["ymax"],boxColor[1],boxColor[2],boxColor[3],boxColor[4]) 171 | end 172 | end 173 | 174 | -------------------------------------------------------------------------------------------------------------------- 175 | 176 | function ClearMenu() 177 | --Menu = {} 178 | Menu.GUI = {} 179 | Menu.buttonCount = 0 180 | Menu.selection = 0 181 | end 182 | 183 | function MenuCallFunction(fnc, arg) 184 | _G[fnc](arg) 185 | end -------------------------------------------------------------------------------- /client/main.lua: -------------------------------------------------------------------------------- 1 | QBCore = exports['qb-core']:GetCoreObject() 2 | local CurrentAction = nil 3 | local CurrentActionMsg = nil 4 | local CurrentActionData = nil 5 | local Licenses = {} 6 | local CurrentTest = nil 7 | local CurrentTestType = nil 8 | local CurrentVehicle = nil 9 | local CurrentCheckPoint, DriveErrors = 0, 0 10 | local LastCheckPoint = -1 11 | local CurrentBlip = nil 12 | local CurrentZoneType = nil 13 | local IsAboveSpeedLimit = false 14 | local LastVehicleHealth = nil 15 | 16 | 17 | RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() 18 | --print('Loaded Driving School') 19 | end) 20 | RegisterNetEvent('qb-drivingschool:start', function() 21 | StartTheoryTest() 22 | end) 23 | 24 | function DrawMissionText(msg, time) 25 | ClearPrints() 26 | BeginTextCommandPrint('STRING') 27 | AddTextComponentSubstringPlayerName(msg) 28 | EndTextCommandPrint(time, true) 29 | end 30 | 31 | function StartTheoryTest() 32 | CurrentTest = 'theory' 33 | SendNUIMessage({ 34 | openQuestion = true 35 | }) 36 | SetTimeout(200, function() 37 | SetNuiFocus(true, true) 38 | end) 39 | end 40 | 41 | function StopTheoryTest(success) 42 | CurrentTest = nil 43 | SendNUIMessage({ 44 | openQuestion = false 45 | }) 46 | SetNuiFocus(false) 47 | if success then 48 | QBCore.Functions.Notify("You passed theory test , Start your practical test!", "success", 5000) 49 | TriggerServerEvent('qb-drivingschool:server:givepermit') 50 | else 51 | QBCore.Functions.Notify("Failed Theory Test", "error") 52 | end 53 | end 54 | 55 | function StartDriveTest() 56 | local coords = { 57 | x = 231.36, 58 | y = -1394.49, 59 | z = 30.5, 60 | h = 239.94, 61 | } 62 | local plate = "TESTDRIVE" .. math.random(1111, 9999) 63 | QBCore.Functions.SpawnVehicle('blista', function(vehicle) 64 | SetVehicleNumberPlateText(vehicle, "TESTDRIVE" .. tostring(math.random(1000, 9999))) 65 | SetEntityHeading(vehicle, coords.h) 66 | exports['LegacyFuel']:SetFuel(vehicle, 100.0) 67 | Menu.hidden = true 68 | TaskWarpPedIntoVehicle(GetPlayerPed(-1), vehicle, -1) 69 | TriggerEvent("vehiclekeys:client:SetOwner", GetVehicleNumberPlateText(vehicle)) 70 | SetVehicleCustomPrimaryColour(vehicle, 0, 0, 0) 71 | SetVehicleEngineOn(vehicle, true, true) 72 | SetVehicleDirtLevel(vehicle) 73 | SetVehicleUndriveable(vehicle, false) 74 | WashDecalsFromVehicle(vehicle, 1.0) 75 | CurrentTest = 'drive' 76 | CurrentTestType = 'drive_test' 77 | CurrentCheckPoint = 0 78 | LastCheckPoint = -1 79 | CurrentZoneType = 'residence' 80 | DriveErrors = 0 81 | IsAboveSpeedLimit = false 82 | CurrentVehicle = vehicle 83 | LastVehicleHealth = GetEntityHealth(vehicle) 84 | end, coords, true) 85 | end 86 | 87 | function StopDriveTest(success) 88 | if success then 89 | QBCore.Functions.Notify("Passed Driving Test", "success") 90 | TriggerServerEvent('qb-drivingschool:server:GetLicense') 91 | else 92 | -- despawn current car and teleport them 93 | ped = PlayerPedId() 94 | RemoveBlip(CurrentBlip) 95 | DeleteVehicle(GetVehiclePedIsUsing(ped)) 96 | SetEntityCoords(ped, 235.283, -1398.329, 28.921) 97 | QBCore.Functions.Notify("Failed Driving Test", "error") 98 | end 99 | CurrentTest = nil 100 | CurrentTestType = nil 101 | end 102 | 103 | function SetCurrentZoneType(type) 104 | CurrentZoneType = type 105 | end 106 | 107 | RegisterNUICallback('question', function(data, cb) 108 | SendNUIMessage({ 109 | openSection = 'question' 110 | }) 111 | cb() 112 | end) 113 | 114 | RegisterNUICallback('close', function(data, cb) 115 | StopTheoryTest(true) 116 | cb() 117 | end) 118 | 119 | RegisterNUICallback('kick', function(data, cb) 120 | StopTheoryTest(false) 121 | cb() 122 | end) 123 | 124 | AddEventHandler('qb-drivingschool:hasEnteredMarker', function(zone) 125 | if zone == 'DMVSchool' then 126 | CurrentAction = 'dmvschool_menu' 127 | CurrentActionMsg = ('Press E to give driving test - $500') 128 | CurrentActionData = {} 129 | end 130 | end) 131 | 132 | AddEventHandler('qb-drivingschool:hasExitedMarker', function(zone) 133 | CurrentAction = nil 134 | end) 135 | 136 | 137 | -- Create Blips 138 | CreateThread(function() 139 | local blip = AddBlipForCoord(Config.Zones.DMVSchool.Pos.x, Config.Zones.DMVSchool.Pos.y, Config.Zones.DMVSchool.Pos.z) 140 | SetBlipSprite(blip, 525) 141 | SetBlipDisplay(blip, 4) 142 | SetBlipScale(blip, 0.7) 143 | SetBlipColour(blip, 4) 144 | SetBlipAsShortRange(blip, true) 145 | BeginTextCommandSetBlipName("STRING") 146 | AddTextComponentString('Driving School') 147 | EndTextCommandSetBlipName(blip) 148 | end) 149 | 150 | -- Display markers 151 | CreateThread(function() 152 | while true do 153 | Wait(0) 154 | local coords = GetEntityCoords(PlayerPedId()) 155 | for k, v in pairs(Config.Zones) do 156 | if (v.Type ~= -1 and GetDistanceBetweenCoords(coords, v.Pos.x, v.Pos.y, v.Pos.z, true) < Config.DrawDistance) then 157 | DrawMarker(v.Type, v.Pos.x, v.Pos.y, v.Pos.z, 0.0, 0.0, 0.0, 0, 0.0, 0.0, v.Size.x, v.Size.y, v.Size.z, v.Color.r, v.Color.g, v.Color.b, 100, false, true, 2, false, false, false, false) 158 | end 159 | end 160 | end 161 | end) 162 | 163 | -- Enter / Exit marker events 164 | CreateThread(function() 165 | while true do 166 | Wait(100) 167 | local coords = GetEntityCoords(PlayerPedId()) 168 | local isInMarker = false 169 | local currentZone = nil 170 | for k, v in pairs(Config.Zones) do 171 | if (GetDistanceBetweenCoords(coords, v.Pos.x, v.Pos.y, v.Pos.z, true) < v.Size.x) then 172 | isInMarker = true 173 | currentZone = k 174 | end 175 | end 176 | if (isInMarker and not HasAlreadyEnteredMarker) or (isInMarker and LastZone ~= currentZone) then 177 | HasAlreadyEnteredMarker = true 178 | LastZone = currentZone 179 | TriggerEvent('qb-drivingschool:hasEnteredMarker', currentZone) 180 | end 181 | if not isInMarker and HasAlreadyEnteredMarker then 182 | HasAlreadyEnteredMarker = false 183 | TriggerEvent('qb-drivingschool:hasExitedMarker', LastZone) 184 | end 185 | end 186 | end) 187 | 188 | -- Block UI 189 | CreateThread(function() 190 | while true do 191 | Wait(1) 192 | if CurrentTest == 'theory' then 193 | local playerPed = PlayerPedId() 194 | DisableControlAction(0, 1, true)-- LookLeftRight 195 | DisableControlAction(0, 2, true)-- LookUpDown 196 | DisablePlayerFiring(playerPed, true)-- Disable weapon firing 197 | DisableControlAction(0, 142, true)-- MeleeAttackAlternate 198 | DisableControlAction(0, 106, true)-- VehicleMouseControlOverride 199 | else 200 | Wait(500) 201 | end 202 | end 203 | end) 204 | 205 | -- Key Controls 206 | CreateThread(function() 207 | while true do 208 | Wait(0) 209 | if CurrentAction then 210 | helpText(CurrentActionMsg) 211 | if IsControlJustReleased(0, 38) then 212 | TriggerEvent('opendriving') 213 | CurrentAction = nil 214 | end 215 | else 216 | Wait(500) 217 | end 218 | end 219 | end) 220 | 221 | -- Drive test 222 | CreateThread(function() 223 | while true do 224 | Wait(0) 225 | if CurrentTest == 'drive' then 226 | local playerPed = PlayerPedId() 227 | local coords = GetEntityCoords(playerPed) 228 | local nextCheckPoint = CurrentCheckPoint + 1 229 | if Config.CheckPoints[nextCheckPoint] == nil then 230 | if DoesBlipExist(CurrentBlip) then 231 | RemoveBlip(CurrentBlip) 232 | end 233 | CurrentTest = nil 234 | QBCore.Functions.Notify("Driving Test Complete", "error") 235 | if DriveErrors < Config.MaxErrors then 236 | StopDriveTest(true) 237 | end 238 | else 239 | if CurrentCheckPoint ~= LastCheckPoint then 240 | if DoesBlipExist(CurrentBlip) then 241 | RemoveBlip(CurrentBlip) 242 | end 243 | CurrentBlip = AddBlipForCoord(Config.CheckPoints[nextCheckPoint].Pos.x, Config.CheckPoints[nextCheckPoint].Pos.y, Config.CheckPoints[nextCheckPoint].Pos.z) 244 | SetBlipRoute(CurrentBlip, 1) 245 | LastCheckPoint = CurrentCheckPoint 246 | end 247 | local distance = GetDistanceBetweenCoords(coords, Config.CheckPoints[nextCheckPoint].Pos.x, Config.CheckPoints[nextCheckPoint].Pos.y, Config.CheckPoints[nextCheckPoint].Pos.z, true) 248 | if distance <= 100.0 then 249 | DrawMarker(1, Config.CheckPoints[nextCheckPoint].Pos.x, Config.CheckPoints[nextCheckPoint].Pos.y, Config.CheckPoints[nextCheckPoint].Pos.z, 0.0, 0.0, 0.0, 0, 0.0, 0.0, 1.5, 1.5, 1.5, 102, 204, 102, 100, false, true, 2, false, false, false, false) 250 | end 251 | if distance <= 3.0 then 252 | Config.CheckPoints[nextCheckPoint].Action(playerPed, CurrentVehicle, SetCurrentZoneType) 253 | CurrentCheckPoint = CurrentCheckPoint + 1 254 | end 255 | if DriveErrors == Config.MaxErrors then 256 | StopDriveTest(false) 257 | end 258 | end 259 | else 260 | -- not currently taking driver test 261 | Wait(500) 262 | end 263 | end 264 | end) 265 | 266 | -- Speed / Damage control 267 | CreateThread(function() 268 | while true do 269 | Wait(10) 270 | if CurrentTest == 'drive' then 271 | local playerPed = PlayerPedId() 272 | if IsPedInAnyVehicle(playerPed, false) then 273 | local vehicle = GetVehiclePedIsIn(playerPed, false) 274 | local speed = GetEntitySpeed(vehicle) * Config.SpeedMultiplier 275 | local tooMuchSpeed = false 276 | for k, v in pairs(Config.SpeedLimits) do 277 | if CurrentZoneType == k and speed > v then 278 | tooMuchSpeed = true 279 | if not IsAboveSpeedLimit then 280 | DriveErrors = DriveErrors + 1 281 | IsAboveSpeedLimit = true 282 | QBCore.Functions.Notify("Driving Too Fast! Allowed Speed: " .. v .. " ", "error") 283 | QBCore.Functions.Notify("Mistakes - " .. DriveErrors .. " / " .. Config.MaxErrors .. " ", "error") 284 | end 285 | end 286 | end 287 | if not tooMuchSpeed then 288 | IsAboveSpeedLimit = false 289 | end 290 | local health = GetEntityHealth(vehicle) 291 | if health < LastVehicleHealth then 292 | DriveErrors = DriveErrors + 1 293 | QBCore.Functions.Notify("You damaged Vehicle!", "error") 294 | QBCore.Functions.Notify("Mistakes - " .. DriveErrors .. " / " .. Config.MaxErrors .. " ", "error") 295 | -- avoid stacking faults 296 | LastVehicleHealth = health 297 | Wait(1500) 298 | end 299 | end 300 | else 301 | -- not currently taking driver test 302 | Wait(500) 303 | end 304 | end 305 | end) 306 | 307 | helpText = function(msg) 308 | BeginTextCommandDisplayHelp('STRING') 309 | AddTextComponentSubstringPlayerName(msg) 310 | EndTextCommandDisplayHelp(0, false, true, -1) 311 | end 312 | 313 | RegisterNetEvent('opendriving', function() 314 | 315 | -- do a trigger to server toi see if meta is set to true for driver license 316 | QBCore.Functions.TriggerCallback('qb-drivingschool:server:HasLicense', function(HasItem) 317 | 318 | if HasItem then 319 | QBCore.Functions.Notify("Looks like you have already passed your test! Go to City Hall to get a new License", "error", 10000) 320 | else 321 | TriggerEvent('opendriving:theory') 322 | end 323 | end) 324 | end) 325 | 326 | 327 | RegisterNetEvent('opendriving:theory', function() 328 | QBCore.Functions.TriggerCallback('qb-drivingschool:server:hasfunds', function(HasFunds) 329 | if HasFunds then 330 | TriggerEvent('qb-drivingschool:client:starttest') 331 | else 332 | QBCore.Functions.Notify("Looks like you do not have enough funds in your bank!") 333 | end 334 | end) 335 | end) 336 | 337 | RegisterNetEvent('qb-drivingschool:client:starttest', function() 338 | QBCore.Functions.TriggerCallback('qb-drivingschool:server:HasPermit', function(HasItem) 339 | if HasItem then 340 | StartDriveTest() 341 | else 342 | QBCore.Functions.Notify("You cannot close screen until and unless you give theory test", "error") 343 | StartTheoryTest() 344 | end 345 | end) 346 | end) --------------------------------------------------------------------------------