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>
161 lines
5.1 KiB
Lua
161 lines
5.1 KiB
Lua
-- ---------------------------------------------------------------------------
|
|
-- Building a freemode ped from an appearance table.
|
|
--
|
|
-- The same code paints the creator preview and the live player, so what you
|
|
-- saw while editing is exactly what walks into the world.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
PedBuild = {}
|
|
|
|
local NO_OVERLAY = 255 -- the native's "none" value; our model stores -1
|
|
|
|
local function loadModel(name)
|
|
local hash = joaat(name)
|
|
if not IsModelInCdimage(hash) or not IsModelValid(hash) then return nil end
|
|
RequestModel(hash)
|
|
local waited = 0
|
|
while not HasModelLoaded(hash) and waited < 10000 do
|
|
Wait(10)
|
|
waited = waited + 10
|
|
end
|
|
if not HasModelLoaded(hash) then return nil end
|
|
return hash
|
|
end
|
|
|
|
PedBuild.LoadModel = loadModel
|
|
|
|
--- Apply an appearance table to an existing freemode ped.
|
|
function PedBuild.Apply(ped, a)
|
|
if not ped or not DoesEntityExist(ped) or type(a) ~= 'table' then return end
|
|
|
|
SetPedHeadBlendData(
|
|
ped,
|
|
a.parents.father, a.parents.mother, 0,
|
|
a.parents.father, a.parents.mother, 0,
|
|
a.parents.shapeMix + 0.0, a.parents.skinMix + 0.0, 0.0,
|
|
false
|
|
)
|
|
|
|
for _, f in ipairs(Appearance.FEATURES) do
|
|
local v = a.features[tostring(f.id)]
|
|
if v then SetPedFaceFeature(ped, f.id, v + 0.0) end
|
|
end
|
|
|
|
for _, o in ipairs(Appearance.OVERLAYS) do
|
|
local ov = a.overlays[o.key]
|
|
if ov then
|
|
local index = (ov.index == nil or ov.index < 0) and NO_OVERLAY or ov.index
|
|
SetPedHeadOverlay(ped, o.id, index, ov.opacity + 0.0)
|
|
if o.tint and index ~= NO_OVERLAY then
|
|
-- tint type 1 = hair palette, 2 = make-up palette
|
|
SetPedHeadOverlayColor(ped, o.id, o.tint == 'hair' and 1 or 2, ov.colour, ov.colour)
|
|
end
|
|
end
|
|
end
|
|
|
|
SetPedComponentVariation(ped, 2, a.hair.style, 0, 0)
|
|
SetPedHairColor(ped, a.hair.colour, a.hair.highlight)
|
|
SetPedEyeColor(ped, a.eyeColour)
|
|
|
|
for _, c in ipairs(Appearance.COMPONENTS) do
|
|
local cc = a.components[c.key]
|
|
if cc then SetPedComponentVariation(ped, c.id, cc.drawable, cc.texture, 0) end
|
|
end
|
|
|
|
for _, p in ipairs(Appearance.PROPS) do
|
|
local pp = a.props[p.key]
|
|
if pp then
|
|
if not pp.drawable or pp.drawable < 0 then
|
|
ClearPedProp(ped, p.id)
|
|
else
|
|
SetPedPropIndex(ped, p.id, pp.drawable, pp.texture, true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- How many variations this particular model actually has, so the creator's
|
|
--- sliders stop at real values instead of guessed ones.
|
|
function PedBuild.Limits(ped)
|
|
local limits = { components = {}, props = {}, overlays = {}, hair = 0 }
|
|
if not ped or not DoesEntityExist(ped) then return limits end
|
|
|
|
limits.hair = math.max(0, GetNumberOfPedDrawableVariations(ped, 2) - 1)
|
|
|
|
for _, c in ipairs(Appearance.COMPONENTS) do
|
|
local drawables = math.max(0, GetNumberOfPedDrawableVariations(ped, c.id) - 1)
|
|
local current = GetPedDrawableVariation(ped, c.id)
|
|
limits.components[c.key] = {
|
|
drawable = drawables,
|
|
texture = math.max(0, GetNumberOfPedTextureVariations(ped, c.id, current) - 1),
|
|
}
|
|
end
|
|
|
|
for _, p in ipairs(Appearance.PROPS) do
|
|
local drawables = math.max(-1, GetNumberOfPedPropDrawableVariations(ped, p.id) - 1)
|
|
local current = GetPedPropIndex(ped, p.id)
|
|
limits.props[p.key] = {
|
|
drawable = drawables,
|
|
texture = math.max(0, GetNumberOfPedPropTextureVariations(ped, p.id, current) - 1),
|
|
}
|
|
end
|
|
|
|
for _, o in ipairs(Appearance.OVERLAYS) do
|
|
limits.overlays[o.key] = o.max
|
|
end
|
|
|
|
return limits
|
|
end
|
|
|
|
--- Create a standalone preview ped (never networked).
|
|
function PedBuild.CreatePreview(appearance, coords, heading)
|
|
local modelName = Appearance.MODELS[appearance.model] or Appearance.MODELS.m
|
|
local hash = loadModel(modelName)
|
|
if not hash then return nil end
|
|
|
|
local ped = CreatePed(2, hash, coords.x, coords.y, coords.z, heading + 0.0, false, false)
|
|
SetModelAsNoLongerNeeded(hash)
|
|
|
|
SetEntityInvincible(ped, true)
|
|
FreezeEntityPosition(ped, true)
|
|
SetBlockingOfNonTemporaryEvents(ped, true)
|
|
SetPedDefaultComponentVariation(ped)
|
|
SetPedCanRagdoll(ped, false)
|
|
SetEntityCollision(ped, false, false)
|
|
|
|
PedBuild.Apply(ped, appearance)
|
|
return ped
|
|
end
|
|
|
|
--- Turn the local player into this character.
|
|
function PedBuild.ApplyToPlayer(appearance)
|
|
local modelName = Appearance.MODELS[appearance.model] or Appearance.MODELS.m
|
|
local hash = loadModel(modelName)
|
|
if not hash then return false end
|
|
|
|
SetPlayerModel(PlayerId(), hash)
|
|
SetModelAsNoLongerNeeded(hash)
|
|
|
|
local ped = PlayerPedId()
|
|
SetPedDefaultComponentVariation(ped)
|
|
PedBuild.Apply(ped, appearance)
|
|
-- freemode peds start with no head blend applied until this settles a frame
|
|
Wait(50)
|
|
PedBuild.Apply(ped, appearance)
|
|
return true
|
|
end
|
|
|
|
--- A calm idle so the preview does not stand like a mannequin.
|
|
function PedBuild.Idle(ped)
|
|
local dict = 'anim@heists@heist_corona@team_idles@female_a'
|
|
RequestAnimDict(dict)
|
|
local waited = 0
|
|
while not HasAnimDictLoaded(dict) and waited < 3000 do
|
|
Wait(10)
|
|
waited = waited + 10
|
|
end
|
|
if HasAnimDictLoaded(dict) then
|
|
TaskPlayAnim(ped, dict, 'idle', 2.0, 2.0, -1, 1, 0, false, false, false)
|
|
end
|
|
end
|