Nothing in this manual matched.

9LABS Marina Creator · v1.0.1

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

  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

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:

  1. The ACE named in adminAce (default 9labs.marina).
  2. Each entry of adminGroups (default group.admin) as an ACE principal.
  3. The player's framework group compared against adminGroups, so an ESX group admin matches group.admin.
  4. On Qbox and QBCore, the framework's own god or admin permission. A normal Qbox or QBCore admin needs no new ACE.
  5. 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.

  1. Stand near the coast and open /marinacreator.
  2. Choose + Create marina, enter its name, pick Ownable business or Public rental, then Start building.
  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 Floors, Coast, Rails and Gear tabs.
  5. On the Stations tab choose Place capsule (jet ski capsule launch) or Place hangar (private boat 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. Under Placed stations, each station has Tablet, Exit and Pickup buttons that let you place its terminal, exit point or pickup point again.
  9. 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.
  10. 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 inputActionBinding
Mouse movementAim the preview using the normal gameplay cameraGame
W / A / S / DWalk normally while placingGame
Shift / SpaceNormal sprint and jumpGame
Left clickPlace the previewRebindable
EPlace the preview (keyboard)Rebindable
Mouse wheel up / downRotate by wheelStep (default 15°). A snapped piece takes the nearest orientation its seam allowsRebindable
ROn a seam: step to the next orientation that seam allows. Free placement: rotate by rotationStep (default 90°)Rebindable
TFine rotate by 5°, for free placement and decorationRebindable
GToggle socket snappingRebindable
Page Up / Page DownRaise or lower the placement by fineStep (default 0.05 m)Rebindable
Z / XPull the reach in or push it out by 0.5 m. Starts at 6 m; default range 2.5–10 mRebindable
TabReturn from placement to the paletteRebindable
Alt + RFine rotate by 5°Fixed fallback
Alt + mouse wheelFine rotate by 5° in either directionFixed fallback
Up / Down arrowsRaise or lower the placement by fineStepFixed fallback
EscapeReturn from placement to the paletteFixed fallback
Undo buttonUndo the last draft change, up to 30 stepsPalette

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 controlWhat it does
Look to editAim 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 deleteAim at a placed piece or station and press left click or E to remove it.
Placed tabLists every placed object with its position and a remove button.
Stations tabLists placed stations with Tablet, Exit, Pickup and a remove button.
UndoUndoes the last draft change, up to 30 steps.
Save marinaPublishes 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.

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

DeductionAmount
Damaged or lost vesselrental.damageFee (default 500)
Late returnOne 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
TotalNever 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.
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.

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 list
marinareconcile 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.

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
debugExtra console tracing for troubleshooting. Leave false in production
command, adminAce, adminGroupsCreator command and administrator checks
accountsPayment accounts offered at the tablet (default cash and bank)
standaloneInitial cash and bank marina credits for new standalone wallets
limitsMarina, piece and station counts, building radius, minimum gap between marinas, employee count, price and fleet limits
builderPlacement reach, snapping distances, height step, rotation, deck height, free placement and preview bounds
noCollisionDecoration models streamed without collision
streamingLoad radius, unload hysteresis, refresh interval, object budget and spawn batch size
rentalInteraction and return radii, animation durations, grace period, rentals per player and service fees
businessPurchase default, income share, resale fraction and public fleet size
displayBrand, subtitle, colour, currency, screen resolution, camera settings, idle screen distance, logo 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

Individual keys

KeyDefaultEffect
limits.minMarinaGap30.0Minimum distance in metres between two marina centres
limits.maxEmployees20Employees per marina
builder.snapDistance3.5Horizontal reach of a socket in metres
builder.snapDistanceDecor1.5Tighter socket reach for rails, fenders and equipment
builder.occupiedDistance0.5Identical pieces closer than this are rejected as duplicates
builder.allowFreePlacementtrueWhen false, every piece after the first must snap to a socket
builder.showBoundsfalseDraw a translucent box around the preview instead of only the outline and marker
streaming.tickMs1000How often, in milliseconds, clients refresh which marina props are streamed in
rental.reservationTimeout45Seconds after the rental ends before an unreturned vessel is recovered; also the stage watchdog
rental.maxPerPlayer1Rentals one character may have at once
rental.damageFee500Kept from the deposit for a damaged or lost vessel
rental.repairCost1000Business cost to repair one vessel
rental.refuelCost200Business cost to refuel one vessel
display.idleDrawDistance35.0Metres 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.

  1. Save your logo as logo/logo.png in the resource, replacing the supplied file.
  2. Use a PNG with transparency, cropped tight around the artwork with no empty margins, up to 2048 px on the long side.
  3. 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'.

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

Vehicle keys

keysBehaviour
autoUses qbx_vehiclekeys if it is started, otherwise qb-vehiclekeys, otherwise gives no keys
qbxqbx_vehiclekeys exports GiveKeys and RemoveKeys
qbqb-vehiclekeys server exports GiveKeys and RemoveKeys (1.6 and newer); older versions get the client events vehiclekeys:client:SetOwner and qb-vehiclekeys:client:RemoveKeys
noneNo keys are given
customCustom.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:

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.

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.

marinasnapdiag
marinaduidiag

Nothing in this manual matched.