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