# :scissors: Hairdresser
Documentation relating to jo_hairdresser.

:::: tabs
::: tab BUY
[Buy the script](https://jumpon-studios.com/redm/hairdresser)
:::
::: tab PREVIEW
<iframe width="560" height="315" src="https://www.youtube.com/embed/fk8_NfIcVTo?si=jPp9u4nlWKLpOtj1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
:::
::::

## 1. Installation
jo_hairdresser works on all frameworks compatible with jo_libs ([the list](/jo_libs/)).

To install jo_hairdresser:
- Download the library: [jo_libs](https://github.com/Jump-On-Studios/RedM-jo_libs/releases/latest/download/jo_libs.zip)
- Unzip the folder and drop it into your resources folder
- Download jo_hairdresser from your [account](https://jumpon-studios.com/account)
- Unzip the folder and drop it into your resources folder
- Add this ensure to your server.cfg
  - `ensure jo_libs`
  - `ensure jo_hairdresser`

Congratulations, the Hairdresser script is ready to use!
:::warning
Make sure you have oxmysql ensured in your server.cfg.
:::

:::: details For VORP
To fix clothes and skin, you have to edit two files:
:::code-group
```lua:line-numbers=403 [vorp_character/client/client.lua]
	LoadCharacterSelect(playerPed, value.skin, value.components)
	CachedSkin = value.skin
	canContinue = false
	ApplyFaceOverlays(value.skin)
	canContinue = true
	FaceOverlay("grime", value.skin.grime_visibility, value.skin.grime_tx_id, 0, 0, 0, 1.0, 0, 1, 0, 0, 0, 1, value.skin.grime_opacity)
	Wait(500)

	TriggerServerEvent("jo_libs:server:vorp:applySkinAndClothes", value.charIdentifier, playerPed) -- [!code ++]
	Wait(2000) -- [!code ++]
	IsPedReadyToRender(playerPed) -- [!code ++]

	data.PedHandler = ClonePed(playerPed, false, false, false, false)
```
:::
::::

## 2. Usage

:::: tabs
::: tab Find a hairdresser
Hairdressers are marked on the map with a barber blip icon when blips are enabled. By default, there are 3 hairdresser locations:
- **Blackwater**
- **Valentine**
- **Saint Denis**

Approach a barber chair and a prompt will appear when you're close enough.
:::

::: tab Hairdresser features
In the hairdresser menu, you can:
- **Change hair** - Browse, preview, and buy hairstyles
- **Change beard** - Browse, preview, and buy beard styles for male characters
- **Hair accessories** - Browse and buy hair accessories for female characters
- **Edit teeth** - Change teeth options when enabled
- **Edit eyebrow** - Change eyebrow style and opacity when enabled
- **Switch payment options** - Choose between available prices when multiple options exist
- **Hat prompt** - Remove or equip your hat while browsing
- **Pomade preview** - Preview pomade on male hairstyles before buying

Use the prompts displayed on screen to navigate, rotate your character, preview options, and purchase changes.
:::

::: tab Commands
The script provides a chat command to open the hairdresser interaction:
- `/hairdresser` - Sit on the nearest barber chair and open the menu

The command can be customized or disabled in the configuration.
:::

::: tab Mouse selection
If you use the mouse-selection integration, right-click a barber chair inside a configured hairdresser shop to open the interaction menu.
:::
::::

## 3. Configuration
The configuration files are located in the `config` folder within the resource. This folder contains two subfolders:

- **`_default.lock/`** - Contains the default configuration files. **Do not edit these files directly** as they may be overwritten during updates.
- **`custom/`** - This is where you place your custom configuration files to override the defaults.

### How to customize the configuration

1. Navigate to `jo_hairdresser/config/_default.lock/`
2. Copy the file you want to modify (e.g., `prices.lua`)
3. Paste it into `jo_hairdresser/config/custom/`
4. Edit the copied file in the `custom` folder

The script automatically loads files from `custom/` first, and only uses `_default.lock/` as a fallback. This system ensures your customizations are preserved when updating the script.

### Default configuration files

`jo_hairdresser/config/_default.lock/global.lua`

```lua
Config.debug = false              -- Enable extra debug output in the console.
Config.commandName = "hairdresser" -- Command used to open the hairdresser menu. Set to false to disable the command.
Config.audioVolume = 0.5          -- Menu sound volume. Use a value between 0.0 (muted) and 1.0 (full volume).
Config.displayBlips = true        -- Show hairdresser shop blips on the map.
Config.displayPromptDistance = 2.0 -- Distance from a shop/chair where the interaction prompt becomes visible.
Config.spawnChairDistance = 100.0 -- Distance from a configured shop where barber chairs are spawned.
Config.canColorEyebrow = true     -- Allow separate eyebrow color selection. If false, eyebrows use the selected hair color.
Config.oldVORPChar = false        -- VORP only: enable support for the old C# version of VORP Character.
```

`jo_hairdresser/config/_default.lock/keys.lua`

```lua
Config.keys = {
  enter = "INPUT_FRONTEND_RB",
  turnLeft = "INPUT_MOVE_LEFT_ONLY",
  turnRight = "INPUT_MOVE_RIGHT_ONLY",
  pomade = "INPUT_SHOP_SPECIAL",
  hat = "INPUT_INTERACT_OPTION2",
  spin = "INPUT_CONTEXT_ACTION",
  switchPrice = "INPUT_OPEN_SATCHEL_MENU"
}

Config.KeysDisabled = {
  `INPUT_MOVE_UD`,
  `INPUT_MOVE_LR`,
  `INPUT_MOVE_LB`,
  `INPUT_COVER`,
  `INPUT_ATTACK`,
  `INPUT_NEXT_WEAPON`,
  `INPUT_PREV_WEAPON`,
  `INPUT_OPEN_WHEEL_MENU`,
  `INPUT_TOGGLE_HOLSTER`,
  `INPUT_TWIRL_PISTOL`,
}
```

`jo_hairdresser/config/_default.lock/lang.lua`

```lua
Lang.back = "Back"
Lang.buy = "Buy"
Lang.color = "Color"
Lang.facialHair = "Facial Hair"
Lang.facialStubble = "Facial Stubble"
Lang.hair = "Hair"
Lang.hairdresser = "Hairdresser"
Lang.nativeDesc = "Browse native hair and beard styles"
Lang.of = "%1 of %2"
Lang.select = "Select"
Lang.selection = "Selection"
Lang.beard = "Beard"
Lang.editYourTeeth = "Edit your teeth"
Lang.free = "Free"
Lang.price = "Price"
--Hair
Lang.curlyAfro = "Curly Afro"
Lang.curlyLeftParted = "Curly Left Parted"
Lang.knottedBandana = "Knotted Bandana"
Lang.longCurlyAfro = "Long Curly Afro"
Lang.longMiddleParted = "Long Middle Parted"
Lang.longPonyTail = "Long Pony Tail"
Lang.longRightParted = "Long Right Parted"
Lang.longSlickedBack = "Long Slicked Back"
Lang.middlePartedFade = "Middle Parted Fade"
Lang.neatCentreParted = "Neat Centre Parted"
Lang.neatLeaftParted = "Neat Leaft Parted"
Lang.pompadourFade = "Pompadour Fade"
Lang.roughRightParted = "Rough Right Parted"
Lang.roughTied = "Rough Tied"
Lang.shortCurlyAfro = "Short Curly Afro"
Lang.shortPeaked = "Short Peaked"
Lang.shoulderWaves = "Shoulder Waves"
Lang.slickedBackFade = "Slicked Back Fade"
Lang.sweptBack = "Swept Back"
Lang.thickCenterParted = "Thick Center Parted"
Lang.Thining = "Thinning"
Lang.tiedShavedSides = "Tied Shaved Sides"
Lang.uncombedBalding = "Uncombed Balding"
Lang.wildBalding = "Wild Balding"
Lang.wildChop = "Wild Chop"
Lang.windswept = "Windswept"
--Beard
Lang.arcadian = "The Arcadian"
Lang.archway = "The Archway"
Lang.barkeep = "The Barkeep"
Lang.beaded = "The Beaded"
Lang.boxHedge = "The Box Hedge"
Lang.chinCurtain = "The Chin Curtain"
Lang.cleanShaven = "Clean Shaven"
Lang.count = "The Count"
Lang.enlightened = "The Enlightened"
Lang.general = "The General"
Lang.goatee = "The Goatee"
Lang.handhold = "The Handhold"
Lang.knightly = "The Knightly"
Lang.mutton = "The Mutton"
Lang.noble = "The Noble"
Lang.oldDutch = "The Old Dutch"
Lang.pioneer = "The Pioneer"
Lang.private = "The Private"
Lang.professor = "The Professor"
Lang.reverend = "The Reverend"
Lang.ruff = "The Ruff"
Lang.seaShanty = "The Sea Shanty"
Lang.twoShanks = "The Two Shanks"
Lang.walrus = "The Walrus"
Lang.woddsman = "The Woddsman"
--female Hair
Lang.bald = "Bald"
Lang.bigTitle = "Hairdresser"
Lang.buzzed = "Buzzed"
Lang.carefreeWaves = "Carefree Waves"
Lang.choppyUndercut = "Choppy Undercut"
Lang.coiffedDropCurls = "Coiffed Drop Curls"
Lang.Color = "Color"
Lang.combedLayers = "Combed Layers"
Lang.crazy = "Crazy"
Lang.customYourHair = "Custom your hair accessory"
Lang.dropCurlChignon = "Drop Curl Chignon"
Lang.fineCurlyPonytail = "Fine Curly Ponytail"
Lang.floralPin = "Floral Pin"
Lang.fullCurlyPonytail = "Full Curly Ponytail"
Lang.hairAccessories = "Hair Accessories"
Lang.haloBraidChignon = "Halo Braid Chignon"
Lang.jeweledBarrette = "Jeweled Barrette"
Lang.kinkyChignons = "Kinky Chignons"
Lang.knottedBouffant = "Knotted Bouffant"
Lang.longDreadlocks = "Long Dreadlocks"
Lang.longLooseWaves = "Long Loose Waves"
Lang.longTiedCurls = "Long Tied Curls"
Lang.longTiedWaves = "Long Tied Waves"
Lang.loopedChignon = "Looped Chignon"
Lang.looseSwirlChignon = "Loose Swirl Chignon"
Lang.menu = "Menu"
Lang.messyFingerWaves = "Messy Finger Waves"
Lang.messyPlait = "Messy Plait"
Lang.natives = "Natives"
Lang.naturalShortAfro = "Natural Short Afro"
Lang.neatPlait = "Neat Plait"
Lang.neatTwistChignon = "Neat Twist Chignon"
Lang.noHairAccessories = "No Hair Accessories"
Lang.pigtails = "Pigtails"
Lang.pompadour = "Pompadour"
Lang.rolledWaves = "Rolled Waves"
Lang.satinRibbon = "Satin Ribbon"
Lang.shortCrownBraid = "Short Crown Braid"
Lang.shortDreadlocks = "Short Dreadlocks"
Lang.sidePlait = "Side Plait"
Lang.slickedBack = "Slicked Back"
Lang.tangledLocks = "Tangled Locks"
Lang.tiedBandana = "Tied Bandana"
Lang.undercutLayers = "Undercut Layers"
--eyebrow
Lang.arched = "Arched"
Lang.blunt = "Blunt"
Lang.blushy = "Blushy"
Lang.cutYourEyebrow = "Cut your eyebrow"
Lang.eyebrow = "Eyebrow"
Lang.faint = "Faint"
Lang.fluffy = "Fluffy"
Lang.fluffySecond = "Fluffy Second"
Lang.full = "Full"
Lang.fullSecond = "Full Second"
Lang.heavy = "Heavy"
Lang.low = "Low"
Lang.natural = "Natural"
Lang.naturalSecond = "Natural Second"
Lang.neat = "Neat"
Lang.none = "None"
Lang.plucked = "Plucked"
Lang.pluckedSecond = "plucked Second"
Lang.pointed = "Pointed"
Lang.shaped = "Shaped"
Lang.sharp = "Sharp"
Lang.shaved = "Shaved"
Lang.short = "Short"
Lang.slight = "Slight"
Lang.strong = "Strong"
Lang.thick = "Thick"
Lang.thickSecond = "Thick Second"
Lang.thin = "Thin"
Lang.trimmed = "Trimmed"
Lang.trimmedSecond = "Trimmed Second"
Lang.variations = "Variations"
--teeth
Lang.noTeeth = "No teeth"
Lang.plumbWoreOut = "Plumb Wore Out"
Lang.teeth = "Teeth"
Lang.theChompers = "The Chompers"
Lang.theGilded = "The Gilded"
Lang.theGummer = "The Gummer"
Lang.theHayseed = "The Hayseed"
Lang.theLongDead = "The Long Dead"
Lang.theYockel = "The Yockel"
Lang.shavedOpacity = "Shaved opacity"

Lang.cutYourBeard = "Cut your beard"
Lang.cutYourHair = "Cut your hair"
Lang.enter = "Enter in hairdresser"
Lang.noMoney = "You don't have enough money"
Lang.noSeat = "No hairdresser seat around you"
Lang.turn = "Rotate"
Lang.previewPomade = "Preview pomade"
Lang.removeHat = "Remove Hat"
Lang.equipHat = "Equip Hat"
Lang.switchPrice = "Switch price"
Lang.buyWithSuccess = "Change applied"
```

`jo_hairdresser/config/_default.lock/prices.lua`

```lua
-- ================================================================
-- PRICE CONFIGURATION GUIDE
-- ================================================================
-- Need to build or adjust a price table quickly?
-- Use the official Price Generator here:
--   https://docs.jumpon-studios.com/DeveloperResources/price-generator
--
-- The generator creates ready-to-paste Lua price tables for this config.
-- Copy the generated Lua code and paste it as the value of the relevant
-- price entry or model override.
-- ================================================================

Config.prices = { --Use false to turn off the submenu
  hair = {
    operator = "or",
    { money = 3.75 },
    { gold = 1 },
  },
  beards_complete = {
    operator = "or",
    { money = 5 },
    { gold = 2 },
  },
  hair_accessories = 1.0,
  teeth = 5,
  eyebrow = {
    operator = "or",
    { money = 3.75 },
    { gold = 1 },
  },
}

Config.modelPrices = {}           --Override prices for specific clothing models

Config.modelPrices["male"] = {}   --Male-specific model price overrides
Config.modelPrices["female"] = {} --Female-specific model price overrides

for category in pairs(Config.prices) do
  Config.modelPrices["male"][category] = {}
  Config.modelPrices["female"][category] = {}
end

Config.modelPrices["male"]["beards_complete"][2] = { gold = 1000 }
Config.modelPrices.male.beards_complete[3] = -1

-- Model-specific price examples:
-- Config.modelPrices["male"]["beards_complete"][2] = { gold = 10 } -- Beard model 2 costs 10 gold for male players
-- Config.modelPrices.female.hair_accessories[1] = -1               -- Disable female hair accessory model 1
-- Config.modelPrices.female.eyebrow[1] = -1                        -- Disable female eyebrow model 1
-- Config.modelPrices.female.teeth[2] = 10                          -- Female teeth model 2 costs $10

-- Known bugged models:
-- Config.modelPrices.male.hair[55] = -1                            -- Disable male hair model 55
```

`jo_hairdresser/config/_default.lock/shops.lua`

```lua
Config.shops = {
  --blackwater
  {
    location = vector4(-815.33, -1368.75, 42.75, 270),
    model = "p_barberchair02x", --uncomment to spawn the chair
    blip = true,
    needExtraLight = true
  },
  {
    location = vector4(-815.33, -1367.05, 42.75, 270),
    model = "p_barberchair02x", --uncomment to spawn the chair
    blip = false,
    needExtraLight = true
  },
  --valentine
  {
    location = vector3(-306.2, 814.2, 118.9),
    blip = true,
    needExtraLight = true
  },
  --saint denis
  {
    location = vector3(2655.4, -1179.9, 53.2),
    blip = true,
    needExtraLight = true
  },
}
```

### Add-ons

The Hairdresser script supports optional add-ons:
- [Hair & Beard Coloring](hairdresser-coloring) - Lets players dye their hair and beard
- [Makeup](hairdresser-makeup) - Lets players customize makeup and lifestyle overlays
- [NPC Hairs & Beards](hairdresser-npc) - Lets players browse NPC hair and beard styles

## 4. For developers

### Actions

Actions are one of the two types of Hooks. They provide a way to run a function at a specific point in the execution of scripts. Callback functions for an Action do not return anything back to the calling Action hook. They are the counterpart to Filters.

Below is a complete list of all available actions in the jo_hairdresser script.

#### `[Client]` InitPrompt
Triggered after the hairdresser prompt groups are created.

```lua
exports.jo_hairdresser:registerAction('InitPrompt', function()
    -- Your code here
end)
```

#### `[Client]` LoopIn
Triggered every frame while the player is inside the hairdresser menu.

```lua
-- @param currentPrompt - string: current prompt group ("select" or "buy")
exports.jo_hairdresser:registerAction('LoopIn', function(currentPrompt)
    -- Your code here
end)
```

#### `[Client]` reloadAll
Triggered after hair, beard, hair accessories, teeth, and overlays are reloaded.

```lua
exports.jo_hairdresser:registerAction('reloadAll', function()
    -- Your code here
end)
```

#### `[Client]` switchPrice
Triggered when the player switches between payment options.

```lua
-- @param priceIndex - int: index of the selected price option
exports.jo_hairdresser:registerAction('switchPrice', function(priceIndex)
    -- Your code here
end)
```

### Events

#### `[Client]` Listen menu closing
Triggered when the hairdresser menu closes.

```lua
AddEventHandler('jo_hairdresser:event:Close', function()
    -- Your code here
end)
```

#### `[Client]` Listen menu opening
Triggered when the hairdresser menu opens.

```lua
AddEventHandler('jo_hairdresser:event:Open', function()
    -- Your code here
end)
```

#### `[Client]` Open the hairdresser
Call this event from your own scripts to start the hairdresser interaction.

```lua
TriggerEvent('jo_hairdresser:Enter')
```

#### `[Client]` Reload skin
Reload the player skin from the server, then apply hairdresser components.

```lua
TriggerEvent('jo_hairdresser:client:reloadSkin')
```

### Filters

[Filters](/DeveloperResources/filters) allow you to modify data or permissions synchronously at specific points in the script. Below is the complete list of `jo_hairdresser` filters and how to use them.

#### `[Server]` Can buy
Control whether the purchase can continue before money is charged.
```lua
-- @param canBuy - boolean (default true)
-- @param source - serverID of the player
-- @param data - table: purchase data
-- @param moneyType - any: selected money type/payment context
exports.jo_hairdresser:registerFilter('canBuy', function(canBuy, source, data, moneyType)
	return canBuy
end)
```

#### `[Client]` Can open menu
Control whether the player can open the hairdresser menu.
```lua
-- @param canOpen - boolean (default true)
exports.jo_hairdresser:registerFilter('canOpenMenu', function(canOpen)
	return canOpen
end)
```

#### `[Client]` Main menu items
Add entries to the native hairdresser menu.
```lua
-- @param items - table
exports.jo_hairdresser:registerFilter('mainMenuItems', function(items)
	return items
end)
```

#### `[Client]` Reload all
Control whether hairdresser components can be reloaded.
```lua
-- @param canUse - boolean
exports.jo_hairdresser:registerFilter('reloadAll', function(canUse)
	return canUse
end)
```

#### `[Client]` Root menu items
Add high-level entries to the root hairdresser menu.
```lua
-- @param items - table
exports.jo_hairdresser:registerFilter('rootMenuItems', function(items)
	return items
end)
```

#### `[Client]` Update current data before buy
Modify the current buy data before it is sent to the server purchase flow.
```lua
-- @param currentData - table: current menu item data
exports.jo_hairdresser:registerFilter('updateCurrentDataBeforeBuy', function(currentData)
	return currentData
end)
```

#### `[Client]` Update lang for NUI
Alter the language table before it is sent to the NUI menu.
```lua
-- @param lang - table
exports.jo_hairdresser:registerFilter('updateLangForNUI', function(lang)
	return lang
end)
```

#### `[Client]` Update menu prompt
Override which prompt group is displayed while navigating menus.
```lua
-- @param promptType - string ("select" or "buy")
-- @param currentData - table: current menu item data
exports.jo_hairdresser:registerFilter('updateMenuPrompt', function(promptType, currentData)
	return promptType
end)
```
