-- --------------------------------------------------------------------------- -- 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