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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
968d047f87
commit
0144e83f05
@@ -0,0 +1,343 @@
|
||||
-- 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.")
|
||||
Reference in New Issue
Block a user