Files
redl-fivem-rp/resources/justrp/client/main.lua
T
RedlHostingandClaude Opus 5 0144e83f05 Part 1: registration, login and character creation written from scratch
Всё, что здесь лежит, написал ИИ-агент на живом сервере под запись — без ESX,
без QBCore, без скачанных с форума скриптов.

- resources/justrp — свой ресурс на Lua: регистрация, вход, создание персонажа, спавн
- resources/justrp/html — экраны NUI: кинематографичная загрузка, вход, редактор персонажа
- api/api.py — внутренний HTTP-API к MariaDB, пароли через scrypt
- server.cfg.example — пример конфига

Секреты (пароль БД, внутренний ключ API) вынесены в переменные окружения и convar.
Ключ Cfx.re в репозиторий не попадает.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 03:28:58 +00:00

344 lines
11 KiB
Lua

-- 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.")