After running my QBCore server for over two years, one thing became painfully clear: my police-related scripts had quietly turned into a mess.
Not because any single one of them was bad. Each script did its job. The problem was that they had no idea the others existed.
This is the story of how I merged 5 separate scripts into a single unified system called tack_billing — and what it taught me about building for the server, not just shipping features.
The Problem: 5 Scripts, 5 UIs, 5 Mental Models
Over the life of my server, I had built or adopted these scripts independently:
| Script | Role |
|---|---|
gg_billing2 |
Issuing bills to players |
gg_oushu |
Seizing (confiscating) items or cash |
gg_police_notice |
Sending alerts between officers |
gg_tack_billing |
A second billing flow (wanted-list related) |
lokat_sharing_money |
Distributing rewards between officers |
Individually, they all worked. Together, they created chaos.
A police officer catching a suspect had to:
- Open one UI to issue a bill
- Switch to another command to seize items
- Manually notify other officers in yet another script
- Remember a separate
/commandto split the reward
Four different interactions, four different mental models, and zero awareness between them. If you were new to the police role, you were basically learning four mini-games just to do your job.
As the server operator, I felt it too. Bug reports came in from one script but the root cause was in another. Database tables had overlapping concepts. Every new feature meant touching code in multiple places.
The scripts weren't a system. They were a pile.
The Vision: One UI, One Flow
I wanted the police officer's experience to feel like one tool, not five. That meant:
- A single, clean UI that covered billing, seizure, reward distribution, and notifications
- Consistent design language — not five visual styles slammed together
- Features that knew about each other (issue a bill → split the reward → notify the right officers, all in one action)
- An external hook: integration with our in-game phone so players could check their wanted status without guessing
That last point mattered more than it sounds. On an RP server, a wanted timer is part of the game loop. Hiding it behind a /command breaks immersion. It belongs on the phone.
Folding 5 Scripts Into 1
The merge wasn't a copy-paste job. Each script had its own database schema, its own event names, its own config patterns. I had to decide what to keep, what to rename, and what to throw away.
What I kept:
- The core billing logic from
gg_billing2— it was battle-tested - The seizure flow from
gg_oushu— unique and well-scoped - The distribution math from
lokat_sharing_money— worked cleanly
What I rebuilt:
- The UI. Completely. The old one had everything crammed onto a single screen with no hierarchy — buttons next to buttons next to more buttons. The new UI is tab-based, so each function has its own space and the officer isn't overwhelmed.
- The notification layer. I wrote a new notification system from scratch and made it the connective tissue between every action. Issue a bill? All relevant officers get notified. Seize an item? Same. Wanted timer expires? Phone updates.
What I threw away:
- Redundant DB tables (three scripts had their own "transactions" concept)
- Duplicate helper functions scattered across all five scripts
- The
gg_tack_billingflow — its functionality got absorbed into the main billing tab
The final resource lives at tack_billing — one folder, one UI, one coherent system.
The Phone Integration
This is the feature I'm proudest of.
We use LB-phone on our server, and I wrote a custom app that talks to tack_billing directly. When a player has an active wanted status, the app shows them a live countdown — how many minutes until the wanted timer expires.
No more asking "am I still wanted?" in OOC chat. No more guessing. Just open the phone, check the app, plan your next move.
It's a small feature in terms of code. But it changed how players play the role. Suspects actually lay low until the timer runs out, because now they can see it. Cops feel the pressure lift at the right moment. The entire cat-and-mouse loop got tighter because information became legible.
That's the kind of change you only notice after you make it.
The Reward Distribution Flow
In the old world, splitting a police reward was a separate action entirely. An officer would issue a bill, then remember to run /split (or whatever the command was), then manually select who gets what.
In the new UI, the split happens on the same screen as the bill. You fill out the bill, check the officers who should share the reward, submit. One action, one UI, one record in the database. Done.
This is the kind of integration that's obvious in hindsight but only possible once the scripts stop being strangers to each other.
What Actually Mattered
Here's the part that might surprise you: the technical execution was the easy part.
Designing what the merged system should be — that was the hard work. Which features deserved to live? Which flows were essential vs. legacy? What should the UI hierarchy look like when you put all of this under one roof?
Those are judgment calls. They require knowing the server, the players, and the police role inside and out. No amount of clever code substitutes for that understanding.
Once the design was clear, building it was mostly execution. I barely hit any real technical blockers — a few minor UI bugs, nothing worth remembering.
That's the shift I keep noticing in 2026: the bottleneck has moved from implementation to integration design. Writing the code is cheap. Knowing what code to write — and how it should connect to everything else — is where the actual work lives now.
The Result
For the police role:
- One UI instead of four
- Faster workflow (bill + seize + split + notify in one place)
- Less onboarding friction for new officers
For players:
- Phone app shows wanted timer in real time
- Cleaner, more readable notifications
- The cat-and-mouse loop finally feels deliberate
For me as the operator:
- One codebase to maintain instead of five
- One database schema to reason about
- When something breaks, I know where to look
The server's been running on tack_billing for a while now, and I can't imagine going back to the old scattered setup.
Takeaway
If you run a FiveM server and you've got more than a handful of custom scripts, ask yourself: do they know about each other?
If the answer is no, you might be sitting on a pile, not a system. Merging isn't always the answer — sometimes separate scripts is the right call. But when features share a role, share a user, and share a purpose, keeping them apart costs more than people realize.
One UI to rule them all, it turns out, is worth the refactor.
Tack k is a freelance full-stack engineer based in Tokyo, building web apps, PWAs, and FiveM scripts. Working alongside AI as a true team member.
Top comments (0)