Skip to main content

Tutorial

Tutorial APIs live under client.automation — a namespace that holds long-running drivers (auto-tutorial) plus the paired read-only state queries.

Why a namespace? client itself stays focused on plain account/world ops (warp, leave, send packets). Drivers + their state live on client.automation so IDE autocomplete on c: doesn't drown in tutorial methods.

TutorialState enum

PlayerData.TutorialState is the server-side enum (sourced from the il2cpp dump and pushed via GPd). Exposed as a global TutorialState table so scripts don't have to hardcode the integer values.

ConstantValueMeaning
TutorialState.NOT_STARTED0Brand new account, no character yet
TutorialState.CHARACTER_CREATION1Picked outfit, hasn't spawned
TutorialState.TUTORIAL_WORLD2Inside TUTORIAL2, mid-flow
TutorialState.COMPLETED3Cleared the chain — cutoff for "done"
TutorialState.PHASE_14Post-intro FTUE checkpoint 1
TutorialState.PHASE_25Post-intro FTUE checkpoint 2
TutorialState.PHASE_36Post-intro FTUE checkpoint 3
TutorialState.PHASE_47Post-intro FTUE checkpoint 4

The < 3 cutoff is what isInTutorial() checks. >= 3 is what isTutorialDone() checks.


State queries

client.automation:tutorialState()

FieldType
Signature(self: Automation) → number?
Returnsnumber? — raw enum int (0..7), or nil before the first GPd profile blob lands
Errorsnone — pure snapshot read
local state = c.automation:tutorialState()
if state == TutorialState.COMPLETED then
print("done")
elseif state == nil then
print("not authenticated yet")
end

client.automation:tutorialStateLabel()

FieldType
Signature(self: Automation) → string?
Returnsstring? — canonical CamelCase name, or nil before first GPd
Possible values"NotStarted", "CharacterCreation", "TutorialWorld", "Completed", "Phase1", "Phase2", "Phase3", "Phase4"
print(c.automation:tutorialStateLabel()) -- e.g. "Phase4"

client.automation:isInTutorial()

FieldType
Signature(self: Automation) → boolean
Returnsbooleantrue when state is 0..2 (mid-tutorial); false when >= 3 OR state is nil
if c.automation:isInTutorial() then
-- account hasn't finished the intro
end

client.automation:isTutorialDone()

FieldType
Signature(self: Automation) → boolean
Returnsbooleantrue when state is >= 3; false when < 3 OR state is nil

Pairs with isInTutorial() — both return false when state is unknown so post-tutorial gates don't trip on nil.

while not c.automation:isTutorialDone() do
task.wait(0.5)
end
c:warp("ASDSA")

Auto-driver

client.automation:tutorial()

Drives the entire intro flow end-to-end (character creation → tutorial world → BasicClothes pack → leave → PIXELSTATION → backpack upgrade).

FieldType
Signature(self: Automation) → string?
Returnsstring?nil on success, or an Errors.* code on failure
Common errorsErrors.TUTORIAL_BUSY (another driver is already running), Errors.NOT_CONNECTED, Errors.DISCONNECTED, Errors.TIMEOUT
Idempotentyes — finished accounts return immediately
Asyncyes — yields until the chain finishes or fails
local err = c.automation:tutorial()
if err == Errors.TUTORIAL_BUSY then
print("already running")
elseif err then
print("tutorial failed:", err)
else
print("done")
end

client.automation:tutorialRunning()

FieldType
Signature(self: Automation) → boolean
Returnsbooleantrue while the auto-driver is active for this bot
if c.automation:tutorialRunning() then
return -- driver owns the bot right now
end

client.automation:tutorialStage()

FieldType
Signature(self: Automation) → string?
Returnsstring? — human-readable label of the current driver step, or nil when no driver is running
Possible values"walking to NPC", "buying clothes pack", "equipping cosmetics", "leaving tutorial world", "post-tutorial spawn world", "buying inventory slot upgrade", "complete", … (non-exhaustive — labels are advisory, don't == compare in production)
c:on("status", function()
if c.automation:tutorialRunning() then
print("step:", c.automation:tutorialStage())
end
end)

End-to-end recipe

local c = getClient()
local a = c.automation

-- 1. Wait for the first GPd profile blob.
while a:tutorialState() == nil do task.wait(0.2) end

-- 2. Auto-run if mid-tutorial.
if a:isInTutorial() then
local err = a:tutorial()
if err then
print("auto-tutorial failed:", err)
return
end
end

-- 3. Now safe to do post-tutorial things.
print("state:", a:tutorialStateLabel()) -- "Completed" or "Phase1+"
c:warp("WORLD_OF_CHOICE")

See also