Part 1 final build: Los Santos RP written on camera by the agent
rp_core (sessions, scrypt auth, self-written MySQL-wire driver rp_db), county-records NUI: residency-file auth, identity-record character intake, survey-grid spawn, procedural night-city loading screen. Secrets replaced with .example files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user