Kartik Scripts

op-multicharacter Setup

This guide shows how to connect kartik-clothing to op-multicharacter (by OTHERPLANET).

op-multicharacter loads one integration file per clothing resource and picks it with Config.Clothing. kartik-clothing gets its own pair of files, the same way op-multicharacter already supports its other clothing resources. None of its escrowed files need changing.


🛠️ Official File Edits

1. op-multicharacter/integrations/client/clothing/kartik-clothing.lua

Create this file. It dresses the selection preview peds and opens the kartik-clothing character creator for new characters:

if Config.Clothing ~= "kartik-clothing" then return end

function applySkinToPed(ped, skin)
    if type(skin) ~= "table" then
        debugPrint("^1[applySkinToPed]^0 Invalid skin data (expected table, got " .. type(skin) .. ").")
        return
    end

    if type(skin.skin) ~= "table" then
        debugPrint("^1[applySkinToPed]^0 Invalid skin.skin data (expected table, got " .. type(skin.skin) .. ").")
        return
    end

    local ok, err = pcall(function()
        exports['kartik-clothing']:setPedAppearance(ped, skin.skin)
    end)
    if not ok then
        debugPrint("^1[applySkinToPed]^0 kartik-clothing setPedAppearance failed:", err)
    end
end

function reapplySavedAppearance()
    if isInsideNew then return end

    local ok, data = pcall(function()
        return lib.callback.await("op-multicharacter:getActiveSkin", false)
    end)
    if not ok or type(data) ~= "table" or type(data.skin) ~= "table" then return end

    local ped = PlayerPedId()
    if not DoesEntityExist(ped) then return end

    applySkinToPed(ped, data)
end

local function revealWorldForClothingCreator()
    if OPMC and type(OPMC.endCharMenuSession) == "function" then
        OPMC.endCharMenuSession()
    else
        SendNUIMessage({ action = "setCharMenuSession", data = false })
    end

    SendNUIMessage({ action = "setBlackScreen", data = false })
    SendNUIMessage({ action = "setloadervisible", data = false })
    SendNUIMessage({ action = "setCreatorMenuVisible", data = false })
    SendNUIMessage({ action = "setCharacterVisible", data = false })
    SendNUIMessage({ action = "setcharVisible", data = false })

    local playerPed = PlayerPedId()
    if DoesEntityExist(playerPed) then
        FreezeEntityPosition(playerPed, false)
        SetEntityCollision(playerPed, true, true)
        SetEntityVisible(playerPed, true, false)
        SetEntityInvincible(playerPed, false)
        ResetEntityAlpha(playerPed)
    end
    SetPlayerControl(PlayerId(), true, 0)

    if IsScreenFadedOut() or IsScreenFadingOut() then
        DoScreenFadeIn(400)
        local deadline = GetGameTimer() + 3000
        while not IsScreenFadedIn() and GetGameTimer() < deadline do
            Wait(0)
        end
    end
end

function openCreationMenu(isMale)
    if type(Config.SwitchHud) == "function" then
        Config.SwitchHud(false)
    end

    revealWorldForClothingCreator()

    exports['kartik-clothing']:startPlayerCustomization(function()
        Fr.CharacterCreated()
    end, { gender = isMale and 'male' or 'female' })
end

Do not trigger op-multicharacter:saveSkin from the creator callback. kartik-clothing has already saved the character by the time the callback runs, and it removes any locked items before saving. Saving again through op-multicharacter skips that check and writes to the playerskins / users.skin tables, which kartik-clothing doesn't read.


2. op-multicharacter/integrations/server/clothing/kartik-clothing.lua

Create this file. It loads each character's skin for the selection screen:

if Config.Clothing ~= "kartik-clothing" then return end

function convertSkin(identifier)
    local ok, skin = pcall(function()
        return exports['kartik-clothing']:getPlayerAppearance(identifier)
    end)
    if ok and type(skin) == "table" then
        return {skin = skin, model = skin.model or "mp_m_freemode_01"}
    end
end

lib.callback.register("op-multicharacter:getActiveSkin", function(source)
    local Player = Fr.getPlayerFromId(source)
    if not Player then return nil end
    local id = Player.identifier or (Player.PlayerData and Player.PlayerData.citizenid)
    if type(id) ~= "string" or id == "" then return nil end
    return convertSkin(id)
end)

Use the export rather than querying kc_player_skins directly. The export swaps unlisted addon peds (saved as hash:<n>) for the default body, because op-multicharacter loads models with joaat() and would otherwise hang on the selection screen.


3. op-multicharacter/config/MainConfig.lua

Add kartik-clothing to the supported list and select it:

-- SUPPORTED CLOTHING SCRIPTS
-- op-clothing (https://otherplanet.dev/product/clothing)
-- kartik-clothing
-- ...

Config.Clothing = "kartik-clothing"

⚙️ Setup Instructions

  1. Start kartik-clothing before op-multicharacter in your server.cfg:

    ensure ox_lib
    ensure kartik-clothing
    ensure op-multicharacter
  2. Set Config.Clothing to kartik-clothing in op-multicharacter/config/MainConfig.lua. Without this, op-multicharacter keeps loading the old clothing integration and kartik-clothing is never used:

    Config.Clothing = "kartik-clothing"
  3. Leave Config.Tattoos = "none". Tattoos are part of the kartik-clothing skin and show on the preview peds without a separate tattoo integration.

  4. The cutscene and spawn selector still run after the character creator is confirmed.

  5. Remove your old clothing resource from your server.cfg. If it was illenium-appearance, run the migration so existing characters show up on the selection screen.

On this page