Files

Lay out a project the way Roblox sees it: a folder per system, one declaration per file, and Roblox paths everywhere.

Every system folder has its own Types.shot, and every other .shot file declares one class, module or func, or is a script. The compiler puts your folders in ReplicatedStorage.Shot as they are on disk, so a path in your code is the instance path in the game. Remotes are .instance files, and plain .luau files can sit beside the rest.

Folders and files

A small project looks like this:

MyProject/                         the project (shot ... --project MyProject)
    Init/                       the two scripts every project has
        Client.shot             @client enabled script: starts the client
        Server.shot             @server enabled script: starts the server
    Wallet/                     a system
        Types.shot              its types (one per folder)
        Wallet.shot             named like the folder: the system's entry point (class Wallet)
        Bank.shot               server module Bank
        BuyRequest.instance     a RemoteEvent called BuyRequest
    Utils/                      a plain folder, with no Utils.shot
        Cleanup/                    a system inside a plain folder
            Types.shot          Cleanup's types
            Cleanup.shot            Cleanup's entry point
  • A folder is a system. Every system has one Types.shot (see Types).
  • Every other .shot file starts with types script.Parent.Types (scripts and Types files have none).
  • Every project has Init/Client.shot and Init/Server.shot (see Scripts).

One declaration per file

A .shot file is one of:

class X = { ... }a class (see Classes)
module X = { ... }a module (see Modules)
func X(...) ... enda function (see Funcs)
split module X = { ... }a client and a server module under one name (see Server and client)
diffsource func X = { ... }a client and a server func under one name (see Server and client)
@client enabled / @server ...a script: runs on its own, declares nothing (see Scripts)
Types.shotimports and types only (see Types)

Put server or client in front of class, module or func to make it server-only or client-only. The file name is the declared name: Bank.shot declares Bank. The file returns it; the compiler adds the return.

The shape of a file

A file has up to four parts, in this order:

types script.Parent.Types                      1. the types line

local Players = game:GetService("Players")     2. plain Luau: services, requires, constants,
local Wallet = require(script.Parent.Wallet)      state, helper functions
local MAX = 50

server module Bank = {                         3. the declaration
    ...
}

Players.PlayerAdded:Connect(...)               4. plain Luau after it (may use Bank)

The declared name exists from its declaration down. Code above the declaration can't use it (see Modules).

The entry point of a folder

A file named like its folder (Wallet/Wallet.shot) is the folder's entry point. require(@shot.Wallet) returns what Wallet.shot returns, and other systems can import every public type of Wallet from @shot.Wallet.

A folder without such a file is a plain Folder. Require its files one by one:

local Prize = require(@shot.Prizes.Prize)

Requiring the folder itself fails with "Unknown require: game/ReplicatedStorage/Shot/Prizes".

Paths

Paths are Roblox instance paths, in Shot and in the compiled Luau. No string requires, no ./.

script.Parent.Bankthe file Bank.shot next to this one (the same for every file in the folder, the entry point included)
script.Parent.Parent.Marketone folder up: the Market system next to this folder
@shot.WalletReplicatedStorage.Shot.Wallet, from anywhere
@shot.Wallet.Bankone file of another system (allowed; side rules apply)

They are used in require(...), the types line, import lines and split / diffsource entries.

@shot works everywhere in code: the types line, imports, split / diffsource entries, the plain Luau around the declaration, method and function bodies, and default values. Inside strings and comments it is left alone. Requiring once at the top of the file and using the local is still the usual style (use, below, is shorter).

Use

use is short for a require, on its own line above or below the declaration:

use script.Parent.Signal       local Signal = require(script.Parent.Signal)
use @shot.Wallet.Bank               local Bank = require(script.Parent.Parent.Wallet.Bank)
use Signal                     the nearest Signal: see below

With only a name, use finds the nearest thing called that. It looks at the children of this file's folder (its files, its folders that have an entry point, its .instance files), then at the children of the folder above, and so on up to the project root. The first match wins; a file and a folder of one name in the same folder: the file. It does not look inside other folders on the way, so a system two levels away needs a path: use @shot.Utils.Signal.

  • The local is named after the last part: use script.Parent.Bank gives Bank.
  • An .instance file gives a typed reference, not a require: use BuyRequest is local BuyRequest = script.Parent.BuyRequest :: RemoteEvent.
  • A plain Luau file (below) is reached the same way: use MathLib requires Utils/MathLib.luau.
  • The client/server rules apply as to require ("client code uses server MarketServer (...)").

Errors from use

  • use Nope: no Nope in this folder or any folder above it (use looks at each folder's own files and folders, up to the project root)
  • use Signal: no Signal in this folder or any folder above it (use looks at each folder's own files and folders, up to the project root); it is in Utils: use @shot.Utils.Signal
  • use Kit: Kit is a folder with no Kit.shot in it, so there is nothing to require
  • use Shapes: a file can't use itself
  • use @shot.Walet.Bank: nothing at @shot.Walet.Bank
  • use needs a name or a path: use Signal, use script.Parent.Signal or use @shot.Utils.Signal

Plain Luau files

A .luau file in a project is plain Luau, not ShotScript: Lib.luau is a ModuleScript, Boot.server.luau and Boot.client.luau are Scripts with that RunContext. It has no types line, no use and no declaration; it is copied into the build as it is, and shot check type-checks it with the rest, reporting its own errors at its own lines.

Utils/MathLib.luau

    local MathLib = {}

    function MathLib.Double(x : number) : number
        return x * 2
    end

    return MathLib

Utils/Doubler.shot

    types script.Parent.Types

    use MathLib

    module Doubler = {
        function Twice(x : number) : number
            return MathLib.Double(x)
        end
    }
  • use and require reach it like a .shot file, with its real types: returning MathLib.Double(x) from a function typed : string is "Expected this to be 'string', but got 'number'" at the .shot line.
  • shot show, shot find and shot search read it too. A .luau Script can't be required ("Utils/Boot.server.luau is a server Script, and a script can't be required").
  • A .luau file and a .shot file of one name in one folder are an error: both would be that name in the place.
  • The root Scenarios folder is different: its .luau files are test scenarios, never part of the game (see Scenarios).

Bookmarks

A line that starts with ─┨ is a bookmark: a comment that splits a file into sections, a banner without the --. A finished one is 108 wide:

─┨ Public Methods ┠────────────────────────────────────────────────────────────────────────────────────────┨

Don't count the dashes. Write ─┨ Name ┠ (any width; the -- form and look-alikes like ━┫ work too) and run shot fix, which rewrites every bookmark of the project to the exact width, keeping its indent. shot bookmark Name prints one finished line, and shot check ends with a note when a bookmark is off.

It can stand anywhere a whole line can: above or below the declaration, between members, inside a body. In the compiled Luau it becomes a -- comment; one between members is left out, like any comment there. The -- form (-- ─┨ Name ...) still works and gets the same colour.

Instances

A file Name.instance holds one class name and becomes that instance, named Name, in the folder:

Wallet/BuyRequest.instance       containing:  RemoteEvent

Code in the same folder reaches it as script.Parent.BuyRequest, typed as a RemoteEvent:

script.Parent.BuyRequest:FireServer("sword")

From another folder: script.Parent.Parent.Wallet.BuyRequest, or @shot.Wallet.BuyRequest above the declaration. Use it for RemoteEvent, RemoteFunction and UnreliableRemoteEvent. Models, maps, meshes and other assets stay in Roblox, outside the Shot tree.

Names

  • A file name must match its declaration ("Wallet.shot declares Walet: the name must match the file").
  • A file called Types.shot is always its folder's types file.
  • Files in different folders may share a name; each folder's types are its own.