# SLEEK TATTOO

{% embed url="<https://youtu.be/X4VxSNKciNU?si=3pv4bpKSYFx_5Hjm>" %}

##

Tattoo shop resource for FiveM — business management, NPC employees, custom addon tattoos, henna, minigames, and a modern UI.

### Features

* Auto-detection of ESX / QBCore — no manual framework config needed
* 6 pre-configured parlor locations
* Full business system with worker/owner ranks and revenue split
* NPC employees that generate passive income
* Supply system — parlors consume supplies per tattoo
* Henna (temporary tattoos) — auto-expire after configurable real-time days
* Tattooing minigame — optional ox\_lib skillCheck
* Duty system — clock in/out with auto-off-duty on distance
* Custom addon tattoos with a **CUSTOM** badge in the shop UI
* Per-parlor tattoo removal station
* Full GTA vanilla catalog + custom collections
* Modern NUI with configurable colors, blur, particles, and sounds
* ox\_target / qb-target or classic marker + keypress
* 9 banking integrations (ESX, QB, neon-boss, OKOK, FD, TGG, WASABI, CODEM, JUSTBANKING)
* 5 clothing resource integrations (illenium-appearance, rcore\_clothing, fivem-appearance, qb-clothing, esx\_skin)
* Discord webhooks for sale logging
* Multi-language (EN, ES, FR, DE, IT, TR, ZH, AR)

### Requirements

| Dependency                                                                                              |   Required  |
| ------------------------------------------------------------------------------------------------------- | :---------: |
| [ox\_lib](https://github.com/overextended/ox_lib)                                                       |     Yes     |
| [oxmysql](https://github.com/overextended/oxmysql) or mysql-async                                       |     Yes     |
| ESX or QBCore                                                                                           |     Yes     |
| A clothing resource (illenium-appearance, rcore\_clothing, fivem-appearance, qb-clothing, or esx\_skin) | Recommended |
| ox\_target or qb-target                                                                                 |   Optional  |

### Installation

#### Step 1 — Place the resource

Copy `forge-tattoo` into your server's `resources/` directory.

#### Step 2 — Database

The resource **auto-creates** all required tables on first start (`Cfg.AutoMigrateDB = true` by default). No manual SQL import is needed.

Tables created:

* `forge_business` — parlor balances, supply levels, NPC employees
* A `tattoos` column in your framework's player/users table

#### Step 3 — server.cfg

```cfg
ensure ox_lib
ensure forge-tattoo
```

`ox_lib` must start **before** `forge-tattoo`.

#### Step 4 — Create jobs (if using business mode)

If your parlors use `isBusiness = true`, register the job names in your framework.

**ESX:**

```sql
INSERT INTO `jobs` (`name`, `label`) VALUES
('tattoo1', 'Ink Studio - El Burro'),
('tattoo2', 'Ink Studio - Vinewood'),
('tattoo3', 'Ink Studio - Chumash'),
('tattoo4', 'Ink Studio - Vespucci'),
('tattoo5', 'Ink Studio - Sandy Shores'),
('tattoo6', 'Ink Studio - Paleto Bay');
```

**QBCore** — add equivalent entries in `qb-core/shared/jobs.lua`.

#### Step 5 — Start your server

The resource auto-detects your framework and clothing resource. Edit the files inside `config/` to customize.

### Configuration

All configuration lives in the `config/` folder:

| File                | Purpose                                                                       |
| ------------------- | ----------------------------------------------------------------------------- |
| `config.lua`        | Master config — economy, NPCs, supplies, henna, minigame, UI, markers, camera |
| `shops.lua`         | Parlor locations, coordinates, NPC spawn, business settings                   |
| `tattoos.lua`       | Full tattoo catalog — vanilla GTA + custom addon entries                      |
| `translation.lua`   | UI and notification text (8 languages)                                        |
| `bridge_client.lua` | Client framework bridge (usually no edits needed)                             |
| `bridge_server.lua` | Server framework bridge (usually no edits needed)                             |

#### Auto-detection

The framework is detected automatically:

* `es_extended` running → ESX mode
* `qb-core` running → QBCore mode

The clothing resource is auto-detected in this priority order: illenium-appearance → rcore\_clothing → fivem-appearance → qb-clothing → esx\_skin.

### Parlor Locations

Defined in `config/shops.lua`. Ships with 6 locations:

| Key               | Location         | Default Job |
| ----------------- | ---------------- | ----------- |
| `parlor_elburro`  | El Burro Heights | `tattoo1`   |
| `parlor_vinewood` | Vinewood         | `tattoo2`   |
| `parlor_chumash`  | Chumash          | `tattoo3`   |
| `parlor_vespucci` | Vespucci Beach   | `tattoo4`   |
| `parlor_sandy`    | Sandy Shores     | `tattoo5`   |
| `parlor_paleto`   | Paleto Bay       | `tattoo6`   |

#### Adding a new parlor

1. Copy an existing parlor entry in `shops.lua`
2. Change the key name (e.g. `'parlor_legion'`)
3. Update all coordinates
4. Set a new `assignedJob` and register it in your framework
5. Restart the resource

#### Non-business mode

Set `isBusiness = false` on any parlor to make it a public self-service shop — no job required.

#### Key parlor fields

```lua
['parlor_myshop'] = {
    blipCoords    = vector3(x, y, z),
    npcModel      = "u_m_y_tattoo_01",
    npcSpawnPoint = vector4(x, y, z, heading),
    headingFront  = 197.46,     -- Customer heading for front tattoos
    headingBack   = 22.31,      -- Customer heading for back tattoos

    seats = {
        [1] = {
            markerPos    = vector3(...),
            workerPos    = vector4(...),
            seatPos      = vector4(...),
            targetExtent = vec(1.0, 2.35, 1.85),
            occupied     = false,
        },
    },

    removalEnabled = true,
    removalPoint   = vector3(x, y, z),
    dutyPoint      = vector4(x, y, z, heading),

    isBusiness   = true,
    assignedJob  = "tattoo1",
    societyKey   = "tattoo1",
    workerRanks  = { 0, 1 },
    ownerRanks   = { 2, 3 },
}
```

### Business & Economy

#### Revenue split

```lua
Cfg.CompanySharePercent = 80   -- Goes to the parlor balance
Cfg.WorkerSharePercent  = 20   -- Goes to the worker's pocket
```

#### Payment routing

**Built-in balance** (default) — stored in `forge_business`, managed via in-game panel:

```lua
Cfg.UseBuiltinBalance = true
```

**External banking** — route money to your society/banking resource:

```lua
Cfg.UseBuiltinBalance = false
Cfg.SocietyMethod = 'ESX'   -- ESX, QB, FORGE, OKOK, FD, TGG, WASABI, CODEM, JUSTBANKING
```

Each parlor's `societyKey` must match what the banking resource expects.

#### Neon-Boss integration

```lua
Cfg.UseNeonBossMenu = true
```

Automatically sets `UseBuiltinBalance = false` and routes all money through neon-boss. Each parlor needs a `bossMenuPoint` coordinate and the `assignedJob` must be registered in neon-boss.

### NPC Employees

Parlors can hire virtual NPC artists from a rotating candidate pool. NPCs earn a salary and generate passive income.

#### How it works

* Candidates are defined in `Cfg.NPCPool` with name, ped model, and skill level (1–4)
* The available-for-hire list rotates every `Cfg.NPCRotationMinutes` (default: 120 min)
* Each parlor holds up to `Cfg.MaxNPCEmployees` (default: 3) NPCs
* Salary is paid from the parlor balance every `NPCSalaryIntervalMin` minutes
* Owners hire/fire through the management panel

#### Passive income

When `Cfg.EnableExtraSales = true`, each NPC generates revenue every `ExtraSaleIntervalMin` minutes:

```
Income = ExtraSaleBase × (Level ^ ExtraSaleLevelExp) × HappinessMod × TeamBonus
```

| Level | Income/tick | Income/hr | Salary/hr | Net/hr |
| :---: | ----------: | --------: | --------: | -----: |
|   1   |         $60 |      $120 |      $150 |  -$130 |
|   2   |        $170 |      $340 |      $300 |   -$60 |
|   3   |        $312 |      $624 |      $450 |   +$74 |
|   4   |        $480 |      $960 |      $600 |  +$260 |

> Level 1–2 NPCs need real customer purchases to break even. With the team bonus, 3 NPCs get ×1.30.

#### NPC happiness

|     Level     | Modifier | Cause                                                        |
| :-----------: | :------: | ------------------------------------------------------------ |
|   3 (Happy)   |   ×1.00  | Supplies above threshold                                     |
|    2 (Okay)   |   ×0.75  | Normal state                                                 |
|  1 (Unhappy)  |   ×0.35  | Supplies below `SupplyAngerThreshold` (50)                   |
| 0 (Miserable) |   ×0.00  | Supplies below `SupplyCriticalThreshold` (10) — NPC may quit |

### Supply System

Parlors consume supply units per tattoo applied/removed. Owners restock through the management panel.

| Setting              | Default | Description                  |
| -------------------- | :-----: | ---------------------------- |
| `DefaultSupplyMax`   |   100   | Max supply capacity          |
| `SupplyPerTattoo`    |    2    | Units consumed per tattoo    |
| `SupplyPerRemoval`   |    1    | Units consumed per removal   |
| `SupplyUnitPrice`    |   $50   | Cost per supply unit         |
| `SupplyBuyIncrement` |    10   | Step size for the buy button |

If supplies run out, tattoos cannot be applied. Supply level also affects NPC happiness (see above).

### Henna (Temporary Tattoos)

```lua
Cfg.EnableHenna       = true
Cfg.HennaDurationDays = 3      -- Real-time days before the tattoo fades
```

Henna tattoos use the same application flow as permanent tattoos and are marked differently in the catalog.

### Minigame

Optional skill check before a worker can apply a tattoo. Only affects real player workers, not NPC-only parlors.

```lua
Cfg.UseMiniGame        = true
Cfg.MinigameDifficulty = 'easy'   -- 'easy', 'medium', 'hard'
```

Uses `ox_lib` skillCheck.

### Duty System

Workers clock in/out at the parlor's `dutyPoint`. The system auto-clocks workers off if they leave the area.

```lua
Cfg.DutyMaxDistance   = 40.0    -- Max distance before auto-clock-off
Cfg.DutyCheckInterval = 5000    -- Check interval in ms
```

### Interaction Modes

**Target mode** (ox\_target / qb-target):

```lua
Cfg.TargetMode   = true
Cfg.TargetScript = 'ox_target'   -- or 'qb-target'
```

**Marker mode** (default):

```lua
Cfg.TargetMode = false
```

Each interaction type (seats, removal, duty, management) has its own marker style configurable in `Cfg.MarkerStyles`.

### UI Customization

The full color palette is configurable in `Cfg.UIColors`:

```lua
Cfg.UIColors = {
    Accent     = { Primary = '#00c5ff', Muted = '#31afd4', Bright = '#00e0ff' },
    Background = { Deep = '#0a0e1a', Surface = '#212534' },
    Text       = { Primary = '#ffffff', Secondary = '#e0e4f0', Muted = '#969696' },
    -- ...
}
```

```lua
Cfg.EnableBlur      = true    -- Blur game world behind the UI
Cfg.EnableParticles = true    -- Floating particles on UI background
Cfg.UIVolume        = 0.4     -- Sound FX volume (0.0 – 1.0)
```

### Camera & Preview

When browsing tattoos, a studio outfit is applied so clothes don't hide the preview. Both the outfit and camera angles are configurable:

```lua
Cfg.StudioOutfit = {
    male   = { sex = 0, arms = 15, tshirt_1 = 15, torso_1 = 91, pants_1 = 61, ... },
    female = { sex = 1, arms = 15, tshirt_1 = 34, torso_1 = 101, pants_1 = 15, ... },
}
```

Camera presets per body zone are in `Cfg.StandingCamAngles` and `Cfg.ProcessCam`.

### Inventory Items

Optionally require physical items to apply tattoos:

```lua
Cfg.RequireTattooGun = false
Cfg.TattooGunItem    = 'tattoo_gun'
Cfg.RequireInkSupply = false
Cfg.InkSupplyItem    = 'tattoo_ink'
Cfg.ConsumeInkOnUse  = false
```

### Webhooks

Discord webhook notifications for sales. Configure the webhook URL in `bridge_server.lua`. Sends embeds with sale title, description, and player identifier.

### Custom Addon Tattoos

Forge Tattoo supports custom addon tattoos that appear with a **CUSTOM** badge in the shop UI.

#### Step 1 — Create the texture

* Use **OpenIV** to extract an existing GTA tattoo `.ytd` as a template, or start from scratch
* Create your design in a graphics editor (Photoshop, GIMP, etc.)
* Save as `.png` with transparency

#### Step 2 — Prepare the .ytd file

1. Name it following the convention: `forge_[name]_[zone].ytd` (e.g. `forge_skull_face.ytd`)
2. Copy any existing `.ytd` from `stream/` as a base, rename it
3. Open in OpenIV → delete the existing texture → import your `.png`
4. Place the finished `.ytd` in the `stream/` folder

#### Step 3 — Register in the XML

Open `ADDON/forge_overlays_custom.xml` and add an `<Item>` entry for **male** and **female**:

```xml
<Item>
    <uvPos x="0.500000" y="0.680000" />
    <scale x="0.100000" y="0.100000" />
    <rotation value="0.000000" />
    <nameHash>forge_skull_face_M</nameHash>
    <txdHash>forge_skull_face</txdHash>
    <txtHash>forge_skull_face</txtHash>
    <zone>ZONE_HEAD</zone>
    <type>TYPE_TATTOO</type>
    <faction>FM</faction>
    <garment>ALL</garment>
    <gender>GENDER_MALE</gender>
</Item>
```

For the female version, use `_F` suffix in `nameHash`, set `faction` to `FM_F`, and `gender` to `GENDER_FEMALE`.

| Field                 | Description                                                                                     |
| --------------------- | ----------------------------------------------------------------------------------------------- |
| `uvPos`               | Position on the body UV map                                                                     |
| `scale`               | Size on the body (default `0.1 / 0.1`)                                                          |
| `nameHash`            | Unique ID — must end in `_M` or `_F`                                                            |
| `txdHash` / `txtHash` | Must match the `.ytd` filename (without extension)                                              |
| `zone`                | `ZONE_HEAD`, `ZONE_TORSO`, `ZONE_LEFT_ARM`, `ZONE_RIGHT_ARM`, `ZONE_LEFT_LEG`, `ZONE_RIGHT_LEG` |
| `faction`             | `FM` (male), `FM_F` (female)                                                                    |

#### Step 4 — Add to the catalog

In `config/tattoos.lua`, find the `forge_overlays_custom` collection and add:

```lua
ctat('Skull Face', 'forge_skull_face_M', 'forge_skull_face_F', 800, 1050, 1, 'f_head'),
```

Parameters: `ctat(label, hashMale, hashFemale, cost, eraseCost, pose, camera)`

| Parameter | Description                                                                                              |
| --------- | -------------------------------------------------------------------------------------------------------- |
| `pose`    | `1` = face-down (front), `2` = face-up (back)                                                            |
| `camera`  | Preset string (`'f_head'`, `'f_neck'`, `'b_torso'`, etc.) or custom `{cameraOffset, lookAtOffset}` table |

**Camera presets:** `f_head`, `f_neck`, `b_neck`, `b_face`, `b_face_r`, `b_face_l`, `b_torso`, `b_torso_r`, `b_torso_l`, `b_belly`

For arms/legs, use a custom offset table:

```lua
ctat('Arm Tat', 'hash_M', 'hash_F', 500, 660, 1, {vec(0.712, 0.472, -0.815), vec(0.212, 0.462, -0.665)})
```

#### Step 5 — Restart and test

```
ensure forge-tattoo
```

Your tattoo will appear in the shop with the **CUSTOM** badge.

### Translations

```lua
Cfg.Locale = 'en'   -- 'en', 'es', 'fr', 'de', 'it', 'tr', 'zh', 'ar'
```

All strings are in `config/translation.lua`. To add a language, copy the `en` block and translate the values.

### Skin Manager Compatibility

Most skin managers call `ClearPedDecorations()` when loading a player's appearance, which **removes all tattoo overlays**. The skin manager must call forge-tattoo's export to re-apply them:

```lua
exports['forge-tattoo']:ReloadPlayerInk()
```

#### Does my skin manager need modification?

| Skin Manager              |    Needs Modification?    |
| ------------------------- | :-----------------------: |
| esx\_skin / skinchanger   | No — works out of the box |
| illenium-appearance (ESX) |          **Yes**          |
| illenium-appearance (QB)  |          **Yes**          |
| fivem-appearance (ESX)    |          **Yes**          |
| qb-clothing (QB)          |          **Yes**          |
| rcore\_clothing           |  No — compatible natively |

#### Pre-modified downloads

Don't want to modify it yourself? Use the version that already includes full forge compatibility:

{% file src="/files/x0wZXf10dSuif6CXXu0b" %}

{% file src="/files/m5iNeQPekpBlkjUihACB" %}

{% file src="/files/rrEXoR9ko0VcOBIenK5T" %}

{% file src="/files/agfn4jcGTlq9TDizKrmG" %}

> These versions auto-detect all three forge scripts (tattoo, barber, clothes). When a forge script is running, the skin manager disables its own overlapping shop and calls the appropriate export after every appearance load.

#### What the modifications do

1. **Auto-detection** — checks `GetResourceState` at startup for each forge script
2. **Shop deactivation** — skips creating the built-in tattoo/barber/clothing blips, zones, and targets when the corresponding forge script is running
3. **Overlay sync** — calls `ReloadPlayerInk()` after every `ClearPedDecorations` to re-apply tattoos

#### Manual modification guide

<details>

<summary><strong>illenium-appearance (ESX &#x26; QB)</strong></summary>

**1.** In `shared/config.lua`, add:

```lua
Config.ForgeScripts = {
    tattoo  = GetResourceState('forge-tattoo')  ~= 'missing',
    barber  = GetResourceState('forge-barber')  ~= 'missing',
    clothes = GetResourceState('forge-clothes') ~= 'missing',
}
```

**2.** In `game/util.lua`, add this helper:

```lua
local function syncForgeOverlays()
    if Config.ForgeScripts.tattoo then exports['forge-tattoo']:ReloadPlayerInk() end
end
```

**3.** In `client/blips.lua`, `client/zones.lua`, and `client/target/target.lua` — skip shop creation when the corresponding `Config.ForgeScripts.*` is true.

**4.** Call `syncForgeOverlays()` after every place that loads a full appearance (`setPedAppearance`, outfit changes, `LoadSkin`).

**5.** Guard the tattoo tab: `config.tattoos = not Config.RCoreTattoosCompatibility and not Config.ForgeScripts.tattoo and (...)`

</details>

<details>

<summary><strong>fivem-appearance (ESX)</strong></summary>

**1.** In `configuration/config.lua`, add:

```lua
Config.Forge = {
    barber  = GetResourceState('forge-barber')  ~= 'missing',
    tattoo  = GetResourceState('forge-tattoo')  ~= 'missing',
    clothes = GetResourceState('forge-clothes') ~= 'missing',
}
```

**2.** In `client/functions.lua`, add:

```lua
local function syncForgeOverlays()
    if Config.Forge.tattoo then exports['forge-tattoo']:ReloadPlayerInk() end
end
```

**3.** Wrap `Config.ClothingShops`, `Config.BarberShops`, and `Config.TattooShops` in `if not Config.Forge.X then ... end`.

**4.** Call `syncForgeOverlays()` after every `setPlayerAppearance` / `setPedAppearance`.

</details>

<details>

<summary><strong>qb-clothing (QB)</strong></summary>

**1.** In `config.lua`, add:

```lua
Config.Forge = {
    tattoo  = GetResourceState('forge-tattoo')  ~= 'missing',
    clothes = GetResourceState('forge-clothes') ~= 'missing',
    barber  = GetResourceState('forge-barber')  ~= 'missing',
}
```

**2.** At the top of `client/main.lua`, add:

```lua
local function syncForgeOverlays()
    if Config.Forge.tattoo then exports['forge-tattoo']:ReloadPlayerInk() end
end
```

**3.** Wrap clothing and barber store entries in `config.lua` with `if not Config.Forge.clothes then ... end` / `if not Config.Forge.barber then ... end`.

**4.** Call `syncForgeOverlays()` after skin load functions (`ChangeToSkinNoUpdate`, `reloadSkin`, `loadPlayerClothing`).

</details>

### Support

[Join our Discord](https://discord.gg/UTVssdrXRV) — installation help, bug reports, feature requests, and free custom tattoo designs.

### License

© CodeForge — All rights reserved.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://codeforge.gitbook.io/codeforge/sleek-series/sleek-tattoo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
