Clothing Integration
Clothing Integration
ML Clothing keeps worn clothes and inventory items in sync in two directions. Saving worn clothing back to the appearance script is automatic: the resource detects the running appearance script at startup and writes through its save path. The opposite direction, turning an outfit picked in an appearance menu into inventory items, needs one line added to the appearance script at every point where an outfit is saved. This page shows where to add that line for illenium-appearance and bl_appearance, and how to adapt any other appearance script.
Sync entry points
Call either of these from the appearance script whenever a player saves or changes an outfit. Both send the appearance data to the server, which translates it through the data adapters and places the matching clothing items into the dedicated inventory slots.
1exports.ml_clothing:syncFromNUI(appearanceData)1TriggerEvent('ml_clothing:client:syncFromNUI', appearanceData)When the appearance data of the script is not available or its format has no adapter, read the live ped instead. The call below captures the current components and props after the appearance script has applied them, so it works with any appearance system:
1exports.ml_clothing:syncFromPed()The server drops syncs that arrive during the first seconds after a character loads, while the inventory is still rebinding. This protects against item corruption on login and character switch. A dropped sync prints syncNewOutfit blocked in the server console when Config.Debug = true. Normal menu flows finish well after the gate opens.
Data adapters
The server does not care which appearance script produced the data. Each shipped format has its own translator file in open/adapters/: illenium.lua, bl_appearance.lua, and native.lua. An adapter turns the appearance payload into entries of { type, id, drawable, texture } where type is component or prop; the first adapter that returns entries wins. To support a different appearance script, drop a new file in the folder and call one of the sync entry points from that script:
1Config.DataAdapters = Config.DataAdapters or {}
2
3function Config.DataAdapters.custom_appearance(appearanceData)
4 local standardClothes = {}
5 -- translate the outfit data into entries like:
6 -- standardClothes[#standardClothes + 1] = { type = 'component', id = 11, drawable = 15, texture = 0 }
7 return standardClothes
8endA file added to the resource is only picked up after refresh followed by ensure ml_clothing. A plain restart without refresh skips new files.
illenium-appearance
Three functions in client/client.lua save an outfit. Add the export call right after each illenium-appearance:server:saveAppearance trigger. Unchanged lines are collapsed with -- ....
Character creation
Covers the starting clothes of a fresh character. Without this call the outfit picked during character creation never becomes inventory items.
1function InitializeCharacter(gender, onSubmit, onCancel)
2 -- ...
3 client.startPlayerCustomization(function(appearance)
4 if (appearance) then
5 TriggerServerEvent("illenium-appearance:server:saveAppearance", appearance)
6 exports.ml_clothing:syncFromNUI(appearance) -- add this line
7 if onSubmit then
8 onSubmit()
9 end
10 elseif onCancel then
11 onCancel()
12 end
13 -- ...
14 end, config)
15endClothing shop
1function OpenShop(config, isPedMenu, shopType)
2 lib.callback("illenium-appearance:server:hasMoney", false, function(hasMoney, money)
3 -- ...
4 client.startPlayerCustomization(function(appearance)
5 if appearance then
6 if not isPedMenu then
7 TriggerServerEvent("illenium-appearance:server:chargeCustomer", shopType)
8 end
9 TriggerServerEvent("illenium-appearance:server:saveAppearance", appearance)
10 exports.ml_clothing:syncFromNUI(appearance) -- add this line
11 else
12 -- ...
13 end
14 Framework.CachePed()
15 end, config)
16 end, shopType)
17endOutfit change
1RegisterNetEvent("illenium-appearance:client:changeOutfit", function(data)
2 -- ...
3 if data.disableSave then
4 -- ...
5 else
6 local appearance = client.getPedAppearance(cache.ped)
7 TriggerServerEvent("illenium-appearance:server:saveAppearance", appearance)
8 exports.ml_clothing:syncFromNUI(appearance) -- add this line
9 end
10 -- ...
11end)codem-appearance
Detection covers the resource under both of its common names, crm-appearance and codem-appearance. Saving worn clothing back to its database is automatic: after every inventory equip or unequip, ML Clothing calls the crm_save_appearance export, which reads the live ped and writes its own row. Nothing to set up for that direction.
Because the resource is escrowed, its files cannot be edited. Wire the outfit-to-items direction from the scripts on your side that call its documented hooks. Character creation, in the multicharacter script:
1TriggerEvent('crm-appearance:init-new-character', 'crm-male', function()
2 exports.ml_clothing:syncFromPed()
3end)Any custom flow that saves through the export:
1exports['crm-appearance']:crm_save_appearance(nil, function()
2 exports.ml_clothing:syncFromPed()
3end)The menus opened with crm-appearance:show-clothing-menu, show-outfits, and show-barber-menu do not expose a save callback, so there is no hook point for those flows. Outfits changed there reach the inventory the next time a flow with a callback runs, or when the player equips something through the inventory.
bl_appearance
bl_appearance is TypeScript. Edit the two spots below in src/client/ and rebuild with pnpm run init, or apply the same lines directly to the deployed dist/client/init.js.
In src/client/appearance/setters.ts, as the last line of setPlayerPedAppearance:
1export async function setPlayerPedAppearance(data) {
2 // ...
3 setPedHairColors(ped, data.hairColor)
4 setPedTattoos(ped, data.tattoos)
5 emit("ml_clothing:client:syncFromNUI", data) // add this line, keep it last
6}In src/client/handlers.ts, inside the save callback, after the save round trip has produced the validated appearance:
1RegisterNuiCallback(Receive.save, async (appearance, cb) => {
2 // ...
3 const newAppearance = await getAppearance(ped)
4 newAppearance.tattoos = appearance.tattoos || null
5 triggerServerCallback("bl_appearance:server:saveAppearance", getFrameworkID(), newAppearance)
6 emit("ml_clothing:client:syncFromNUI", newAppearance) // add this line
7 setPedTattoos(ped, newAppearance.tattoos)
8 closeMenu()
9 cb(1)
10})Placing the emit at the top of setPlayerPedAppearance sends the raw menu payload. Out of range drawables from presets or missing addon packs would then be written into item metadata and spam SetVariation errors at the next login. Keep the emit after the setters have run.
Verify
- Set
Config.Debug = trueinshared/config.luaand restartml_clothing. - Change outfit in the appearance menu and save.
- The clothing slots in the inventory update to the new outfit and the server console logs the sync.
If the console prints syncNewOutfit blocked instead, the player was not fully loaded yet; wait a few seconds in game and save again.