Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 216 additions & 0 deletions [esx_addons]/esx_dmvschoolv2/client.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
local currentSession = nil

RegisterCommand('spawndui', function()
local playerPed = PlayerPedId()
local coords = GetEntityCoords(playerPed)
local heading = GetEntityHeading(playerPed)

local propModel = `prop_laptop_lester`
RequestModel(propModel)
while not HasModelLoaded(propModel) do
Wait(0)
end

local prop = CreateObject(propModel, coords.x + 1, coords.y + 1, coords.z, false, false, false)
SetEntityHeading(prop, heading)
FreezeEntityPosition(prop, true)

local resourceName = GetCurrentResourceName()
local page = 'progress'
local duiUrl = 'https://cfx-nui-' .. resourceName .. '/web/dist/index.html?page=' .. page .. '&dui=yes'
local duiWidth, duiHeight = 1920, 1080
local dui = CreateDui(duiUrl, duiWidth, duiHeight)
local duiHandle = GetDuiHandle(dui)

local txd = CreateRuntimeTxd('duiTxd_' .. prop)
local tx = CreateRuntimeTextureFromDuiHandle(txd, 'duiTex', duiHandle)
AddReplaceTexture('prop_laptop_lester', 'prop_lester_screen', 'duiTxd_' .. prop, 'duiTex')

local propCoords = GetEntityCoords(prop)
local propHeading = GetEntityHeading(prop)
local radians = math.rad(propHeading)
local camX = propCoords.x + math.sin(radians) * 0.3
local camY = propCoords.y - math.cos(radians) * 0.3
local camZ = propCoords.z + 0.2

local cam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
SetCamCoord(cam, camX, camY, camZ)
PointCamAtCoord(cam, propCoords.x, propCoords.y, propCoords.z + 0.2)
SetCamActive(cam, true)
RenderScriptCams(true, false, 0, true, true)

currentSession = {
prop = prop,
dui = dui,
cam = cam,
width = duiWidth,
height = duiHeight,
isActive = true,
lastCursorX = 0,
lastCursorY = 0
}

SetNuiFocus(true, false)

CreateThread(function()
while currentSession and currentSession.isActive do
DisableAllControlActions(0)

local screenWidth, screenHeight = GetActiveScreenResolution()
local cursorX, cursorY = GetNuiCursorPosition()

local duiX = math.floor((cursorX / screenWidth) * currentSession.width)
local duiY = math.floor((cursorY / screenHeight) * currentSession.height)
duiX = math.max(0, math.min(currentSession.width, duiX))
duiY = math.max(0, math.min(currentSession.height, duiY))

if duiX ~= currentSession.lastCursorX or duiY ~= currentSession.lastCursorY then
SendDuiMouseMove(currentSession.dui, duiX, duiY)
currentSession.lastCursorX = duiX
currentSession.lastCursorY = duiY
end

if IsDisabledControlJustPressed(0, 24) then
SendDuiMouseDown(currentSession.dui, "left")
end
if IsDisabledControlJustReleased(0, 24) then
SendDuiMouseUp(currentSession.dui, "left")
end

if IsDisabledControlJustPressed(0, 25) then
SendDuiMouseDown(currentSession.dui, "right")
end
if IsDisabledControlJustReleased(0, 25) then
SendDuiMouseUp(currentSession.dui, "right")
end

if IsDisabledControlJustPressed(0, 241) then
SendDuiMouseWheel(currentSession.dui, 100, 0)
end
if IsDisabledControlJustPressed(0, 242) then
SendDuiMouseWheel(currentSession.dui, -100, 0)
end

Wait(0)
end

SetNuiFocus(false, false)
end)
end, false)

RegisterCommand('dmvsdtui', function()
SendNUIMessage({
action = 'openPage',
data = {
page = 'studentdrivingtest'
}
})
print('Opening Student Driving Test UI')
end, false)

RegisterNUICallback("ready", function(_, cb)
cb({
config = {
licenses = {
['motorcycle'] = {
label = 'Motorcycle License',
price = 500,
category = 'A',
imageSrc = 'assets/motorcycle.png'
},
['car'] = {
label = 'Car License',
price = 1500,
category = 'B',
imageSrc = 'assets/car.png'
},
['truck'] = {
label = 'Truck License',
price = 2500,
category = 'C',
imageSrc = 'assets/truck.png'
}
},
questions = {
{
question = "Question 1?",
options = {"Option 1", "Option 2", "Option 3", "Option 4"},
selected = 0,
imageSrc = nil
}
},
progressdata = {
lessons = {
{ label = "Lesson 1", completed = true },
{ label = "Lesson 2", completed = true },
},
tests = {
{ label = "Test 1", completed = false },
{ label = "Test 2", completed = false }
},
progress = 50
},
licenseresult = {
licensed = true,
fullname = "John Doe",
age = 25,
category = "B",
progress = 100
},
resourceName = GetCurrentResourceName(),
studentDrivingTest = {
maxMistakes = 5,
currentObjective = "Wait for instructor",
progress = 0,
checkpointsLeft = 10,
mistakes = 0
}
}
})
end)

AddEventHandler('onResourceStop', function(resourceName)
if GetCurrentResourceName() == resourceName and currentSession then
SetNuiFocus(false, false)
currentSession.isActive = false

if DoesEntityExist(currentSession.prop) then
DeleteEntity(currentSession.prop)
end
DestroyDui(currentSession.dui)
RenderScriptCams(false, false, 0, true, true)
DestroyCam(currentSession.cam, false)

currentSession = nil
end
end)

RegisterCommand('killdmvdui', function()
if currentSession then
SetNuiFocus(false, false)
currentSession.isActive = false

if DoesEntityExist(currentSession.prop) then
DeleteEntity(currentSession.prop)
end
DestroyDui(currentSession.dui)
RenderScriptCams(false, false, 0, true, true)
DestroyCam(currentSession.cam, false)

currentSession = nil
end
end, false)

RegisterCommand('dmvsdtstoptimer', function(source, args)
SendNUIMessage({
action = 'startTimer',
})
print('Timer started')
end, false)

RegisterCommand('dmvsdtstarttimer', function()
SendNUIMessage({
action = 'stopTimer'
})
print('Timer stopped')
end, false)
14 changes: 14 additions & 0 deletions [esx_addons]/esx_dmvschoolv2/fxmanifest.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
fx_version 'cerulean'
game 'gta5'

client_scripts {
'client.lua'
}

files {
'web/dist/index.html',
'web/dist/assets/*.*',
'web/dist/**/*.*'
}

ui_page 'web/dist/index.html'
3 changes: 3 additions & 0 deletions [esx_addons]/esx_dmvschoolv2/web/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[*]
indent_style = tab
indent_size = 4
24 changes: 24 additions & 0 deletions [esx_addons]/esx_dmvschoolv2/web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
bun.lock
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions [esx_addons]/esx_dmvschoolv2/web/dist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>esx_dmvschool</title>
<script type="module" crossorigin src="./assets/index-CSSZRfrO.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-D0lcKGTg.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
12 changes: 12 additions & 0 deletions [esx_addons]/esx_dmvschoolv2/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>esx_dmvschool</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
27 changes: 27 additions & 0 deletions [esx_addons]/esx_dmvschoolv2/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"watch": "vite build --watch",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"preact": "^10.26.9"
},
"devDependencies": {
"@preact/preset-vite": "^2.10.2",
"autoprefixer": "^10.4.21",
"baseline-browser-mapping": "^2.9.19",
"eslint": "^9.38.0",
"eslint-config-preact": "^2.0.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.18",
"typescript": "^5.9.3",
"vite": "^7.0.4"
},
"eslintConfig": {
"extends": "preact"
}
}
6 changes: 6 additions & 0 deletions [esx_addons]/esx_dmvschoolv2/web/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions [esx_addons]/esx_dmvschoolv2/web/src/components/Cursor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useState, useEffect } from 'preact/hooks';

interface CursorProps {
color?: string;
size?: number;
}

export function Cursor({ color = '#fb9b04', size = 16 }: CursorProps) {
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
const [isClicking, setIsClicking] = useState(false);

useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setCursorPos({ x: e.clientX, y: e.clientY });
};

const handleMouseDown = () => setIsClicking(true);
const handleMouseUp = () => setIsClicking(false);

window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mousedown', handleMouseDown);
window.addEventListener('mouseup', handleMouseUp);

return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mousedown', handleMouseDown);
window.removeEventListener('mouseup', handleMouseUp);
};
}, []);

return (
<>
{/* Outer ring */}
<div
className="fixed pointer-events-none z-[9999] rounded-full border-2 transition-transform duration-150 ease-out"
style={{
left: `${cursorPos.x}px`,
top: `${cursorPos.y}px`,
width: `${size * 2}px`,
height: `${size * 2}px`,
marginLeft: `${-size}px`,
marginTop: `${-size}px`,
borderColor: color,
transform: `scale(${isClicking ? 0.8 : 1})`,
boxShadow: `0 0 20px ${color}40`,
}}
/>

{/* Inner dot */}
<div
className="fixed pointer-events-none z-[10000] rounded-full transition-transform duration-150 ease-out"
style={{
left: `${cursorPos.x}px`,
top: `${cursorPos.y}px`,
width: `${size / 2}px`,
height: `${size / 2}px`,
marginLeft: `${-size / 4}px`,
marginTop: `${-size / 4}px`,
backgroundColor: color,
transform: `scale(${isClicking ? 1.5 : 1})`,
boxShadow: `0 0 10px ${color}`,
}}
/>
</>
);
}
Loading