Skip to content

Commit 383c855

Browse files
committed
Overhaul plugin examples to focus on essential patterns
- Simplify to one /hello command showing isinstance sender checks (Player vs ConsoleCommandSender) and config.toml usage - Simplify listener to player join/quit with custom messages - Add default config.toml for save_default_config() demo - Remove separate CommandExecutor, scheduler, permission attachment, priority, and ServerListPingEvent examples
1 parent 5ae0e25 commit 383c855

File tree

6 files changed

+41
-119
lines changed

6 files changed

+41
-119
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
77
## [Unreleased]
88

99
### Changed
10+
- Overhauled plugin examples to focus on essential patterns: lifecycle, config, commands, events
11+
- Simplified to one command (`/hello`) demonstrating `isinstance` sender checks and config usage
12+
- Simplified listener to player join/quit with custom messages
13+
- Removed scheduler, permission attachment, and priority examples (too advanced for a starter template)
1014
- Modernized build system with hatch-vcs for automatic git-tag versioning
1115
- Switched from pip to uv for dependency management and builds
1216
- Updated minimum Python version to 3.10
1317
- Bumped api_version from 0.6 to 0.11
14-
- Simplified source file names (plugin.py, listener.py, command.py)
18+
- Simplified source file names (plugin.py, listener.py)
1519
- CI now runs linting (ruff)
1620
- Replaced publish workflow with full release automation (changelog, tagging, PyPI, GitHub release)
1721

1822
### Added
23+
- Default config.toml demonstrating plugin configuration
1924
- Ruff linting configuration
2025
- Dependabot for automated dependency updates
2126
- CHANGELOG.md following Keep a Changelog format
27+
28+
### Removed
29+
- Separate CommandExecutor class (command.py); on_command approach is simpler for examples

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Build](https://github.com/EndstoneMC/python-example-plugin/actions/workflows/build.yml/badge.svg)](https://github.com/EndstoneMC/python-example-plugin/actions/workflows/build.yml)
44

55
An example Python plugin for [Endstone](https://github.com/EndstoneMC/endstone) servers, demonstrating commands,
6-
events, permissions, scheduled tasks, and separate listener/executor classes.
6+
events, configuration, and permissions.
77

88
## Prerequisites
99

@@ -46,9 +46,9 @@ python-example-plugin/
4646
├── src/
4747
│ └── endstone_example/
4848
│ ├── __init__.py # Package entry point, re-exports ExamplePlugin
49-
│ ├── plugin.py # Plugin class with commands, events, scheduler
50-
│ ├── listener.py # Separate event listener class
51-
│ └── command.py # Custom CommandExecutor for /python
49+
│ ├── plugin.py # Plugin class: lifecycle, config, commands
50+
│ ├── listener.py # Event listener: player join/quit
51+
│ └── config.toml # Default plugin configuration
5252
├── .github/
5353
│ ├── dependabot.yml # Automated dependency updates
5454
│ └── workflows/

src/endstone_example/command.py

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/endstone_example/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# The greeting message sent by the /hello command
2+
greeting = "Hello"

src/endstone_example/listener.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,18 @@
1-
import datetime
2-
31
from endstone import ColorFormat
4-
from endstone.event import EventPriority, PlayerJoinEvent, PlayerQuitEvent, ServerListPingEvent, event_handler
2+
from endstone.event import PlayerJoinEvent, PlayerQuitEvent, event_handler
53
from endstone.plugin import Plugin
64

75

86
class ExampleListener:
97
def __init__(self, plugin: Plugin) -> None:
108
self._plugin = plugin
119

12-
@event_handler(priority=EventPriority.HIGHEST)
13-
def on_server_list_ping(self, event: ServerListPingEvent) -> None:
14-
event.motd = ColorFormat.BOLD + ColorFormat.AQUA + datetime.datetime.now().strftime("%c")
15-
addr = event.address
16-
event.level_name = f"Your IP is {ColorFormat.YELLOW}{addr.hostname}:{addr.port}{ColorFormat.RESET}"
17-
1810
@event_handler
1911
def on_player_join(self, event: PlayerJoinEvent) -> None:
2012
player = event.player
21-
self._plugin.logger.info(
22-
ColorFormat.YELLOW + f"{player.name}[/{player.address}] joined the game with UUID {player.unique_id}"
23-
)
24-
25-
# example of explicitly removing one's permission of using /me command
26-
player.add_attachment(self._plugin, "minecraft.command.me", False)
27-
player.update_commands() # don't forget to resend the commands
13+
event.join_message = f"{ColorFormat.YELLOW}{player.name} joined the game"
14+
self._plugin.logger.info(f"{player.name} joined from {player.address}")
2815

2916
@event_handler
3017
def on_player_quit(self, event: PlayerQuitEvent) -> None:
31-
player = event.player
32-
self._plugin.logger.info(ColorFormat.YELLOW + f"{player.name}[/{player.address}] left the game.")
18+
event.quit_message = f"{ColorFormat.YELLOW}{event.player.name} left the game"

src/endstone_example/plugin.py

Lines changed: 22 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,48 @@
1-
import datetime
2-
3-
from endstone.command import Command, CommandSender
4-
from endstone.event import EventPriority, ServerLoadEvent, event_handler
1+
from endstone import Player
2+
from endstone.command import Command, CommandSender, ConsoleCommandSender
53
from endstone.plugin import Plugin
64

7-
from endstone_example.command import PythonCommandExecutor
85
from endstone_example.listener import ExampleListener
96

107

118
class ExamplePlugin(Plugin):
12-
prefix = "PythonExamplePlugin"
9+
prefix = "ExamplePlugin"
1310
api_version = "0.11"
14-
load = "POSTWORLD"
1511

1612
commands = {
17-
"python": {
18-
"description": "Zen of python",
19-
"usages": ["/python"],
20-
"aliases": ["py"],
21-
"permissions": ["python_example.command.python"],
22-
},
23-
"kickme": {
24-
"description": "Ask the server to kick you with a custom message",
25-
"usages": ["/kickme [reason: message]"],
26-
"permissions": ["python_example.command.kickme"],
13+
"hello": {
14+
"description": "Send a greeting",
15+
"usages": ["/hello"],
16+
"permissions": ["example.command.hello"],
2717
},
2818
}
2919

3020
permissions = {
31-
"python_example.command": {
32-
"description": "Allow users to use all commands provided by this plugin.",
33-
"default": True,
34-
"children": {
35-
"python_example.command.python": True,
36-
"python_example.command.kickme": True,
37-
},
38-
},
39-
"python_example.command.python": {
40-
"description": "Allow users to use the /python command.",
41-
"default": "op",
42-
},
43-
"python_example.command.kickme": {
44-
"description": "Allow users to use the /kickme command.",
21+
"example.command.hello": {
22+
"description": "Allow users to use the /hello command.",
4523
"default": True,
4624
},
4725
}
4826

49-
def on_load(self) -> None:
50-
self.logger.info("on_load is called!")
51-
5227
def on_enable(self) -> None:
53-
self.logger.info("on_enable is called!")
54-
self.get_command("python").executor = PythonCommandExecutor()
55-
56-
self.register_events(self) # register event listeners defined directly in Plugin class
57-
self.register_events(ExampleListener(self)) # you can also register event listeners in a separate class
58-
59-
self.server.scheduler.run_task(self, self.log_time, delay=0, period=20 * 1) # every second
28+
self.save_default_config()
29+
self.register_events(ExampleListener(self))
30+
self.logger.info("ExamplePlugin enabled!")
6031

6132
def on_disable(self) -> None:
62-
self.logger.info("on_disable is called!")
33+
self.logger.info("ExamplePlugin disabled!")
6334

6435
def on_command(self, sender: CommandSender, command: Command, args: list[str]) -> bool:
65-
# You can also handle commands here instead of setting an executor in on_enable if you prefer
6636
match command.name:
67-
case "kickme":
68-
player = sender.as_player()
69-
if player is None:
70-
sender.send_error_message("You must be a player to execute this command.")
71-
return False
72-
73-
if len(args) > 0:
74-
player.kick(args[0])
37+
case "hello":
38+
greeting = self.config["greeting"] # from config.toml
39+
40+
# Use isinstance to check the sender type
41+
if isinstance(sender, Player):
42+
sender.send_message(f"{greeting}, {sender.name}!")
43+
elif isinstance(sender, ConsoleCommandSender):
44+
self.logger.info(f"{greeting} from the console!")
7545
else:
76-
player.kick("You asked for it!")
46+
sender.send_message(f"{greeting}!")
7747

7848
return True
79-
80-
@event_handler
81-
def on_server_load(self, event: ServerLoadEvent) -> None:
82-
self.logger.info(f"{event.event_name} is passed to on_server_load")
83-
84-
@event_handler(priority=EventPriority.HIGH)
85-
def on_server_load_2(self, event: ServerLoadEvent) -> None:
86-
# this will be called after on_server_load because of a higher priority
87-
self.logger.info(f"{event.event_name} is passed to on_server_load2")
88-
89-
def log_time(self) -> None:
90-
now = datetime.datetime.now().strftime("%c")
91-
for player in self.server.online_players:
92-
player.send_popup(now)

0 commit comments

Comments
 (0)