Editable

This document covers all files in the `editable` folders that can be safely modified to customize the script for your server needs.

Overview

Files in editable folders are designed to be modified without breaking the core functionality. These files handle:

  • Compatibility with other scripts

  • Custom behaviors and checks

  • Database operations

  • UI interactions

  • Development tools

Client Editable Files

Located in client/editable/

compatibility.lua

Purpose: Ensures compatibility with other clothing/skin scripts like skinchanger.

Key Functions

function getClothingOptions(excludedComponents, excludedProps)
    -- Gets clothing options excluding specified components/props
    -- Parameters:
    --   excludedComponents (table): Component IDs to exclude
    --   excludedProps (table): Prop IDs to exclude
    -- Returns: table with components and props
end
function setClothingOptions(clothingOptions)
    -- Sets clothing options (excluding face components)
    -- Parameters:
    --   clothingOptions (table): Clothing data to apply
end
function getSkinOptionsDb()
    -- Retrieves skin data from database
    -- Returns: skin data table or nil
end
function saveSkinOptions(skinOptions)
    -- Saves skin options to database
    -- Parameters:
    --   skinOptions (table, optional): Data to save (defaults to current)
    -- Returns: boolean success status
end

Registered Events

RegisterNetEvent('skinchanger:loadClothes', setClothingOptions)
RegisterNetEvent('skinchanger:change', setComponentOption)

Exports

exports('getClothingOptions', getClothingOptions)
exports('setClothingOptions', setClothingOptions)
exports('getSkinOptionsDb', getSkinOptionsDb)
exports('getSkinOptions', getSkinOptions)
exports('saveSkinOptions', saveSkinOptions)

const/constants.lua

Purpose: Defines all constants and mappings used throughout the script.

Component IDs

constants.PED_COMPONENTS_IDS = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }
constants.PED_PROPS_IDS = { 0, 1, 2, 3, 4, 5, 6, 7 }

Component Name Mapping

constants.componentsId = {
    ["face"] = 0,      -- Face structure
    ["masks"] = 1,     -- Masks
    ["tshirts"] = 8,   -- T-shirts/tops
    ["pants"] = 4,     -- Pants/legs
    ["shoes"] = 6,     -- Footwear
    ["hoodies"] = 11,  -- Hoodies/torso
    ["jewelry"] = 7,   -- Necklaces/accessories
    ["bags"] = 5,      -- Bags/backpacks
    ["decals"] = 10,   -- Decals
    ["vests"] = 9,     -- Vests/armor
    ["arms"] = 3,      -- Arms/gloves
}

Props Name Mapping

constants.propsId = {
    ["hats"] = 0,      -- Hats/helmets
    ["glasses"] = 1,   -- Glasses/eyewear
    ["ear"] = 2,       -- Earpieces
    ["watch"] = 6,     -- Watches
    ["bracelet"] = 7,  -- Bracelets
}

Face Features

constants.FACE_FEATURES = {
    "noseWidth", "nosePeakHigh", "nosePeakSize", "noseBoneHigh",
    "nosePeakLowering", "noseBoneTwist", "eyeBrownHigh", "eyeBrownForward",
    "cheeksBoneHigh", "cheeksBoneWidth", "cheeksWidth", "eyesOpening",
    "lipsThickness", "jawBoneWidth", "jawBoneBackSize", "chinBoneLowering",
    "chinBoneLenght", "chinBoneSize", "chinHole", "neckThickness",
}

Head Overlays

constants.HEAD_OVERLAYS = {
    "blemishes", "beard", "eyebrows", "ageing", "makeUp", "blush",
    "complexion", "sunDamage", "lipstick", "moleAndFreckles",
    "chestHair", "bodyBlemishes",
}

Hair Decorations

Contains extensive mappings for male and female hair decorations with texture dictionaries.

devCommands.lua

Purpose: Development commands for testing and debugging (only active when debug mode is enabled).

if not fs.config.debug then
    return  -- Skip loading if not in debug mode
end

RegisterCommand('openskin', function()
    openSkinMenu(false)
end, false)

RegisterCommand('closeskin', function()
    closeSkinMenu()
end, false)

Default Available Commands (only in debug mode)

  • /openskin - Opens the skin menu

  • /closeskin - Closes the skin menu

disabledClothings.lua

Purpose: Configuration for default clothing when items are toggled off.

Structure

defaultDisabledClothings = {
    hats = {
        enabled = true,        -- Whether this option is available
        propIndex = 0,         -- Prop index (for accessories)
        componentIndex = nil,  -- Component index (for clothing)
        drawable = 0,          -- Default drawable ID
        variant = 0           -- Default texture variant
    },
    -- ... other items
}

Configurable Items

Item
Type
Description

hats

Prop

Hats and helmets

masks

Component

Face masks

glasses

Prop

Eyewear

hoodies

Component

Hoodies/torso (multi-component)

bags

Component

Backpacks and bags

pants

Component

Pants and legs

shoes

Component

Footwear

Multi-Component Items

Some items affect multiple components:

hoodies = {
    enabled = true,
    propIndex = nil,
    componentIndex = { 11, 8, 3 },  -- Multiple components
    default = { 91, 15, 15 },       -- Default values for males
    defaultF = { 15, 15, 15 },      -- Default values for females
    drawable = { 0, 0, 0 },         -- Drawable when disabled
    variant = { 0, 0, 0 }           -- Variants when disabled
}

handlers.lua

Purpose: Handles menu opening/closing events and player loading.

Event Handlers

function onSkinMenuOpened()
    -- Called when skin menu opens
    -- Default: Shows key hints for camera controls
    showKeyhintsPermanent({
        {
            keyIcon = { "ph-fill ph-mouse-left-click", "ph-fill ph-arrows-horizontal" },
            keyClass = "flex flex-col items-center align-center keyhint_iconsVertical",
            info = locale('keyhint_rotateCharacter'),
        },
        -- ... more hints
    }, 'bottom-left')
end
function onSkinMenuClosed()
    -- Called when skin menu closes
    clearKeyhints()
end
AddEventHandler('5s_lib:onPlayerLoaded', function()
    -- Automatically load saved skin when player joins
    local skin = lib.callback.await('skinMenu/getSkin', false)
    if skin ~= nil and skin ~= {} then
        setSkinOptions(skin)
    end
end)

Customization Examples

Custom opening behavior:

function onSkinMenuOpened()
    -- Your custom logic here
    TriggerEvent('myserver:logSkinMenuOpen')
    
    -- Still show keyhints
    showKeyhintsPermanent(yourCustomHints, 'top-right')
end

Integration with other systems:

function onSkinMenuClosed()
    clearKeyhints()
    
    -- Example: Trigger outfit save in custom system
    if isInClothingStore then
        TriggerServerEvent('clothingstore:purchaseOutfit')
    end
end

maskFix.lua

Purpose: Automatically adjusts facial features when wearing masks for better compatibility.

Key Features

  • Automatically saves face blend data when mask is equipped

  • Shrinks facial features to prevent clipping

  • Restores original features when mask is removed

Main Functions

function forceRestoreFace()
    -- Manually restore saved facial features
    -- Use when mask removal doesn't trigger automatically
end

function fixClothing()
    -- Fix clothing-related issues
    -- Calls mask fix if needed
end

Exports

exports("forceRestoreFace", forceRestoreFace)
exports("fixClothing", fixClothing)

prechecks.lua

Purpose: Validation checks before allowing menu access.

Main Function

function canOpenSkinMenu()
    -- Add your custom checks here
    
    -- Example: Check if player is morphed
    -- if exports['other_script']:isPlayerMorphed() then
    --     fs.utils.notify('Cannot change appearance right now', 'error')
    --     return false
    -- end
    
    -- Example: Check player permissions
    -- if not exports['permission_system']:hasPermission('use.skinmenu') then
    --     return false
    -- end
    
    -- Example: Check if player is in jail
    -- if exports['esx_jail']:isPlayerInJail() then
    --     return false
    -- end
    
    return true
end

Common Use Cases

Permission-based access:

function canOpenSkinMenu()
    local playerGroup = ESX.GetPlayerData().group
    if playerGroup == 'user' and not isInClothingStore then
        fs.utils.notify('You need to be in a clothing store', 'error')
        return false
    end
    return true
end

Location-based restrictions:

function canOpenSkinMenu()
    local playerCoords = GetEntityCoords(PlayerPedId())
    local inRestrictedZone = IsPlayerInRestrictedZone(playerCoords)
    
    if inRestrictedZone then
        fs.utils.notify('Cannot change appearance in this area', 'error')
        return false
    end
    return true
end

State-based checks:

function canOpenSkinMenu()
    if IsPedInAnyVehicle(PlayerPedId()) then
        fs.utils.notify('Exit vehicle first', 'error')
        return false
    end
    
    if GetEntityHealth(PlayerPedId()) <= 0 then
        return false
    end
    
    return true
end

Server Editable Files

Located in server/editable/

database.lua

Purpose: Database operations for saving and loading skin data.

Core Functions

function saveSkinData(source, skinOptions)
    -- Saves player skin data to database
    -- Parameters:
    --   source (number): Player server ID
    --   skinOptions (table): Skin data to save
    -- Returns: boolean success status
    
    local identifier = fs.framework.getIdentifier(source)
    
    local existingData = retrieveSkinData(source)
    if not existingData then
        -- Insert new record
        MySQL.update.await('INSERT INTO skin_data(identifier, skinData) VALUES (?, ?)', {
            identifier,
            json.encode(skinOptions)
        })
    else
        -- Update existing record
        success = MySQL.update.await('UPDATE skin_data SET skinData = ? WHERE identifier = ?', {
            json.encode(skinOptions),
            identifier
        }) ~= 0
    end
    
    return success
end
function retrieveSkinData(source)
    -- Retrieves player skin data from database
    -- Parameters:
    --   source (number): Player server ID
    -- Returns: table with skin data or nil
    
    local identifier = fs.framework.getIdentifier(source)
    
    local result = MySQL.single.await('select skinData from skin_data where identifier = ?', { identifier })
    return result?.skinData and json.decode(result.skinData) or nil
end

Last updated