FlexHour is a luxury watch system for FiveM. Players buy watches from a boutique clerk, configure them, wear them, show them off, have them serviced, and trade them. A whole counterfeit trade runs alongside.
The package is two resources: concept_flexhour (the script) and flexhour_boutique (the walk-in boutique interior). The boutique has its own guide: FlexHour Boutique.
FlexHour uses Cfx Asset Escrow. After you pay on Tebex, the resources appear on Keymaster under Granted Assets, on the Cfx.re account you signed in with at checkout.
concept_flexhour and flexhour_boutique come in the same purchase, as two separate downloads. Download and install both.concept_flexhour. If you rename the folder, you get No such export useBox in resource concept_flexhour.config.lua and the locales/ files stay editable. The rest is escrowed.| Resource | Notes |
|---|---|
ox_lib |
Used for callbacks and locales. FlexHour draws its own menus, forms, yes/no boxes and progress bar. |
oxmysql |
The database tables are created on the first start. |
concept_bridge 1.1.0 or newer |
Tested with 1.1.1. A free, separate GPL-3.0 resource: concept_bridge. It is not included in this package. Run only one bridge. |
| A framework | QBCore, qbx_core, ESX or ox_core |
| An inventory with per-item metadata | ox_inventory is recommended (see below) |
| A target resource | ox_target, qb-target or sleepless_interact |
concept_flexhour and flexhour_boutique in your resources folder.server.cfg after their dependencies. The bridge loads its modules from whatever is already running, so start it after your framework, inventory and target:ensure ox_lib
ensure oxmysql
ensure your_framework # es_extended / qb-core / qbx_core / ox_core
ensure ox_inventory # or your inventory
ensure ox_target # or your target
ensure concept_bridge
ensure concept_flexhour
ensure flexhour_boutique # the boutique interior, after concept_flexhour
add_ace group.admin flexhour.admin allow
/fhmodels. It lists any watch model that is not streamed. If the list is empty, the stream/ folder is complete.Every watch model is its own item, named fh_. The build is stored in the item's metadata: case metal, dial, bezel, ice, serial, engraving and condition. Shops, trades and other scripts can hand out a named model.
ox_inventory
docs/ox_items.lua (all 23 models) into ox_inventory/data/items.lua.['flexhour_box'] = {
label = 'FlexHour Box',
weight = 450,
stack = false,
close = true,
description = 'Something shiny inside.',
client = { export = 'concept_flexhour.useBox' },
},
['flexhour_replica_kit'] = {
label = 'Replica Kit',
weight = 900,
stack = true,
close = true,
description = 'Blank case, cheap movement, dial stencils, gold plating. Not from any boutique.',
},
web/img/*.png and web/img/items/*.png into ox_inventory/web/images/. There are 78 watch icons: one for each model and case material, plus one for each limited edition. Each watch points to its own icon through metadata.image.qb-style inventories
Add the items from docs/qb_items.lua (QBCore format). FlexHour registers them as usable when it starts. On these inventories the item shows the model's name and picture. The build details are in the item's info and in the Inspect view (I while the watch is worn).
FlexHour reaches the framework (money, identity, jobs), the inventory, the target and notifications through concept_bridge.
| Part | Supported | Status |
|---|---|---|
| Framework | QBCore, qbx_core, ESX, ox_core | Money accounts are cash and bank. The bridge maps cash to ESX's money. |
| Inventory | ox_inventory | Recommended. Tested in game. |
| Inventory | qb-inventory, ps-inventory, qs-inventory, tgiann-inventory, origen_inventory, codem-inventory, core_inventory, one-inventory | Must keep per-item metadata. Covered by automated tests, not yet tested in game. |
| Inventory | ESX's built-in inventory | Not supported. It has no item metadata or slots, and a watch is its metadata. The console warns you at start. |
| Target | ox_target, qb-target, sleepless_interact | You can also use a key prompt instead of a target: see Config.Interaction. |
config.lua holds every setting a server can change. Edit a value and restart the resource. If you leave a setting out, FlexHour uses its shipped default. Values that are not settings cannot be overridden from config.lua.
| Setting | Default | What it does |
|---|---|---|
Config.PayAccounts |
{ 'cash', 'bank' } |
Accounts to charge, in order. The first one that covers the price pays. |
Config.Delivery |
'box' |
'box': purchases arrive boxed and the player unboxes them. 'direct': the watch goes straight into the inventory. |
Config.Interaction |
{ mode = 'target', key = 38, keyName = 'E' } |
How players use clerks, vitrines and spots: with the target, or with a key prompt. |
Config.Notify |
{ system = 'flexhour', position = 'top-right' } |
FlexHour's own notifications, or your server's (through the bridge). |
Config.InspectKey |
'I' |
Key to inspect your worn watch. Players can rebind it in GTA's key settings. |
Config.FlexCommand |
'flex' |
Command for the wrist flex. |
Config.FlexLoopCommand |
'flex2' |
Command for the looping flex. |
Config.AdminAce |
'flexhour.admin' |
ACE permission for the admin tools. |
Config.AdminOpenToAll |
false |
true makes every player an admin. Use it for testing only. |
Config.TryOnSeconds |
12 |
How long a try-on lasts at the clerk. |
Config.Clerks is the list of boutique clerks. The shipped clerk stands in Vinewood with model a_f_y_business_02, a WORLD_HUMAN_STAND_IMPATIENT scenario, a blip (sprite 617, colour 46, scale 0.8), and sells the budget, mid, luxury and mythic tiers.
If flexhour_boutique is running, its clerk stands behind the boutique counter. That position is saved in data/clerks.json, which takes priority over config.lua. If you run FlexHour without the boutique, open /fhclerk and choose Put the config.lua clerks back.
Config.Negotiate)| Setting | Default | What it does |
|---|---|---|
enabled |
true |
Players can haggle with clerks. |
rounds |
3 |
Minigame rounds before the final offer: reading the clerk, a riddle, and a memory game. |
cooldownSeconds |
90 |
Wait time per player after any negotiation. |
showFloor |
true |
Show the lowest price the clerk would accept. |
onWalkAway |
'list' |
'list': the player can still buy at list price. 'abort': the sale ends. |
Config.Services)| Setting | Default | What it does |
|---|---|---|
strapSwap |
1500 |
Strap swap fee, plus the price difference between the straps. |
replatePercent |
0.35 |
Replating cost, as a share of the list price. |
replateMin |
2500 |
The lowest replating price. |
icePerLevel |
0.40 |
Cost of each ice step, as a share of the case's list price. |
engrave |
750 |
Engraving (up to 24 characters). |
polish |
400 |
Polishing, which restores condition. |
authenticate |
150 |
Authentication papers. |
authDays |
14 |
How many days the papers stay valid. 0 means forever. |
Config.Limited)Config.Limited sets how many pieces of each limited model can ever exist. See Limited editions.
Config.Wear)| Setting | Default | What it does |
|---|---|---|
keepWorn |
false |
false: a worn watch stays an inventory item. true: while worn, FlexHour keeps it in its own table so it cannot be lost. See Worn watches. |
restoreOnSpawn |
true |
The watch is still on the wrist after a relog. This applies when keepWorn = false. A kept watch always stays on. |
gameWatch |
'hide' |
What happens to GTA's own wrist accessories. 'hide': they are taken off while a FlexHour watch is worn. 'block': the left-wrist slot is always kept empty. 'off': they are left alone. |
conditionLossPerHour |
0.5 |
Condition lost per hour of wear. Replicas wear faster. |
hideInVehicle |
false |
Hide watches inside vehicles. |
playerAdjust |
true |
Players can fine-tune their own fit with /watchfit. Other players see the result. |
allowScale |
true |
The fit gizmo can also resize the watch (0.75 to 1.25). |
fitPresets |
3 |
Saved fit slots per player, used with /watchpreset. |
sleeve.enabled |
true |
Players can pull back the sleeve with /watchsleeve. |
Config.Wear.rob)| Setting | Default | What it does |
|---|---|---|
enabled |
true |
A worn watch can be robbed. |
seconds |
6 |
How long taking the watch takes. |
when |
{ dead = true, handsUp = true, cuffed = true } |
The states the wearer must be in. |
cooldownMinutes |
10 |
How long a robber waits before robbing again. |
protectMinutes |
30 |
How long a victim is protected after being robbed. |
command |
'stealwatch' |
Robbery command. false disables the command. |
handsUpAnims |
{} |
Your server's own hands-up animations: { { 'dict', 'clip' }, ... }. |
minigame |
false |
Your own minigame: function(victim) return true end. Return true to continue, false if it failed. |
Config.Fakes)| Setting | Default | What it does |
|---|---|---|
enabled |
true |
The counterfeit trade is on. |
dealer.coords / dealer.model |
in config.lua / g_m_y_ballaeast_01 |
Where the kit dealer stands, and his ped model. |
dealer.kitPrice |
2500 |
Base kit price. The kit's tier scales it. |
dealer.cooldown |
900 |
Seconds per character between kits. |
workshop.coords / workshop.prop |
in config.lua / 'prop_tool_bench02' |
Where the bench is. prop = false uses a table from the map instead of spawning a bench. |
fence.coords / fence.model |
in config.lua / s_m_y_dealer_01 |
Where the fence stands, and his ped model. |
fence.account |
'cash' |
Account the fence pays into. Use 'black_money' if your framework has it. |
fence.maxPayout |
30000 |
The most the fence pays for a single watch. |
fence.cooldown |
600 |
Seconds per character between sales. |
fence.alertChance |
0.10 |
Chance that a sale is reported to the police. |
fence.alertChanceFake |
0.25 |
The same chance when the watch is a replica. |
onAlert |
function(src, coords, build) end |
Server-side hook for your dispatch script. |
To place the three spots in game, use /fhfake dealer, /fhfake workshop or /fhfake fence. Placed spots are saved in data/fakes.json, which takes priority over config.lua.
Everything players read comes from locales/.json: notifications, menus, the shop page (ui_*), the negotiation cards (card_*), the clerk's tells (tell_*), and what the clerks, the fence and the dealer say (neg_*).
Included languages: English (en), Hungarian (hu), German (de), Spanish (es) and French (fr). FlexHour uses ox_lib's language setting. Put it in server.cfg:
setr ox:locale "en" # or "hu", "de", "es", "fr"
To add a language, copy en.json to .json and translate the values. The negotiation riddles are kept server-side in all five languages. The watch, material and strap names are product names and are not translated.
| Command | What it does |
|---|---|
/watch |
Put on or take off a watch. |
/watchfit |
Fine-tune the fit with a gizmo (see below). |
/watchhand |
Move the watch to the other wrist. |
/watchsleeve |
Pull back or drop the sleeve. |
/watchpreset |
Save or load a fit preset. |
/flex |
Wrist flex. |
/flex2 |
Looping flex. |
I |
Inspect your worn watch. |
X |
Put away the watch box you are holding. Rebindable. |
/stealwatch |
Rob a worn watch (see Robbery). |
/fhhelp |
List the commands in chat. |
The fit gizmo works like this:
allowScale is on.flexhour.admin)| Command | What it does |
|---|---|
/fhclerk |
Move, rename or remove the placed clerks, or put the config.lua clerks back. |
/fhclerk |
Place a new clerk. Aim the ghost where the box should stand on a counter. |
/fhstand |
Place a vitrine where you stand. |
/fhfake [dealer|workshop|fence] |
Manage or place the counterfeit spots. |
/fhgive [id] |
Give a boxed watch and choose the build. |
/fhwrist |
Wrist fit gizmo. G saves the fit as the server default for your ped model. |
/fhmodels |
List watch models that are not streamed. |
Admin tools check the ACE in Config.AdminAce (default flexhour.admin):
add_ace group.admin flexhour.admin allow
Before players join, make sure Config.AdminOpenToAll is false. With true, every player is an admin: they can give themselves watches and place clerks and vitrines, and the ACE line does nothing. The console prints a red warning on every start while it is on.
The clerk opens the shop. The player chooses a model, then its case metal, dial, strap and strap colour, bezel, hands, and ice level. Each tier has its own options. The player can try the watch on for TryOnSeconds, then buy it, or negotiate first. With Delivery = 'box', the watch arrives in a flexhour_box. Using the box plays an unboxing ritual on the counter, and the player keeps holding the box until they put it away with X.
Every genuine watch gets a unique serial (FH-…), which is stored in the registry (flexhour_serials).
Each clerk gets a hidden personality for every session, made of five traits: vanity, greed, pride, loyalty and nerves. Body language gives hints. The player plays cards such as Compliment, Cash, Lowball, Bluff, Regular, Rival, Bundle and Story, and works through mixed rounds of reading the clerk, riddles and a memory game. The result is the final offer. The kit dealer and the fence use a street deck in the same way.
Admins place vitrines with /fhstand and stock each cushion from the vitrine's menu. Vitrines sell at their ticket price, which must be 70% to 160% of the list price, and have unlimited stock. A vitrine within 30 m of a clerk can be negotiated with that clerk. Players cannot own vitrines in this version, and limited editions cannot be shown in one.
A player wears one watch at a time, on either wrist. Built-in fits cover the freemode characters. For custom peds, admins save a fit per ped model, and players can fine-tune their own fit. Other players see the watch close up: live hands, glints, and stones that glow at night. Player actions include flex, inspect and sleeve.
The clerk offers strap swaps, replating, ice, engraving, polishing and authentication. Authentication gives a genuine watch papers that are valid for authDays. On a replica, authentication shows that it is fake.
There are 14 numbered pieces. Each one is a fixed build with a dial, finish or strap that no standard watch can have. The server caps how many exist, and every buyer gets their own number (for example "No. 3 / 10"). Once all the pieces are sold, the edition leaves the shop. Clerks sell them in the Limited editions group of the shop list.
On a limited edition, ice, engraving and polishing are allowed. Strap swaps and replating are refused, so the piece stays original.
The counterfeit trade works in three steps:
A replica looks real in the inventory, but its serial is not in the registry, and the lower its quality, the more tells it shows: an off-angle dial print, drifting time, a stuttering or stopped seconds hand, faster wear, and gold plating that rubs through.
With the default Config.Wear.keepWorn = false, a watch is always an inventory item, even while it is worn. It stays in its slot, and anything that happens to the inventory happens to the watch too: a death wipe, a search, or another robbery script.
With keepWorn = true, putting a watch on removes it from the inventory, and FlexHour keeps it in flexhour_wrist. Taking it off makes it an item again. While it is worn, nothing that happens to the inventory can take it: a death wipe, a confiscation, a search, a drop, a stash or a trunk. To sell, gift, store or service the watch, the player takes it off first with /watch.
If you switch keepWorn back to false, every character gets their worn watch back into their inventory at their next login.
A worn watch can be taken in only one way: through FlexHour's robbery. A player standing next to a wearer who is dead, has their hands up or is cuffed can take the watch with the target option, the [E] prompt or /stealwatch. The same watch, with the same serial, ends up in the robber's pockets. Other resources can react to the flexhour:watchRobbed event.
The game's clothing menus can put a watch on the left wrist (prop slot 6) or a bracelet on the right wrist (slot 7). With gameWatch = 'hide', the GTA accessory comes off while a FlexHour watch is on that wrist. If a clothing menu saves the outfit in the meantime, the outfit is saved without that accessory. Either take the FlexHour watch off before saving an outfit, or set gameWatch to 'off'.
-- Give a player a watch. build = { base = '', material = ..., dial = ..., strap = ..., iced = 0 }
-- Only base is required; anything you leave out or set to something invalid falls back to that model's defaults.
-- boxed = true (default) delivers it in a flexhour_box. Returns true, serial.
local ok, serial = exports.concept_flexhour:GiveWatch(source, { base = 'timor_basic' }, true)
-- Release the watch a deleted or banned character was wearing (keepWorn = true).
-- identifier = the character id from your framework (the owner column of flexhour_wrist), or an online player's server id.
-- Returns true, serial, or false when nothing was on that wrist.
local released, serial = exports.concept_flexhour:ReleaseWrist(identifier)
You can find the model ids in shared/catalog.lua (FH.BaseOrder). The item name without the fh_ prefix works too.
concept_flexhour.useBox and concept_flexhour.useWatch are the item exports that the inventory calls. You do not need to call them yourself.
| Event | Arguments | When |
|---|---|---|
flexhour:purchased |
src, build, price, kind |
A player bought a watch. |
flexhour:watchRobbed |
robber, victim, serial, build |
A worn watch was robbed. |
flexhour:fenceAlert |
src, coords, build |
A fence sale was reported. Also calls Config.Fakes.onAlert. |
flexhour:wristReleased |
owner, serial, build |
ReleaseWrist removed a worn watch. |
flexhour:suspect |
src, reason, serial |
A possible duplicate ('duplicate', 'watch_twice'). |
AddEventHandler('flexhour:watchRobbed', function(robber, victim, serial, build)
-- log it, alert the police, ...
end)
The tables are created on the first start:
| Table | Holds |
|---|---|
flexhour_serials |
The registry of genuine watches (serial, build, owner). |
flexhour_wrist |
Worn watches kept by FlexHour (keepWorn = true). |
flexhour_worn |
What each character has on. |
flexhour_fit |
Saved wrist fits. |
flexhour_fit_presets |
Players' fit presets. |
flexhour_stands |
Vitrines and their stock. |
flexhour_meta |
Internal state. |
FlexHour only builds what you can actually see. The limits are fixed:
| Problem | Fix |
|---|---|
missing ox_inventory items in the console |
Add the items from Items. |
model "fh_…" is not streamed in F8 |
The stream/ folder is incomplete. Run /fhmodels, then copy stream/ again from the download. |
| The watch floats next to the hand | Run /watchfit. For a whole ped model, an admin saves the fit as the default with G. |
No such export useBox in resource concept_flexhour |
The folder was renamed. Keep it concept_flexhour. |
ESX's built-in inventory cannot hold a watch |
Install an inventory with item metadata. |
| The watch disappears from the inventory when put on | That is keepWorn = true: the watch is on the wrist. /watch takes it off. |
| The clerk or the fence does not list my watch | It is on your wrist. Take it off first. |
| Money is not taken or not paid | Config.PayAccounts must use account names your framework knows through the bridge (cash, bank, money). |
| Everyone can use admin commands | Set Config.AdminOpenToAll = false. |
An inventory does not write every change to the database immediately, so a hard crash can set it back a few minutes.
Shut the server down through txAdmin, not by closing the window.
Config.AdminOpenToAll is false./fhmodels lists nothing.setr ox:locale).flexhour_boutique starts after concept_flexhour.We aim to reply on Discord within 24 hours on business days.
Need help with setup? Join Discord or support@conceptstudio.store.