The server itself: repo now mirrors the machine it runs on, plus install.sh

The repository carried two different servers side by side - the early
`justrp` prototype with its Python auth API, and a snapshot of the real
one under `server-code/`. Only the second one is a server anyone should
start from, so the prototype is gone and the real one moved to the root.

Taken from the live box, so the UI is the finished version (the tokens,
the county-records sheets and the variable fonts landed after the last
snapshot was pushed):

  resources/[rp]/   rp_db, rp_core, rp_session, rp_loading, rp_ui,
                    rp_selftest, rp_dbtest
  bin/              supervise.sh (keep-alive + console FIFO), rcon, init script
  etc/schema.sql    accounts, characters, transactions, inventory, vehicles
  assets/fonts/     the bundled subsets
  docs/SERVER.md    how it is put together, resource by resource

install.sh turns a clean Ubuntu/Debian box into this server in one
command: recommended FXServer build, MariaDB with the schema, resources,
server.cfg + a generated database password, boot entry (systemd or
init.d), then it waits for the Cfx registration. --name/--port/--db-name
let a second server live on the same machine. Tested end to end on a
spare install root: build 25770 fetched, schema applied, boot entry
written, FXServer started and stopped exactly where a wrong licence key
should stop it.

No secrets travel with it: the licence key and the database password live
in data/secrets.cfg on the machine, and .gitignore now names it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude Opus 5
2026-08-12 23:23:33 +00:00
co-authored by Claude Opus 5
parent 80c1d75d2f
commit 71856b15e9
66 changed files with 2030 additions and 3814 deletions
+188
View File
@@ -0,0 +1,188 @@
-- ---------------------------------------------------------------------------
-- Scripted cameras for the pre-spawn screens.
--
-- One camera object is reused throughout. Everything is driven from a single
-- render thread that eases the live values towards target values, so a change
-- requested by the UI never snaps.
-- ---------------------------------------------------------------------------
Cam = {}
local cam = nil
local mode = 'off' -- 'off' | 'shots' | 'orbit' | 'fly'
local renderThread = false
-- live and target orbit values
local orbit = {
angle = 180.0, targetAngle = 180.0,
radius = 1.6, targetRadius = 1.6,
height = 0.65, targetHeight = 0.65,
focus = nil, -- entity to orbit
pitch = 0.0, targetPitch = 0.0,
}
local shots = { list = {}, index = 0, startedAt = 0, duration = 22000 }
local fly = { from = nil, to = nil, lookFrom = nil, lookTo = nil, startedAt = 0, duration = 3000, done = nil }
local function ensureCam()
if cam and DoesCamExist(cam) then return cam end
cam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
SetCamActive(cam, true)
RenderScriptCams(true, false, 0, true, true)
return cam
end
local function lerp(a, b, t) return a + (b - a) * t end
local function vlerp(a, b, t)
return vector3(lerp(a.x, b.x, t), lerp(a.y, b.y, t), lerp(a.z, b.z, t))
end
-- smoothstep, so fly-throughs start and end at rest
local function ease(t)
t = math.max(0.0, math.min(1.0, t))
return t * t * (3.0 - 2.0 * t)
end
local function startRenderThread()
if renderThread then return end
renderThread = true
CreateThread(function()
while mode ~= 'off' do
local c = ensureCam()
if mode == 'shots' then
local shot = shots.list[shots.index]
if shot then
local t = (GetGameTimer() - shots.startedAt) / shots.duration
if t >= 1.0 then
shots.index = (shots.index % #shots.list) + 1
shots.startedAt = GetGameTimer()
shot = shots.list[shots.index]
t = 0.0
end
-- a slow linear dolly reads as a helicopter, not a spline
local pos = vlerp(shot.from, shot.to, t)
SetCamCoord(c, pos.x, pos.y, pos.z)
PointCamAtCoord(c, shot.look.x, shot.look.y, shot.look.z)
end
elseif mode == 'orbit' then
orbit.angle = lerp(orbit.angle, orbit.targetAngle, 0.14)
orbit.radius = lerp(orbit.radius, orbit.targetRadius, 0.10)
orbit.height = lerp(orbit.height, orbit.targetHeight, 0.10)
orbit.pitch = lerp(orbit.pitch, orbit.targetPitch, 0.10)
local ent = orbit.focus
if ent and DoesEntityExist(ent) then
local base = GetEntityCoords(ent)
local rad = math.rad(orbit.angle)
local px = base.x + math.sin(rad) * orbit.radius
local py = base.y + math.cos(rad) * orbit.radius
local pz = base.z + orbit.height
SetCamCoord(c, px, py, pz)
PointCamAtCoord(c, base.x, base.y, base.z + orbit.height - orbit.pitch)
end
elseif mode == 'fly' then
local t = ease((GetGameTimer() - fly.startedAt) / fly.duration)
local pos = vlerp(fly.from, fly.to, t)
local look = vlerp(fly.lookFrom, fly.lookTo, t)
SetCamCoord(c, pos.x, pos.y, pos.z)
PointCamAtCoord(c, look.x, look.y, look.z)
if t >= 1.0 then
mode = 'idle'
if fly.done then
local fn = fly.done
fly.done = nil
fn()
end
end
end
Wait(0)
end
renderThread = false
end)
end
--- Slow establishing shots over the city, cycled forever.
function Cam.Shots(list)
shots.list = list
shots.index = 1
shots.startedAt = GetGameTimer()
mode = 'shots'
ensureCam()
SetCamFov(cam, 42.0)
startRenderThread()
end
--- Orbit an entity. `snap` places the camera immediately instead of easing in.
function Cam.Orbit(entity, radius, height, angle, snap)
orbit.focus = entity
orbit.targetRadius = radius or orbit.targetRadius
orbit.targetHeight = height or orbit.targetHeight
if angle then orbit.targetAngle = angle end
if snap then
orbit.radius = orbit.targetRadius
orbit.height = orbit.targetHeight
orbit.angle = orbit.targetAngle
end
mode = 'orbit'
ensureCam()
SetCamFov(cam, 34.0)
startRenderThread()
end
--- Framing presets used by the creator's section tabs.
function Cam.OrbitFrame(radius, height, pitch)
orbit.targetRadius = radius
orbit.targetHeight = height
orbit.targetPitch = pitch or 0.0
end
function Cam.Nudge(deltaDegrees)
orbit.targetAngle = orbit.targetAngle + deltaDegrees
end
function Cam.SetAngle(deg)
orbit.targetAngle = deg
end
function Cam.Angle() return orbit.targetAngle end
--- Fly from wherever we are to a point looking at a target, then call `done`.
function Cam.FlyTo(fromPos, fromLook, toPos, toLook, duration, done)
fly.from = fromPos
fly.to = toPos
fly.lookFrom = fromLook
fly.lookTo = toLook
fly.duration = duration or 3000
fly.startedAt = GetGameTimer()
fly.done = done
mode = 'fly'
ensureCam()
startRenderThread()
end
function Cam.SetFov(fov)
if cam and DoesCamExist(cam) then SetCamFov(cam, fov + 0.0) end
end
function Cam.Position()
if cam and DoesCamExist(cam) then return GetCamCoord(cam) end
return GetEntityCoords(PlayerPedId())
end
--- Hand control back to the gameplay camera. With a duration the engine
--- interpolates between the two, which hides the cut entirely.
function Cam.Release(duration)
mode = 'off'
duration = duration or 0
RenderScriptCams(false, duration > 0, duration, true, true)
if cam and DoesCamExist(cam) then
DestroyCam(cam, true)
end
cam = nil
end