Configuration
Overview
shared/config.lua: Currency, prices, custom tattoos, uses, item payment, restrictions, shops and commandsserver/config_server.lua: Discord webhooks and the rate-limit fallback (server-only, never sent to clients)
The shop runs item/admin-only out of the box: Config.Shops is empty, so there are no public NPCs or blips until you add them.
General
1Config.Locale = 'en'
2Config.Currency = 'bank'
3Config.UIPosition = 'right'
4Config.MetaResource = 'ml_tattoos_meta'
5Config.AdminCommand = 'tattooadmin'
6Config.AdminPermission = 'admin'
7Config.PremiumCommand = 'mytattoos'
8Config.StudioCommand = 'mystudio'Locale: Interface language. Loads the matching file inlocales/, with English as the fallback for any missing keyCurrency: Money account used for every charge:'bank','cash'or'money'UIPosition: Panel side:'left'or'right'MetaResource: Name of the companion resource that streams the custom-tattoo overlay slots. Leave asml_tattoos_metaunless you renamed itAdminCommand: Command that opens the shop straight on the admin tab (admin only)AdminPermission:ml_bridgepermission (ACE node or framework group) that grants admin access to the command and the admin panelPremiumCommand: Command that opens a read-only view of the tattoos the player ownsStudioCommand: Command that opens the My Studio tab for players who hold uses or own custom tattoos
Currency & Prices
1Config.RemovePrice = 1000
2Config.DefaultTattooPrice = 500
3Config.CategoryPrices = {
4 ['mpheist4_overlays'] = 400,
5 ['mplowrider_overlays'] = 500,
6 ['mpluxe_overlays'] = 500,
7 ['mpbeach_overlays'] = 200,
8 -- ...one entry per DLC overlay pack
9}RemovePrice: Cost charged per tattoo removedDefaultTattooPrice: Fallback price when a tattoo has no specific priceCategoryPrices: Per-DLC price, keyed by the overlay pack slug. Price priority is per-tattoo override →CategoryPrices[dlc]→DefaultTattooPrice
Custom Tattoos
1Config.EnableCustomTattoos = true
2Config.CustomTattooPrice = 500
3Config.CustomTattooPrefix = 'MLTattoo'
4
5Config.UserCustom = {
6 enabled = true,
7 imageInput = 'paste', -- 'paste' | 'upload' | 'both'
8 uploadMaxBytes = 2097152,
9 editWindow = { enabled = true, ttlMinutes = 1440 },
10 maxCredits = { solo = 20, faction = 10, ytd = 10 },
11 refundOnAdminDeleteHours = 24,
12 maxCustomsPerPlayer = 30,
13}
14
15Config.SlotsPerPreset = 100EnableCustomTattoos: Master switch for player custom-image tattoosCustomTattooPrice: Money charged to apply a custom tattoo through the normal shop flow (admins are exempt)CustomTattooPrefix: Naming prefix for custom overlaysUserCustom.enabled: Lets players who hold uses create their own custom without admin involvementUserCustom.imageInput: How a player supplies the image:'paste'a link,'upload'a PNG in-game (needs FiveManage), or'both'UserCustom.uploadMaxBytes: Maximum PNG size accepted by the in-game uploaderUserCustom.editWindow: Free rework window after creation.ttlMinutes= 1440 gives 24h; after that a rework consumes another useUserCustom.maxCredits: Per-type cap on how many uses a single player can hold at onceUserCustom.refundOnAdminDeleteHours: If an admin deletes a user-created custom within this many hours, the creator is refunded one useUserCustom.maxCustomsPerPlayer: Maximum custom tattoos a single player can ownSlotsPerPreset: How many unique customs can coexist per body position. Size it to your expected total number of customs; a higher value reserves more streamed pool space. Changes take effect after a server restart
Uses & Optional Donations
This whole layer is opt-in. With no Tebex packages configured it does nothing, and the normal money/item tattoo shop keeps working. Use it only if you want to let players optionally support the server in exchange for custom-tattoo uses.
A use is a token that lets a player create one custom tattoo. Uses come in three types: solo (a personal custom), faction (a custom shared with the player's gang/faction), and ytd (unlocks one installed Full-Body pack permanently).
1Config.TebexPackages = {
2 ['ml_tattoos_solo_1'] = { type = 'solo', amount = 1, eur = 15, money = 15000, source = 'tebex' },
3 ['ml_tattoos_solo_3'] = { type = 'solo', amount = 3, eur = 30, money = 40000, source = 'tebex' },
4 ['ml_tattoos_faction_1'] = { type = 'faction', amount = 1, eur = 40, money = 30000, source = 'tebex' },
5 ['ml_tattoos_ytd_1'] = { type = 'ytd', amount = 1, eur = 30, money = 25000, source = 'tebex' },
6}
7
8Config.AllowedConsumerResources = {
9 -- 'ml_donator_coins',
10}TebexPackages: Maps a Tebex package id to a use grant. Match each key to the package identifier on your Tebex storetype:'solo','faction'or'ytd'amount: How many uses the package grantseur: Real-money price on your Tebex store;money: in-game price (charged fromConfig.Currency)source: Which rail the player may use:'tebex'(real-money donation through your store, delivered by webhook),'money'(in-game cash at themoneyprice), or'both'. Defaults to'tebex'AllowedConsumerResources: Server resources permitted to grant/consume/refund uses through the exports (e.g. a coin-donator system). Read access is always open
When any package uses source = 'tebex', set these convars in server.cfg so ml_tebex can verify and deliver donations:
1set ml_tebex_webhook_secret "your-webhook-secret"
2set ml_tebex_plugin_secret "your-plugin-secret"Uses can also be granted by staff from the admin panel, or by another resource through exports.ml_tattoos:GrantTattooCredit (see Developer).
Full-Body (YTD) Packs
1Config.YtdPacks = {
2 -- ['oldschool'] = {
3 -- label = 'Old School Full Body',
4 -- collection = 'mlytd_oldschool',
5 -- thumbnail = 'https://i.imgur.com/xxxxxxx.png',
6 -- tattoos = {
7 -- { hash = 'OS_SLEEVE_L', zone = 'ZONE_LEFT_ARM', label = 'Left sleeve' },
8 -- },
9 -- -- allowedFactions / allowedIdentifiers restrict who can unlock it
10 -- },
11}YtdPacks: Declares each separately-installed full-body pack (its.ytddictionaries plus a decoration collection). Oneytduse unlocks one whole pack permanently per playercollection: The decoration collection name shipped inside the packtattoos: Every overlay in the pack a player can apply, with its zone and labelallowedFactions / allowedIdentifiers: Optional. Omit both for an open pack; otherwise only matched factions or identifiers see and unlock it
Item Payment
1Config.EnableItemPayment = false
2Config.ItemPerTattoo = { item = 'tattoo_kit', count = 1 }
3
4Config.UnlockableHashes = {
5 -- ['MP_MP_Heist4_Tat_000_M'] = { item = 'rare_tattoo_scroll', count = 1 },
6}
7
8Config.ItemPresets = {
9 tattoo_kit_basic = {
10 label = 'Basic Tattoo Kit', limit = 1, pay = 'item', count = 1,
11 categories = { 'mphipster_overlays', 'mpbeach_overlays' },
12 allowCustom = false,
13 },
14}EnableItemPayment: Whentrue, tattoos cost an item instead of money. Off by defaultItemPerTattoo: The item and quantity consumed per tattoo while item payment is onUnlockableHashes: Locks specific tattoo hashes behind owning an item (the item is checked, not consumed)ItemPresets: Each usable item opens the shop pre-filtered to a single tattoo.pay='item'consumes the item,'free'applies for free,'shop'charges money. Filter withzones,categories,hashes,allowCustom
Shops, NPC & Clothing
1Config.UseTarget = true
2Config.InteractDistance = 2.5
3Config.Shops = {}
4
5Config.UseNPC = false
6Config.NPC = { model = 'mp_m_waremech_01', scenario = 'WORLD_HUMAN_CLIPBOARD', invincible = true, freeze = true }
7
8Config.EnableClothesHide = true
9Config.ArtistScene = { enabled = true, duration = 4000, animDict = 'mini@repair', animName = 'fixing_a_player', flag = 49 }UseTarget:trueuses ox_target on shop locations,falseuses proximity + TextUIInteractDistance: Interaction distance (metres) for a shopShops: Public shop locations (coords, heading, label, blip). Empty by default for an item/admin-only flowUseNPC / NPC: Spawn a tattoo-artist ped at each shop and its appearance/behaviourEnableClothesHide: Strips clothes while inside the shop so tattoos are visible (configurable viaConfig.InvisibleClothes)ArtistScene: Animation played on the player when a checkout succeeds
Admin Grant
1Config.AdminGrantCommand = 'opentattoo'
2Config.AdminGrantDefaults = { limit = 1, customOnly = false, allowCustom = true, duration = 120000 }AdminGrantCommand: Admin command that opens the shop on a target player in free mode:/opentattoo <playerId> [limit] [customOnly]AdminGrantDefaults: Default grant limit, whether to show only customs, whether to include the custom catalog, and the grant lifetime in ms
Webhooks
1ServerConfig.DiscordWebhook = GetConvar('ml_tattoos_webhook', '')
2ServerConfig.AdminWebhook = GetConvar('ml_tattoos_admin_webhook', ServerConfig.DiscordWebhook)
3ServerConfig.RateLimitCooldown = 500DiscordWebhook: Player-action log webhook, read from theml_tattoos_webhookconvar so no URL ships in the buildAdminWebhook: Admin-action log webhook (ml_tattoos_admin_webhook); falls back to the main webhook when unsetRateLimitCooldown: Fallback minimum ms between repeated actions from one player
Set the URLs in server.cfg: setr ml_tattoos_webhook "https://discord.com/api/webhooks/..." (and optionally ml_tattoos_admin_webhook).