-- JustRP — Client-side core -- Manages NUI screens, cameras, spawning and position sync local inSession = false local charLoaded = false local camHandle = nil local saveTimer = 0 -- ─── NUI helper ───────────────────────────────────────────────────────────── local function nuiMsg(action, data) SendNUIMessage({ action = action, data = data or {} }) end -- ─── On resource start (loading screen is already showing) ────────────────── AddEventHandler("onClientMapStart", function() Wait(1500) -- give the game a moment to settle TriggerServerEvent("justrp:clientReady") end) -- ─── Session start from server ─────────────────────────────────────────────── RegisterNetEvent("justrp:startSession") AddEventHandler("justrp:startSession", function() -- Freeze world while auth is happening SetEntityVisible(PlayerPedId(), false, false) FreezeEntityPosition(PlayerPedId(), true) DisplayHud(false) DisplayRadar(false) -- Shut loading screen, then show auth UI Wait(800) ShutdownLoadingScreenNui() Wait(300) SetNuiFocus(true, true) nuiMsg("showAuth") inSession = true end) -- ─── Auth results ──────────────────────────────────────────────────────────── RegisterNetEvent("justrp:auth:ok") AddEventHandler("justrp:auth:ok", function(accountId, username) nuiMsg("authOk", { accountId = accountId, username = username }) TriggerServerEvent("justrp:chars:load") end) RegisterNetEvent("justrp:auth:fail") AddEventHandler("justrp:auth:fail", function(err) nuiMsg("authFail", { error = err }) end) -- ─── Character list from server ────────────────────────────────────────────── RegisterNetEvent("justrp:chars:list") AddEventHandler("justrp:chars:list", function(chars) nuiMsg("showChars", { characters = chars }) end) -- ─── Character creation result ─────────────────────────────────────────────── RegisterNetEvent("justrp:char:created") AddEventHandler("justrp:char:created", function(charId) -- Reload character list so the new one shows TriggerServerEvent("justrp:chars:load") end) RegisterNetEvent("justrp:char:createFail") AddEventHandler("justrp:char:createFail", function(err) nuiMsg("charCreateFail", { error = err }) end) -- ─── Character selected → load appearance ──────────────────────────────────── RegisterNetEvent("justrp:char:loaded") AddEventHandler("justrp:char:loaded", function(ch) -- Apply appearance to player ped local ped = PlayerPedId() local model = ch.gender == 1 and GetHashKey("mp_f_freemode_01") or GetHashKey("mp_m_freemode_01") RequestModel(model) while not HasModelLoaded(model) do Wait(10) end SetPlayerModel(PlayerId(), model) SetModelAsNoLongerNeeded(model) ped = PlayerPedId() -- Face blend SetPedHeadBlendData( ped, math.floor(ch.face_blend * 45), math.floor(ch.face_blend * 45), 0, math.floor(ch.face_blend * 45), math.floor(ch.face_blend * 45), 0, ch.face_blend, ch.face_blend, 0.0, false ) -- Skin overlay (skin tone) SetPedMicroblendData(ped, ch.skin_tone, ch.skin_tone, ch.skin_tone) -- Eyes SetPedEyeColor(ped, ch.eye_color) -- Hair SetPedComponentVariation(ped, 2, ch.hair_style, 0, 2) SetPedHairColor(ped, ch.hair_color, 0) -- Body shape SetPedFaceFeature(ped, 16, (ch.body_weight - 0.5) * 2) -- body weight feature -- Outfit SetPedComponentVariation(ped, 11, ch.outfit_top, 0, 2) SetPedComponentVariation(ped, 4, ch.outfit_pants, 0, 2) SetPedComponentVariation(ped, 6, ch.outfit_shoes, 0, 2) -- Tell NUI to show spawn screen nuiMsg("showSpawn", { name = ch.firstname .. " " .. ch.lastname, }) end) -- ─── Spawn ─────────────────────────────────────────────────────────────────── RegisterNetEvent("justrp:spawn:go") AddEventHandler("justrp:spawn:go", function(pos) local ped = PlayerPedId() -- Teleport to position and unfreeze SetEntityCoords(ped, pos.x, pos.y, pos.z, false, false, false, true) SetEntityHeading(ped, pos.h) -- Cinematic camera fly-down local camX = pos.x + math.sin(math.rad(pos.h)) * 200 local camY = pos.y + math.cos(math.rad(pos.h)) * 200 camHandle = CreateCameraWithParams( "DEFAULT_SCRIPTED_CAMERA", camX, camY, pos.z + 180, 0.0, 0.0, 0.0, 60.0, false, 0 ) SetCamActive(camHandle, true) RenderScriptCams(true, true, 1000, true, true) -- Animate camera flying down to player local steps = 80 Citizen.CreateThread(function() for i = 1, steps do local t = i / steps local ease = 1 - math.pow(1 - t, 3) -- ease out cubic local cx = camX + (pos.x - camX) * ease local cy = camY + (pos.y - camY) * ease local cz = (pos.z + 180) + ((pos.z + 2) - (pos.z + 180)) * ease SetCamCoord(camHandle, cx, cy, cz) PointCamAtCoord(camHandle, pos.x, pos.y, pos.z) Wait(16) end -- Transition back to player cam RenderScriptCams(false, true, 1200, true, true) Wait(1300) DestroyCam(camHandle, false) camHandle = nil -- Reveal player SetEntityVisible(ped, true, false) FreezeEntityPosition(ped, false) DisplayHud(true) DisplayRadar(true) SetNuiFocus(false, false) nuiMsg("hide") charLoaded = true end) end) -- ─── NUI Callbacks ─────────────────────────────────────────────────────────── -- Auth RegisterNUICallback("authRegister", function(data, cb) TriggerServerEvent("justrp:auth:register", data.username, data.password) cb("ok") end) RegisterNUICallback("authLogin", function(data, cb) TriggerServerEvent("justrp:auth:login", data.username, data.password) cb("ok") end) -- Character management RegisterNUICallback("charSelect", function(data, cb) TriggerServerEvent("justrp:char:select", tonumber(data.id)) cb("ok") end) RegisterNUICallback("charCreate", function(data, cb) TriggerServerEvent("justrp:char:create", data) cb("ok") end) -- Spawn selection RegisterNUICallback("spawnRequest", function(data, cb) TriggerServerEvent("justrp:spawn:request", data.location) cb("ok") end) -- Character creation — camera rotation control RegisterNUICallback("rotateCam", function(data, cb) if camHandle then -- data.delta is horizontal rotation amount local heading = GetEntityHeading(PlayerPedId()) SetEntityHeading(PlayerPedId(), heading - (data.delta or 0) * 0.4) end cb("ok") end) -- ─── Character creation preview camera ─────────────────────────────────────── local charCamActive = false local charCamAngle = 0.0 RegisterNUICallback("startCharCam", function(data, cb) if charCamActive then cb("ok") return end charCamActive = true local ped = PlayerPedId() SetEntityVisible(ped, true, false) camHandle = CreateCamera("DEFAULT_SCRIPTED_CAMERA", false) SetCamActive(camHandle, true) RenderScriptCams(true, false, 0, true, true) Citizen.CreateThread(function() while charCamActive do local pedPos = GetEntityCoords(ped) local angle = charCamAngle local dist = 1.6 local cx = pedPos.x + math.sin(math.rad(angle)) * dist local cy = pedPos.y - math.cos(math.rad(angle)) * dist local cz = pedPos.z + 0.7 SetCamCoord(camHandle, cx, cy, cz) PointCamAtCoord(camHandle, pedPos.x, pedPos.y, pedPos.z + 0.7) charCamAngle = charCamAngle + 0.15 -- slow auto-orbit Wait(16) end end) cb("ok") end) RegisterNUICallback("stopCharCam", function(data, cb) charCamActive = false if camHandle then RenderScriptCams(false, true, 600, true, true) Wait(700) DestroyCam(camHandle, false) camHandle = nil end cb("ok") end) RegisterNUICallback("charCamDrag", function(data, cb) charCamAngle = charCamAngle + (data.delta or 0) * 0.5 charCamActive = true -- stop auto-orbit briefly cb("ok") end) -- Apply live appearance updates during char creation RegisterNUICallback("applyAppearance", function(data, cb) local ped = PlayerPedId() if data.gender ~= nil then local model = data.gender == 1 and GetHashKey("mp_f_freemode_01") or GetHashKey("mp_m_freemode_01") if not HasModelLoaded(model) then RequestModel(model) while not HasModelLoaded(model) do Wait(10) end end SetPlayerModel(PlayerId(), model) SetModelAsNoLongerNeeded(model) ped = PlayerPedId() end if data.face_blend ~= nil then SetPedHeadBlendData( ped, math.floor(data.face_blend * 45), math.floor(data.face_blend * 45), 0, math.floor(data.face_blend * 45), math.floor(data.face_blend * 45), 0, data.face_blend, data.face_blend, 0.0, false ) end if data.skin_tone ~= nil then SetPedMicroblendData(ped, data.skin_tone, data.skin_tone, data.skin_tone) end if data.eye_color ~= nil then SetPedEyeColor(ped, data.eye_color) end if data.hair_style ~= nil or data.hair_color ~= nil then SetPedComponentVariation(ped, 2, data.hair_style or 0, 0, 2) SetPedHairColor(ped, data.hair_color or 0, 0) end if data.body_weight ~= nil then SetPedFaceFeature(ped, 16, (data.body_weight - 0.5) * 2) end if data.outfit_top ~= nil then SetPedComponentVariation(ped, 11, data.outfit_top, 0, 2) end if data.outfit_pants ~= nil then SetPedComponentVariation(ped, 4, data.outfit_pants, 0, 2) end if data.outfit_shoes ~= nil then SetPedComponentVariation(ped, 6, data.outfit_shoes, 0, 2) end cb("ok") end) -- ─── Position auto-save ─────────────────────────────────────────────────────── Citizen.CreateThread(function() while true do Wait(60000) if charLoaded then local ped = PlayerPedId() local pos = GetEntityCoords(ped) local hdg = GetEntityHeading(ped) TriggerServerEvent("justrp:savePosition", pos.x, pos.y, pos.z, hdg) end end end) print("[JustRP] Client core loaded.")