Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin system #339

Draft
wants to merge 69 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
656b916
Add pumpkin-api crate for API definitions
vyPal Nov 24, 2024
3e1f84a
Add proc-macro definitions for pumpkin-api
vyPal Nov 24, 2024
9b8d209
Add pumpkin-api and pumpkin-api-macros to workspace
vyPal Nov 24, 2024
274fab6
Add basic PluginManager implementation plugin loading
vyPal Nov 24, 2024
cd38d40
Update .gitignore to include specific plugin file types
vyPal Nov 24, 2024
dfc869b
Add example plugin
vyPal Nov 24, 2024
6f2e361
Cargo fmt
vyPal Nov 24, 2024
66c6e3f
Fix clippy issues
vyPal Nov 24, 2024
b486bce
Refactoring to prevent a circular import
vyPal Nov 25, 2024
3e25550
Add base impl for plugin hooks
vyPal Nov 25, 2024
b7dc70b
Move plugin manager to server
vyPal Nov 25, 2024
55ad639
Make metadata have static lifetime
vyPal Nov 25, 2024
906e1e0
Add default implementations to events
vyPal Nov 25, 2024
50bdf0d
Add hooks to proc macro
vyPal Nov 25, 2024
3388734
Update example
vyPal Nov 25, 2024
8f7dc04
Fix formatting
vyPal Nov 25, 2024
c3b7d15
Fix clippy warnings
vyPal Nov 25, 2024
b60a2cc
Load metadata from Cargo.toml
vyPal Nov 26, 2024
6ab3d18
Mark plugins as an implemented feature :D
vyPal Nov 26, 2024
c8ba17e
Implement new event handling
vyPal Nov 26, 2024
fd2fbdb
Create a static global reference to the plugin manager
vyPal Nov 26, 2024
8848e9b
Emit player join and leave events
vyPal Nov 26, 2024
32f340e
Update macro generation
vyPal Nov 26, 2024
4eb45b5
Update example
vyPal Nov 26, 2024
8f3cc78
Fix formatting
vyPal Nov 26, 2024
c25d9eb
Fix clippy issue
vyPal Nov 26, 2024
be6468f
Simplify event handling
vyPal Nov 27, 2024
dd6a2b9
Add plugin command to list plugins
vyPal Nov 28, 2024
0213975
Make handlers async
vyPal Nov 29, 2024
07ba3bb
Update macros
vyPal Nov 29, 2024
69c932e
Update example
vyPal Nov 29, 2024
efed9bd
Merge branch 'master' into plugins
vyPal Nov 30, 2024
49da053
Merge branch 'Snowiiii:master' into plugins
vyPal Dec 2, 2024
5681fa4
Fix formatting and clippy issues
vyPal Dec 2, 2024
5c7568c
Better styling on plugins command
vyPal Dec 2, 2024
b4088d9
Fix clippy issues
vyPal Dec 2, 2024
bf9af2f
Cargo fmt
vyPal Dec 2, 2024
363e30b
Disable doctest for lib target on pumpkin crate
vyPal Dec 2, 2024
fc3131c
Merge branch 'Snowiiii:master' into plugins
vyPal Dec 3, 2024
fa911fe
New API for plugins
vyPal Dec 5, 2024
c00039a
Update api macros
vyPal Dec 5, 2024
ce6ad0e
Update plugin example
vyPal Dec 5, 2024
ca30edf
A bit of clean up
vyPal Dec 6, 2024
d279df6
Some QoL (and performance) improvements
vyPal Dec 16, 2024
64468e1
Merge branch 'master' into plugins
vyPal Dec 20, 2024
237c280
Cargo fmt and clippy fixes
vyPal Dec 20, 2024
bdb684b
refactoring, better event handling, new context functions
vyPal Dec 22, 2024
2f71fd7
Async on_load and on_unload
vyPal Dec 23, 2024
9bd82c8
Fix mutex lock never going out of scope
vyPal Dec 23, 2024
fed1364
Async plugin loading
vyPal Dec 23, 2024
bb8f0ba
Add plugin management command
vyPal Dec 23, 2024
6d7ec38
Fix clippy issues
vyPal Dec 23, 2024
198f0f5
Merge branch 'master' into plugins
vyPal Dec 25, 2024
e04dda1
Fix import issues
vyPal Dec 26, 2024
94cb711
Move TcpConnection out of client
vyPal Dec 26, 2024
499df22
Move packet encoding out of client
vyPal Dec 26, 2024
87f23ec
Allow plugins to register commands
vyPal Dec 26, 2024
5354835
Fix fmt and clippy
vyPal Dec 26, 2024
2a3a6ac
Implement plugin list in query
vyPal Dec 26, 2024
7402f28
Make arguments public so that plugins can use them
vyPal Dec 27, 2024
96b302c
Update proc_macros to handle runtime
vyPal Dec 27, 2024
5927ce6
Make tree_builder public for use in plugins
vyPal Dec 27, 2024
888276c
Make FindArg trait public for use in plugins
vyPal Dec 27, 2024
8ed5ffc
Update example plugin
vyPal Dec 27, 2024
5654002
Merge branch 'master' into plugins
vyPal Dec 27, 2024
4b31468
Fix merge related issues
vyPal Dec 27, 2024
80b1d62
Fix cargo fmt
vyPal Dec 27, 2024
c2a041d
Merge branch 'master' into plugins
vyPal Dec 30, 2024
b5615bb
Post-merge fixes (also 69th commit, nice)
vyPal Dec 30, 2024
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ Cargo.lock
#.idea/

# === PROJECT SPECIFIC ===
plugins/*
plugins/**/*.so
plugins/**/*.dylib
plugins/**/*.dll
world/*

# docker-compose
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = [
"pumpkin-api-macros",
"pumpkin-config",
"pumpkin-core",
"pumpkin-entity",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi
- [ ] Entity AI
- [ ] Boss
- Server
- [ ] Plugins
- [x] Plugins
- [x] Query
- [x] RCON
- [x] Inventories
Expand Down
32 changes: 32 additions & 0 deletions plugins/hello-plugin-source/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[workspace]

[package]
name = "hello-plugin-source"
edition = "2021"
version = "0.1.0"
authors = ["vyPal"]
description = "An example plugin for Pumpkin"

[lib]
crate-type = ["dylib"]

[dependencies]
pumpkin = { path = "../../pumpkin" }
pumpkin-core = { path = "../../pumpkin-core" }
pumpkin-protocol = { path = "../../pumpkin-protocol" }
pumpkin-api-macros = { path = "../../pumpkin-api-macros" }
serde = { version = "1.0", features = ["derive"] }
toml = "0.8.19"
async-trait = "0.1.83"
tokio = { version = "1.42", features = [
"fs",
"io-util",
"macros",
"net",
"rt-multi-thread",
"sync",
"io-std",
"signal",
] }
env_logger = "0.11.6"
log = "0.4.22"
2 changes: 2 additions & 0 deletions plugins/hello-plugin-source/data.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[bans]
players = []
214 changes: 214 additions & 0 deletions plugins/hello-plugin-source/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
use async_trait::async_trait;
use pumpkin::command::args::arg_block::BlockArgumentConsumer;
use pumpkin::command::args::arg_position_block::BlockPosArgumentConsumer;
use pumpkin::command::args::ConsumedArgs;
use pumpkin::command::args::FindArg;
use pumpkin::command::dispatcher::CommandError;
use pumpkin::command::tree::CommandTree;
use pumpkin::command::tree_builder::argument;
use pumpkin::command::tree_builder::literal;
use pumpkin::command::tree_builder::require;
use pumpkin::command::CommandExecutor;
use pumpkin::command::CommandSender;
use pumpkin::plugin::api::types::player::PlayerEvent;
use pumpkin::plugin::*;
use pumpkin::server::Server;
use pumpkin_api_macros::{plugin_event, plugin_impl, plugin_method, with_runtime};
use pumpkin_core::text::color::NamedColor;
use pumpkin_core::text::TextComponent;
use pumpkin_core::PermissionLvl;
use serde::{Deserialize, Serialize};
use std::fs;

#[derive(Serialize, Deserialize, Debug)]
struct Config {
bans: Bans,
}

#[derive(Serialize, Deserialize, Debug)]
struct Bans {
players: Vec<String>,
}

const NAMES: [&str; 1] = ["setblock2"];

const DESCRIPTION: &str = "Place a block.";

const ARG_BLOCK: &str = "block";
const ARG_BLOCK_POS: &str = "position";

#[derive(Clone, Copy)]
enum Mode {
/// with particles + item drops
Destroy,

/// only replaces air
Keep,

/// default; without particles
Replace,
}

struct SetblockExecutor(Mode);

// IMPORTANT: If using something that requires a tokio runtime, the #[with_runtime] attribute must be used.
// EVEN MORE IMPORTANT: The #[with_runtime] attribute must be used **BRFORE** the #[async_trait] attribute.
#[with_runtime(global)]
#[async_trait]
impl CommandExecutor for SetblockExecutor {
async fn execute<'a>(
&self,
sender: &mut CommandSender<'a>,
_server: &Server,
args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> {
let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?;
let block_state_id = block.default_state_id;
let pos = BlockPosArgumentConsumer::find_arg(args, ARG_BLOCK_POS)?;
let mode = self.0;
// TODO: allow console to use the command (seed sender.world)
let world = sender.world().ok_or(CommandError::InvalidRequirement)?;

let success = match mode {
Mode::Destroy => {
world.break_block(pos, None).await;
world.set_block_state(pos, block_state_id).await;
true
}
Mode::Replace => {
world.set_block_state(pos, block_state_id).await;
true
}
Mode::Keep => match world.get_block_state(pos).await {
Ok(old_state) if old_state.air => {
world.set_block_state(pos, block_state_id).await;
true
}
Ok(_) => false,
Err(e) => return Err(CommandError::OtherPumpkin(e.into())),
},
};

sender
.send_message(if success {
TextComponent::text(format!("Placed block {} at {pos}", block.name,))
} else {
TextComponent::text(format!("Kept block at {pos}")).color_named(NamedColor::Red)
})
.await;

Ok(())
}
}

pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(|sender| sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some())
.with_child(
argument(ARG_BLOCK_POS, BlockPosArgumentConsumer).with_child(
argument(ARG_BLOCK, BlockArgumentConsumer)
.with_child(literal("replace").execute(SetblockExecutor(Mode::Replace)))
.with_child(literal("destroy").execute(SetblockExecutor(Mode::Destroy)))
.with_child(literal("keep").execute(SetblockExecutor(Mode::Keep)))
.execute(SetblockExecutor(Mode::Replace)),
),
),
)
}

#[plugin_method]
async fn on_load(&mut self, server: &Context) -> Result<(), String> {
env_logger::init();
server
.register_command(init_command_tree(), PermissionLvl::Two)
.await;
let data_folder = server.get_data_folder();
if !fs::exists(format!("{}/data.toml", data_folder)).unwrap() {
let cfg = toml::to_string(&self.config).unwrap();
fs::write(format!("{}/data.toml", data_folder), cfg).unwrap();
server
.get_logger()
.info(format!("Created config in {} with {:#?}", data_folder, self.config).as_str());
} else {
let data = fs::read_to_string(format!("{}/data.toml", data_folder)).unwrap();
self.config = toml::from_str(data.as_str()).unwrap();
server
.get_logger()
.info(format!("Loaded config from {} with {:#?}", data_folder, self.config).as_str());
}

server.get_logger().info("Plugin loaded!");
Ok(())
}

#[plugin_method]
async fn on_unload(&mut self, server: &Context) -> Result<(), String> {
let data_folder = server.get_data_folder();
let cfg = toml::to_string(&self.config).unwrap();
fs::write(format!("{}/data.toml", data_folder), cfg).unwrap();

server.get_logger().info("Plugin unloaded!");
Ok(())
}

#[plugin_event(blocking = true, priority = Highest)]
async fn on_player_join(&mut self, server: &Context, player: &PlayerEvent) -> Result<bool, String> {
server.get_logger().info(
format!(
"Player {} joined the game. Config is {:#?}",
player.gameprofile.name, self.config
)
.as_str(),
);

if self.config.bans.players.contains(&player.gameprofile.name) {
let _ = player
.kick(TextComponent::text("You are banned from the server"))
.await;
return Ok(true);
}

let _ = player
.send_message(
TextComponent::text(format!(
"Hello {}, welocme to the server",
player.gameprofile.name
))
.color_named(NamedColor::Green),
)
.await;
Ok(true)
}

#[plugin_event]
async fn on_player_leave(
&mut self,
server: &Context,
player: &PlayerEvent,
) -> Result<bool, String> {
server
.get_logger()
.info(format!("Player {} left the game", player.gameprofile.name).as_str());
Ok(false)
}

#[plugin_impl]
pub struct MyPlugin {
config: Config,
}

impl MyPlugin {
pub fn new() -> Self {
MyPlugin {
config: Config {
bans: Bans { players: vec![] },
},
}
}
}

impl Default for MyPlugin {
fn default() -> Self {
Self::new()
}
}
14 changes: 14 additions & 0 deletions pumpkin-api-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "pumpkin-api-macros"
version.workspace = true
edition.workspace = true

[lib]
proc-macro = true

[dependencies]
syn = { version = "2.0.89", features = ["full"] }
quote = "1.0.37"
proc-macro2 = "1.0.92"
once_cell = "1.20.2"
pumpkin = { path = "../pumpkin" }
Loading
Loading