612 lines
18 KiB
Lua
612 lines
18 KiB
Lua
local reverseCamera
|
|
|
|
local function getRgbColors()
|
|
local colors = {
|
|
hair = {},
|
|
makeUp = {}
|
|
}
|
|
|
|
for i = 0, GetNumHairColors() - 1 do
|
|
colors.hair[i+1] = {GetPedHairRgbColor(i)}
|
|
end
|
|
|
|
for i = 0, GetNumMakeupColors() - 1 do
|
|
colors.makeUp[i+1] = {GetPedMakeupRgbColor(i)}
|
|
end
|
|
|
|
return colors
|
|
end
|
|
|
|
local playerAppearance
|
|
|
|
local function getAppearance()
|
|
if not playerAppearance then
|
|
playerAppearance = client.getPedAppearance(cache.ped)
|
|
end
|
|
|
|
return playerAppearance
|
|
end
|
|
client.getAppearance = getAppearance
|
|
|
|
local function addToBlacklist(item, drawable, drawableId, blacklistSettings)
|
|
if drawable == drawableId and item.textures then
|
|
for i = 1, #item.textures do
|
|
blacklistSettings.textures[#blacklistSettings.textures + 1] = item.textures[i]
|
|
end
|
|
end
|
|
if not item.textures or #item.textures == 0 then
|
|
blacklistSettings.drawables[#blacklistSettings.drawables + 1] = drawable
|
|
end
|
|
end
|
|
|
|
local function listContains(items, item)
|
|
for i = 1, #items do
|
|
if items[i] == item then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function listContainsAny(items, containedItems)
|
|
for i = 1, #items do
|
|
if listContains(containedItems, items[i]) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function allowedForPlayer(item, allowedAces)
|
|
return (item.jobs and listContains(item.jobs, client.job.name)) or (item.gangs and listContains(item.gangs, client.gang.name)) or (item.aces and listContainsAny(item.aces, allowedAces) or (item.citizenids and listContains(item.citizenids, client.citizenid)))
|
|
end
|
|
|
|
local function filterPedModelsForPlayer(pedConfigs)
|
|
local playerPeds = {}
|
|
local allowedAces = lib.callback.await("illenium-appearance:server:GetPlayerAces", false)
|
|
|
|
for i = 1, #pedConfigs do
|
|
local config = pedConfigs[i]
|
|
if (not config.jobs and not config.gangs and not config.aces and not config.citizenids) or allowedForPlayer(config, allowedAces) then
|
|
for j = 1, #config.peds do
|
|
playerPeds[#playerPeds + 1] = config.peds[j]
|
|
end
|
|
end
|
|
end
|
|
return playerPeds
|
|
end
|
|
|
|
local function filterTattoosByGender(tattoos)
|
|
local filtered = {}
|
|
local gender = client.getPedDecorationType()
|
|
for k, v in pairs(tattoos) do
|
|
filtered[k] = {}
|
|
for i = 1, #v do
|
|
local tattoo = v[i]
|
|
if tattoo["hash" .. gender:gsub("^%l", string.upper)] ~= "" then
|
|
filtered[k][#filtered[k] + 1] = tattoo
|
|
end
|
|
end
|
|
end
|
|
return filtered
|
|
end
|
|
|
|
local function filterBlacklistSettings(items, drawableId)
|
|
local blacklistSettings = {
|
|
drawables = {},
|
|
textures = {}
|
|
}
|
|
|
|
local allowedAces = lib.callback.await("illenium-appearance:server:GetPlayerAces", false)
|
|
|
|
for i = 1, #items do
|
|
local item = items[i]
|
|
if not allowedForPlayer(item, allowedAces) and item.drawables then
|
|
for j = 0, #item.drawables do
|
|
addToBlacklist(item, item.drawables[j], drawableId, blacklistSettings)
|
|
end
|
|
end
|
|
end
|
|
|
|
return blacklistSettings
|
|
end
|
|
|
|
local function componentBlacklistMap(gender, componentId)
|
|
local genderSettings = Config.Blacklist[gender].components
|
|
if componentId == 1 then
|
|
return genderSettings.masks
|
|
elseif componentId == 3 then
|
|
return genderSettings.upperBody
|
|
elseif componentId == 4 then
|
|
return genderSettings.lowerBody
|
|
elseif componentId == 5 then
|
|
return genderSettings.bags
|
|
elseif componentId == 6 then
|
|
return genderSettings.shoes
|
|
elseif componentId == 7 then
|
|
return genderSettings.scarfAndChains
|
|
elseif componentId == 8 then
|
|
return genderSettings.shirts
|
|
elseif componentId == 9 then
|
|
return genderSettings.bodyArmor
|
|
elseif componentId == 10 then
|
|
return genderSettings.decals
|
|
elseif componentId == 11 then
|
|
return genderSettings.jackets
|
|
end
|
|
|
|
return {}
|
|
end
|
|
|
|
local function propBlacklistMap(gender, propId)
|
|
local genderSettings = Config.Blacklist[gender].props
|
|
|
|
if propId == 0 then
|
|
return genderSettings.hats
|
|
elseif propId == 1 then
|
|
return genderSettings.glasses
|
|
elseif propId == 2 then
|
|
return genderSettings.ear
|
|
elseif propId == 6 then
|
|
return genderSettings.watches
|
|
elseif propId == 7 then
|
|
return genderSettings.bracelets
|
|
end
|
|
|
|
return {}
|
|
end
|
|
|
|
local function getComponentSettings(ped, componentId)
|
|
local drawableId = GetPedDrawableVariation(ped, componentId)
|
|
local gender = client.getPedDecorationType()
|
|
|
|
local blacklistSettings = {
|
|
drawables = {},
|
|
textures = {}
|
|
}
|
|
|
|
if client.isPedFreemodeModel(ped) then
|
|
blacklistSettings = filterBlacklistSettings(componentBlacklistMap(gender, componentId), drawableId)
|
|
end
|
|
|
|
return {
|
|
component_id = componentId,
|
|
drawable = {
|
|
min = 0,
|
|
max = GetNumberOfPedDrawableVariations(ped, componentId) - 1
|
|
},
|
|
texture = {
|
|
min = 0,
|
|
max = GetNumberOfPedTextureVariations(ped, componentId, drawableId) - 1
|
|
},
|
|
blacklist = blacklistSettings
|
|
}
|
|
end
|
|
client.getComponentSettings = getComponentSettings
|
|
|
|
local function getPropSettings(ped, propId)
|
|
local drawableId = GetPedPropIndex(ped, propId)
|
|
local gender = client.getPedDecorationType()
|
|
|
|
local blacklistSettings = {
|
|
drawables = {},
|
|
textures = {}
|
|
}
|
|
|
|
if client.isPedFreemodeModel(ped) then
|
|
blacklistSettings = filterBlacklistSettings(propBlacklistMap(gender, propId), drawableId)
|
|
end
|
|
|
|
local settings = {
|
|
prop_id = propId,
|
|
drawable = {
|
|
min = -1,
|
|
max = GetNumberOfPedPropDrawableVariations(ped, propId) - 1
|
|
},
|
|
texture = {
|
|
min = -1,
|
|
max = GetNumberOfPedPropTextureVariations(ped, propId, drawableId) - 1
|
|
},
|
|
blacklist = blacklistSettings
|
|
}
|
|
return settings
|
|
end
|
|
client.getPropSettings = getPropSettings
|
|
|
|
local function getHairSettings(ped)
|
|
local colors = getRgbColors()
|
|
local gender = client.getPedDecorationType()
|
|
local blacklistSettings = {
|
|
drawables = {},
|
|
textures = {}
|
|
}
|
|
|
|
if client.isPedFreemodeModel(ped) then
|
|
blacklistSettings = filterBlacklistSettings(Config.Blacklist[gender].hair, GetPedDrawableVariation(ped, 2))
|
|
end
|
|
|
|
local settings = {
|
|
style = {
|
|
min = 0,
|
|
max = GetNumberOfPedDrawableVariations(ped, 2) - 1
|
|
},
|
|
color = {
|
|
items = colors.hair
|
|
},
|
|
highlight = {
|
|
items = colors.hair
|
|
},
|
|
texture = {
|
|
min = 0,
|
|
max = GetNumberOfPedTextureVariations(ped, 2, GetPedDrawableVariation(ped, 2)) - 1
|
|
},
|
|
blacklist = blacklistSettings
|
|
}
|
|
|
|
return settings
|
|
end
|
|
client.getHairSettings = getHairSettings
|
|
|
|
local function getAppearanceSettings()
|
|
local ped = {
|
|
model = {
|
|
items = filterPedModelsForPlayer(Config.Peds.pedConfig)
|
|
}
|
|
}
|
|
|
|
local tattoos = {
|
|
items = filterTattoosByGender(Config.Tattoos),
|
|
opacity = {
|
|
min = 0.1,
|
|
max = 1,
|
|
factor = 0.1,
|
|
}
|
|
}
|
|
|
|
local components = {}
|
|
for i = 1, #constants.PED_COMPONENTS_IDS do
|
|
components[i] = getComponentSettings(cache.ped, constants.PED_COMPONENTS_IDS[i])
|
|
end
|
|
|
|
local props = {}
|
|
for i = 1, #constants.PED_PROPS_IDS do
|
|
props[i] = getPropSettings(cache.ped, constants.PED_PROPS_IDS[i])
|
|
end
|
|
|
|
local headBlend = {
|
|
shapeFirst = {
|
|
min = 0,
|
|
max = 45
|
|
},
|
|
shapeSecond = {
|
|
min = 0,
|
|
max = 45
|
|
},
|
|
shapeThird = {
|
|
min = 0,
|
|
max = 45
|
|
},
|
|
skinFirst = {
|
|
min = 0,
|
|
max = 45
|
|
},
|
|
skinSecond = {
|
|
min = 0,
|
|
max = 45
|
|
},
|
|
skinThird = {
|
|
min = 0,
|
|
max = 45
|
|
},
|
|
shapeMix = {
|
|
min = 0,
|
|
max = 1,
|
|
factor = 0.1,
|
|
},
|
|
skinMix = {
|
|
min = 0,
|
|
max = 1,
|
|
factor = 0.1,
|
|
},
|
|
thirdMix = {
|
|
min = 0,
|
|
max = 1,
|
|
factor = 0.1,
|
|
}
|
|
}
|
|
|
|
local size = #constants.FACE_FEATURES
|
|
local faceFeatures = table.create(0, size)
|
|
for i = 1, size do
|
|
local feature = constants.FACE_FEATURES[i]
|
|
faceFeatures[feature] = { min = -1, max = 1, factor = 0.1}
|
|
end
|
|
|
|
local colors = getRgbColors()
|
|
|
|
local colorMap = {
|
|
beard = colors.hair,
|
|
eyebrows = colors.hair,
|
|
chestHair = colors.hair,
|
|
makeUp = colors.makeUp,
|
|
blush = colors.makeUp,
|
|
lipstick = colors.makeUp,
|
|
}
|
|
|
|
size = #constants.HEAD_OVERLAYS
|
|
local headOverlays = table.create(0, size)
|
|
|
|
for i = 1, size do
|
|
local overlay = constants.HEAD_OVERLAYS[i]
|
|
local settings = {
|
|
style = {
|
|
min = 0,
|
|
max = GetPedHeadOverlayNum(i - 1) - 1
|
|
},
|
|
opacity = {
|
|
min = 0,
|
|
max = 1,
|
|
factor = 0.1,
|
|
}
|
|
}
|
|
|
|
if colorMap[overlay] then
|
|
settings.color = {
|
|
items = colorMap[overlay]
|
|
}
|
|
end
|
|
|
|
headOverlays[overlay] = settings
|
|
end
|
|
|
|
local eyeColor = {
|
|
min = 0,
|
|
max = 30
|
|
}
|
|
|
|
return {
|
|
ped = ped,
|
|
components = components,
|
|
props = props,
|
|
headBlend = headBlend,
|
|
faceFeatures = faceFeatures,
|
|
headOverlays = headOverlays,
|
|
hair = getHairSettings(cache.ped),
|
|
eyeColor = eyeColor,
|
|
tattoos = tattoos
|
|
}
|
|
end
|
|
client.getAppearanceSettings = getAppearanceSettings
|
|
|
|
local config
|
|
function client.getConfig() return config end
|
|
|
|
local isCameraInterpolating
|
|
local currentCamera
|
|
local cameraHandle
|
|
local function setCamera(key)
|
|
if not isCameraInterpolating then
|
|
if key ~= "current" then
|
|
currentCamera = key
|
|
end
|
|
|
|
local coords, point = table.unpack(constants.CAMERAS[currentCamera])
|
|
local reverseFactor = reverseCamera and -1 or 1
|
|
|
|
if cameraHandle then
|
|
local camCoords = GetOffsetFromEntityInWorldCoords(cache.ped, coords.x * reverseFactor, coords.y * reverseFactor, coords.z * reverseFactor)
|
|
local camPoint = GetOffsetFromEntityInWorldCoords(cache.ped, point.x, point.y, point.z)
|
|
local tmpCamera = CreateCameraWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z, 0.0, 0.0, 0.0, 49.0, false, 0)
|
|
|
|
PointCamAtCoord(tmpCamera, camPoint.x, camPoint.y, camPoint.z)
|
|
SetCamActiveWithInterp(tmpCamera, cameraHandle, 1000, 1, 1)
|
|
|
|
isCameraInterpolating = true
|
|
|
|
CreateThread(function()
|
|
repeat Wait(500)
|
|
until not IsCamInterpolating(cameraHandle) and IsCamActive(tmpCamera)
|
|
DestroyCam(cameraHandle, false)
|
|
cameraHandle = tmpCamera
|
|
isCameraInterpolating = false
|
|
end)
|
|
else
|
|
local camCoords = GetOffsetFromEntityInWorldCoords(cache.ped, coords.x, coords.y, coords.z)
|
|
local camPoint = GetOffsetFromEntityInWorldCoords(cache.ped, point.x, point.y, point.z)
|
|
cameraHandle = CreateCameraWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z, 0.0, 0.0, 0.0, 49.0, false, 0)
|
|
|
|
PointCamAtCoord(cameraHandle, camPoint.x, camPoint.y, camPoint.z)
|
|
SetCamActive(cameraHandle, true)
|
|
end
|
|
end
|
|
end
|
|
client.setCamera = setCamera
|
|
|
|
function client.rotateCamera(direction)
|
|
if not isCameraInterpolating then
|
|
local coords, point = table.unpack(constants.CAMERAS[currentCamera])
|
|
local offset = constants.OFFSETS[currentCamera]
|
|
local sideFactor = direction == "left" and 1 or -1
|
|
local reverseFactor = reverseCamera and -1 or 1
|
|
|
|
local camCoords = GetOffsetFromEntityInWorldCoords(
|
|
cache.ped,
|
|
(coords.x + offset.x) * sideFactor * reverseFactor,
|
|
(coords.y + offset.y) * reverseFactor,
|
|
coords.z
|
|
)
|
|
|
|
local camPoint = GetOffsetFromEntityInWorldCoords(cache.ped, point.x, point.y, point.z)
|
|
local tmpCamera = CreateCameraWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z, 0.0, 0.0, 0.0, 49.0, false, 0)
|
|
|
|
PointCamAtCoord(tmpCamera, camPoint.x, camPoint.y, camPoint.z)
|
|
SetCamActiveWithInterp(tmpCamera, cameraHandle, 1000, 1, 1)
|
|
|
|
isCameraInterpolating = true
|
|
|
|
CreateThread(function()
|
|
repeat Wait(500)
|
|
until not IsCamInterpolating(cameraHandle) and IsCamActive(tmpCamera)
|
|
DestroyCam(cameraHandle, false)
|
|
cameraHandle = tmpCamera
|
|
isCameraInterpolating = false
|
|
end)
|
|
end
|
|
end
|
|
|
|
local playerCoords
|
|
local function pedTurn(ped, angle)
|
|
reverseCamera = not reverseCamera
|
|
local sequenceTaskId = OpenSequenceTask()
|
|
if sequenceTaskId then
|
|
TaskGoStraightToCoord(0, playerCoords.x, playerCoords.y, playerCoords.z, 8.0, -1, GetEntityHeading(ped) - angle, 0.1)
|
|
TaskStandStill(0, -1)
|
|
CloseSequenceTask(sequenceTaskId)
|
|
ClearPedTasks(ped)
|
|
TaskPerformSequence(ped, sequenceTaskId)
|
|
ClearSequenceTask(sequenceTaskId)
|
|
end
|
|
end
|
|
client.pedTurn = pedTurn
|
|
|
|
local function wearClothes(data, typeClothes)
|
|
local dataClothes = constants.DATA_CLOTHES[typeClothes]
|
|
local animationsOn = dataClothes.animations.on
|
|
local components = dataClothes.components[client.getPedDecorationType()]
|
|
local appliedComponents = data.components
|
|
local props = dataClothes.props[client.getPedDecorationType()]
|
|
local appliedProps = data.props
|
|
|
|
RequestAnimDict(animationsOn.dict)
|
|
while not HasAnimDictLoaded(animationsOn.dict) do
|
|
Wait(0)
|
|
end
|
|
|
|
for i = 1, #components do
|
|
local componentId = components[i][1]
|
|
for j = 1, #appliedComponents do
|
|
local applied = appliedComponents[j]
|
|
if applied.component_id == componentId then
|
|
SetPedComponentVariation(cache.ped, componentId, applied.drawable, applied.texture, 2)
|
|
end
|
|
end
|
|
end
|
|
|
|
for i = 1, #props do
|
|
local propId = props[i][1]
|
|
for j = 1, #appliedProps do
|
|
local applied = appliedProps[j]
|
|
if applied.prop_id == propId then
|
|
SetPedPropIndex(cache.ped, propId, applied.drawable, applied.texture, true)
|
|
end
|
|
end
|
|
end
|
|
|
|
TaskPlayAnim(cache.ped, animationsOn.dict, animationsOn.anim, 3.0, 3.0, animationsOn.duration, animationsOn.move, 0, false, false, false)
|
|
end
|
|
client.wearClothes = wearClothes
|
|
|
|
local function removeClothes(typeClothes)
|
|
local dataClothes = constants.DATA_CLOTHES[typeClothes]
|
|
local animationsOff = dataClothes.animations.off
|
|
local components = dataClothes.components[client.getPedDecorationType()]
|
|
local props = dataClothes.props[client.getPedDecorationType()]
|
|
|
|
RequestAnimDict(animationsOff.dict)
|
|
while not HasAnimDictLoaded(animationsOff.dict) do
|
|
Wait(0)
|
|
end
|
|
|
|
for i = 1, #components do
|
|
local component = components[i]
|
|
SetPedComponentVariation(cache.ped, component[1], component[2], 0, 2)
|
|
end
|
|
|
|
for i = 1, #props do
|
|
ClearPedProp(cache.ped, props[i][1])
|
|
end
|
|
|
|
TaskPlayAnim(cache.ped, animationsOff.dict, animationsOff.anim, 3.0, 3.0, animationsOff.duration, animationsOff.move, 0, false, false, false)
|
|
end
|
|
client.removeClothes = removeClothes
|
|
|
|
local playerHeading
|
|
function client.getHeading() return playerHeading end
|
|
|
|
local callback
|
|
function client.startPlayerCustomization(cb, conf)
|
|
playerAppearance = client.getPedAppearance(cache.ped)
|
|
playerCoords = GetEntityCoords(cache.ped, true)
|
|
playerHeading = GetEntityHeading(cache.ped)
|
|
|
|
BackupPlayerStats()
|
|
|
|
callback = cb
|
|
config = conf
|
|
reverseCamera = false
|
|
isCameraInterpolating = false
|
|
|
|
setCamera("default")
|
|
SetNuiFocus(true, true)
|
|
SetNuiFocusKeepInput(false)
|
|
RenderScriptCams(true, false, 0, true, true)
|
|
SetEntityInvincible(cache.ped, Config.InvincibleDuringCustomization)
|
|
TaskStandStill(cache.ped, -1)
|
|
|
|
if Config.HideRadar then DisplayRadar(false) end
|
|
|
|
SendNuiMessage(json.encode({
|
|
type = "appearance_display",
|
|
payload = {
|
|
asynchronous = Config.AsynchronousLoading
|
|
}
|
|
}))
|
|
end
|
|
|
|
function client.exitPlayerCustomization(appearance)
|
|
RenderScriptCams(false, false, 0, true, true)
|
|
DestroyCam(cameraHandle, false)
|
|
SetNuiFocus(false, false)
|
|
|
|
if Config.HideRadar then DisplayRadar(true) end
|
|
|
|
ClearPedTasksImmediately(cache.ped)
|
|
SetEntityInvincible(cache.ped, false)
|
|
|
|
SendNuiMessage(json.encode({
|
|
type = "appearance_hide",
|
|
payload = {}
|
|
}))
|
|
|
|
if not appearance then
|
|
client.setPlayerAppearance(getAppearance())
|
|
else
|
|
client.setPedTattoos(cache.ped, appearance.tattoos)
|
|
end
|
|
|
|
RestorePlayerStats()
|
|
|
|
if callback then
|
|
callback(appearance)
|
|
end
|
|
|
|
callback = nil
|
|
config = nil
|
|
playerAppearance = nil
|
|
playerCoords = nil
|
|
cameraHandle = nil
|
|
currentCamera = nil
|
|
reverseCamera = nil
|
|
isCameraInterpolating = nil
|
|
|
|
end
|
|
|
|
AddEventHandler("onResourceStop", function(resource)
|
|
if resource == GetCurrentResourceName() then
|
|
SetNuiFocus(false, false)
|
|
SetNuiFocusKeepInput(false)
|
|
end
|
|
end)
|
|
|
|
exports("startPlayerCustomization", client.startPlayerCustomization)
|