Skip to main content

Custom

This item only works when running on the client. Client

The escape hatch, for the parts of a tutorial the kit does not model.

It is a first-class action, not a workaround: you get the same ActionContext and the same cleanup as everything built in, which is what keeps custom logic from leaking between steps.

Functions

new

constructor
Custom.new(
run(contextActionContext) → ()--

Runs once, when the step opens

) → Action

Wraps your own function as an action.

Waiting for something to happen

The most common use: end the step on a game event rather than a click.

Custom.new(function(context)
	const connection = QuestService.Completed:Connect(function(questId)
		if questId == "FirstDelivery" then
			context:Advance()
		end
	end)

	context.Janitor:add(connection, "Disconnect")
end)

Registering the connection on context.Janitor is what matters here. Without it the listener survives the step and fires again later, on a tutorial that has moved on.

Reaching the guidance tools directly

Custom.new(function(context)
	context.Pointer:Outline(bossModel)
	task.delay(5, function()
		context.Spotlight:Clear()
	end)
end)

A timed step

Custom.new(function(context)
	const thread = task.delay(10, function()
		context:Advance()
	end)

	context.Janitor:add(thread, true)
end)
CAUTION

run is called synchronously while the step opens, and the other actions of that step have not all started yet. Do not yield in it; start a thread instead, and put that thread on the Janitor so leaving the step cancels it.

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Wraps your own function as an action.\n\n### Waiting for something to happen\n\nThe most common use: end the step on a game event rather than a click.\n\n```lua\nCustom.new(function(context)\n\tconst connection = QuestService.Completed:Connect(function(questId)\n\t\tif questId == \"FirstDelivery\" then\n\t\t\tcontext:Advance()\n\t\tend\n\tend)\n\n\tcontext.Janitor:add(connection, \"Disconnect\")\nend)\n```\n\nRegistering the connection on `context.Janitor` is what matters here. Without it the listener\nsurvives the step and fires again later, on a tutorial that has moved on.\n\n### Reaching the guidance tools directly\n\n```lua\nCustom.new(function(context)\n\tcontext.Pointer:Outline(bossModel)\n\ttask.delay(5, function()\n\t\tcontext.Spotlight:Clear()\n\tend)\nend)\n```\n\n### A timed step\n\n```lua\nCustom.new(function(context)\n\tconst thread = task.delay(10, function()\n\t\tcontext:Advance()\n\tend)\n\n\tcontext.Janitor:add(thread, true)\nend)\n```\n\n:::caution\n`run` is called synchronously while the step opens, and the other actions of that step have not\nall started yet. Do not yield in it; start a thread instead, and put that thread on the Janitor\nso leaving the step cancels it.\n:::",
            "params": [
                {
                    "name": "run",
                    "desc": "Runs once, when the step opens",
                    "lua_type": "(context: ActionContext) -> ()"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Action"
                }
            ],
            "function_type": "static",
            "tags": [
                "constructor"
            ],
            "source": {
                "line": 74,
                "path": "src/Actions/Custom.luau"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "Custom",
    "desc": "The escape hatch, for the parts of a tutorial the kit does not model.\n\nIt is a first-class action, not a workaround: you get the same [ActionContext] and the same\ncleanup as everything built in, which is what keeps custom logic from leaking between steps.",
    "realm": [
        "Client"
    ],
    "source": {
        "line": 11,
        "path": "src/Actions/Custom.luau"
    }
}