Files
redl-fivem-rp/resources/[rp]/rp_core/shared/appearance.lua
T
Claude Opus 5andClaude Opus 5 71856b15e9 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>
2026-08-12 23:23:33 +00:00

186 lines
6.6 KiB
Lua

-- ---------------------------------------------------------------------------
-- Freemode ped appearance model.
--
-- The client builds a ped from this table and the server stores it verbatim,
-- so it is sanitised here - once - and both sides use the same code. Anything
-- the client sends that is not described below is dropped.
-- ---------------------------------------------------------------------------
Appearance = {}
Appearance.MODELS = {
m = 'mp_m_freemode_01',
f = 'mp_f_freemode_01',
}
-- SetPedHeadBlendData: 46 heritage faces per parent.
Appearance.PARENT_MAX = 45
-- SetPedFaceFeature indices, in the order the creator shows them.
Appearance.FEATURES = {
{ id = 0, label = 'Nose width' },
{ id = 1, label = 'Nose height' },
{ id = 2, label = 'Nose length' },
{ id = 3, label = 'Nose bridge' },
{ id = 4, label = 'Nose tip' },
{ id = 5, label = 'Nose shift' },
{ id = 6, label = 'Brow height' },
{ id = 7, label = 'Brow depth' },
{ id = 8, label = 'Cheekbone height' },
{ id = 9, label = 'Cheekbone width' },
{ id = 10, label = 'Cheek width' },
{ id = 11, label = 'Eye opening' },
{ id = 12, label = 'Lip thickness' },
{ id = 13, label = 'Jaw width' },
{ id = 14, label = 'Jaw length' },
{ id = 15, label = 'Chin height' },
{ id = 16, label = 'Chin length' },
{ id = 17, label = 'Chin width' },
{ id = 18, label = 'Chin dimple' },
{ id = 19, label = 'Neck thickness' },
}
-- SetPedHeadOverlay slots that the creator exposes.
Appearance.OVERLAYS = {
{ id = 2, key = 'eyebrows', label = 'Eyebrows', max = 33, tint = 'hair' },
{ id = 1, key = 'beard', label = 'Facial hair', max = 28, tint = 'hair' },
{ id = 0, key = 'blemishes', label = 'Blemishes', max = 23, tint = false },
{ id = 3, key = 'ageing', label = 'Ageing', max = 14, tint = false },
{ id = 6, key = 'complexion',label = 'Complexion', max = 11, tint = false },
{ id = 7, key = 'sundamage', label = 'Sun damage', max = 10, tint = false },
{ id = 9, key = 'freckles', label = 'Freckles', max = 17, tint = false },
{ id = 5, key = 'blush', label = 'Blush', max = 6, tint = 'makeup' },
{ id = 8, key = 'lipstick', label = 'Lipstick', max = 9, tint = 'makeup' },
{ id = 4, key = 'makeup', label = 'Make-up', max = 74, tint = 'makeup' },
}
-- Clothing components the creator exposes (0-11 exist; these are the useful ones).
Appearance.COMPONENTS = {
{ id = 11, key = 'jacket', label = 'Top' },
{ id = 8, key = 'undershirt',label = 'Undershirt' },
{ id = 4, key = 'legs', label = 'Legs' },
{ id = 6, key = 'shoes', label = 'Shoes' },
{ id = 3, key = 'torso', label = 'Arms' },
{ id = 1, key = 'mask', label = 'Mask' },
{ id = 9, key = 'vest', label = 'Vest' },
{ id = 7, key = 'accessory', label = 'Accessory' },
{ id = 10, key = 'decal', label = 'Decal' },
{ id = 5, key = 'bag', label = 'Bag' },
}
Appearance.PROPS = {
{ id = 0, key = 'hat', label = 'Hat' },
{ id = 1, key = 'glasses', label = 'Glasses' },
{ id = 2, key = 'ear', label = 'Earrings' },
}
local function num(v, lo, hi, fallback)
v = tonumber(v)
if not v or v ~= v then return fallback end -- also rejects NaN
if v < lo then return lo end
if v > hi then return hi end
return v
end
local function int(v, lo, hi, fallback)
return math.floor(num(v, lo, hi, fallback) + 0.0)
end
function Appearance.Default(gender)
local a = {
model = (gender == 'f') and 'f' or 'm',
parents = { father = 0, mother = 21, shapeMix = 0.5, skinMix = 0.5 },
features = {},
overlays = {},
hair = { style = 0, colour = 0, highlight = 0 },
eyeColour = 0,
components = {},
props = {},
}
for _, f in ipairs(Appearance.FEATURES) do a.features[tostring(f.id)] = 0.0 end
for _, o in ipairs(Appearance.OVERLAYS) do
a.overlays[o.key] = { index = -1, opacity = 1.0, colour = 0 }
end
for _, c in ipairs(Appearance.COMPONENTS) do
a.components[c.key] = { drawable = 0, texture = 0 }
end
for _, p in ipairs(Appearance.PROPS) do
a.props[p.key] = { drawable = -1, texture = 0 }
end
-- a plain default outfit rather than the naked base ped
a.components.jacket = { drawable = 15, texture = 0 }
a.components.undershirt = { drawable = 15, texture = 0 }
a.components.torso = { drawable = 15, texture = 0 }
a.components.legs = { drawable = 21, texture = 0 }
a.components.shoes = { drawable = 34, texture = 0 }
return a
end
--- Returns a clean appearance table built only from known keys.
function Appearance.Sanitise(input)
local gender = (type(input) == 'table' and input.model == 'f') and 'f' or 'm'
local out = Appearance.Default(gender)
if type(input) ~= 'table' then return out end
if type(input.parents) == 'table' then
out.parents.father = int(input.parents.father, 0, Appearance.PARENT_MAX, 0)
out.parents.mother = int(input.parents.mother, 0, Appearance.PARENT_MAX, 21)
out.parents.shapeMix = num(input.parents.shapeMix, 0.0, 1.0, 0.5)
out.parents.skinMix = num(input.parents.skinMix, 0.0, 1.0, 0.5)
end
if type(input.features) == 'table' then
for _, f in ipairs(Appearance.FEATURES) do
local k = tostring(f.id)
out.features[k] = num(input.features[k], -1.0, 1.0, 0.0)
end
end
if type(input.overlays) == 'table' then
for _, o in ipairs(Appearance.OVERLAYS) do
local given = input.overlays[o.key]
if type(given) == 'table' then
out.overlays[o.key] = {
index = int(given.index, -1, o.max, -1),
opacity = num(given.opacity, 0.0, 1.0, 1.0),
colour = int(given.colour, 0, 63, 0),
}
end
end
end
if type(input.hair) == 'table' then
out.hair.style = int(input.hair.style, 0, 80, 0)
out.hair.colour = int(input.hair.colour, 0, 63, 0)
out.hair.highlight = int(input.hair.highlight, 0, 63, 0)
end
out.eyeColour = int(input.eyeColour, 0, 31, 0)
if type(input.components) == 'table' then
for _, c in ipairs(Appearance.COMPONENTS) do
local given = input.components[c.key]
if type(given) == 'table' then
out.components[c.key] = {
drawable = int(given.drawable, 0, 511, 0),
texture = int(given.texture, 0, 63, 0),
}
end
end
end
if type(input.props) == 'table' then
for _, p in ipairs(Appearance.PROPS) do
local given = input.props[p.key]
if type(given) == 'table' then
out.props[p.key] = {
drawable = int(given.drawable, -1, 255, -1),
texture = int(given.texture, 0, 63, 0),
}
end
end
end
return out
end