9LABS Marina Creator A marina you build in game, on water.
Place docks, capsule launches and boat hangars where you want them, then let players rent from a real tablet on the pier.
Introduction
A server-authoritative marina builder and boat rental. Administrators lay out docks and rental stations in game, players rent vessels from a physical tablet, and a marina can be sold to a player to run as a business.
What is in the resource
- An in-game creator for docks, supports, rails, ropes, mooring equipment and decoration.
- Two rental station types: capsule launch and private boat hangar, each with its own separately placed tablet.
- Ownable marinas with fleet stock, balances, employees and a retained ledger.
- Qbox, QBCore, ESX, standalone credits and custom framework bridges, plus optional vehicle keys.
- Server exports and a reconciliation console for interrupted transactions.
The server decides everything that costs money. Price and deposit are calculated
from server configuration and business prices. A client-supplied price never authorizes
a charge, and no public client event permits arbitrary vehicle models, payments, deletion
or ownership changes.
Requirements
One framework choice, the supplied streamed models, and OneSync.
ServerFiveM with OneSync and the supplied streamed models.
FrameworkQbox, QBCore, ESX, standalone credits, or a custom bridge.
DatabaseNone. This resource installs no SQL and runs no migrations.
Optionalox_lib, ox_target, qb-target and a vehicle-key resource.
ox_lib is not a hard dependency. Runtime Lua files use the manifest's load order
rather than require, so they can be loaded with Asset Escrow. Do not remove
ox_lib from your server merely because this resource does not need it.
Installation
Start it after the framework, target and key resources, then place a marina.
- Put the runtime folder in
resources/[9labs]/9labs_marinabuilder. - Keep the resource name
9labs_marinabuilder. - Start your framework, and any optional target and key resources, first.
- Configure
config.lua. The defaults auto-detect the framework and use the included interaction prompts and notifications. - Start the resource from the server console.
refresh
ensure 9labs_marinabuilder
For later code updates use restart 9labs_marinabuilder.
Back up storage before replacing an installed version. Stop the resource first,
and never overwrite a customer's storage/ directory with an empty
development copy.
Do not stream the models twice
Only one copy of the supplied model pack may be streamed. Do not also start the nested
asset-delivery resource from output/fivem_resource/ — it contains the same
model and texture names.
Creator access
Administrators create and delete marinas. Owners and managers edit their own.
The default command is /marinacreator. Ordinary renters cannot save layouts or alter ownership.
If your administrator integration does not already grant access, grant the configured ACE to your existing admin principal:
add_ace group.admin 9labs.marina allow
Do not grant this to builtin.everyone. The example assumes your server
already assigns its administrators to group.admin. Custom servers can
implement Custom.isAdmin in server/bridge.lua instead.
Build your first marina
Stand near the coast, open the creator, and place the first piece. Only saving publishes the layout.
- Stand near the coast and open
/marinacreator. - Choose Create marina, enter its name, and choose Public rental or Ownable business.
- Place a shore section or dock floor. Its first placed piece becomes the marina centre.
- Add connected floors, supports, rails, ropes, mooring equipment and decorations from the palette.
- Under Rental stations choose Capsule launch or Private hangar.
- Position the station's rear dock connection. Its forward exit must be over water.
- Place that station's tablet separately, on an accessible dock. The capsule or hangar stays visible while you choose its terminal location.
- Adjust its Exit point or Pickup point if needed. Move tablet relocates an existing terminal.
- Under Marina settings configure the map blip: visibility, sprite, colour and scale.
- Save marina. Unsaved editing remains local.
What the grid does
The modules use a 4 m grid with half-edge sockets for smaller pieces. A half-edge socket only
accepts a piece whose joining edge is short enough to cover that half, so a full 4 m tile
always lands on the grid. Surface Z is the deck top, not the model's bounding-box centre.
Over water, a new dock defaults to 1.05 m above the detected water surface. Height-adjusted
pieces become free placements, so they do not claim a socket they no longer occupy.
The creator does not cut holes in the GTA map. Put the capsule and hangar over open
water connected to a dock, and do not place the launch mechanism inside sand, rocks or an
existing collision mesh. Preview ghosts deliberately have no collision; saved props use the
collision embedded in their YDRs.
The supplied hangar station is the complete authored assembly: its rear, middle and front
parts and walkways are positioned together, and this version does not offer arbitrary
hangar-length changes. Individual equipment and rail objects are decoration unless explicitly
connected to a rental station — placing an extra decorative tablet does not create another
business terminal.
Placement controls
Placement keeps your normal gameplay camera. It never forces first person or a flying camera.
The preview follows your crosshair and lands where you are looking, on the deck or the ground,
clamped between the minimum and current reach measured from the player. A red preview means
placement is blocked, and clicking it does not add the object.
| Input | Action |
| Mouse movement | Aim the preview using the normal gameplay camera |
| W / A / S / D | Walk normally while placing |
| Shift / Space | Normal sprint and jump |
| Left click or E | Place the preview |
| Mouse wheel | Rotate by wheelStep (default 15°). A snapped piece takes the nearest orientation its seam allows. ALT + wheel rotates by 5° |
| R | On a seam: step to the next orientation that seam allows. Free placement: rotate by rotationStep (default 90°) |
| Alt + R | Rotate by 5°, for free placement and decoration |
| G | Toggle socket snapping |
| Up / Down arrows | Raise or lower the placement by 0.05 m by default |
| Z / X | Pull the reach in or push it out. Starts at 6 m; default range 2.5–10 m |
| Tab / Escape | Return from placement to the palette |
| Undo button | Undo the last draft change, up to 30 steps |
Every creator key is a rebindable key mapping (Settings, Key bindings, FiveM), so a
resource that disables the same game control can never swallow a press.
Rental flow
Approach a station tablet and press E, or use the configured target.
The camera faces the physical screen and the local player is hidden while browsing. Choose a
vessel, duration and payment account.
Payment -> opening -> launch -> handover -> active rental
Active rental -> return accepted -> recovery -> closing -> deposit settlement
The server calculates the price and deposit from its own configuration and business prices.
The rental timer starts at handover, not while the mechanism is opening.
Returning
Park the rented vessel near a compatible launch or return point at the same marina and step
out. Press E at the return prompt or use My rental on the tablet. The system checks the
tracked vehicle, ownership, station compatibility, distance and current movement state.
The engine does not have to be turned off, but all occupants must leave the vessel.
A launch bay cannot dispense or recover two vehicles at once, so move a released boat away from
the exit before the next customer rents. Boats stay frozen and locked only during their
mechanical movement; control is released at handover.
Damage, lateness or a lost vehicle can reduce the returned deposit. The default configuration
allows one rental per character. An overdue vehicle is recovered after the configured grace
period, and a disconnect also ends the rental.
Ownable businesses
Public marinas are server-operated. Ownable marinas are bought by a player at the tablet, which then becomes their business workspace.
An owned business begins without purchased fleet stock. The owner deposits business funds, opens
Fleet and purchases vessels. Rental income goes to the business balance according to
business.ownerShare.
On the tablet
- Rental catalog with vessel images, durations, price and deposit breakdown.
- Current reservation and return action.
- Business balance, lifetime earnings, booking count and recent income chart.
- Fleet stock, availability, rental prices, enabled or paused status, repair and refuel.
- Deposits, withdrawals and retained transaction history.
- Employee management and business-name settings.
- Creator access, ownership transfer and sale back to the server.
| Capability | Owner | Manager | Staff |
| View business dashboard | Yes | Yes | Yes |
| Repair and refuel fleet | Yes | Yes | Yes |
| Change prices, buy fleet, deposit | Yes | Yes | No |
| Edit marina layout | Yes | Yes | No |
| Hire and remove staff | Yes | Yes | No |
| Hire and remove managers | Yes | No | No |
| Withdraw, transfer, sell business | Yes | No | No |
Employees are saved by stable character identity, not their temporary server ID. Use an
online player's server ID when hiring or transferring. Ownership changes are restricted while
the marina has active rentals.
The income chart is calculated from the retained ledger, not an unlimited accounting archive.
Storage and recovery
Plain JSON on disk, with backups kept before every replacement.
storage/marinas.jsonLayouts, business ownership, fleet and ledger.
storage/rentals.jsonRental records and the payout/recovery journal.
.bak filesPrevious contents, retained before replacing a JSON file.
Standalone walletsServer resource KVP, not the JSON files above.
Files are created on startup or the first saved change. Corrupt storage fails closed with a
console error instead of silently replacing a customer's data. Stop the resource, preserve the
damaged file, validate a backup and restore it before restarting.
Uncertain transactions
Framework money calls and resource files are not one atomic transaction. A crash during an
external debit or credit can leave an uncertain entry, which requires administrator
reconciliation rather than a blind retry of a possible duplicate payment.
marinareconcile list
marinareconcile pay <journalId>
marinareconcile drop <journalId>
pay explicitly authorizes a payout after review; drop explicitly
discards that obligation. These are console and admin operations, not client-side economic
endpoints. Regular pending refunds settle when their character becomes available, and restart
recovery clears stale rental vehicles and settles or flags interrupted reservations according
to their persisted state.
Review the journal and your framework's transaction logs before paying anything by hand.
Keep backups outside the web and client file list, and never expose storage/,
server code, transaction journals or framework credentials through a web server.
Configuration reference
config.lua is readable and intentionally kept editable under Asset Escrow.
| Section | Purpose |
framework | auto, qbx, qb, esx, standalone, custom |
interaction | Included 9labs_ui, optional ox_lib, ox_target, qb-target, or custom |
notify | Included 9labs_ui, ox_lib, native GTA notifications, or custom |
keys | auto, qbx, qb, none, or custom |
command, adminAce, adminGroups | Creator command and administrator checks |
standalone | Initial cash and bank marina credits for new standalone wallets |
limits | Marina, piece and station counts, building radius, price and fleet limits |
builder | Gameplay view, placement reach, snapping distance, height step, rotation and deck height |
streaming | Load radius, unload hysteresis, object budget and spawn batch size |
rental | Interaction and return radii, animation durations, grace period and service fees |
business | Purchase default, income share, resale fraction and public fleet size |
display | Brand, subtitle, colour, currency, screen resolution, camera settings and rentalHud |
audio | Positional mechanism sounds, range, volume and per-stage files |
vehicles | Allowlisted models, images, type, base price, deposit and purchase cost |
plans | Duration IDs, minutes and price multipliers |
rentalHud controls the on-screen rental timer: enabled,
anchor (top or bottom, left, right or center) and offsetX /
offsetY in screen percent.
Do not change vehicle IDs while rentals are active. IDs also persist owned fleet
entries. To add a custom boat, add its model resource first, then a vehicle definition and a
local image in web/assets/. Add any new image extension to
fxmanifest.lua if it is not PNG or WebP.
Vessel dimensions
The capsule has 2.64 m clear width, allowing a maximum vessel width of 2.24 m with the authored
margin. The hangar channel is 6.2 m wide with 15.6 m internal clear length.
originOffset adjusts the vehicle's height relative to its launch or water anchor;
it does not raise the dock objects.
maxWidth is configured metadata, not a server-side measurement of a
third-party model. Measure custom vessels and test them in game.
The physical screen uses 1440 × 1140 by default to match the supplied landscape mesh. Keep its
aspect ratio if changing resolution. The UI uses bundled files and system fonts: no CDN, no
Alpine installation and no build step.
Framework and custom bridges
auto checks Qbox, QBCore, ESX, then standalone. Choose explicitly when running unusual compatibility layers.
Server bridge
Edit the clearly marked CUSTOM section of server/bridge.lua and set framework = 'custom'.
| Hook | Contract |
Custom.identity(src) | Stable character ID string, or nil while loading |
Custom.getMoney(src, account) | Numeric account balance |
Custom.removeMoney(src, account, amount, reason) | Return true only after a successful debit |
Custom.addMoney(src, account, amount, reason) | Return true only after a successful credit |
Custom.isAdmin(src) | Explicit administrator authorization |
Custom.giveKeys(src, vehicle, plate) | Grant keys; vehicle is a SERVER entity handle |
Custom.removeKeys(src, vehicle, plate) | Revoke those keys |
Keep money operations on the server. Do not use a temporary server ID as character
identity, do not trust amounts from NUI, and never return true when a payment failed. Set
keys = 'custom' when using the custom key hooks.
Client bridge
client/bridge.lua handles only presentation and interaction:
Custom.showPrompt(text, key) and Custom.hidePrompt(). Custom.notify(data) with description and type. Custom.addTarget(id, coords, onSelect) returning a handle, and Custom.removeTarget(handle).
Select interaction = 'custom' and notify = 'custom' as needed. The
custom prompt can keep the included E interaction, and a target hook can invoke the supplied
onSelect callback. No business permission or financial authority belongs in this
file.
Standalone credits
Persistent resource-KVP marina credits, separate from any framework economy.
Initial cash and bank values apply only when a wallet is first created. Back up server KVP data
as well as storage/ when moving servers.
Console or authorized administrators can add credits:
marinacredits <onlinePlayerId> <positiveAmount> [cash|bank]
This command is disabled outside standalone mode. Use a custom bridge to connect an
existing standalone economy rather than running two independent balances.
Synchronization and DUI
The server owns the money and the truth. Buildings are streamed client-side from the same saved transforms.
The server persists layouts, authorizes edits, controls reservations, owns rental vehicle
entities and determines movement timestamps. Marina buildings are not spawned on the
server: each nearby client streams its own non-networked props from the same saved
transforms. Snap metadata and sampled rigid-part quaternions come from the supplied Blender
export.
Clients animate the local mechanism from server timestamps. Only the current network owner moves
a rental vehicle, and passive peers do not repeatedly request control. Newly joined clients
receive a snapshot of layouts and movement stages. Saving uses revision checks to reject stale
edits.
The tablet is real DUI
The rental and business screen is DUI, not a full-screen HTML imitation. CEF is rendered to
runtime textures. Shared texture replacement provides the neutral tablet and sign displays,
while the selected terminal's private service texture is drawn on that screen's physical plane,
so another nearby tablet does not display your private finance page. An invisible NUI input
relay forwards mouse and text input to the DUI. The creator palette itself is ordinary NUI.
References:
DUI rendering,
secure network events and
resource manifests.
Server exports
Three server exports. Nothing client-side can spend money or change ownership.
local ready = exports['9labs_marinabuilder']:IsReady()
local publicMarinas = exports['9labs_marinabuilder']:GetMarinas()
local rental = exports['9labs_marinabuilder']:GetRental(playerSource)
GetMarinas returns the public projection, not bank balances or private
employee records. No public client event permits arbitrary vehicle models, payments, deletion
or ownership changes.