193 lines
6.0 KiB
Lua
193 lines
6.0 KiB
Lua
|
---@class HeadBlendData
|
||
|
---@field shapeFirst integer
|
||
|
---@field shapeSecond integer
|
||
|
---@field shapeThird integer
|
||
|
---@field skinFirst integer
|
||
|
---@field skinSecond integer
|
||
|
---@field skinThird integer
|
||
|
---@field shapeMix number
|
||
|
---@field skinMix number
|
||
|
---@field thirdMix number
|
||
|
|
||
|
local freemodeModels<const> = {
|
||
|
[`mp_m_freemode_01`] = 'mp_m_freemode_01',
|
||
|
[`mp_f_freemode_01`] = 'mp_f_freemode_01'
|
||
|
}
|
||
|
|
||
|
---Is the model either of the freemode models?
|
||
|
---@param modelHash integer
|
||
|
local function isFreemodeModel(modelHash)
|
||
|
return freemodeModels[modelHash] ~= nil
|
||
|
end
|
||
|
|
||
|
---Gets head blend data
|
||
|
---@param ped integer
|
||
|
---@return HeadBlendData
|
||
|
local function getHeadBlendData(ped)
|
||
|
-- GTA returns some dumb struct with pointers
|
||
|
-- This is a goofy way to get the data in Lua.
|
||
|
-- Alternatively, you would need a C# or JS
|
||
|
-- script to get the data. However, this is
|
||
|
-- a lot less work.
|
||
|
-- People discussed this here:
|
||
|
-- https://forum.cfx.re/t/head-blend-data/212575/24
|
||
|
local tbl<const> = {
|
||
|
Citizen.InvokeNative(0x2746BD9D88C5C5D0, ped,
|
||
|
Citizen.PointerValueIntInitialized(0),
|
||
|
Citizen.PointerValueIntInitialized(0),
|
||
|
Citizen.PointerValueIntInitialized(0),
|
||
|
Citizen.PointerValueIntInitialized(0),
|
||
|
Citizen.PointerValueIntInitialized(0),
|
||
|
Citizen.PointerValueIntInitialized(0),
|
||
|
Citizen.PointerValueFloatInitialized(0),
|
||
|
Citizen.PointerValueFloatInitialized(0),
|
||
|
Citizen.PointerValueFloatInitialized(0)
|
||
|
)
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
shapeFirst = tbl[1],
|
||
|
shapeSecond = tbl[2],
|
||
|
shapeThird = tbl[3],
|
||
|
skinFirst = tbl[4],
|
||
|
skinSecond = tbl[5],
|
||
|
skinThird = tbl[6],
|
||
|
shapeMix = tbl[7],
|
||
|
skinMix = tbl[8],
|
||
|
thirdMix = tbl[9]
|
||
|
}
|
||
|
end
|
||
|
|
||
|
local savedBlendData, savedFaceFeatures
|
||
|
|
||
|
---Restores saved blend data
|
||
|
---@param ped integer
|
||
|
local function restoreSavedBlendData(ped)
|
||
|
SetPedHeadBlendData(ped,
|
||
|
savedBlendData.shapeFirst, savedBlendData.shapeSecond, savedBlendData.shapeThird,
|
||
|
savedBlendData.skinFirst, savedBlendData.skinSecond, savedBlendData.skinThird,
|
||
|
savedBlendData.shapeMix, savedBlendData.skinMix, savedBlendData.thirdMix,
|
||
|
false
|
||
|
)
|
||
|
end
|
||
|
|
||
|
---Shrinks face features
|
||
|
---@param ped integer
|
||
|
local function restoreSavedFaceFeatures(ped)
|
||
|
for i = 0, 19 do
|
||
|
SetPedFaceFeature(ped, i, savedFaceFeatures[i])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
---Shrinks face features
|
||
|
---@param ped integer
|
||
|
local function shrinkFaceFeatures(ped)
|
||
|
repeat Wait(0) until HasPedHeadBlendFinished(ped)
|
||
|
for i = 0, 19 do
|
||
|
if not savedFaceFeatures[i] then savedFaceFeatures[i] = GetPedFaceFeature(ped, i) end
|
||
|
SetPedFaceFeature(ped, i, 0.0)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---Shrink ped head
|
||
|
---@param ped integer
|
||
|
---@param pedModelHash integer
|
||
|
local function shrinkHead(ped, pedModelHash)
|
||
|
SetPedHeadBlendData(ped,
|
||
|
freemodeModels[pedModelHash] == 'mp_m_freemode_01' and 0 or 21, 0, 0, -- Reset shape
|
||
|
savedBlendData.skinFirst, savedBlendData.skinSecond, savedBlendData.skinThird, -- Keep skin
|
||
|
0.0, savedBlendData.skinMix, 0.0, -- Reset all but skin mix
|
||
|
false -- isParent (Unk effect)
|
||
|
)
|
||
|
end
|
||
|
|
||
|
-- local lastMaskDrawable, lastMaskTexture = -1, -1
|
||
|
|
||
|
---Handles mask fix
|
||
|
---@param ped integer
|
||
|
---@param pedModelHash integer
|
||
|
local function fixMask(ped, pedModelHash)
|
||
|
local currentMaskDrawable<const> = GetPedDrawableVariation(ped, 1)
|
||
|
local currentMaskTexture<const> = GetPedTextureVariation(ped, 1)
|
||
|
|
||
|
-- if currentMaskDrawable == lastMaskDrawable and currentMaskTexture == lastMaskTexture then return end
|
||
|
-- lastMaskDrawable = currentMaskDrawable
|
||
|
-- lastMaskTexture = currentMaskTexture
|
||
|
|
||
|
local maskHash<const> = GetHashNameForComponent(ped, 1, currentMaskDrawable, currentMaskTexture)
|
||
|
if currentMaskDrawable > 0 then
|
||
|
if maskHash == 0 then return end
|
||
|
|
||
|
local headBlendData<const> = getHeadBlendData(ped)
|
||
|
|
||
|
if
|
||
|
DoesShopPedApparelHaveRestrictionTag(maskHash, `SHRINK_HEAD`, 0) or
|
||
|
|
||
|
-- HARD CODED MASKS
|
||
|
currentMaskDrawable == 108 -- This god damn skull mask has no restriction tag but needs to shrink the head
|
||
|
or currentMaskDrawable == 30 -- Same goes for this hockey mask. It has no tags, but your nose clips through it
|
||
|
then
|
||
|
if not savedBlendData then savedBlendData = headBlendData end
|
||
|
shrinkHead(ped, pedModelHash)
|
||
|
elseif savedBlendData then
|
||
|
restoreSavedBlendData(ped)
|
||
|
savedBlendData = nil
|
||
|
end
|
||
|
|
||
|
if
|
||
|
not DoesShopPedApparelHaveRestrictionTag(maskHash, `HAT`, 0)
|
||
|
and not DoesShopPedApparelHaveRestrictionTag(maskHash, `EAR_PIECE`, 0)
|
||
|
|
||
|
-- HARD CODED MASKS
|
||
|
and not (
|
||
|
currentMaskDrawable == 11 -- Gay super hero
|
||
|
-- or currentMaskDrawable == 73 -- No mask (this will remain as is - it might be useful to put under specific helmets)
|
||
|
or currentMaskDrawable == 114 -- Open face headscarf
|
||
|
-- or currentMaskDrawable == 120 -- No mask bald (this will remain as is - it might be useful to put under specific helmets)
|
||
|
or currentMaskDrawable == 145 -- Cluckin Bell chicken
|
||
|
or currentMaskDrawable == 148 -- Super hero
|
||
|
)
|
||
|
then
|
||
|
if not savedFaceFeatures then savedFaceFeatures = {} end
|
||
|
shrinkFaceFeatures(ped)
|
||
|
elseif savedFaceFeatures then
|
||
|
restoreSavedFaceFeatures(ped)
|
||
|
savedFaceFeatures = nil
|
||
|
end
|
||
|
else
|
||
|
if savedBlendData then
|
||
|
restoreSavedBlendData(ped)
|
||
|
savedBlendData = nil
|
||
|
end
|
||
|
|
||
|
if savedFaceFeatures then
|
||
|
restoreSavedFaceFeatures(ped)
|
||
|
savedFaceFeatures = nil
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function fixClothing()
|
||
|
local ped<const> = PlayerPedId()
|
||
|
if not DoesEntityExist(ped) then return end
|
||
|
|
||
|
local pedModelHash<const> = GetEntityModel(ped)
|
||
|
if not isFreemodeModel(pedModelHash) then return end
|
||
|
|
||
|
fixMask(ped, pedModelHash)
|
||
|
end
|
||
|
|
||
|
-- Usually with FiveM, players can change their clothing
|
||
|
-- freely and on the fly. Ideally, you would want to fix
|
||
|
-- clothing right after the player changes their clothes.
|
||
|
-- However, as there is 1000 different ways of changing
|
||
|
-- clothes, I have decided to fix clothing on a loop.
|
||
|
-- I suggest you replace this with an event handler for
|
||
|
-- yourclothing system.
|
||
|
CreateThread(function()
|
||
|
while true do
|
||
|
fixClothing()
|
||
|
Wait(10)
|
||
|
end
|
||
|
end)
|