Auto Char
The core.autochar module drives scripted character creation. It wraps the same Auto Char machinery that lives under the Autos → Auto Login panel, writing through the settings the web UI exposes — so the class, Burning flag, and enabled state stay in sync between your script and the menu.
There are two ways to use it:
- Ambient mode (
enable): create a character whenever the configured Auto Login slot is empty. Names come from the built-in generator and the whole thing runs unattended. This is the checkbox in the menu. - One-shot mode (
create): a single, script-initiated creation with a terminal outcome you can poll. This is independent of the checkbox and is what you want when a script decides to make a character on demand.
Character creation is driven from the character-select screen forward: pick an empty slot → choose class → submit a name → click through the intro story. create() takes over that flow from character select (world select stays Auto Login's job). Run it at character select, or with Auto Login enabled for a fully unattended path.
The create() lifecycle
A one-shot create() is one atomic attempt with a terminal outcome. Poll get_status().request — it is the single field a retry loop needs:
request | Meaning |
|---|---|
"none" | No create() in play. |
"pending" | In flight — routing to the new-char flow, submitting the name, awaiting the server's verdict. Survives disconnects (a mid-flow crash retries). |
"finishing" | Name accepted (the character exists); the post-creation story clicker is running. |
"created" | Terminal success. Latched until the next create()/cancel(). |
"rejected" | Terminal: the script's name was refused. The flow has returned to character select and holds there (it will not log into an existing character) until you retry with a new name or cancel(). name.code carries the server's reason. |
On a "rejected" outcome, call create() (or set_name()) again with a different name — the class is remembered, so you can omit it. There is no in-place name polling to manage; each attempt is self-contained.
The return-to-character-select behavior applies only to a script-supplied name. If you omit name, the built-in generator picks names and retries them in place until one is accepted — it never bounces to character select, and request goes straight from "pending" to "created". So a name list plus a final generator fallback is the natural "always succeeds" pattern.
Functions
core.autochar.enable([opts: table]) -> boolean, string?
Turns on ambient Auto Char: whenever the configured Auto Login character slot is empty, a character is created automatically using generated names. All opts fields are optional and are applied before enabling.
opts fields (shared with create):
| Field | Type | Description |
|---|---|---|
class | string | number | Class to create — a class label (case-insensitive, e.g. "Kanna") or a 0-based index into get_classes(). |
burning | boolean | Whether to accept a Burning-character prompt if one appears during creation. Defaults to off (declines), since it can't be undone. |
name | string | A specific IGN to arm (1–12 alphanumeric characters). Omit to use generated names. |
Returns: true on success, or false, "<error>" if the class is unknown, the name is invalid, or the autos module isn't ready.
core.autochar.enable({ class = "Kanna", burning = false })core.autochar.disable() -> boolean
Turns off the ambient Auto Char checkbox. A one-shot create() request is separate state and keeps running — withdraw that with cancel(). Returns false only if the autos module isn't loaded.
core.autochar.create([opts: table]) -> boolean, string?
Starts a one-shot creation, independent of the ambient checkbox. Takes over the login flow from character select; with Auto Login enabled the whole path is unattended. See The create() lifecycle above for how the outcome is reported.
opts fields are the same as enable. Omitting name uses the generator. A create() request survives disconnects until the name is accepted, so a mid-flow crash retries instead of dropping the job.
Returns: true if the attempt was started, or false, "<error>" if a creation is already in flight (pending/finishing), the class/name is invalid, or the autos module isn't ready. Calling create() from a terminal state (created/rejected) or idle (none) is allowed and starts a fresh attempt.
-- Try a specific name; on rejection you'll get request == "rejected"
core.autochar.create({ class = "Kanna", name = "IceFarmer" })
-- Or let the engine generate a name (retries internally until accepted)
core.autochar.create({ class = "Kanna" })core.autochar.cancel() -> boolean
Withdraws a one-shot create() request and clears the script-name latch (including a terminal created/rejected). Does not touch the ambient checkbox. Returns false only if the autos module isn't loaded.
core.autochar.set_name(name: string | nil) -> boolean, string?
Arms (or clears, with nil) the script-supplied IGN. The verdict arrives through get_status().name. Two contexts:
- During a
create()request: if that request is holding at character select after a rejection, arming a new name here resumes it in place — the same effect as callingcreate()again with the new name. - With ambient
enable(): the name preempts the generator once; on rejection the generator resumes unless another name is armed within the flow's retry window.
The name must be 1–12 alphanumeric characters, or the call returns false, "<error>".
core.autochar.get_status() -> table | nil
Returns a snapshot of Auto Char state, or nil if the autos module isn't loaded.
| Field | Type | Description |
|---|---|---|
enabled | boolean | Whether the ambient Auto Char checkbox is on. |
class | string | The selected class label. |
class_index | number | 0-based index of the selected class. |
set_burning | boolean | Whether a Burning prompt will be accepted. |
request | string | One-shot create() lifecycle: "none", "pending", "finishing", "created", "rejected" (see above). |
phase | string | Where the flow is: "idle", "selecting_class", "naming", "story". |
last_submitted_name | string | The most recently submitted IGN (the accepted one after success). |
name | table | Script-name latch: { value, state, code }. |
The name latch:
| Field | Type | Description |
|---|---|---|
value | string | The armed name (empty when none). |
state | string | "none", "pending", "accepted", or "rejected". |
code | number | Server result byte — 0 when accepted, non-zero rejection code otherwise. |
core.autochar.get_classes() -> table
Returns the array of class label strings, in the order the class dropdown uses. The array index minus one is the 0-based class/class_index that enable/create accept, and labels are matched case-insensitively.
for i, name in ipairs(core.autochar.get_classes()) do
core.log(("%d = %s"):format(i - 1, name))
endExample: a "Character Creator" menu
A small tool: a Job dropdown, a comma-separated name list (or empty for auto-generated), and a Create Character button. Names are tried in order; when the list is exhausted it falls back to the generator so the button always ends in a created character. Uses the handle-based core.ui API for the menu.
plugin = {
name = "Character Creator",
description = "Pick a job, give a name list (or leave empty), press Create Character",
author = "you",
version = "1",
}
local classes = core.autochar.get_classes()
local TAB, PANEL = "Character Creator", "Create"
local job_dd = core.ui.dropdown{ id = "job", label = "Job", tab = TAB, panel = PANEL,
options = classes, default = 0 }
local names_in = core.ui.input_text{ id = "names", label = "Names (comma-separated)",
tab = TAB, panel = PANEL, default = "",
tooltip = "Tried in order; empty = auto-generated" }
local create_btn = core.ui.button{ id = "create", label = "Create Character",
tab = TAB, panel = PANEL }
local job = { active = false, class = "", names = {}, idx = 1 }
local function trim(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end
local function parse_names(s)
local out = {}
for token in string.gmatch(s or "", "[^,]+") do
local name = trim(token)
if #name > 0 then out[#out + 1] = name end
end
return out
end
-- Try names[idx], names[idx+1], ... then fall back to the generator.
local function submit()
while job.names[job.idx] do
local ok = core.autochar.create{ class = job.class, name = job.names[job.idx] }
if ok then return end
job.idx = job.idx + 1 -- invalid name; skip it
end
core.autochar.create{ class = job.class } -- generator
end
local function poll()
if create_btn:pressed() and not job.active then
job.class = job_dd:get_selected()
job.names = parse_names(names_in:get())
job.idx = 1
job.active = true
submit()
end
if not job.active then return end
local st = core.autochar.get_status()
if not st then return end
if st.request == "rejected" then
core.log(("'%s' taken (code %d)"):format(st.name.value, st.name.code or 0))
job.idx = job.idx + 1
submit()
elseif st.request == "created" then
core.log("created: " .. tostring(st.last_submitted_name))
core.autochar.cancel()
job.active = false
end
end
function on_login_tick() poll() end
function on_tick() poll() end