# Tattoo catalog

The catalog brings together tattoos from CXG Base and the packs you have installed. External packs are optional: you can enable none, one, or several. If a pack is missing or stopped, its tattoos are hidden from the shop and the CXG Base catalog remains available.

Also see [Introduction](https://docs.cxgstudios.com/docs/en/01-cxg-tattoos/01-introduccion.md), [Installation](https://docs.cxgstudios.com/docs/en/01-cxg-tattoos/02-instalacion.md), [Configuration](https://docs.cxgstudios.com/docs/en/01-cxg-tattoos/03-configuracion.md), [Integrations](https://docs.cxgstudios.com/docs/en/01-cxg-tattoos/05-integraciones.md), and [Interface](https://docs.cxgstudios.com/docs/en/01-cxg-tattoos/06-interfaz.md).

## Optional packs

The initial configuration includes these optional sources:

| ID | Resource | Catalog file |
| --- | --- | --- |
| `xgc-classic` | `xgc_TattooClasic` | `tatoo_clasic_full_packconfigdump.json` |
| `xgc-gangs` | `xgc_TattooGangs` | `gangs_full_packconfigdump.json` |
| `xgc-japanese-mafia` | `xgc_TattooJapaneseMafia` | `japanese_mafia_full_packconfigdump.json` |
| `xgc-police` | `xgc_TattooPolice` | `police_full_packconfigdump.json` |
| `xgc-world-countries` | `xgc_TattooWorldCountries` | `world_country_full_packconfigdump.json` |
| `cxg-blackout` | `CXGtatoo_balckout` | `blackout_full_packconfigdump.json` |

Install only packs you own and start them before `cxg-tattoos`. Keep the resource name and path as they exist on your server, respecting uppercase and lowercase letters. To temporarily hide a source, set its `enabled` to `false`; to show it again, set it to `true` and make sure the resource is started.

CXG Base uses `shared/tattoos.json` inside the resource. The `native-game` source reads native in-game tattoos and is disabled by default. To use it, also configure the validation sources in `native.serverMetas` with the actual resources and files on your server. The example entry points to `tatto` and `shop_tattoo.meta`; it does not assume that resource is installed. Setting `enabled` by itself does not guarantee that those designs can be purchased.

## Add a JSON source

Each object in `Config.TattooCatalog.sources` configures a source. This example follows the pack resource format and can be adapted with the actual resource name and its file:

```lua
{
    id = 'mi-pack',
    label = 'Mi Pack',
    color = '#62b6cb',
    enabled = true,
    resource = 'mi_pack',
    path = 'tattoos.json',
    thumbnailPattern = 'miniatures/{Name}.webp',
    root = { mode = 'auto', field = 'Overlays' },
    defaults = {
        collection = 'mi_pack_overlays',
        zone = 'ZONE_TORSO',
        price = 5000,
        requiredLevel = 1,
        overlayTarget = 'male',
    },
}
```

`id` must identify the source consistently; `label` and `color` help identify it in the interface. `resource` and `path` point to the JSON file. The file must be inside an available resource. Do not add the same design to two sources unless you want it to appear twice; `deduplicateAcrossSources` can remove duplicates between them.

These general settings are in `Config.TattooCatalog`:

| Field | Initial value | Purpose |
| --- | --- | --- |
| `deduplicate` | `true` | Removes duplicate designs within a source. |
| `deduplicateAcrossSources` | `false` | Keeps designs from different sources separate. |
| `serverFallback` | `true` | Requests a source from the server when the client cannot read it. |
| `serverFallbackTimeoutMs` | `7000` | Maximum wait for that request, in milliseconds. |

The read fallback does not make a stopped pack available.

## Tattoo entry format

The file can be a JSON array or an object whose property contains the array. With `root.mode = 'auto'`, the reader accepts both formats; with `root.mode = 'field'`, `root.field` specifies where the list is. `root.mode = 'array'` forces an array at the root.

This array shows the JSON format. The collection and hashes in the example are fictional: replace them with the actual names from the installed pack. Adding a JSON entry does not install the graphic overlays.

```json
[
  {
    "Collection": "mi_pack_overlays",
    "Name": "Rosa del desierto",
    "HashNameMale": "MP_MI_PACK_ROSE_M",
    "HashNameFemale": "MP_MI_PACK_ROSE_F",
    "Zone": "ZONE_TORSO",
    "Price": 5000,
    "Xp": 15,
    "RequiredLevel": 2
  }
]
```

`Collection` and at least one overlay hash are required to identify the design. Include `HashNameMale` and `HashNameFemale` if the tattoo has a variant for each character. Use one of these zones: `ZONE_HEAD`, `ZONE_TORSO`, `ZONE_LEFT_ARM`, `ZONE_RIGHT_ARM`, `ZONE_LEFT_LEG`, or `ZONE_RIGHT_LEG`. `Name` is the name buyers will see. If you omit `Price`, the source's default price is used; if you omit `RequiredLevel`, `defaults.requiredLevel` is used, initially set to 1. XP can come from the tattoo or the level configuration.

For files with different field names, adjust `fields` in `Config.TattooCatalog`. Each entry in that table is a list of aliases checked to find the corresponding value. For example, a file that uses `OverlayHash`, `Cost`, and `BodyZone` can be mapped like this:

```lua
fields = {
    collection = { 'Collection', 'CollectionName' },
    name = { 'Name', 'DisplayName' },
    overlay = { 'OverlayHash' },
    hashNameMale = { 'MaleOverlay' },
    hashNameFemale = { 'FemaleOverlay' },
    zone = { 'BodyZone' },
    price = { 'Cost' },
    xp = { 'Xp', 'Experience' },
    requiredLevel = { 'RequiredLevel' },
}
```

You can also assign common values through `rootFields`, such as a collection name or zone defined once in the root object. Each source's `defaults` fills in any missing values. If your JSON contains a single generic field, such as `OverlayHash`, `defaults.overlayTarget` specifies whether it should be treated as a male (`male`) or female (`female`) tattoo.

Very different formats can be transformed with the optional functions `converters.decode(decoded, settings)` or `converters.entry(entry, context)` in the configuration. The first adapts the entire document; the second adapts each design. Return a table with the fields prepared for normal mapping.

## Thumbnails

To use images stored in the same pack, keep its `miniatures/` folder in that resource and define a pattern such as:

```lua
thumbnailPattern = 'miniatures/{Name}.webp'
```

`{Name}`, `{HashNameMale}`, `{HashNameFemale}`, and `{HashName}` are replaced with tattoo data. The resulting file must exist in the resource, for example `miniatures/Rosa del desierto.webp`, and the resource must be started. The interface requests each thumbnail when needed; do not copy images into the CXG package.

You can also set `Thumbnail` or `ThumbnailUrl` on an entry to specify an image. `http://`, `https://`, and `data:` URLs are used as-is. A local pack thumbnail path must match its `thumbnailPattern`; the file must exist and the resource must be able to serve it to the NUI. External resources must keep their file inside the pack.

## Changes through configuration and administration

Edit the configuration to add sources, change how they are read, enable or disable packs, and adjust their default values. Use catalog administration to change tattoo names, prices, levels, and availability without editing the JSON. Administrative changes are saved and persist across catalog synchronization. The restore action returns that tattoo to its source values.

Tattoos a player has already purchased remain linked to their character even if the pack that provided them is temporarily turned off. The pack does not appear in the shop again until it is available.
