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.

  1. Put the runtime folder in resources/[9labs]/9labs_marinabuilder.
  2. Keep the resource name 9labs_marinabuilder.
  3. Start your framework, and any optional target and key resources, first.
  4. Configure config.lua. The defaults auto-detect the framework and use the included interaction prompts and notifications.
  5. 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.

  1. Stand near the coast and open /marinacreator.
  2. Choose Create marina, enter its name, and choose Public rental or Ownable business.
  3. Place a shore section or dock floor. Its first placed piece becomes the marina centre.
  4. Add connected floors, supports, rails, ropes, mooring equipment and decorations from the palette.
  5. Under Rental stations choose Capsule launch or Private hangar.
  6. Position the station's rear dock connection. Its forward exit must be over water.
  7. Place that station's tablet separately, on an accessible dock. The capsule or hangar stays visible while you choose its terminal location.
  8. Adjust its Exit point or Pickup point if needed. Move tablet relocates an existing terminal.
  9. Under Marina settings configure the map blip: visibility, sprite, colour and scale.
  10. 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.

InputAction
Mouse movementAim the preview using the normal gameplay camera
W / A / S / DWalk normally while placing
Shift / SpaceNormal sprint and jump
Left click or EPlace the preview
Mouse wheelRotate by wheelStep (default 15°). A snapped piece takes the nearest orientation its seam allows. ALT + wheel rotates by 5°
ROn a seam: step to the next orientation that seam allows. Free placement: rotate by rotationStep (default 90°)
Alt + RRotate by 5°, for free placement and decoration
GToggle socket snapping
Up / Down arrowsRaise or lower the placement by 0.05 m by default
Z / XPull the reach in or push it out. Starts at 6 m; default range 2.5–10 m
Tab / EscapeReturn from placement to the palette
Undo buttonUndo 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.
CapabilityOwnerManagerStaff
View business dashboardYesYesYes
Repair and refuel fleetYesYesYes
Change prices, buy fleet, depositYesYesNo
Edit marina layoutYesYesNo
Hire and remove staffYesYesNo
Hire and remove managersYesNoNo
Withdraw, transfer, sell businessYesNoNo

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.

SectionPurpose
frameworkauto, qbx, qb, esx, standalone, custom
interactionIncluded 9labs_ui, optional ox_lib, ox_target, qb-target, or custom
notifyIncluded 9labs_ui, ox_lib, native GTA notifications, or custom
keysauto, qbx, qb, none, or custom
command, adminAce, adminGroupsCreator command and administrator checks
standaloneInitial cash and bank marina credits for new standalone wallets
limitsMarina, piece and station counts, building radius, price and fleet limits
builderGameplay view, placement reach, snapping distance, height step, rotation and deck height
streamingLoad radius, unload hysteresis, object budget and spawn batch size
rentalInteraction and return radii, animation durations, grace period and service fees
businessPurchase default, income share, resale fraction and public fleet size
displayBrand, subtitle, colour, currency, screen resolution, camera settings and rentalHud
audioPositional mechanism sounds, range, volume and per-stage files
vehiclesAllowlisted models, images, type, base price, deposit and purchase cost
plansDuration 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'.

HookContract
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.

Stuck on something this page does not cover? Ask in Discord. Support