Integrations
Integrations
Everything another resource needs to work with ATY Clothing: exports, menu types and category keys, events, and the qb-clothing, esx_skin and skinchanger events it answers. Exports are called as exports['aty_clothing']:Name(...).
Client exports
| Export | Returns | What it does |
|---|---|---|
OpenCustomMenu(menuType, categories, title, shopId) |
— | Opens a menu with your own category list |
OpenWardrobeMenu(title) |
— | Saved outfits only |
OpenClothingShop(title, shopId) |
— | A clothing shop with Apparel |
OpenBarberShop(title, shopId) |
— | A barber with hair, overlays and the face |
OpenTattooShop(title, shopId) |
— | A tattoo parlour |
CloseMenu(restoreSkin) |
— | Closes the menu |
IsMenuOpen() |
boolean | Whether the menu is open |
ResetPedAppearance() |
boolean | Discards unsaved changes |
GetPedSkinData() |
table or nil | The player's current skin |
ApplySkinData(skinData) |
— | Applies a skin to the player's ped |
GetNearbyInteraction() |
table or nil | The shop the player is standing at |
OpenCustomMenu
| Parameter | Default | Notes |
|---|---|---|
menuType |
"creator" |
creator, clothing, market, barber or tattoo — see Menu types |
categories |
The type's default list | Category keys, "category:part" entries or pre-v3 keys |
title |
"Character Customization" | Header text. The creator always shows its own heading |
shopId |
"custom_api" |
Handed to menuOpened. Prices and shop balances ignore it — the server works out the shop from where the player stands |
-- A barber that also does facial features
exports['aty_clothing']:OpenCustomMenu("barber", { "hair", "overlays", "faceAdjust" }, "Downtown Barber", "barber_downtown")
-- A jewellery counter: one Apparel shelf
exports['aty_clothing']:OpenCustomMenu("market", { "apparel:handWrist" }, "Vangelico", "jewellery_1")
Calling any open export while a menu is already open does nothing.
Shop shortcuts
| Export | Type | Categories | Default title |
|---|---|---|---|
OpenWardrobeMenu(title) |
clothing |
wardrobe |
Wardrobe |
OpenClothingShop(title, shopId) |
clothing |
apparel |
Clothing Store |
OpenBarberShop(title, shopId) |
barber |
hair, overlays, faceAdjust, faceBody |
Barber Shop |
OpenTattooShop(title, shopId) |
tattoo |
tattoos |
Tattoo Studio |
CloseMenu and ResetPedAppearance
CloseMenu(true) puts the model and look back to how they were when the menu opened. CloseMenu(false) closes and leaves the ped as it is, previewed pieces included. Nothing is saved either way — /refreshskin or a reconnect brings back the saved look.
ResetPedAppearance() puts back the look the menu opened with and reloads the saved skin. It returns false when there is nothing to go back to. Use it while the menu is open: the ped is frozen again three seconds later.
GetPedSkinData and ApplySkinData
GetPedSkinData() returns the skin table plus two things the menu uses: maxValues (the highest valid value of each key for this ped) and tattoo_<zone>_overlay keys. Face and heritage values come from the character's saved data when the framework holds it, otherwise from the ped.
ApplySkinData(skinData) applies a skin to the current model — it never switches model — and redraws tattoos when the table has tattoos. To switch model as well, use setPedSkin below or the server export LoadPlayerSkin.
GetNearbyInteraction
local here = exports['aty_clothing']:GetNearbyInteraction()
-- { active = true, kind = "shop", shopType = "clothing", shopId = 3,
-- title = "Clothing Shop", coords = vector3(...) }
kind is "shop" or "jobshop"; nil when the player is not at a shop. Wardrobes are announced only through the nearInteraction event. Neither is tracked with Config.DisplayMethod = "target", where the target resource handles proximity.
Lower-level client exports
| Export | Returns | What it does |
|---|---|---|
OpenClothingMenu(menuType, shopData) |
— | What the open exports call. shopData = { title, allowedCategories, shopId } |
CloseClothingMenu(restoreSkin) |
— | Same as CloseMenu |
ResetPed() |
boolean | Same as ResetPedAppearance |
getPedSkin(ped) |
table or nil | GetPedSkinData for any ped |
setPedSkin(ped, skin, changeModel) |
boolean | Applies a full skin; switches model unless changeModel is false |
getMaxValues(ped) |
table or nil | Highest valid value of each key for that ped |
getPedClothingOnly(ped) |
table | Clothing and prop keys only — what the wardrobe stores |
setPedClothingOnly(ped, clothing) |
boolean | Applies clothing and prop keys; face, hair and tattoos stay |
loadPlayerClothing(skinData, ped) |
— | Applies a skin to a ped (the player's by default) without switching model |
setPedComponent(data) |
boolean | Applies one key, e.g. { name = "torso_1", value = 4 }, to the player's ped. Used by the menu |
ResetCameraFocus, RotatePed, RotatePedMouse, SmoothFocusCamera, FocusOnPoint, FocusAnchor, GetCameraState and StartHotspotTracking drive the menu's preview camera and only have an effect while the menu is open.
Server exports
| Export | Returns | What it does |
|---|---|---|
OpenClothingMenu(source, menuType, shopData) |
— | Opens a menu on that player |
CloseClothingMenu(source, restoreSkin) |
— | Closes it and restores the look — see below |
LoadPlayerSkin(source) |
boolean | Applies the saved skin to the player's ped |
GetPlayerSkin(source, cb) |
— | cb(skinData, model) from the database |
SavePlayerSkin(source, skinData) |
boolean | Replaces the saved skin |
GetPlayerOutfits(source, cb) |
— | cb(outfits) from the wardrobe |
OpenClothingMenu takes the same menuType (default "creator") and the same shopData fields as the client export: title, allowedCategories, shopId.
LoadPlayerSkin reads the active playerskins row (QBCore) or users.skin (ESX) and applies it on the client, switching model when needed. It returns false when the player has no loaded character; the database read happens after it returns.
exports['aty_clothing']:GetPlayerSkin(source, function(skinData, model)
if not skinData then return end -- nothing saved
print(model, json.encode(skinData))
end)
SavePlayerSkin replaces the stored skin — on QBCore it becomes the active playerskins row for its model, on ESX it overwrites users.skin. The model comes from model, then ped, then sex. It returns true once the write is queued, not when it has finished, and it does not touch the ped: call LoadPlayerSkin to show the result.
exports['aty_clothing']:GetPlayerOutfits(source, function(outfits)
for _, outfit in ipairs(outfits) do
local clothes = json.decode(outfit.skin) -- clothing and prop keys
print(outfit.outfitId, outfit.outfitname, outfit.model)
end
end)
Each row is a player_outfits record: id, citizenid, outfitname, model, skin (JSON), outfitId and created_at.
Menu types and categories
Menu types
| Type | Default categories | Checkout |
|---|---|---|
creator |
pedSelector, faceBody, faceAdjust, overlays, hair, apparel, tattoos |
Save Character — free unless the player is within 30 m of a shop |
clothing |
wardrobe, apparel, hair, overlays |
Purchase |
market |
apparel |
Purchase |
barber |
hair, overlays |
Purchase |
tattoo |
tattoos |
Purchase |
The defaults apply when an export or /skin passes no list. Shops placed in config.lua use Config.ShopCategories for their shopType. A purchase charges only for changed clothing slots — see Prices.
Category keys and parts
"category:part" offers one group of a category. Several parts of the same category add up, and a bare "category" in the same list wins over any narrowing.
| Key | Contains | Parts |
|---|---|---|
pedSelector |
Model list; dropped when Config.PedSelector.Enabled = false |
— |
faceBody |
Sex, character presets, heritage, skin tone, eye colour | sex, presets, heritage, skin, eyecolor |
faceAdjust |
Face shape | nose, brow, cheeks, eyes, lips, jaw, chin, neck |
overlays |
Skin and makeup overlays | blemishes, ageing, makeup, blush, complexion, sundamage, lipstick, moles, bodyblemishes, addbodyblemishes |
hair |
Hair and its colour, eyebrows, beard, chest hair | hairs, eyebrows, beard, chest |
apparel |
Five shelves of clothing and accessories | A shelf or a group — see below |
tattoos |
Six zones | tattooHead, tattooTorso, tattooLeftArm, tattooRightArm, tattooLeftLeg, tattooRightLeg |
wardrobe |
Saved outfits | — |
| Apparel shelf | Groups on it |
|---|---|
outfits |
outfitsCasual, outfitsWork, outfitsActivity |
clothing |
torso, tshirt, decals, arms, pants, shoes |
upperBody |
chains, bproof, bags |
handWrist |
watches, bracelets |
head |
helmet, glasses, ears, mask |
A Config.Slots menu is the group slot_<key> (for example slot_gloves) on the shelf its section names.
{ "hair:beard" } -- beard trimmer
{ "apparel:handWrist", "apparel:head" } -- accessories counter
{ "tattoos:tattooLeftArm", "tattoos:tattooRightArm" } -- arm work only
Pre-v3 keys
Keys from before v3 still work, in config and in exports:
| Old key | Opens |
|---|---|
dna |
faceBody, faceAdjust |
face |
faceAdjust |
hairs |
hair, overlays |
clothes, accessories |
apparel |
tattoo |
tattoos |
headTattoo |
tattoos:tattooHead |
bodyTattoo |
tattoos:tattooTorso |
armsTattoo |
tattoos:tattooLeftArm, tattoos:tattooRightArm |
legsTattoo |
tattoos:tattooLeftLeg, tattoos:tattooRightLeg |
The old tattoo tabs keep their exact zones, so a parlour set up to sell only head tattoos does not start selling leg tattoos.
Events and hooks
Local client events, for AddEventHandler:
| Event | Arguments | When |
|---|---|---|
aty_clothing:client:menuOpened |
menuType, shopData |
The menu opens |
aty_clothing:client:menuClosed |
menuType |
The menu starts closing |
aty_clothing:client:nearInteraction |
data |
The player enters or leaves a shop or wardrobe |
nearInteraction sends { active = false } on leaving. On entering, data.kind is "shop", "jobshop", "wardrobe" or "jobwardrobe"; shops add shopType and shopId, wardrobes wardrobeId, and both carry title and coords. It fires on changes only, and not with Config.DisplayMethod = "target".
After a creator save is stored, the server also sends aty_clothing:client:characterSaved with the saved skin. It is a network event: register it with RegisterNetEvent to listen.
Without writing a resource, the same moments are reachable from config.lua. A hook that errors is caught and printed, and cannot break the menu:
Config.Hooks = {
OnMenuOpen = function(menuType, shopData) --[[ e.g. hide your HUD ]] end,
OnMenuClose = function(menuType) --[[ e.g. show it again ]] end,
}
Compatibility with other clothing resources
ATY Clothing answers the events below. It does not stand in for another resource's exports (the manifest provides none), so code that calls another clothing resource's exports has to move to the exports on this page. Events that are not listed, such as skinchanger:getSkin or qb-clothing:client:openMenu, are not handled.
qb-clothing
Active while Config.QBClothingEvents.Enabled = true. Only Enabled is read; the Events list beside it changes nothing.
| Event | Side | What happens |
|---|---|---|
qb-clothes:client:CreateFirstCharacter |
Client | Sets the freemode model for the character's gender and opens the creator |
qb-clothing:client:loadPlayerClothing (data, ped) |
Client | Applies a skin table to a ped — multicharacter previews |
qb-clothes:loadSkin (_, model, data) |
Client | Sets the model (a hash) and applies the skin (JSON) |
qb-clothing:saveSkin (model, skin) |
Server | Saves the skin (JSON) as the active playerskins row. QBCore only |
qb-clothes:loadPlayerSkin, qb-clothing:loadPlayerSkin |
Server | Loads the saved skin onto the player. QBCore only |
qb-clothes:saveOutfit (name, model, skinData) |
Server | Passed to the wardrobe's own save |
qb-clothing:server:removeOutfit (name, outfitId) |
Server | Passed to the wardrobe's own delete |
qb-clothing:server:getOutfits |
Server callback | Returns the player's outfits |
esx_skin and skinchanger
Active with Config.Framework = "esx".
| Event | What happens |
|---|---|
skinchanger:loadSkin (skin, cb) |
Applies the skin, then calls cb |
esx_skin:openSaveableMenu |
Sets the freemode model for the player's sex and opens the creator. It does not call the save callback it is given |
esx:playerLoaded (playerData, isNew, skin) |
Applies skin for a returning character. With Config.Multicharacter = false it loads the saved skin itself |
When the creator saves on ESX, the client fires skinchanger:loadSkin and esx_skin:characterSaved for freemode models. With Config.TriggerPlayerLoadedOnCreation = true the framework's player-loaded events fire as well, on QBCore and ESX; the server-side one only once per connection.
Examples
Multicharacter: a new character
-- Client
local creating = false
-- Your multicharacter has just created a character
RegisterNetEvent('my_multicharacter:client:newCharacter', function(gender)
local model = gender == 1 and `mp_f_freemode_01` or `mp_m_freemode_01`
RequestModel(model)
while not HasModelLoaded(model) do Wait(0) end
SetPlayerModel(PlayerId(), model)
SetModelAsNoLongerNeeded(model)
creating = true
exports['aty_clothing']:OpenCustomMenu("creator", {
"faceBody", "faceAdjust", "overlays", "hair", "apparel", "tattoos",
})
end)
-- Continue once the save has reached the database
RegisterNetEvent('aty_clothing:client:characterSaved', function(savedSkin)
if not creating then return end
creating = false
-- spawn the player, show your HUD, ...
end)
The menu closes on its own right after characterSaved. menuClosed fires too, but also when the player leaves without saving. A QBCore multicharacter that already triggers qb-clothes:client:CreateFirstCharacter needs no change.
House or motel wardrobe
-- Client: inside your closet interaction
if nearCloset and IsControlJustPressed(0, 38) then -- E
exports['aty_clothing']:OpenWardrobeMenu("House Wardrobe")
end
Admin edit
-- Server
RegisterCommand('editlook', function(source, args)
local target = tonumber(args[1])
if not target or not GetPlayerName(target) then return end
exports['aty_clothing']:OpenClothingMenu(target, "creator", {
allowedCategories = { "faceBody", "faceAdjust", "overlays", "hair", "apparel", "tattoos" },
})
end, true) -- restricted: needs the command.editlook ACE
creator is used so the change is saved without charging the player (unless they stand within 30 m of a shop). /skin [id] creator does the same without any code.
Your own prompt
-- config.lua: Config.DisplayMethod = "none"
AddEventHandler('aty_clothing:client:nearInteraction', function(data)
if data.active then
exports['my_prompt']:show(('[E] %s'):format(data.title or 'Clothing'))
else
exports['my_prompt']:hide()
end
end)
With "none" only the built-in prompt is hidden: the zones and the E key still open the shop or wardrobe.
Menu presentation
| Setting | Default | Effect |
|---|---|---|
Config.UseScreenFade |
true |
The screen fades out (400 ms) while the ped and camera are set up and back in (600 ms), on opening and on closing |
Config.HideMinimap |
true |
The minimap is hidden while the menu is open, and shown again on close only if it was visible before |
Config.Camera.FocusOnCategory |
false |
true: picking a category moves the camera to that part of the body. Clicking a marker on the character always does |
While the menu is open the ped is frozen, invincible and holds a still idle pose, so ambient animations and head turns do not move it. Freezing and invincibility end when the menu closes. If the resource stops while a menu is open, the minimap, focus, freeze and pose are all restored, so a restart never leaves a player stuck.