Skip to content

Commit 6955b4e

Browse files
committed
Add AGENTS.md to guide AI coding agents
Covers project setup, plugin lifecycle, commands, events, config, scheduler, color formatting, common imports, and player methods.
1 parent 383c855 commit 6955b4e

File tree

1 file changed

+266
-0
lines changed

1 file changed

+266
-0
lines changed

AGENTS.md

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# Endstone Plugin Development Guide
2+
3+
This file helps AI coding agents understand how to build Endstone plugins in Python.
4+
5+
Endstone is a plugin framework for Minecraft Bedrock Dedicated Server (BDS). Plugins are
6+
Python packages installed into the server's `plugins/` folder as `.whl` files.
7+
8+
Docs: https://endstone.dev/latest/
9+
Example plugin: see `src/endstone_example/` in this repo.
10+
11+
## Project Setup
12+
13+
Package name convention: `endstone-<name>` (PyPI) / `endstone_<name>` (Python package).
14+
15+
Minimal `pyproject.toml`:
16+
17+
```toml
18+
[build-system]
19+
requires = ["hatchling"]
20+
build-backend = "hatchling.build"
21+
22+
[project]
23+
name = "endstone-my-plugin"
24+
version = "0.1.0"
25+
description = "My Endstone plugin"
26+
27+
[project.entry-points."endstone"]
28+
my-plugin = "endstone_my_plugin:MyPlugin"
29+
```
30+
31+
The entry point under `[project.entry-points."endstone"]` tells Endstone which class to load.
32+
33+
## Plugin Class
34+
35+
Every plugin extends `endstone.plugin.Plugin` and declares `api_version`:
36+
37+
```python
38+
from endstone.plugin import Plugin
39+
40+
class MyPlugin(Plugin):
41+
api_version = "0.11"
42+
43+
def on_enable(self) -> None:
44+
self.logger.info("Plugin enabled!")
45+
46+
def on_disable(self) -> None:
47+
self.logger.info("Plugin disabled!")
48+
```
49+
50+
Lifecycle methods (all optional):
51+
- `on_load()` -- called when the plugin is loaded (before enable, rarely needed)
52+
- `on_enable()` -- called when the plugin is enabled (register events, load config here)
53+
- `on_disable()` -- called when the plugin is disabled (cleanup here)
54+
55+
Key properties available on `self`:
56+
- `self.logger` -- plugin logger
57+
- `self.server` -- the Server instance
58+
- `self.config` -- plugin config (dict-like, loaded from config.toml)
59+
- `self.data_folder` -- path to plugin's data directory
60+
61+
## Commands
62+
63+
Define commands and permissions as class-level dicts:
64+
65+
```python
66+
from endstone import Player
67+
from endstone.command import Command, CommandSender, ConsoleCommandSender
68+
from endstone.plugin import Plugin
69+
70+
class MyPlugin(Plugin):
71+
api_version = "0.11"
72+
73+
commands = {
74+
"greet": {
75+
"description": "Send a greeting",
76+
"usages": ["/greet [player: target]"],
77+
"aliases": ["hi"],
78+
"permissions": ["my_plugin.command.greet"],
79+
},
80+
}
81+
82+
permissions = {
83+
"my_plugin.command.greet": {
84+
"description": "Allow users to use the /greet command.",
85+
"default": True, # True = everyone, "op" = operators only, False = no one
86+
},
87+
}
88+
89+
def on_command(self, sender: CommandSender, command: Command, args: list[str]) -> bool:
90+
match command.name:
91+
case "greet":
92+
if isinstance(sender, Player):
93+
sender.send_message(f"Hello, {sender.name}!")
94+
elif isinstance(sender, ConsoleCommandSender):
95+
self.logger.info("Hello from the console!")
96+
return True
97+
```
98+
99+
### Command Parameter Types
100+
101+
Parameters in `usages` use the syntax `<name: type>` (mandatory) or `[name: type]` (optional).
102+
103+
Built-in types: `int`, `float`, `bool`, `str`, `message`, `json`, `target`, `block_pos`, `pos`,
104+
`block`, `block_states`, `entity_type`.
105+
106+
User-defined enums: `(value1|value2|value3)` -- e.g. `/home (add|list|del)<action: HomeAction>`.
107+
108+
### Permission Defaults
109+
110+
- `True` or `"true"` -- everyone can use
111+
- `False` or `"false"` -- no one can use (must be granted)
112+
- `"op"` -- operators only (default if not specified)
113+
- `"not_op"` -- non-operators only
114+
- `"console"` -- console only
115+
116+
## Events
117+
118+
Use the `@event_handler` decorator. Register listeners with `self.register_events()`:
119+
120+
```python
121+
from endstone import ColorFormat
122+
from endstone.event import PlayerJoinEvent, PlayerQuitEvent, event_handler
123+
from endstone.plugin import Plugin
124+
125+
class MyListener:
126+
def __init__(self, plugin: Plugin) -> None:
127+
self._plugin = plugin
128+
129+
@event_handler
130+
def on_player_join(self, event: PlayerJoinEvent) -> None:
131+
event.join_message = f"{ColorFormat.YELLOW}{event.player.name} joined"
132+
133+
@event_handler
134+
def on_player_quit(self, event: PlayerQuitEvent) -> None:
135+
event.quit_message = f"{ColorFormat.YELLOW}{event.player.name} left"
136+
```
137+
138+
Register in `on_enable`:
139+
140+
```python
141+
def on_enable(self) -> None:
142+
self.register_events(MyListener(self))
143+
# You can also register the plugin itself if it has @event_handler methods:
144+
self.register_events(self)
145+
```
146+
147+
Event handlers can also be defined directly on the Plugin class.
148+
149+
### Event Priorities
150+
151+
```python
152+
from endstone.event import EventPriority, event_handler
153+
154+
@event_handler(priority=EventPriority.HIGH, ignore_cancelled=True)
155+
def on_player_join(self, event: PlayerJoinEvent) -> None:
156+
...
157+
```
158+
159+
Priorities (lowest runs first): `LOWEST`, `LOW`, `NORMAL` (default), `HIGH`, `HIGHEST`, `MONITOR`.
160+
161+
### Common Events
162+
163+
Player: `PlayerJoinEvent`, `PlayerQuitEvent`, `PlayerChatEvent`, `PlayerCommandEvent`,
164+
`PlayerInteractEvent`, `PlayerDeathEvent`, `PlayerMoveEvent`, `PlayerTeleportEvent`,
165+
`PlayerLoginEvent`, `PlayerKickEvent`, `PlayerGameModeChangeEvent`.
166+
167+
Block: `BlockBreakEvent`, `BlockPlaceEvent`, `BlockExplodeEvent`.
168+
169+
Actor: `ActorSpawnEvent`, `ActorDeathEvent`, `ActorDamageEvent`.
170+
171+
Server: `ServerLoadEvent`, `ServerListPingEvent`, `ServerCommandEvent`.
172+
173+
Packet: `PacketReceiveEvent`, `PacketSendEvent` (for low-level protocol access).
174+
175+
## Configuration
176+
177+
Place a `config.toml` next to your plugin module. Call `self.save_default_config()` in
178+
`on_enable` to copy it to the plugin's data folder on first run:
179+
180+
```toml
181+
# config.toml
182+
greeting = "Hello"
183+
max_homes = 3
184+
```
185+
186+
```python
187+
def on_enable(self) -> None:
188+
self.save_default_config()
189+
greeting = self.config["greeting"]
190+
max_homes = self.config["max_homes"]
191+
```
192+
193+
## Scheduler
194+
195+
Schedule delayed or repeating tasks (1 second = 20 ticks):
196+
197+
```python
198+
def on_enable(self) -> None:
199+
# Run once after 5 seconds
200+
self.server.scheduler.run_task(self, self.my_task, delay=100)
201+
202+
# Run every 10 seconds
203+
self.server.scheduler.run_task(self, self.my_repeating_task, delay=0, period=200)
204+
```
205+
206+
## Color Formatting
207+
208+
```python
209+
from endstone import ColorFormat
210+
211+
msg = f"{ColorFormat.GREEN}Success! {ColorFormat.RESET}Back to normal."
212+
```
213+
214+
Common codes: `BLACK`, `DARK_BLUE`, `DARK_GREEN`, `DARK_AQUA`, `DARK_RED`, `DARK_PURPLE`,
215+
`GOLD`, `GRAY`, `DARK_GRAY`, `BLUE`, `GREEN`, `AQUA`, `RED`, `LIGHT_PURPLE`, `YELLOW`,
216+
`WHITE`, `BOLD`, `ITALIC`, `OBFUSCATED`, `RESET`.
217+
218+
Always end colored text with `ColorFormat.RESET`.
219+
220+
## Common Imports
221+
222+
```python
223+
# Plugin base
224+
from endstone.plugin import Plugin
225+
226+
# Commands
227+
from endstone.command import Command, CommandSender, CommandExecutor, ConsoleCommandSender
228+
229+
# Events
230+
from endstone.event import event_handler, EventPriority
231+
from endstone.event import PlayerJoinEvent, PlayerQuitEvent, PlayerChatEvent
232+
from endstone.event import BlockBreakEvent, BlockPlaceEvent
233+
from endstone.event import ActorDamageEvent, ActorDeathEvent
234+
235+
# Entities and formatting
236+
from endstone import Player, ColorFormat, GameMode
237+
238+
# Forms (GUI)
239+
from endstone.form import ActionForm, ModalForm, MessageForm
240+
```
241+
242+
## Player Methods
243+
244+
```python
245+
player.send_message("text") # Chat message
246+
player.send_popup("text") # Popup on screen
247+
player.send_tip("text") # Tip at top
248+
player.send_title("title", "sub") # Title screen
249+
player.send_toast("title", "body") # Toast notification
250+
player.kick("reason") # Kick from server
251+
player.perform_command("say hi") # Execute command as player
252+
player.teleport(location) # Teleport
253+
```
254+
255+
## Building and Installing
256+
257+
```bash
258+
uv build # Build .whl
259+
uv sync --extra dev # Install dev dependencies
260+
uv run ruff check src/ # Lint
261+
```
262+
263+
Copy the `.whl` from `dist/` to your server's `plugins/` folder.
264+
265+
For development, use editable installs: `pip install -e .` (with the server's venv activated),
266+
then `/reload` in-game to pick up changes.

0 commit comments

Comments
 (0)