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
40 changes: 0 additions & 40 deletions NOTES.md

This file was deleted.

116 changes: 109 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# GUI API — Fabric 26.2

A Fabric mod that lets datapacks define and open chest GUIs via JSON files.
No client mod required. No macros. No external dependencies beyond Fabric API.
No client mod required. No external dependencies beyond Fabric API.

---

Expand Down Expand Up @@ -53,11 +53,18 @@ The GUI ID used in commands is `<namespace>:<name>` — matching the file path u
|-------|------|---------|-------------|
| `title` | string | `"GUI"` | Inventory title. Supports `§` color codes and placeholders. |
| `rows` | int 1–6 | `3` | Number of rows (9 slots each). |
| `container_type` | string | `"barrel"` | `barrel` · `chest` · `player` · `ender_chest` · `chest_minecart`. `ender_chest`/`chest_minecart` force 3 rows, `player` forces 4. |
| `tick_rate` | int | `0` | Auto-refresh interval in ticks (e.g., `20` = 1s). Set `0` to disable. |
| `close_on_move` | boolean | `false` | If true, closes screen if player walks away (> 1.5 blocks). |
| `filler` | object | — | Background filler configuration (see below). |
| `on_open` | action[] | `[]` | Actions executed when the GUI is opened. |
| `on_close` | action[] | `[]` | Actions executed when the GUI is closed (any reason). |
| `open_condition` | condition | — | Player must meet this condition to open the GUI (see [Open gate](#open-gate)). |
| `on_deny` | action[] | `[]` | Actions executed instead of opening when `open_condition` is false. Empty = short action-bar notice. |
| `open_cost` | string | — | Entrance fee as `"itemId:amount"` (e.g. `"minecraft:gold_ingot:5"`). Charged once per open from outside the GUI; page navigation is free. If unaffordable, `on_deny` runs. |
| `macros` | object | `{}` | Named, reusable action lists — run them with `run_function` / `run_random_function`. |
| `progress_bars` | object[] | `[]` | Progress-bar widgets (see [Widgets](#widgets)). |
| `displays` | object[] | `[]` | Read-only info items (see [Widgets](#widgets)). |
| `buttons` | button[] | `[]` | List of button definitions. |

#### Filler fields
Expand Down Expand Up @@ -90,7 +97,10 @@ Any empty slot in the inventory is automatically populated with this background
| `item_model` | string | — | Custom item model component ID (1.21.2+). |
| `click_type` | string | `"any"` | Which click triggers actions: `any` · `left` · `right` · `shift` |
| `condition` | object | — | Visibility condition (see below). |
| `else_item` | object | — | Alternate appearance shown when `condition` is false (button stays visible but inert). Takes the same visual fields as a button. |
| `cooldown` | int | `0` | Per-player click cooldown in ticks. Survives closing/reopening the GUI. Also applies to toggle buttons. |
| `actions` | action[] | `[close]` | Actions executed in order on click. Supports `"delay": int` (ticks). |
| `action` | action | — | Shorthand for a single action; used only when `actions` is absent. |
| `toggle` | object | — | Toggle definition — replaces `item`/`actions` (see below). |

---
Expand All @@ -108,13 +118,27 @@ Supported in `title`, button `name`, `lore`, `message` values, and `run_command`
| `{pages}` | Total page count |
| `{score:objective}` | Player's score in the given scoreboard objective |
| `{var:key}` | Player's runtime variable `key` (empty string if unset) |
| `{xp}` | Player's experience level |
| `{input}` | Last text entered through an `anvil_input` action |
| `{health}` / `{max_health}` | Current / maximum health in half-hearts, rounded up |
| `{food}` | Hunger level (0–20) |
| `{online}` | Number of players currently online |
| `{pos_x}` `{pos_y}` `{pos_z}` | Player's block coordinates |

Text inserted by `{var:key}` and `{input}` is treated as plain text — it is never scanned for further placeholders.

---

## Action types

Any action can be delayed by adding `"delay": int` (in ticks) to its JSON block.

Any action can also carry a `"condition"` (same format as button conditions, including `all` / `any` / `not`). It is checked right when the action is about to run — after its delay — and a false condition skips just that action while the rest of the chain continues:

```json
{ "type": "message", "value": "§6VIP bonus applied!", "condition": { "type": "has_tag", "value": "vip" } }
```

| Type | `value` format | `run_with` | Description |
|------|--------------|------------|-------------|
| `run_command` | Command string | `player` · `console` | Run a command. Default: player. Supports placeholders. |
Expand All @@ -128,6 +152,13 @@ Any action can be delayed by adding `"delay": int` (in ticks) to its JSON block.
| `add_score` | `objective:value` | — | Add score to player's scoreboard objective directly. |
| `sub_score` | `objective:value` | — | Subtract score from player's scoreboard objective directly. |
| `take_item` | `itemId:amount` | — | Deduct a specified amount of an item from the player's inventory. |
| `give_item` | `itemId:amount` | — | Give item(s); overflow that doesn't fit is dropped at the player's feet. |
| `add_xp` | `n` or `Ln` | — | Add `n` XP points, or `n` levels with the `L` prefix (e.g. `L2`). |
| `run_function` | macro name | — | Run a named action list from `macros`. |
| `run_random_function` | `name[*weight],…` | — | Run one macro chosen at random, e.g. `common*70,rare*25,legendary*5`. Weight defaults to 1. |
| `set_gamemode` | `survival` · `creative` · `adventure` · `spectator` | — | Change the player's game mode. Can be disabled in config (`allow_gamemode_change`). |
| `anvil_input` | `Title\|Default` | — | Open an anvil text prompt; the result is stored in `"var"` (default `input`) and `{input}`. |
| `none` | — | — | Stop the action chain here without doing anything. |
| `add_effect` | `effect_id:duration:amplifier:particles` | — | Give player status effect (duration in seconds, particles true/false). |
| `remove_effect` | `effect_id` | — | Remove a specific status effect from the player. |
| `clear_effects` | — | — | Clear all status effects from the player. |
Expand All @@ -136,6 +167,9 @@ Any action can be delayed by adding `"delay": int` (in ticks) to its JSON block.
| `sub_var` | Integer to subtract | — | Subtract an integer from a runtime variable. Requires `"var": "key"`. |
| `reset_var` | — | — | Delete a single runtime variable. Requires `"var": "key"`. |
| `clear_vars` | — | — | Delete all runtime variables for this player. |
| `add_tag` | Tag name | — | Add a scoreboard tag to the player (no `run_with: console` needed). Supports placeholders. |
| `remove_tag` | Tag name | — | Remove a scoreboard tag from the player. |
| `broadcast` | Text string | — | Send a chat message to every online player. Supports placeholders. |
| `next_page` | — | — | Go to the next page. |
| `prev_page` | — | — | Go to the previous page. |
| `goto_page` | Page index (string) | — | Jump to a specific page. |
Expand Down Expand Up @@ -165,6 +199,64 @@ Conditions control button **visibility**. Hidden buttons cannot be clicked.
| `health_lt` | `value` | Player's current health < value |
| `food_gt` | `value` | Player's hunger level > value |
| `food_lt` | `value` | Player's hunger level < value |
| `permission` | `0`–`4` | Player's command permission level is at least that value |
| `gamemode` | `survival` · `creative` · `adventure` · `spectator` | Player is in that game mode |
| `in_dimension` | dimension id (`minecraft:the_nether`, or bare `the_nether`) | Player is in that dimension |
| `all` | `"conditions": [ … ]` | **Every** listed condition is true (empty list = true) |
| `any` | `"conditions": [ … ]` | **At least one** listed condition is true (empty list = false) |
| `not` | `"condition": { … }` | The nested condition is **not** true (no nested condition = false) |

Composite conditions can be nested (up to 8 levels) and work everywhere a condition does — buttons, displays, actions and `open_condition`:

```json
"condition": {
"type": "all",
"conditions": [
{ "type": "has_tag", "value": "vip" },
{ "type": "not", "condition": { "type": "has_tag", "value": "banned" } },
{ "type": "any", "conditions": [
{ "type": "level_gt", "value": "10" },
{ "type": "score_gt", "value": "coins:100" }
] }
]
}
```

### Open gate

`open_condition` restricts who can open a GUI — through `/guiapi open`, an `open_gui` action, page navigation or an item with the `guiapi:open_gui` component. When it is false the GUI does not open and `on_deny` runs instead:

```json
{
"title": "VIP Lounge",
"open_condition": { "type": "has_tag", "value": "vip" },
"on_deny": [
{ "type": "message", "value": "§cVIP only!" },
{ "type": "sound", "value": "minecraft:entity.villager.no" }
],
"buttons": [ ... ]
}
```

---

## Widgets

Non-button elements, defined as top-level arrays. They are read-only: clicks on their slots are ignored.

**`progress_bars`** — a horizontal run of slots that fills according to a runtime value, recalculated on every open/refresh (use `tick_rate` for live updates).

| Field | Default | Description |
|-------|---------|-------------|
| `start_slot` | `0` | First slot of the bar. |
| `length` | `9` | Number of slots. |
| `page` | `0` | Page the bar appears on. |
| `value_source` | `"var:progress"` | `"score:<objective>"` or `"var:<key>"`. |
| `max_value` | `100` | Value at which the bar is full. |
| `filled_item` / `empty_item` | lime / gray glass pane | Items for filled and empty slots. |
| `name` / `lore` | — | Optional text; supports placeholders. |

**`displays`** — one read-only info item. Fields: `slot`, `page`, `item`, `name`, `lore`, `glint`, `amount`, `condition`.

---

Expand All @@ -186,6 +278,21 @@ Toggle actions also fully support the multi-action engine (separated by `;` in t

---

## Configuration

`config/guiapi.json` (editable through Mod Menu). Notable options:

| Key | Default | Description |
|-----|---------|-------------|
| `chat_prefix_enabled` | `false` | Prepend `chat_prefix` to `message` (in `CHAT` mode), `broadcast` and error chat messages. Action-bar text is never prefixed. |
| `chat_prefix` | `§8[§6GuiAPI§8] §f` | The prefix text. |
| `allow_console_run_with` | `true` | Allow `run_with: console`. |
| `allow_gamemode_change` | `true` | Allow `set_gamemode`. |
| `allow_status_effects` | `true` | Allow effect actions. |
| `permission_level` | `2` | Permission level for `/guiapi`. |

---

## Client-Side features (Optional)

Installing this mod on the client-side unlocks powerful, highly-polished user experience features:
Expand All @@ -198,14 +305,9 @@ Installing this mod on the client-side unlocks powerful, highly-polished user ex
* Click **Apply & Back** to open the **Gui Save Loading Screen** which finds the target datapack folder on the server, safely writes the JSON to disk, and reloads the API definitions. No edits are ever lost on rejoin!

### 2. Native Keybindings
Integrates natively with Minecraft's official controls menu (**Options > Controls > Key Binds > GUI API**):
Integrates with Minecraft's official controls menu (**Options > Controls > Key Binds > GUI API**):
* **Accept Rules (Open GUI):** Opens the default welcome GUI (Defaults to **`G`**).
* **Toggle Search in GUI:** Activates the interactive slot search (Defaults to **`L`**).

### 3. Interactive Slot Search (`L`)
* Press **`L`** inside any GUI (or any chest/barrel container enventories!) to toggle the Search bar.
* Type alphanumeric characters to search. Matching items are highlighted with a gorgeous HSB glowing neon color-cycling gradient, while non-matching slots are dimmed.
* Minecraft closing/dropping hotkeys (like `E` and `Q`) are safely blocked while search is focused to ensure a pristine typing experience. Press `ESC` or `L` again to close.

---

Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ dependencies {

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
// Gradle 9+ no longer bundles the launcher; it must be on the test runtime classpath.
// Version must match the Jupiter line above (Jupiter 5.10.x <-> Platform 1.10.x).
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2'
}

test {
Expand Down
131 changes: 131 additions & 0 deletions example-datapack/data/example/gui/gates_and_logic_demo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{
"title": "§6Gates & Logic Demo",
"rows": 3,
"tick_rate": 20,

"open_condition": {
"type": "not",
"condition": { "type": "has_tag", "value": "guiapi_demo_locked" }
},
"on_deny": [
{ "type": "message", "value": "§cThis demo is locked for you. Unlock with: /tag @s remove guiapi_demo_locked" },
{ "type": "sound", "value": "minecraft:entity.villager.no" }
],

"filler": {
"item": "minecraft:black_stained_glass_pane",
"name": " ",
"hide_tooltip": true
},

"buttons": [
{
"slot": 10,
"item": "minecraft:emerald",
"name": "§a1. any / all conditions",
"lore": [
"§7Unlocked for VIPs (tag §fvip§7) or anyone",
"§7with an XP level above 9 — and never for",
"§7players tagged §fbanned§7.",
"§8Try: /tag @s add vip"
],
"condition": {
"type": "all",
"conditions": [
{ "type": "not", "condition": { "type": "has_tag", "value": "banned" } },
{ "type": "any", "conditions": [
{ "type": "has_tag", "value": "vip" },
{ "type": "level_gt", "value": "9" }
] }
]
},
"actions": [
{ "type": "sound", "value": "minecraft:entity.player.levelup" },
{ "type": "message", "value": "§aAccess granted, {player}!" }
],
"else_item": {
"item": "minecraft:barrier",
"name": "§c1. any / all conditions §7(Locked)",
"lore": [ "§7Needs the §fvip §7tag or XP level 10+." ]
}
},

{
"slot": 12,
"item": "minecraft:name_tag",
"name": "§e2. Conditional actions + tags",
"lore": [
"§7One click runs a chain where each step",
"§7can have its own condition:",
"§7 • §fadd_tag §7/ §fremove_tag §7need no console",
"§7 • §fbroadcast §7talks to the whole server",
"§7 • the VIP line only shows for §fvip §7players"
],
"actions": [
{ "type": "add_tag", "value": "guiapi_demo_seen" },
{ "type": "message", "value": "§7You clicked the demo button." },
{
"type": "message",
"value": "§6VIP bonus: thanks for supporting the server!",
"condition": { "type": "has_tag", "value": "vip" }
},
{
"type": "broadcast",
"value": "§e{player} §7({health}/{max_health} HP) is playing with GUI API — {online} online.",
"condition": { "type": "has_tag", "value": "vip" }
},
{ "type": "refresh" }
]
},

{
"slot": 14,
"toggle": {
"tag": "guiapi_demo_seen",
"item_on": "minecraft:lime_dye",
"item_off": "minecraft:gray_dye",
"name_on": "§a3. Tag guiapi_demo_seen: ON",
"name_off": "§73. Tag guiapi_demo_seen: OFF",
"lore_on": [ "§7Click to switch it off." ],
"lore_off": [ "§7Click to switch it on." ]
}
},

{
"slot": 16,
"item": "minecraft:iron_door",
"name": "§c4. Lock me out (open gate)",
"lore": [
"§7Tags you with §fguiapi_demo_locked§7.",
"§7Reopening this GUI is then denied by",
"§7its §fopen_condition§7 and §fon_deny §7runs.",
"§8Undo: /tag @s remove guiapi_demo_locked"
],
"actions": [
{ "type": "add_tag", "value": "guiapi_demo_locked" },
{ "type": "close" }
]
},

{
"slot": 22,
"item": "minecraft:barrier",
"name": "§cClose",
"actions": [ { "type": "close" } ]
}
],

"displays": [
{
"slot": 4,
"item": "minecraft:player_head",
"name": "§b5. New placeholders",
"lore": [
"§7Health: §c{health}§7/§c{max_health}",
"§7Food: §6{food}",
"§7Block: §f{pos_x} {pos_y} {pos_z}",
"§7Players online: §a{online}"
]
}
]
}
22 changes: 22 additions & 0 deletions example-datapack/data/example/gui/paid_room.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"title": "§6Paid Room §7(5 gold)",
"rows": 3,

"open_cost": "minecraft:gold_ingot:5",
"on_deny": [
{ "type": "message", "value": "§cThe door costs 5 gold ingots." },
{ "type": "sound", "value": "minecraft:entity.villager.no" }
],

"filler": { "item": "minecraft:black_stained_glass_pane", "name": " ", "hide_tooltip": true },

"buttons": [
{
"slot": 13,
"item": "minecraft:emerald",
"name": "§aYou paid, {player}!",
"lore": [ "§7Paging inside this GUI never charges again.", "§7Closing and re-opening it does." ],
"actions": [ { "type": "close" } ]
}
]
}
Loading
Loading