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. It needs a writable storage/ directory.
Optionalox_lib, ox_target, qb-target, and qbx_vehiclekeys or qb-vehiclekeys (or your own key resource through the custom bridge).
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.
refreshensure 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
The models and textures are streamed from this resource's own stream/ folder.
Do not run another resource that streams the same model and texture names: it can replace the
tablet and sign textures.
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.
How a player is recognised as an administrator
The server checks these in order and stops at the first one that passes:
The ACE named in adminAce (default 9labs.marina).
Each entry of adminGroups (default group.admin) as an ACE principal.
The player's framework group compared against adminGroups, so an ESX group admin matches group.admin.
On Qbox and QBCore, the framework's own god or admin permission. A normal Qbox or QBCore admin needs no new ACE.
Custom.isAdmin(src) in server/bridge.lua, consulted only when framework = 'custom'.
If none of these already covers your administrators, 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.
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, pick Ownable business or Public rental, then Start building.
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 Floors, Coast, Rails and Gear tabs.
On the Stations tab choose Place capsule (jet ski capsule launch) or Place hangar (private boat 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.
Under Placed stations, each station has Tablet, Exit and Pickup buttons that let you place its terminal, exit point or pickup point again.
On the Settings tab set the marina name, ownership, purchase Price and the map blip (visible or hidden, sprite, colour and scale), then choose Apply settings.
Choose Save marina. Unsaved editing remains local.
Price is what a player pays to buy this marina at its tablet when it is ownable. It
accepts 0 to 1,000,000 (capped by limits.maxPrice) and overrides
business.defaultPurchasePrice, which is what a new marina starts with. Only an
administrator can change the ownership mode or the price. A marina cannot be saved with its
centre closer than limits.minMarinaGap (default 30 m) to another marina.
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 exception is noCollision in config.lua. Models listed there are
pure decoration and are streamed without collision, so a player who climbs a railing does not
end up standing in mid air on a sign or lifering. By default it lists the lifering, hose reel,
cleat, overhead sign, 4 m fender, corner fender and hangar interior fender. Remove a model from
the list if you want it solid.
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.
Default input
Action
Binding
Mouse movement
Aim the preview using the normal gameplay camera
Game
W / A / S / D
Walk normally while placing
Game
Shift / Space
Normal sprint and jump
Game
Left click
Place the preview
Rebindable
E
Place the preview (keyboard)
Rebindable
Mouse wheel up / down
Rotate by wheelStep (default 15°). A snapped piece takes the nearest orientation its seam allows
Rebindable
R
On a seam: step to the next orientation that seam allows. Free placement: rotate by rotationStep (default 90°)
Rebindable
T
Fine rotate by 5°, for free placement and decoration
Rebindable
G
Toggle socket snapping
Rebindable
Page Up / Page Down
Raise or lower the placement by fineStep (default 0.05 m)
Rebindable
Z / X
Pull the reach in or push it out by 0.5 m. Starts at 6 m; default range 2.5–10 m
Rebindable
Tab
Return from placement to the palette
Rebindable
Alt + R
Fine rotate by 5°
Fixed fallback
Alt + mouse wheel
Fine rotate by 5° in either direction
Fixed fallback
Up / Down arrows
Raise or lower the placement by fineStep
Fixed fallback
Escape
Return from placement to the palette
Fixed fallback
Undo button
Undo the last draft change, up to 30 steps
Palette
Rebindable keys are FiveM key mappings. Change them under Settings, Key Bindings, FiveM,
where they are listed as Marina creator: …. They arrive through FiveM's input layer, so a
resource that disables the same game control cannot swallow the press. Fixed fallbacks are
read directly as game controls and cannot be rebound.
Editing a layout
Pick placed pieces back up, remove them by looking at them, or manage them from lists in the palette.
Opening /marinacreator lists the marinas you may edit. Open creator loads a
marina into the editor; administrators also see Delete, which removes a marina and its
placed objects after a confirmation.
Palette control
What it does
Look to edit
Aim at a placed piece and press left click or E. The piece is picked up with its rotation and you place it again. Stations are not picked up this way; move them with the buttons on the Stations tab.
Look to delete
Aim at a placed piece or station and press left click or E to remove it.
Placed tab
Lists every placed object with its position and a remove button.
Stations tab
Lists placed stations with Tablet, Exit, Pickup and a remove button.
Undo
Undoes the last draft change, up to 30 steps.
Save marina
Publishes the layout. Nothing reaches other players before this.
In either look mode, Tab or Escape returns to the palette.
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.
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 (rental.maxPerPlayer). A disconnect also ends the
rental.
Late and lost vessels
When the rental time runs out the renter is told to bring the boat back. If it is still out
rental.reservationTimeout seconds later (default 45), the station recovers it
remotely. A boat that no longer exists counts as damaged. The same timeout is also the
watchdog for a mechanism stage that never finishes; a stalled launch refunds the rental price.
Deduction
Amount
Damaged or lost vessel
rental.damageFee (default 500)
Late return
One slice of the plan price per started minute past the end, where a slice is the price divided by the plan's minutes, rounded up
Total
Never more than the deposit
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.
Repairs cost rental.repairCost (default 1000) and refuelling
rental.refuelCost (default 200) per vessel, paid from the business balance. A
marina can have up to limits.maxEmployees employees (default 20).
The income chart and transaction history are calculated from the retained ledger, not an
unlimited accounting archive. Each marina keeps its last 50 ledger entries. Rentals, fees,
deposits, withdrawals, fleet purchases, repairs, refuels, settings changes, employee changes,
transfers and sales all use entries, and the oldest entry is dropped when a new one is added.
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 listmarinareconcile pay <obligationId>marinareconcile drop <obligationId>
Without an argument the command lists the open obligations. pay explicitly
authorizes a payout after review and works only while the affected player is online;
otherwise it reports that the player is not online. drop explicitly discards that
obligation without paying. 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
debug
Extra console tracing for troubleshooting. Leave false in production
command, adminAce, adminGroups
Creator command and administrator checks
accounts
Payment accounts offered at the tablet (default cash and bank)
standalone
Initial cash and bank marina credits for new standalone wallets
limits
Marina, piece and station counts, building radius, minimum gap between marinas, employee count, price and fleet limits
Interaction and return radii, animation durations, grace period, rentals per player and service fees
business
Purchase default, income share, resale fraction and public fleet size
display
Brand, subtitle, colour, currency, screen resolution, camera settings, idle screen distance, logo 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
Individual keys
Key
Default
Effect
limits.minMarinaGap
30.0
Minimum distance in metres between two marina centres
limits.maxEmployees
20
Employees per marina
builder.snapDistance
3.5
Horizontal reach of a socket in metres
builder.snapDistanceDecor
1.5
Tighter socket reach for rails, fenders and equipment
builder.occupiedDistance
0.5
Identical pieces closer than this are rejected as duplicates
builder.allowFreePlacement
true
When false, every piece after the first must snap to a socket
builder.showBounds
false
Draw a translucent box around the preview instead of only the outline and marker
streaming.tickMs
1000
How often, in milliseconds, clients refresh which marina props are streamed in
rental.reservationTimeout
45
Seconds after the rental ends before an unreturned vessel is recovered; also the stage watchdog
rental.maxPerPlayer
1
Rentals one character may have at once
rental.damageFee
500
Kept from the deposit for a damaged or lost vessel
rental.repairCost
1000
Business cost to repair one vessel
rental.refuelCost
200
Business cost to refuel one vessel
display.idleDrawDistance
35.0
Metres within which idle tablet screens are drawn
display.logo
'logo/logo.png'
Image drawn on the overhead and pylon signs
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 runs from the resource's own files and uses system
fonts (Arial Narrow and Arial): no CDN, no Alpine installation and no build step.
Logo and signs
Put your own logo on the overhead sign above the hangar and on the pylon sign.
Save your logo as logo/logo.png in the resource, replacing the supplied file.
Use a PNG with transparency, cropped tight around the artwork with no empty margins, up to 2048 px on the long side.
Restart the resource.
Both signs are drawn at runtime; the models are never touched. The image is fitted inside the
sign and never stretched. Landscape logos suit the overhead sign, square logos suit the pylon.
Below the logo the pylon shows display.subtitle.
No logo file means text signs. Remove logo.png and both signs fall back to
display.brand and display.subtitle from config.lua.
display.logo sets the path if you want a different PNG file name inside
logo/.
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
Vehicle keys
keys
Behaviour
auto
Uses qbx_vehiclekeys if it is started, otherwise qb-vehiclekeys, otherwise gives no keys
qbx
qbx_vehiclekeys exports GiveKeys and RemoveKeys
qb
qb-vehiclekeys server exports GiveKeys and RemoveKeys (1.6 and newer); older versions get the client events vehiclekeys:client:SetOwner and qb-vehiclekeys:client:RemoveKeys
none
No keys are given
custom
Custom.giveKeys and Custom.removeKeys in server/bridge.lua
There is no built-in ESX key adapter. On ESX use custom for your key resource, or none.
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:
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.
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.
Before going live
Test on your own server with two players before opening a marina to the public.
Rotated dock snapping and saved prop collisions.
Capsule and hangar movement and clearance for every vessel you offer.
Immediate handover, and rejection when a bay is occupied.
Clean, damaged and late returns, and simultaneous requests.
Reconnect and restart recovery.
Your target and key integrations.
Owner, manager and staff permissions.
Streaming and resmon at the marina size you intend to build.
Your framework, at the version you actually run.
A 36 MiB texture warning at startup is expected. The supplied shared texture dictionary
reports 36 MiB physical memory when FXServer starts. This is a streaming-budget warning, not a
Lua startup failure. The models and textures ship at full quality and have not been downscaled.
Troubleshooting
If a tablet does not respond, check the client F8 console and the server console for errors,
verify the resource name and that the web/ files are present, and make sure no
other resource streams the same model pack over its textures.
If saving fails, check creator permissions, that you are close enough to the marina, stale
revision errors, and that the server can write to storage/, before reopening the
editor.
Two client console commands print diagnostic information to F8: marinasnapdiag for placement and snapping, and marinaduidiag for the tablet and sign screens.