Step 6 of 14, updated

Modules and funcs

A module is one shared table for the whole game, typed from its declaration. A func is a file that returns one function.

You'll learn a module with state, module inside it, a func, and when to pick which.

Class, module or func

class

Many objects, each with its own state.

one player's wallet
module

One of it in the whole game.

a registry, a service, a catalog
func

Just one function in a file.

Whole

A module with state

Bank keeps every player's wallet, on the server. Its properties are declared with their types and values, its functions beside them:

struck ShotScript writes it for youhighlighted new in ShotScript
Luau todayBank (a ModuleScript)
--!strictlocal Players = game:GetService("Players")local Wallet = require(script.Parent.Wallet)local Whole = require(script.Parent.Whole)local Bank = {}Bank.Wallets = {} :: {[Player]: Wallet.Wallet}Bank.Count = 0function Bank.Open(player: Player)	Bank.Wallets[player] = Wallet.new(0)	Bank.Count += 1endfunction Bank.Give(player: Player,	amount: number)	local wallet = Bank.Wallets[player]	if not wallet then		return	end	wallet:Add(Whole(amount), "Pickup")endfunction Bank.Start()	Players.PlayerAdded:Connect(Bank.Open)endreturn Bank
ShotScriptWallet/Bank.shot
types script.Parent.Typeslocal Players = game:GetService("Players")use Walletuse Wholeserver module Bank = {	doc { Every player's wallet }	Wallets : {[Player] : Wallet} = {}	Count : number = 0	doc { Opens a wallet for the player }	function Open(player : Player)		module.Wallets[player] = Wallet.new(0)		module.Count += 1	end	function Give(player : Player,		amount : number)		local wallet = module.Wallets[player]		if not wallet then			return		end		wallet:Add(Whole(amount), "Pickup")	end	function Start()		Players.PlayerAdded:Connect(			module.Open)	end}
What it compiles toWallet/Bank.luau
--!strict
local Types = require(script.Parent.Types)
export type Reason = Types.Reason
export type Entry = Types.Entry
export type Wallet = Types.Wallet
type WalletClass = Types.WalletClass
export type Bank = Types.Bank
export type Coins = Types.Coins
export type Whole = Types.Whole

local Players = game:GetService("Players")

local Wallet = require(script.Parent.Wallet)
local Whole = require(script.Parent.Whole)

--[[$server]]
local Bank : Bank = {} :: any
local module = Bank
--[[$doc
	Every player's wallet
]]
Bank.Wallets = {}
Bank.Count = 0

--[[$doc
	Opens a wallet for the player
]]
function Bank.Open(player : Player)
	module.Wallets[player] = Wallet.new(0)
	module.Count += 1
end

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

function Bank.Start()
	Players.PlayerAdded:Connect(
		module.Open)
end

return Bank
  • A property is Name : Type = value: every one needs a type and a value. Others read it as Bank.Count.
  • Functions are called with a dot, Bank.Give(player, 25). There is no self.
  • Inside, the module is module: module.Count, module.Open(player). A bare Open(...) is an error.
  • Everything in a module is public. What it keeps to itself goes above the declaration, as plain Luau.
  • server in front makes it server only (page 8).
  • use Wallet finds Wallet.shot beside it, and {[Player] : Wallet} names the class's type with no import.

A func

A file that is one function is a func. Its type goes into the folder's Types under its name, so Whole is a type too:

Luau todayWhole (a ModuleScript)
--!strict-- Rounds down, and never below 0local function whole(amount: number): number	return math.max(0, math.floor(amount))endreturn whole
ShotScript65 linesWallet/Whole.shot
types script.Parent.Typesdoc { Rounds down, and never below 0 }func Whole(amount : number) : number	return math.max(0, math.floor(amount))end
What it compiles toWallet/Whole.luau
--!strict
local Types = require(script.Parent.Types)
export type Reason = Types.Reason
export type Entry = Types.Entry
export type Wallet = Types.Wallet
type WalletClass = Types.WalletClass
export type Bank = Types.Bank
export type Coins = Types.Coins
export type Whole = Types.Whole

--[[$rootdoc
	Rounds down, and never below 0
]]
local function Whole(amount : number) : number
	return math.max(0, math.floor(amount))
end

return Whole

Tip A func has no members, so the doc { } above it documents the whole file. State it keeps between calls goes in locals above it.

Run it

Init/Server.shot now only starts the bank, and a scenario can reach any module. This one replaces Hello:

ShotScriptInit/Server.shot
@server enabled

use @shot.Wallet.Bank

Bank.Start()
LuauScenarios/Pickup.luau
-- Pickup: a player who joins gets a wallet, and Bank.Give adds coins to it.
return function(world)
	local Bank = world.Server.Require("Wallet.Bank")
	world:Boot()
	local ana = world:AddPlayer("Ana")
	world:Step(0.1)
	Bank.Give(ana, 25.7)
	local wallet = Bank.Wallets[ana]
	world.Logf("Ana has %s", wallet:Describe())
	assert(wallet.Balance == 25, "25.7 rounds down to 25")
end
$ shot run Pickup
==== Pickup
[client:Ana print] client started
Ana has 25 coins

==== summary
  Pickup         OK  (2 lines, 0 errors, 0.1s virtual)
1 of 1 scenario(s) passed

Inside the module, the other functions are reached through module. With a bare Open in Start:

$ shot check
./Wallet/Bank.shot:28   TypeError: Unknown global 'Open'; consider assigning to it first
1 errors in 1 file (7 files checked, 2.2 s)
Try it

Add a function Balance(player : Player) : number to Bank that returns the player's balance, or 0 with no wallet, and log it from the scenario with world.Logf("%s", Bank.Balance(ana)).