Auth becomes RESIDENT INTAKE (form LS-01), character creation an identity-record intake with a live paper card (LS-02), spawn select a county survey grid with five entry points (LS-03). Live validation, click-drag character rotation, animated transitions throughout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
209 lines
7.8 KiB
Lua
209 lines
7.8 KiB
Lua
-- JustRP — Server-side core
|
|
-- All DB operations go through the internal Python API on :8787
|
|
|
|
local API_BASE = "http://127.0.0.1:8787"
|
|
local API_KEY = "JustRP_Internal_2026_Secret"
|
|
|
|
-- 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.")
|