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
.shotfile starts withtypes script.Parent.Types(scripts and Types files have none). - Every project has
Init/Client.shotandInit/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(...) ... end | a 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.shot | imports 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/
Paths
Paths are Roblox instance paths, in Shot and in the compiled Luau. No string requires, no ./.
script.Parent.Bank | the file Bank.shot next to this one (the same for every file in the folder, the entry point included) |
script.Parent.Parent.Market | one folder up: the Market system next to this folder |
@shot.Wallet | ReplicatedStorage. |
@shot.Wallet.Bank | one 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 belowWith 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.BankgivesBank. - An
.instancefile gives a typed reference, not a require:use BuyRequestislocal BuyRequest = script.Parent.BuyRequest :: RemoteEvent. - A plain Luau file (below) is reached the same way:
use MathLibrequiresUtils/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.Signaluse Kit: Kit is a folder with no Kit.shot in it, so there is nothing to requireuse Shapes: a file can't use itselfuse @shot.Walet.Bank: nothing at @shot.Walet.Bankuse 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
}useandrequirereach it like a.shotfile, with its real types: returningMathLib.Double(x)from a function typed: stringis "Expected this to be 'string', but got 'number'" at the.shotline.shot show,shot findandshot searchread it too. A.luauScript can't be required ("Utils/Boot. server. luau is a server Script, and a script can't be required"). - A
.luaufile and a.shotfile of one name in one folder are an error: both would be that name in the place. - The root
Scenariosfolder is different: its.luaufiles 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: RemoteEventCode 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.shotis always its folder's types file. - Files in different folders may share a name; each folder's types are its own.
