This commit is contained in:
Nordi98 2025-08-06 16:37:06 +02:00
parent 510e3ffcf2
commit f43cf424cf
305 changed files with 34683 additions and 0 deletions

View file

@ -0,0 +1,8 @@
---@diagnostic disable: duplicate-set-field
if GetResourceState('illenium-appearance') == 'missing' then return end
if GetResourceState('rcore_clothing') ~= 'missing' then return end
Clothing = Clothing or {}
function Clothing.OpenMenu()
TriggerEvent('qb-clothing:client:openMenu')
end

View file

@ -0,0 +1,253 @@
---@diagnostic disable: duplicate-set-field
if GetResourceState('illenium-appearance') == 'missing' then return end
if GetResourceState('rcore_clothing') ~= 'missing' then return end
Clothing = Clothing or {}
Clothing.Players = {}
Callback = Callback or Require("lib/utility/shared/callbacks.lua")
Table = Table or Require('lib/utility/shared/tables.lua')
--- Internal function to get the full appearance data including skin, model, and converted format
---@param src number The server ID of the player
---@return table|nil The player's full appearance data or nil if not found
function Clothing.GetFullAppearanceData(src)
src = src and tonumber(src)
assert(src, "src is nil")
local citId = Bridge.Framework.GetPlayerIdentifier(src)
if not citId then return end
if Clothing.Players[citId] then return Clothing.Players[citId] end
local result = MySQL.query.await('SELECT * FROM playerskins WHERE citizenid = ? AND active = ?', { citId, 1 })
if result[1] == nil then return end
local model = result[1].model
local skinData = json.decode(result[1].skin)
local converted = skinData
-- Store complete data in the cache
Clothing.Players[citId] = {
model = model,
skin = skinData,
converted = converted
}
return Clothing.Players[citId]
end
--- Retrieves a player's converted appearance data for easy use across modules
---@param src number The server ID of the player
---@param fullData boolean Optional - If true, returns the full data object including skin and model
---@return table|nil The player's converted appearance data or full appearance data if fullData=true
function Clothing.GetAppearance(src, fullData)
if fullData then
return Clothing.GetFullAppearanceData(src)
end
local completeData = Clothing.GetFullAppearanceData(src)
if not completeData then return nil end
return completeData.converted
end
--- Sets a player's appearance based on the provided data
---@param src number The server ID of the player
---@param data table The appearance data to apply
---@param updateBackup boolean Whether to update the backup appearance data
---@return table|nil The updated player appearance data or nil if failed
function Clothing.SetAppearance(src, data, updateBackup, save)
src = src and tonumber(src)
assert(src, "src is nil")
local citId = Bridge.Framework.GetPlayerIdentifier(src)
if not citId then return end
local model = GetEntityModel(GetPlayerPed(src))
if not model then return end
local converted = data
-- Get full appearance data
local currentClothing = Clothing.GetFullAppearanceData(src)
if not currentClothing then return end
local currentSkin = currentClothing.skin
for k, v in pairs(converted) do
currentSkin[k] = v
end
if not Clothing.Players[citId].backup or updateBackup then
Clothing.Players[citId].backup = currentClothing.converted
end
Clothing.Players[citId] = Clothing.Players[citId] or {}
Clothing.Players[citId].skin = currentSkin
Clothing.Players[citId].model = model
Clothing.Players[citId].converted = converted
if save then
MySQL.update.await('UPDATE playerskins SET skin = ? WHERE citizenid = ? AND active = ?', {
json.encode(currentSkin),
citId,
1
})
end
TriggerClientEvent('community_bridge:client:SetAppearance', src, Clothing.Players[citId].converted)
return Clothing.Players[citId]
end
--- Sets a player's appearance based on gender-specific data
---@param src number The server ID of the player
---@param data table Table containing separate appearance data for male and female characters
---@return table|nil Appearance updated player appearance data or nil if failed
function Clothing.SetAppearanceExt(src, data)
local tbl = Clothing.IsMale(src) and data.male or data.female
Clothing.SetAppearance(src, tbl)
end
--- Reverts a player's appearance to their backup appearance
---@param src number The server ID of the player
---@return boolean|nil Returns true if successful or nil if failed
function Clothing.Revert(src)
src = src and tonumber(src)
assert(src, "src is nil")
local currentClothing = Clothing.GetFullAppearanceData(src)
if not currentClothing then return end
local backup = currentClothing.backup
print(src, backup)
if not backup then return end
return Clothing.SetAppearance(src, backup)
end
function Clothing.OpenMenu(src)
src = src and tonumber(src)
assert(src, "src is nil")
TriggerClientEvent('qb-clothing:client:openMenu', src)
end
--- Event handler for when a player loads into the server
--- Caches the player's appearance data
AddEventHandler('community_bridge:Server:OnPlayerLoaded', function(src)
src = src and tonumber(src)
assert(src, "src is nil")
-- Use GetFullAppearanceData to cache the complete appearance
Clothing.GetFullAppearanceData(src)
end)
--- Event handler for when a player disconnects from the server
--- Removes the player's appearance data from the cache
AddEventHandler('community_bridge:Server:OnPlayerUnload', function(src)
src = src and tonumber(src)
assert(src, "src is nil")
local strSrc = tostring(src)
Clothing.Players[strSrc] = nil
end)
--- Event handler for when the resource starts
--- Caches appearance data for all currently connected players
AddEventHandler('onResourceStart', function(resource)
if resource ~= GetCurrentResourceName() then return end
for _, playerId in ipairs(GetPlayers()) do
local src = tonumber(playerId)
local strSrc = tostring(src)
if not Clothing.Players[strSrc] then
Clothing.Players[strSrc] = Clothing.GetAppearance(src)
end
end
end)
--- Callback handler for retrieving a player's appearance data
Callback.Register('community_bridge:cb:GetAppearance', function(source)
local src = source
return Clothing.GetAppearance(src)
end)
-- The below Should go to unit test
RegisterCommand('clothing:debug', function(source, args, rawCommand)
local src = source
Clothing.SetAppearance(src, {
components = {
{ component_id = 0, drawable = math.random(0, 50), texture = 0 },
{ component_id = 1, drawable = math.random(0, 50), texture = 0 },
{ component_id = 2, drawable = math.random(0, 50), texture = 0 },
{ component_id = 3, drawable = math.random(0, 50), texture = 0 },
{ component_id = 4, drawable = math.random(0, 50), texture = 0 },
{ component_id = 5, drawable = math.random(0, 50), texture = 0 },
{ component_id = 6, drawable = math.random(0, 50), texture = 0 },
{ component_id = 7, drawable = math.random(0, 50), texture = 0 },
{ component_id = 8, drawable = math.random(0, 50), texture = 0 },
{ component_id = 9, drawable = math.random(0, 50), texture = 0 },
{ component_id = 10, drawable = math.random(0, 50), texture = 0 },
{ component_id = 11, drawable = math.random(0, 50), texture = 0 },
},
props = {
{ prop_id = 0, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 1, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 2, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 3, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 4, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 5, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 6, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 7, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 8, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 9, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 10, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 11, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 12, drawable = math.random(0, 50), texture = 0 },
{ prop_id = 13, drawable = math.random(0, 50), texture = 0 },
}
}, false, true)
end, false)
RegisterCommand('clothing:revert', function(source, args, rawCommand)
local src = source
Clothing.Revert(src)
end, false)
RegisterCommand('clothing:current', function(source, args, rawCommand)
local src = source
local currentClothing = Clothing.GetAppearance(src)
if not currentClothing then return end
print(json.encode(currentClothing, { indent = true }))
end, false)
RegisterCommand('clothing:openmenu', function(source, args, rawCommand)
local src = source
Clothing.OpenMenu(src)
end, false)
RegisterCommand('clothing:crowley', function(source, args, rawCommand)
local src = source
Clothing.SetAppearance(src, {
components = {
{ drawable = 0, texture = 0, component_id = 0 },
{ drawable = 0, texture = 0, component_id = 1 },
{ drawable = 19, texture = 0, component_id = 2 },
{ drawable = 6, texture = 0, component_id = 3 },
{ drawable = 0, texture = 0, component_id = 4 },
{ drawable = 0, texture = 0, component_id = 5 },
{ drawable = 0, texture = 0, component_id = 6 },
{ drawable = 0, texture = 0, component_id = 7 },
{ drawable = 23, texture = 0, component_id = 8 },
{ drawable = 0, texture = 0, component_id = 9 },
{ drawable = 0, texture = 0, component_id = 10 },
{ drawable = 4, texture = 2, component_id = 11 }
},
props = {
{ drawable = 27, prop_id = 0, texture = 0 },
{ drawable = 0, prop_id = 1, texture = 0 },
{ drawable = 0, prop_id = 2, texture = 0 },
{ drawable = 0, prop_id = 3, texture = 0 },
{ drawable = 0, prop_id = 4, texture = 0 },
{ drawable = 0, prop_id = 5, texture = 0 },
{ drawable = 0, prop_id = 6, texture = 0 },
{ drawable = 0, prop_id = 7, texture = 0 },
{ drawable = 0, prop_id = 8, texture = 0 },
{ drawable = 0, prop_id = 9, texture = 0 },
{ drawable = 0, prop_id = 10, texture = 0 },
{ drawable = 0, prop_id = 11, texture = 0 },
{ drawable = 0, prop_id = 12, texture = 0 },
{ drawable = 0, prop_id = 13, texture = 0 }
}
}, false, true)
end, false)

View file

@ -0,0 +1,222 @@
-- Get appearence data
-- {
-- "tattoos": {
-- "ZONE_HAIR": [
-- {
-- "label": "hair-0-188",
-- "collection": "multiplayer_overlays",
-- "name": "hair-0-188",
-- "hashFemale": "FM_F_Hair_003_c",
-- "hashMale": "FM_M_Hair_003_c",
-- "zone": "ZONE_HAIR"
-- }
-- ]
-- },
-- "components": [
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 0
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 1
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 2
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 3
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 4
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 5
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 6
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 7
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 8
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 9
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 10
-- },
-- {
-- "texture": -1,
-- "drawable": -1,
-- "component_id": 11
-- }
-- ],
-- "headBlend": {
-- "skinSecond": 0,
-- "skinMix": 0.0,
-- "shapeFirst": 0,
-- "shapeThird": 0,
-- "skinFirst": 0,
-- "shapeMix": 0.0,
-- "shapeSecond": 0,
-- "skinThird": 0,
-- "thirdMix": 0.0
-- },
-- "hair": {
-- "texture": -1,
-- "style": -1,
-- "color": -1,
-- "highlight": -1
-- },
-- "eyeColor": -1,
-- "headOverlays": {
-- "lipstick": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "moleAndFreckles": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "ageing": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "makeUp": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "beard": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "blush": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "complexion": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "bodyBlemishes": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "eyebrows": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "chestHair": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "sunDamage": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- },
-- "blemishes": {
-- "color": 0,
-- "style": 0,
-- "opacity": 0.0,
-- "secondColor": 0
-- }
-- },
-- "faceFeatures": {
-- "jawBoneBackSize": 0.0,
-- "noseWidth": 0.0,
-- "eyeBrownHigh": 0.0,
-- "chinBoneSize": 0.0,
-- "chinHole": 0.0,
-- "neckThickness": 0.0,
-- "noseBoneTwist": 0.0,
-- "noseBoneHigh": 0.0,
-- "nosePeakHigh": 0.0,
-- "nosePeakSize": 0.0,
-- "eyesOpening": 0.0,
-- "cheeksBoneHigh": 0.0,
-- "cheeksBoneWidth": 0.0,
-- "cheeksWidth": 0.0,
-- "lipsThickness": 0.0,
-- "nosePeakLowering": 0.0,
-- "jawBoneWidth": 0.0,
-- "eyeBrownForward": 0.0,
-- "chinBoneLenght": 0.0,
-- "chinBoneLowering": 0.0
-- },
-- "model": "mp_m_freemode_01",
-- "props": [
-- {
-- "drawable": -1,
-- "prop_id": 0,
-- "texture": -1
-- },
-- {
-- "drawable": -1,
-- "prop_id": 1,
-- "texture": -1
-- },
-- {
-- "drawable": -1,
-- "prop_id": 2,
-- "texture": -1
-- },
-- {
-- "drawable": -1,
-- "prop_id": 6,
-- "texture": -1
-- },
-- {
-- "drawable": -1,
-- "prop_id": 7,
-- "texture": -1
-- }
-- ]
-- }
-- this still needed?