Overview

Write your Roblox game as classes, modules and funcs in plain Luau, and ShotScript writes the metatables, types and boilerplate around them.

This page shows how a project is laid out, walks through a complete small example, lists every keyword ShotScript adds to Luau, and ends with the order to read the other docs in.

What it is

ShotScript is Luau with a thin layer on top. Each .shot file is plain Luau plus one declaration: a class, a module or a func. The compiler (shot) turns every file into a --!strict Luau ModuleScript in ReplicatedStorage.Shot and writes the boilerplate for you: metatables, type definitions, the Types module, re-exports. Everything inside a function body is ordinary Luau, checked by Studio's type checker.

A project is a folder. Its subfolders are systems. Every project has an Init folder with two scripts that start the game.

MyProject/
    Init/
        Client.shot        @client enabled: starts the client
        Server.shot        @server enabled: starts the server
    Wallet/
        Types.shot         the types of the Wallet system
        Wallet.shot        class Wallet (named like its folder, so it is the system's entry point)
        Bank.shot          server module Bank

A complete small example

A Wallet system in three files (its types, a class for one player's coins and a server module that keeps every player's wallet), and the server script that starts it.

Wallet/Types.shot

doc { Why a player's coins changed }
public type Reason = "Pickup" | "Shop"

Wallet/Wallet.shot

types script.Parent.Types

rootdoc {
    The coins one player owns.
}
class Wallet = {
    doc { Coins the player has now }
    public Coins : number
    internal _history : {Reason}

    constructor new(coins : number)
        local self : Wallet = init()
        self.Coins = coins
        self._history = {}
        return self
    end

    doc { Adds coins and remembers why }
    public Add(amount : number, reason : Reason)
        self.Coins += amount
        table.insert(self._history, reason)
    end

    public static Empty() : Wallet
        return Wallet.new(0)
    end
}

Wallet/Bank.shot

types script.Parent.Types

local Players = game:GetService("Players")

local Wallet = require(script.Parent.Wallet)

server module Bank = {
    Wallets : {[Player] : Wallet} = {}

    function Start()
        Players.PlayerAdded:Connect(function(player : Player)
            module.Wallets[player] = Wallet.Empty()
        end)
    end

    function Give(player : Player, amount : number)
        local wallet = module.Wallets[player]
        if wallet then
            wallet:Add(amount, "Pickup")
        end
    end
}

Init/Server.shot

@server enabled

local Bank = require(@shot.Wallet.Bank)

Bank.Start()

What to notice:

  • Every file except scripts and Types files starts with "types script.Parent.Types".
  • Reason, Wallet and Bank are types every file of the Wallet folder can use without importing anything.
  • Plain Luau goes above the declaration (services, requires, constants, helper functions, state).
  • Methods are called with ":" (wallet:Add(...)). Constructors, static functions and module functions with "." (Wallet.new(...), Wallet.Empty(), Bank.Give(...)).
  • Paths are Roblox paths: script.Parent.Wallet is the file next to this one, @shot is ReplicatedStorage.Shot.
  • shot check finds mistakes in all of it and reports them at the .shot line and column.

Keywords

class, module, func, interfacewhat a file declares (one per file)
server, clientput before class / module / func: which side may use it
split module, diffsource funcone name with a client and a server version
public, internal, override, staticclass members
constructora class's creating function
init()inside a class: a new, empty object of the class
selfinside a class: the object
moduleinside a module: the module itself (module.Count, module.Add())
extendsinheritance
implementsa class promises an interface's members (Interfaces)
requireda function the game must call somewhere (Modules)
matchcode by the value of a string union, every value covered (Match)
usesin Types.shot: the systems this one may use (Uses)
eventin Types.shot: an EventBus event this system publishes (Events)
idin Types.shot: a kind of string id this system defines (Id kinds)
ownsin Types.shot: only this system writes a field (Owned fields)
after, beforeon a Start's header: which start calls come first (Start order)
invariantin Types.shot: a rule shot run checks after every frame (Invariants)
useshort for a require: use Signal (the nearest one) or a path
types, import, public type, internal typethe Types system
doc { }, rootdoc { }documentation
─┨ Name ┠──┨a bookmark: a section banner, a comment (Files, BOOKMARKS)
section Rules { } (inside a doc)a named part of a doc: Rules, Extend, Examples, or your own
@client / @server enabled|disabledfirst line of a script
@shotReplicatedStorage.Shot in paths
Fileshow a project is laid out, paths, .instance files
Remotessignatures on .instance files: what crosses the network, typed Fire and handlers
Classesclasses, class properties, constructors, methods, statics, inheritance, XClass
Modulesmodules
Funcsfuncs
Scripts@client / @server scripts and Init
Server and clientserver, client and shared code, split modules, diffsource funcs
TypesTypes files, what each file sees, imports
Useswhich systems a system may use
Eventstyped EventBus events: declared once, checked at every publish and listener
Id kindsid kinds: string ids one system defines, checked wherever they are written
Owned fieldswhich system may change a field, and through which function
Start orderwhich Starts must run before which, checked against the start script
Saved data lockSaved.lock: the shape each saved section promises to old saves, Version and Migrate checked
Invariantsrules that always hold across systems, checked by shot run after every frame
Matchmatch on a string union
Interfacesinterfaces and implements
Doc commentsdoc and rootdoc
The shot commandshot map, outline, show, find, check, and the workflow for a change
Scenariostesting a change: your own scenarios, played by shot run, never part of the game
Adoptshot's tools on an existing game whose scripts are mirrored to disk as plain Luau
Rulesevery error shot check gives
Luau tipstype-checker traps and their fixes
Compiledwhat the compiled Luau looks like

In a terminal, read one with "shot docs <name>" and search them all with "shot docs search <text>".