Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ jobs:
pxluanova/pxluanova-test/build/reports/tests/
pxluanova/pxluanova-test/build/test-results/

tutorial:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Install Lua 5.2 and lua-language-server
run: |
sudo apt-get update && sudo apt-get install -y lua5.2
mkdir -p "$RUNNER_TEMP/luals"
curl -sSL https://github.com/LuaLS/lua-language-server/releases/download/3.13.9/lua-language-server-3.13.9-linux-x64.tar.gz \
| tar xz -C "$RUNNER_TEMP/luals"

- name: Check the arena tutorial
run: LUALS="$RUNNER_TEMP/luals/bin/lua-language-server" site/tutorial/test.sh

build:
runs-on: ubuntu-latest
strategy:
Expand Down
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ JUnit 5 via `kotlin-test-junit5`. Pure logic, no MC runtime. Two tests have quir
Loaded libs, `package.path`, globals, lambda syntax, scheduler tick, built-in `require` libs (`format`, `simple`,
`chestgui`).

## Tutorial

`site/src/content/docs/tutorial/` is a step-by-step arena minigame; its code is in `site/tutorial/arena/`
(one checkpoint file per chapter) and is checked by `site/tutorial/test.sh` (snippets match checkpoints,
LuaLS against `lua-types/`, a fake-API game run). API changes that touch it must keep that script green.

## Design docs → see `docs/`

Changelog (`site/src/content/docs/changelog.md`) says WHAT changed; `docs/` (e.g. `async-suspend-bridge.md`) documents
Expand Down
5 changes: 1 addition & 4 deletions lua-types/01-vec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ local Vec = {}
---@operator div(number): Vec
---Negation.
---@operator unm: Vec
---Component-wise equality.
---@operator eq(Vec|Vec3Like): boolean
---Renders as `"(x, y, z)"`.
---@operator tostring: string
-- `==` compares components; `tostring(v)` renders as `"(x, y, z)"`.

---Returns sqrt(x² + y² + z²).
---@return number
Expand Down
3 changes: 2 additions & 1 deletion lua-types/11-region.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ Region.entities = nil
---@param callback fun(e: RegionEvent)
---@param opts? EventOptions
---@return integer
---@overload fun(self: Region, event: '"enter"'|'"leave"', callback: fun(e: RegionEntityEvent), opts?: EventOptions): integer
---@overload fun(self: Region, event: '"enter"', callback: fun(e: RegionEntityEvent), opts?: EventOptions): integer
---@overload fun(self: Region, event: '"leave"', callback: fun(e: RegionEntityEvent), opts?: EventOptions): integer
---@overload fun(self: Region, event: '"move"', callback: fun(e: RegionMoveEvent), opts?: EventOptions): integer
---@overload fun(self: Region, event: '"death"', callback: fun(e: RegionDeathEvent), opts?: EventOptions): integer
function Region:on(event, callback, opts) end
Expand Down
25 changes: 18 additions & 7 deletions lua-types/14-mc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
---@class mcApi
mc = {}

---@class ItemOpts
---@field count? integer
---@field name? string
---@field lore? string[]
---@field unbreakable? boolean
---@field custom_model_data? integer

---@class ItemSpec : ItemOpts
---@field id string

-- === Functions =============================================================

---@param text string
Expand Down Expand Up @@ -116,7 +126,7 @@ function mc.mapped(className) end

---Registers a custom mob behaviour handler. See /reference/mob-ai.
---@param id string
---@param fn fun(entity. Mob, ctx. table)
---@param fn fun(entity: Mob, ctx: table)
function mc.registerBehaviour(id, fn) end

---@param title string
Expand All @@ -131,13 +141,14 @@ function mc.createBossBar(title, color, style) end
---@return boolean, integer|string
function mc.execute(command, opts) end

---Creates an ItemStack. Two overloads.
--- createItem("minecraft.stone", 64)
--- createItem({ id = "minecraft.diamond_sword", count = 1, name = "Excalibur", lore = {...}, unbreakable = true, custom_model_data = 1 })
---@param idOrSpec string|table
---@param count? integer
---Creates an ItemStack. Three forms:
--- createItem("minecraft:stone", 64)
--- createItem("minecraft:diamond_sword", { name = "Excalibur", lore = {...}, unbreakable = true })
--- createItem({ id = "minecraft:diamond_sword", count = 1, name = "Excalibur", custom_model_data = 1 })
---@param idOrSpec string|ItemSpec
---@param countOrOpts? integer|ItemOpts
---@return Item
function mc.createItem(idOrSpec, count) end
function mc.createItem(idOrSpec, countOrOpts) end

-- === Event methods =========================================================

Expand Down
55 changes: 37 additions & 18 deletions lua-types/17-modules.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ function Format.format(template, args) end
function Format.broadcastFormat(template, args) end

---@class SimpleLib
local Simple = {}

---@field defaultOverlay integer -- ticks; default 140 (7s)
local Simple = {}

---Concise command registration. The template is rendered with `args.ctx` set
---to the command context (so `{ctx.player.name}` works), and `args.x` etc.
Expand All @@ -31,44 +30,64 @@ local Simple = {}
---@param overlay? boolean|integer -- true = default overlay; integer = ticks
function Simple.register(syntax, template, range, overlay) end

---`require "core:chestgui"`.
---@class ChestGui
local ChestGui = {}

---Creates a new chest GUI. Returns an object with a grid API.
---Click callback. Return `false` to cancel the click (items in an open GUI are locked
---either way, but a cancelled click is also not shown to the player as a pickup).
---@alias ChestGuiCallback fun(player: Player, slot: integer, clickType: string, slotItem: Item|nil, cursorItem: Item|nil): boolean|nil

---Creates a new chest GUI.
---@param rows integer -- 1..6
---@param title? string
---@return ChestGuiInstance
function ChestGui.create(rows, title) end

---@class ChestGuiInstance
---@field inventory Inventory
---@field title string
---@field rows integer
---@field cols integer -- always 9
---@field onClick? fun(player: Player, slot: integer, clickType: string, slotItem: Item|nil, cursorItem: Item|nil):boolean|nil
---@field slots integer -- rows * 9
local ChestGuiInstance = {}

---Opens the GUI for the given player.
---@param player Player
---@return Container
function ChestGuiInstance:open(player) end

---@param player Player
function ChestGuiInstance:close(player) end

---Sets the item in a slot.
---Puts an item at a grid position (1-based) with a click callback.
---@param row integer
---@param col integer
---@param item? Item
function ChestGuiInstance:setItem(row, col, item) end
---@param item Item
---@param callback? ChestGuiCallback
function ChestGuiInstance:set(row, col, item, callback) end

---Puts an item at a raw slot (1-based) with a click callback.
---@param slot integer
---@param item Item
---@param callback? ChestGuiCallback
function ChestGuiInstance:button(slot, item, callback) end

---Puts an item without a callback; clicks on it are cancelled.
---@param row integer
---@param col integer
---@return Item|nil
function ChestGuiInstance:getItem(row, col) end
---@param item Item
function ChestGuiInstance:decorate(row, col, item) end

---Puts `item` into every slot (callbacks stay; call it before `set`/`button`).
---@param item? Item
function ChestGuiInstance:fill(item) end

function ChestGuiInstance:clear() end

---Opens the GUI for the player.
---@param player Player
---@return Container|nil
function ChestGuiInstance:open(player) end

---@param player Player
function ChestGuiInstance:close(player) end

---Changes the title used by the next `open`.
---@param title string
function ChestGuiInstance:setTitle(title) end

---`require "async"`. Only `sleep` and `fetch` are typed here; see
---https://ignis.pyxiion.ru/reference/async-api for tasks, promises and mutexes.
---@class AsyncLib
Expand Down
22 changes: 16 additions & 6 deletions lua-types/19-events.lua
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,14 @@ function Event:cancel(reason) end
---@param handler fun(...)
---@param opts? EventOptions
---@return integer
---@overload fun(event: '"init"'|'"uninit"'|'"server_start"'|'"server_stop"'|'"tick"', handler: fun(e: Event), opts?: EventOptions): integer
---@overload fun(event: '"player.login"'|'"player.join"'|'"player.leave"', handler: fun(e: PlayerEvent), opts?: EventOptions): integer
---@overload fun(event: '"init"', handler: fun(e: Event), opts?: EventOptions): integer
---@overload fun(event: '"uninit"', handler: fun(e: Event), opts?: EventOptions): integer
---@overload fun(event: '"server_start"', handler: fun(e: Event), opts?: EventOptions): integer
---@overload fun(event: '"server_stop"', handler: fun(e: Event), opts?: EventOptions): integer
---@overload fun(event: '"tick"', handler: fun(e: Event), opts?: EventOptions): integer
---@overload fun(event: '"player.login"', handler: fun(e: PlayerEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.join"', handler: fun(e: PlayerEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.leave"', handler: fun(e: PlayerEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.respawn"', handler: fun(e: PlayerRespawnEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.chat"', handler: fun(e: PlayerChatEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.move"', handler: fun(e: PlayerMoveEvent), opts?: EventOptions): integer
Expand All @@ -109,10 +115,14 @@ function Event:cancel(reason) end
---@overload fun(event: '"player.interact"', handler: fun(e: PlayerInteractEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.kill"', handler: fun(e: PlayerKillEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.consume"', handler: fun(e: PlayerItemEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.pickup"'|'"player.drop"', handler: fun(e: PlayerItemCountEvent), opts?: EventOptions): integer
---@overload fun(event: '"block.break"'|'"block.place"', handler: fun(e: BlockEvent), opts?: EventOptions): integer
---@overload fun(event: '"entity.spawn"'|'"entity.despawn"', handler: fun(e: EntityEvent), opts?: EventOptions): integer
---@overload fun(event: '"entity.hurt"'|'"entity.death"', handler: fun(e: EntityDamageEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.pickup"', handler: fun(e: PlayerItemCountEvent), opts?: EventOptions): integer
---@overload fun(event: '"player.drop"', handler: fun(e: PlayerItemCountEvent), opts?: EventOptions): integer
---@overload fun(event: '"block.break"', handler: fun(e: BlockEvent), opts?: EventOptions): integer
---@overload fun(event: '"block.place"', handler: fun(e: BlockEvent), opts?: EventOptions): integer
---@overload fun(event: '"entity.spawn"', handler: fun(e: EntityEvent), opts?: EventOptions): integer
---@overload fun(event: '"entity.despawn"', handler: fun(e: EntityEvent), opts?: EventOptions): integer
---@overload fun(event: '"entity.hurt"', handler: fun(e: EntityDamageEvent), opts?: EventOptions): integer
---@overload fun(event: '"entity.death"', handler: fun(e: EntityDamageEvent), opts?: EventOptions): integer
---@overload fun(event: '"entity.damaged"', handler: fun(e: EntityDamagedEvent), opts?: EventOptions): integer
---@overload fun(event: PxIgnisEventName|string, opts: EventOptions, handler: fun(e: Event)): integer -- Nova trailing block order
function mc.on(event, handler, opts) end
Expand Down
15 changes: 15 additions & 0 deletions site/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ export default defineConfig({
{ label: "2. Events and storage", slug: "guide/02-events-and-storage" },
],
},
{
label: "Tutorial: Arena",
items: [
{ label: "Overview", slug: "tutorial" },
{ label: "1. Commands and settings", slug: "tutorial/01-commands" },
{ label: "2. The arena region", slug: "tutorial/02-region" },
{ label: "3. Rounds", slug: "tutorial/03-rounds" },
{ label: "4. Fights", slug: "tutorial/04-fights" },
{ label: "5. Boss bar and scoreboard", slug: "tutorial/05-interface" },
{ label: "6. Statistics and leaderboard", slug: "tutorial/06-stats" },
{ label: "7. Kits and inventories", slug: "tutorial/07-kits" },
{ label: "8. Events for other scripts", slug: "tutorial/08-events" },
{ label: "9. Reloads and crashes", slug: "tutorial/09-reliability" },
],
},
{
label: "Core API",
items: [
Expand Down
4 changes: 2 additions & 2 deletions site/src/content/docs/examples/whitelist.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ and the cancellable `player.login` event.
Save this as `config/ignis/whitelist.lua`:

```lua
local whitelist = mc.data.whitelist or {}
mc.data.whitelist = whitelist
mc.data.whitelist = mc.data.whitelist or {}
local whitelist = mc.data.whitelist

mc.on("player.login", function(e)
-- Keep operators able to join while setting up the whitelist.
Expand Down
5 changes: 5 additions & 0 deletions site/src/content/docs/guide/02-events-and-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ mc.on("player.join") \{ e ->

Keep going with the topics that match what you want to build:

**Build a whole game**

- [Tutorial: an arena minigame](/tutorial) — commands, regions, rounds, kits, statistics and more, in one
script that grows chapter by chapter

**More about events and data**

- [Events reference](/reference/events) — complete event list and handler signatures
Expand Down
6 changes: 6 additions & 0 deletions site/src/content/docs/guide/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,9 @@ Errors in event handlers, commands and scheduled tasks, and warnings such as an
the server log and also sent to online players with the `px.ignis.prompt_errors` permission (operators level 4 by
default), in the form `event 'block.break': shop.lua:12: attempt to index a nil value`. The same error is repeated in
chat at most once every 10 seconds, so a handler failing every tick does not flood it.

## Next steps

- [1. Your first command](/guide/01-your-first-command) and [2. Events and storage](/guide/02-events-and-storage):
short recipes for the basics.
- [Tutorial: an arena minigame](/tutorial): build a complete minigame step by step.
2 changes: 2 additions & 0 deletions site/src/content/docs/reference/commands-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ register("warn <target:player> <reason:text>") \{ctx, target, reason ->
## Permissions

Pass a permission node as the third argument. Players won't even see the command.
Without a permissions mod, operators (op level 4) have every permission; `player:hasPermission(node)`
follows the same rule.
If omitted, all players can use the command.

```lua
Expand Down
19 changes: 19 additions & 0 deletions site/src/content/docs/reference/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ data.stats.kills = 5
data.stats.name = "player_stats"
```

## Assigning a Table Stores a Copy

Assigning a table to `mc.data` or `player.data` stores a copy of it. Changes to the original table after the
assignment are not saved:

```lua
-- Wrong: `homes` is the local table, the saved copy stays empty
local homes = {}
player.data.homes = homes
homes.base = { x = 0, y = 64, z = 0 }

-- Right: assign first, then work with the saved table
player.data.homes = player.data.homes or {}
local homes = player.data.homes
homes.base = { x = 0, y = 64, z = 0 }
```

The table you read back from storage is the saved one, so changes to it (at any depth) are saved.

## Data Validation

The following types are **not** allowed in storage and will raise an error:
Expand Down
Loading
Loading