Files
redl-fivem-rp/resources/justrp/server/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

211 lines
7.9 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- JustRP — Server-side core
-- All DB operations go through the internal Python API on :8787
local API_BASE = "http://127.0.0.1:8787"
-- Должен совпадать с JUSTRP_API_SECRET у Python-API. Задаётся в server.cfg:
-- set justrp_api_key "ваш-ключ"
local API_KEY = GetConvar("justrp_api_key", "change-me")
-- Per-player session state
local Sessions = {}
-- ─── HTTP helper ────────────────────────────────────────────────────────────
local function apiRequest(method, path, data, cb)
PerformHttpRequest(
API_BASE .. path,
function(status, body, _headers)
local ok, result = pcall(json.decode, body or "{}")
if ok then
cb(status, result)
else
cb(status, { success = false, error = "parse error" })
end
end,
method,
data and json.encode(data) or "",
{
["Content-Type"] = "application/json",
["X-API-Key"] = API_KEY,
}
)
end
-- ─── Player connecting ───────────────────────────────────────────────────────
AddEventHandler("playerConnecting", function(name, setKickReason, deferrals)
local src = source
local license = GetPlayerIdentifierByType(src, "license") or
GetPlayerIdentifierByType(src, "license2") or
"license:" .. tostring(src)
deferrals.defer()
Wait(0)
deferrals.update("Checking account…")
apiRequest("GET", "/account?license=" .. license, nil, function(status, res)
if status == 200 and res.success then
if res.account.banned == 1 then
deferrals.done("You are banned from JustRP.\nReason: " .. (res.account.ban_reason or "No reason given"))
else
deferrals.done()
end
else
-- No account yet — let them through, they'll register in-game
deferrals.done()
end
end)
end)
-- ─── Client ready → start session ───────────────────────────────────────────
RegisterNetEvent("justrp:clientReady")
AddEventHandler("justrp:clientReady", function()
local src = source
local license = GetPlayerIdentifierByType(src, "license") or
GetPlayerIdentifierByType(src, "license2") or
"license:" .. tostring(src)
Sessions[src] = { license = license, accountId = nil, charId = nil }
TriggerClientEvent("justrp:startSession", src)
end)
-- ─── Auth events ────────────────────────────────────────────────────────────
RegisterNetEvent("justrp:auth:register")
AddEventHandler("justrp:auth:register", function(username, password)
local src = source
local ses = Sessions[src]
if not ses then return end
apiRequest("POST", "/auth/register", {
license = ses.license,
username = username,
password = password,
}, function(status, res)
if res.success then
ses.accountId = res.account_id
TriggerClientEvent("justrp:auth:ok", src, res.account_id, res.username)
else
TriggerClientEvent("justrp:auth:fail", src, res.error or "Unknown error")
end
end)
end)
RegisterNetEvent("justrp:auth:login")
AddEventHandler("justrp:auth:login", function(username, password)
local src = source
local ses = Sessions[src]
if not ses then return end
apiRequest("POST", "/auth/login", {
license = ses.license,
username = username,
password = password,
}, function(status, res)
if res.success then
ses.accountId = res.account_id
TriggerClientEvent("justrp:auth:ok", src, res.account_id, res.username)
else
TriggerClientEvent("justrp:auth:fail", src, res.error or "Unknown error")
end
end)
end)
-- ─── Character events ────────────────────────────────────────────────────────
RegisterNetEvent("justrp:chars:load")
AddEventHandler("justrp:chars:load", function()
local src = source
local ses = Sessions[src]
if not ses or not ses.accountId then
TriggerClientEvent("justrp:chars:list", src, {})
return
end
apiRequest("GET", "/characters?account_id=" .. ses.accountId, nil, function(status, res)
if res.success then
TriggerClientEvent("justrp:chars:list", src, res.characters or {})
else
TriggerClientEvent("justrp:chars:list", src, {})
end
end)
end)
RegisterNetEvent("justrp:char:create")
AddEventHandler("justrp:char:create", function(data)
local src = source
local ses = Sessions[src]
if not ses or not ses.accountId then return end
data.account_id = ses.accountId
apiRequest("POST", "/character/create", data, function(status, res)
if res.success then
ses.charId = res.character_id
TriggerClientEvent("justrp:char:created", src, res.character_id)
else
TriggerClientEvent("justrp:char:createFail", src, res.error or "Unknown error")
end
end)
end)
RegisterNetEvent("justrp:char:select")
AddEventHandler("justrp:char:select", function(charId)
local src = source
local ses = Sessions[src]
if not ses or not ses.accountId then return end
apiRequest("GET", "/character?id=" .. tostring(charId), nil, function(status, res)
if res.success and res.character then
local ch = res.character
if ch.account_id ~= ses.accountId then
return -- character doesn't belong to this account
end
ses.charId = charId
TriggerClientEvent("justrp:char:loaded", src, ch)
end
end)
end)
-- ─── Spawn ───────────────────────────────────────────────────────────────────
RegisterNetEvent("justrp:spawn:request")
AddEventHandler("justrp:spawn:request", function(spawnKey)
local src = source
local ses = Sessions[src]
if not ses or not ses.charId then return end
local spawns = {
legion = { x = -166.7, y = -928.9, z = 31.4, h = 120.0 },
vespucci = { x = -1380.0, y = -1520.0, z = 4.9, h = 75.0 },
sandy = { x = 1843.4, y = 3683.0, z = 34.3, h = 200.0 },
paleto = { x = -165.8, y = 6330.0, z = 31.5, h = 270.0 },
airport = { x = -1037.0, y = -2738.0, z = 20.0, h = 330.0 },
}
local pos = spawns[spawnKey] or spawns["legion"]
TriggerClientEvent("justrp:spawn:go", src, pos)
end)
-- ─── Auto-save position every 60s ────────────────────────────────────────────
RegisterNetEvent("justrp:savePosition")
AddEventHandler("justrp:savePosition", function(x, y, z, h)
local src = source
local ses = Sessions[src]
if not ses or not ses.charId then return end
apiRequest("POST", "/character/save", {
character_id = ses.charId,
last_x = x,
last_y = y,
last_z = z,
last_heading = h,
}, function() end)
end)
-- ─── Disconnect ──────────────────────────────────────────────────────────────
AddEventHandler("playerDropped", function()
local src = source
Sessions[src] = nil
end)
print("[JustRP] Server core loaded.")