From da3f482da4d0229a21d796264fcd4c8829752edf Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sat, 5 Sep 2026 12:45:36 +0200 Subject: [PATCH] move to rust folder --- rust/Cargo.lock | 570 +++++++++++++++++++++++ rust/Cargo.toml | 6 + rust/basics/01-hello-world/Cargo.lock | 530 +++++++++++++++++++++ rust/basics/01-hello-world/Cargo.toml | 14 + rust/basics/01-hello-world/src/lib.rs | 52 +++ rust/basics/02-custom-command/Cargo.lock | 530 +++++++++++++++++++++ rust/basics/02-custom-command/Cargo.toml | 14 + rust/basics/02-custom-command/src/lib.rs | 138 ++++++ rust/basics/03-event-listener/Cargo.lock | 530 +++++++++++++++++++++ rust/basics/03-event-listener/Cargo.toml | 14 + rust/basics/03-event-listener/src/lib.rs | 140 ++++++ rust/minigames/spleef/Cargo.toml | 14 + rust/minigames/spleef/src/lib.rs | 412 ++++++++++++++++ rust/minigames/sumo/Cargo.toml | 14 + rust/minigames/sumo/src/lib.rs | 408 ++++++++++++++++ rust/minigames/tnt-run/Cargo.toml | 14 + rust/minigames/tnt-run/src/lib.rs | 422 +++++++++++++++++ 17 files changed, 3822 insertions(+) create mode 100644 rust/Cargo.lock create mode 100644 rust/Cargo.toml create mode 100644 rust/basics/01-hello-world/Cargo.lock create mode 100644 rust/basics/01-hello-world/Cargo.toml create mode 100644 rust/basics/01-hello-world/src/lib.rs create mode 100644 rust/basics/02-custom-command/Cargo.lock create mode 100644 rust/basics/02-custom-command/Cargo.toml create mode 100644 rust/basics/02-custom-command/src/lib.rs create mode 100644 rust/basics/03-event-listener/Cargo.lock create mode 100644 rust/basics/03-event-listener/Cargo.toml create mode 100644 rust/basics/03-event-listener/src/lib.rs create mode 100644 rust/minigames/spleef/Cargo.toml create mode 100644 rust/minigames/spleef/src/lib.rs create mode 100644 rust/minigames/sumo/Cargo.toml create mode 100644 rust/minigames/sumo/src/lib.rs create mode 100644 rust/minigames/tnt-run/Cargo.toml create mode 100644 rust/minigames/tnt-run/src/lib.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..8f4b661 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,570 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "custom-command" +version = "0.1.0" +dependencies = [ + "pumpkin-plugin-api", + "tracing", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "event-listener" +version = "0.1.0" +dependencies = [ + "pumpkin-plugin-api", + "tracing", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hello-world" +version = "0.1.0" +dependencies = [ + "pumpkin-plugin-api", + "tracing", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pumpkin-plugin-api" +version = "0.1.0-dev+26.2-26.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09559c59b3c594994856d81a4101b6b10be3843542b191bb1d0a4fff19058888" +dependencies = [ + "postcard", + "serde_json", + "tracing", + "tracing-serde-structured", + "wit-bindgen", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spleef" +version = "0.1.0" +dependencies = [ + "pumpkin-plugin-api", + "tracing", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "sumo" +version = "0.1.0" +dependencies = [ + "pumpkin-plugin-api", + "tracing", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tnt-run" +version = "0.1.0" +dependencies = [ + "pumpkin-plugin-api", + "tracing", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-serde-structured" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0832510e9838a4ff7e45e278602ab0533686f9507bc6189e024e488602f29820" +dependencies = [ + "hash32", + "heapless", + "serde", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-encoder" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e974fe6821a8cf64575d51ea2194e2c8f77e7b66e9afe7419ce8a97f9ee0d251" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a11585adb92fe9b55ad1d760e8d8fb5d87e0d2e303cb8eed57f078d54293a2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a61719f93a87b16d325921e251800c4833f8fab50fa21c7de73aed50086313" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e473fd0095479f9689ac7d2a52c427cc96bb2b973ace50238dfcc1ab1cd52d93" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87445680dfe6d6b5369e884bd63c03a3335268e855365b2fc3f7bcdce96a630e" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f2fe494de0d898216caf0fd0ae76af75753fcb3ff4f465b46131537cf24cf8" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.119", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59283a01aec94f60f92ada22ffe182a5cea8acfce43be3be688de8d52df55312" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "481b5c47b2ecce0389b5e08a05557d6a190c9cd761773b8880a8017ee04dc7ef" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4daaa3cd97ae49ecd0a99dc009d453f93e0f083dd3be38c0f24a83a93e37ac" +dependencies = [ + "anyhow", + "hashbrown", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..3c2646c --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +resolver = "2" +members = [ + "basics/*", + "minigames/*", +] diff --git a/rust/basics/01-hello-world/Cargo.lock b/rust/basics/01-hello-world/Cargo.lock new file mode 100644 index 0000000..0ace098 --- /dev/null +++ b/rust/basics/01-hello-world/Cargo.lock @@ -0,0 +1,530 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hello-world" +version = "0.1.0" +dependencies = [ + "pumpkin-plugin-api", + "tracing", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pumpkin-plugin-api" +version = "0.1.0-dev+26.2-26.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09559c59b3c594994856d81a4101b6b10be3843542b191bb1d0a4fff19058888" +dependencies = [ + "postcard", + "serde_json", + "tracing", + "tracing-serde-structured", + "wit-bindgen", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-serde-structured" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0832510e9838a4ff7e45e278602ab0533686f9507bc6189e024e488602f29820" +dependencies = [ + "hash32", + "heapless", + "serde", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-encoder" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e974fe6821a8cf64575d51ea2194e2c8f77e7b66e9afe7419ce8a97f9ee0d251" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a11585adb92fe9b55ad1d760e8d8fb5d87e0d2e303cb8eed57f078d54293a2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a61719f93a87b16d325921e251800c4833f8fab50fa21c7de73aed50086313" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e473fd0095479f9689ac7d2a52c427cc96bb2b973ace50238dfcc1ab1cd52d93" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87445680dfe6d6b5369e884bd63c03a3335268e855365b2fc3f7bcdce96a630e" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f2fe494de0d898216caf0fd0ae76af75753fcb3ff4f465b46131537cf24cf8" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.119", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59283a01aec94f60f92ada22ffe182a5cea8acfce43be3be688de8d52df55312" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "481b5c47b2ecce0389b5e08a05557d6a190c9cd761773b8880a8017ee04dc7ef" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4daaa3cd97ae49ecd0a99dc009d453f93e0f083dd3be38c0f24a83a93e37ac" +dependencies = [ + "anyhow", + "hashbrown", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/rust/basics/01-hello-world/Cargo.toml b/rust/basics/01-hello-world/Cargo.toml new file mode 100644 index 0000000..9579420 --- /dev/null +++ b/rust/basics/01-hello-world/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "hello-world" +version = "0.1.0" +edition = "2024" +authors = ["Pumpkin Developer"] +description = "A minimal hello-world plugin example for the Pumpkin Minecraft server" +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +pumpkin-plugin-api = "0.1.0-dev" +tracing = "0.1" diff --git a/rust/basics/01-hello-world/src/lib.rs b/rust/basics/01-hello-world/src/lib.rs new file mode 100644 index 0000000..3c6e6f0 --- /dev/null +++ b/rust/basics/01-hello-world/src/lib.rs @@ -0,0 +1,52 @@ +//! # 01 - Hello World Plugin Example for Pumpkin +//! +//! This is the simplest possible Pumpkin plugin. It demonstrates: +//! - Defining a plugin struct and implementing the [`Plugin`] trait. +//! - Setting up metadata (name, version, authors, permissions). +//! - Handling the [`on_load`] and [`on_unload`] lifecycle callbacks. +//! - Logging using the `tracing` crate, which connects to Pumpkin's server log system. +//! - Registering the plugin with the [`register_plugin!`] macro. + +use pumpkin_plugin_api::{ + Context, Plugin, PluginMetadata, permissions, register_plugin, +}; + +/// The main plugin state struct. +pub struct HelloWorldPlugin; + +impl Plugin for HelloWorldPlugin { + /// Constructs a new instance of the plugin. + /// + /// Called once by the runtime before [`on_load`](Plugin::on_load). + fn new() -> Self { + HelloWorldPlugin + } + + /// Provides metadata describing this plugin to the Pumpkin server. + fn metadata(&self) -> PluginMetadata { + PluginMetadata { + name: "hello-world".into(), + version: env!("CARGO_PKG_VERSION").into(), + authors: vec!["Pumpkin Developer".into()], + description: "A minimal Hello World plugin for Pumpkin Minecraft server.".into(), + dependencies: vec![], + permissions: vec![permissions::FS_READ_DATA.into()], + } + } + + /// Invoked when the server loads and enables the plugin. + fn on_load(&self, context: Context) -> Result<(), String> { + tracing::info!("Hello, world from Pumpkin plugin!"); + tracing::info!("Plugin private data directory: {}", context.get_data_folder()); + Ok(()) + } + + /// Invoked when the server unloads or stops the plugin. + fn on_unload(&self, _context: Context) -> Result<(), String> { + tracing::info!("Goodbye from Hello World plugin!"); + Ok(()) + } +} + +// Register HelloWorldPlugin as the entrypoint for the WebAssembly component. +register_plugin!(HelloWorldPlugin); diff --git a/rust/basics/02-custom-command/Cargo.lock b/rust/basics/02-custom-command/Cargo.lock new file mode 100644 index 0000000..79d9b42 --- /dev/null +++ b/rust/basics/02-custom-command/Cargo.lock @@ -0,0 +1,530 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "custom-command" +version = "0.1.0" +dependencies = [ + "pumpkin-plugin-api", + "tracing", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pumpkin-plugin-api" +version = "0.1.0-dev+26.2-26.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09559c59b3c594994856d81a4101b6b10be3843542b191bb1d0a4fff19058888" +dependencies = [ + "postcard", + "serde_json", + "tracing", + "tracing-serde-structured", + "wit-bindgen", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-serde-structured" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0832510e9838a4ff7e45e278602ab0533686f9507bc6189e024e488602f29820" +dependencies = [ + "hash32", + "heapless", + "serde", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-encoder" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e974fe6821a8cf64575d51ea2194e2c8f77e7b66e9afe7419ce8a97f9ee0d251" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a11585adb92fe9b55ad1d760e8d8fb5d87e0d2e303cb8eed57f078d54293a2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a61719f93a87b16d325921e251800c4833f8fab50fa21c7de73aed50086313" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e473fd0095479f9689ac7d2a52c427cc96bb2b973ace50238dfcc1ab1cd52d93" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87445680dfe6d6b5369e884bd63c03a3335268e855365b2fc3f7bcdce96a630e" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f2fe494de0d898216caf0fd0ae76af75753fcb3ff4f465b46131537cf24cf8" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.119", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59283a01aec94f60f92ada22ffe182a5cea8acfce43be3be688de8d52df55312" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "481b5c47b2ecce0389b5e08a05557d6a190c9cd761773b8880a8017ee04dc7ef" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4daaa3cd97ae49ecd0a99dc009d453f93e0f083dd3be38c0f24a83a93e37ac" +dependencies = [ + "anyhow", + "hashbrown", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/rust/basics/02-custom-command/Cargo.toml b/rust/basics/02-custom-command/Cargo.toml new file mode 100644 index 0000000..826bb7b --- /dev/null +++ b/rust/basics/02-custom-command/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "custom-command" +version = "0.1.0" +edition = "2024" +authors = ["Pumpkin Developer"] +description = "A custom command plugin example for the Pumpkin Minecraft server" +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +pumpkin-plugin-api = "0.1.0-dev" +tracing = "0.1" diff --git a/rust/basics/02-custom-command/src/lib.rs b/rust/basics/02-custom-command/src/lib.rs new file mode 100644 index 0000000..70d32ad --- /dev/null +++ b/rust/basics/02-custom-command/src/lib.rs @@ -0,0 +1,138 @@ +//! # 02 - Custom Command Plugin Example for Pumpkin +//! +//! Demonstrates: +//! - Building and registering custom Minecraft commands using the Pumpkin Plugin API. +//! - Setting up subcommands with [`CommandNode::literal`]. +//! - Defining typed arguments with [`CommandNode::argument`] and [`ArgumentType`]. +//! - Implementing [`CommandHandler`] to handle command execution. +//! - Extracting consumed arguments from [`ConsumedArgs`]. +//! - Sending feedback messages back to the [`CommandSender`]. +//! - Accessing server methods from [`Server`]. + +use pumpkin_plugin_api::{ + command::{ + Arg, ArgumentType, Command, CommandError, CommandNode, CommandSender, ConsumedArgs, + StringType, + }, + commands::CommandHandler, + Context, Plugin, PluginMetadata, register_plugin, Server, + text::TextComponent, +}; + +/// Handler for the root `/greet` command. +struct GreetRootHandler; + +impl CommandHandler for GreetRootHandler { + fn handle( + &self, + sender: CommandSender, + _server: Server, + _args: ConsumedArgs, + ) -> Result { + let sender_name = sender.get_name(); + let message = format!("Hello, {sender_name}! Welcome to Pumpkin!"); + sender.send_message(TextComponent::text(&message)); + Ok(0) + } +} + +/// Handler for `/greet broadcast `. +struct GreetBroadcastHandler; + +impl CommandHandler for GreetBroadcastHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + args: ConsumedArgs, + ) -> Result { + let message = match args.get_value("message") { + Arg::Simple(s) | Arg::Msg(s) => s, + _ => "Hello everyone!".to_string(), + }; + + let sender_name = sender.get_name(); + let broadcast_text = format!("[Broadcast by {sender_name}] {message}"); + server.broadcast(&broadcast_text); + + sender.send_message(TextComponent::text("Broadcast sent to all online players!")); + Ok(0) + } +} + +/// Handler for `/greet stats`. +struct GreetStatsHandler; + +impl CommandHandler for GreetStatsHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let tps = server.get_tps(); + let mspt = server.get_mspt(); + let player_count = server.get_player_count(); + let max_players = server.get_max_players(); + + let stats_info = format!( + "Server Stats:\n - TPS: {:.2}\n - MSPT: {:.2}ms\n - Players: {}/{}", + tps, mspt, player_count, max_players + ); + + sender.send_message(TextComponent::text(&stats_info)); + Ok(0) + } +} + +/// Main plugin struct for custom command example. +pub struct CustomCommandPlugin; + +impl Plugin for CustomCommandPlugin { + fn new() -> Self { + CustomCommandPlugin + } + + fn metadata(&self) -> PluginMetadata { + PluginMetadata { + name: "custom-command".into(), + version: env!("CARGO_PKG_VERSION").into(), + authors: vec!["Pumpkin Developer".into()], + description: "A custom command example for the Pumpkin Minecraft server.".into(), + dependencies: vec![], + permissions: vec![], + } + } + + fn on_load(&self, context: Context) -> Result<(), String> { + let names = ["greet".to_string(), "greeting".to_string()]; + + // Build command tree: + // /greet + // /greet broadcast + // /greet stats + let command = Command::new(&names, "A friendly greeting and server stats command") + .then( + CommandNode::literal("broadcast").then( + CommandNode::argument("message", &ArgumentType::String(StringType::Greedy)) + .execute(GreetBroadcastHandler), + ), + ) + .then( + CommandNode::literal("stats").execute(GreetStatsHandler), + ) + .execute(GreetRootHandler); + + context.register_command(command, "pumpkin.command.greet"); + tracing::info!("Registered /greet command with aliases and subcommands!"); + + Ok(()) + } + + fn on_unload(&self, _context: Context) -> Result<(), String> { + tracing::info!("Unloaded custom command plugin!"); + Ok(()) + } +} + +register_plugin!(CustomCommandPlugin); diff --git a/rust/basics/03-event-listener/Cargo.lock b/rust/basics/03-event-listener/Cargo.lock new file mode 100644 index 0000000..468d343 --- /dev/null +++ b/rust/basics/03-event-listener/Cargo.lock @@ -0,0 +1,530 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "event-listener" +version = "0.1.0" +dependencies = [ + "pumpkin-plugin-api", + "tracing", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pumpkin-plugin-api" +version = "0.1.0-dev+26.2-26.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09559c59b3c594994856d81a4101b6b10be3843542b191bb1d0a4fff19058888" +dependencies = [ + "postcard", + "serde_json", + "tracing", + "tracing-serde-structured", + "wit-bindgen", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-serde-structured" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0832510e9838a4ff7e45e278602ab0533686f9507bc6189e024e488602f29820" +dependencies = [ + "hash32", + "heapless", + "serde", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-encoder" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e974fe6821a8cf64575d51ea2194e2c8f77e7b66e9afe7419ce8a97f9ee0d251" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a11585adb92fe9b55ad1d760e8d8fb5d87e0d2e303cb8eed57f078d54293a2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a61719f93a87b16d325921e251800c4833f8fab50fa21c7de73aed50086313" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e473fd0095479f9689ac7d2a52c427cc96bb2b973ace50238dfcc1ab1cd52d93" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87445680dfe6d6b5369e884bd63c03a3335268e855365b2fc3f7bcdce96a630e" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f2fe494de0d898216caf0fd0ae76af75753fcb3ff4f465b46131537cf24cf8" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.119", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59283a01aec94f60f92ada22ffe182a5cea8acfce43be3be688de8d52df55312" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "481b5c47b2ecce0389b5e08a05557d6a190c9cd761773b8880a8017ee04dc7ef" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.258.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4daaa3cd97ae49ecd0a99dc009d453f93e0f083dd3be38c0f24a83a93e37ac" +dependencies = [ + "anyhow", + "hashbrown", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/rust/basics/03-event-listener/Cargo.toml b/rust/basics/03-event-listener/Cargo.toml new file mode 100644 index 0000000..44d25ed --- /dev/null +++ b/rust/basics/03-event-listener/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "event-listener" +version = "0.1.0" +edition = "2024" +authors = ["Pumpkin Developer"] +description = "An event listener plugin example for the Pumpkin Minecraft server" +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +pumpkin-plugin-api = "0.1.0-dev" +tracing = "0.1" diff --git a/rust/basics/03-event-listener/src/lib.rs b/rust/basics/03-event-listener/src/lib.rs new file mode 100644 index 0000000..7534b9e --- /dev/null +++ b/rust/basics/03-event-listener/src/lib.rs @@ -0,0 +1,140 @@ +//! # 03 - Event Listener Plugin Example for Pumpkin +//! +//! Demonstrates: +//! - Subscribing to Pumpkin server events using [`EventHandler`]. +//! - Registering listeners with [`Context::register_event_handler`]. +//! - Setting [`EventPriority`] and configuring blocking vs non-blocking handlers. +//! - Handling player join and leave lifecycle events ([`PlayerJoinEvent`], [`PlayerLeaveEvent`]). +//! - Interacting with players (custom messages, titles, action bar). +//! - Handling block events ([`BlockBreakEvent`]) and cancelling events (`cancelled = true`) to protect blocks. + +use pumpkin_plugin_api::{ + events::{ + EventHandler, EventPriority, + block::BlockBreakEvent, + player::{PlayerJoinEvent, PlayerLeaveEvent}, + }, + events_wit::{BlockBreakEventData, PlayerJoinEventData, PlayerLeaveEventData}, + Context, Plugin, PluginMetadata, register_plugin, Server, + text::TextComponent, +}; + +/// Listens for players joining the server. +struct JoinListener; + +impl EventHandler for JoinListener { + fn handle(&self, _server: Server, event: PlayerJoinEventData) -> PlayerJoinEventData { + let name = event.player.get_name(); + tracing::info!("Player joined the game: {name}"); + + // Send a private chat message to the player + event.player.send_system_message( + TextComponent::text(&format!("Welcome to the server, {name}!")), + false, + ); + + // Show a welcoming title screen to the player + event + .player + .show_title(TextComponent::text("Welcome to Pumpkin!")); + event + .player + .show_subtitle(TextComponent::text("Have fun building and exploring!")); + + event + } +} + +/// Listens for players leaving the server. +struct LeaveListener; + +impl EventHandler for LeaveListener { + fn handle(&self, _server: Server, event: PlayerLeaveEventData) -> PlayerLeaveEventData { + let name = event.player.get_name(); + tracing::info!("Player left the game: {name}"); + event + } +} + +/// Listens for block breaking events and demonstrates event cancellation. +struct BlockBreakListener; + +impl EventHandler for BlockBreakListener { + fn handle(&self, _server: Server, mut event: BlockBreakEventData) -> BlockBreakEventData { + let block_name = &event.block; + let pos = &event.block_pos; + + tracing::info!( + "Block broken: {block_name} at X:{} Y:{} Z:{}", + pos.x, pos.y, pos.z + ); + + // Example: Protect bedrock and barrier blocks from being broken + if block_name.contains("bedrock") || block_name.contains("barrier") { + tracing::warn!("Cancelled breaking protected block '{block_name}' at ({}, {}, {})", pos.x, pos.y, pos.z); + event.cancelled = true; + + if let Some(player) = &event.player { + player.send_system_message( + TextComponent::text("You cannot break this protected block!"), + true, // overlay = true displays in the action bar + ); + } + } + + event + } +} + +/// Main plugin struct for the event listener example. +pub struct EventListenerPlugin; + +impl Plugin for EventListenerPlugin { + fn new() -> Self { + EventListenerPlugin + } + + fn metadata(&self) -> PluginMetadata { + PluginMetadata { + name: "event-listener".into(), + version: env!("CARGO_PKG_VERSION").into(), + authors: vec!["Pumpkin Developer".into()], + description: "An event listener example for the Pumpkin Minecraft server.".into(), + dependencies: vec![], + permissions: vec![], + } + } + + fn on_load(&self, context: Context) -> Result<(), String> { + // Register PlayerJoinEvent (non-blocking notification) + context.register_event_handler( + JoinListener, + EventPriority::Normal, + false, + )?; + + // Register PlayerLeaveEvent (non-blocking notification) + context.register_event_handler( + LeaveListener, + EventPriority::Normal, + false, + )?; + + // Register BlockBreakEvent (blocking = true allows event.cancelled to take effect) + context.register_event_handler( + BlockBreakListener, + EventPriority::High, + true, + )?; + + tracing::info!("Registered player join, player leave, and block break event handlers!"); + Ok(()) + } + + fn on_unload(&self, _context: Context) -> Result<(), String> { + tracing::info!("Unloaded event listener plugin!"); + Ok(()) + } +} + +register_plugin!(EventListenerPlugin); diff --git a/rust/minigames/spleef/Cargo.toml b/rust/minigames/spleef/Cargo.toml new file mode 100644 index 0000000..9571637 --- /dev/null +++ b/rust/minigames/spleef/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "spleef" +version = "0.1.0" +edition = "2024" +authors = ["Pumpkin Developer"] +description = "A complete Spleef minigame for the Pumpkin Minecraft server" +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +pumpkin-plugin-api = "0.1.0-dev" +tracing = "0.1" diff --git a/rust/minigames/spleef/src/lib.rs b/rust/minigames/spleef/src/lib.rs new file mode 100644 index 0000000..17ab02f --- /dev/null +++ b/rust/minigames/spleef/src/lib.rs @@ -0,0 +1,412 @@ +//! # Spleef Minigame for Pumpkin +//! +//! A complete, runnable Spleef minigame showcasing: +//! - State management (`Waiting`, `Active`, `Ended`). +//! - Commands: `/spleef join`, `/spleef leave`, `/spleef start`, `/spleef status`, `/spleef reset`. +//! - [`BlockBreakEvent`]: +//! - Allows breaking snow blocks inside the active arena. +//! - Protects blocks outside the arena boundary or before the game starts. +//! - [`PlayerMoveEvent`]: +//! - Detects players falling below the snow platform into the elimination zone. +//! - Automatic win condition check (last player standing). +//! - Arena regeneration (`/spleef reset`). + +use std::{ + collections::HashSet, + sync::{Arc, Mutex}, +}; + +use pumpkin_plugin_api::{ + command::{Command, CommandError, CommandNode, CommandSender, ConsumedArgs}, + commands::CommandHandler, + common::{BlockPos, Position}, + events::{ + EventHandler, EventPriority, + block::BlockBreakEvent, + player::PlayerMoveEvent, + }, + events_wit::{BlockBreakEventData, PlayerMoveEventData}, + world::BlockFlags, + Context, Plugin, PluginMetadata, register_plugin, Server, + text::TextComponent, +}; + +/// Spleef arena boundaries and platform heights. +#[derive(Clone, Debug)] +pub struct SpleefConfig { + pub min_x: i32, + pub max_x: i32, + pub min_z: i32, + pub max_z: i32, + pub floor_y: i32, + pub elimination_y: f64, + pub spawn_pos: Position, + pub lobby_pos: Position, +} + +impl Default for SpleefConfig { + fn default() -> Self { + Self { + min_x: -12, + max_x: 12, + min_z: -12, + max_z: 12, + floor_y: 100, + elimination_y: 92.0, + spawn_pos: (0.5, 101.0, 0.5), + lobby_pos: (0.5, 115.0, 0.5), + } + } +} + +impl SpleefConfig { + pub fn is_inside_floor(&self, x: i32, y: i32, z: i32) -> bool { + y == self.floor_y && x >= self.min_x && x <= self.max_x && z >= self.min_z && z <= self.max_z + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GameState { + Waiting, + Active, + Ended, +} + +pub struct SpleefSession { + pub state: GameState, + pub config: SpleefConfig, + pub queued_players: HashSet, + pub alive_players: HashSet, +} + +impl SpleefSession { + pub fn new(config: SpleefConfig) -> Self { + Self { + state: GameState::Waiting, + config, + queued_players: HashSet::new(), + alive_players: HashSet::new(), + } + } +} + +static SESSION: Mutex>>> = Mutex::new(None); + +fn get_session() -> Arc> { + SESSION + .lock() + .unwrap_or_else(|e| e.into_inner()) + .clone() + .expect("SpleefSession must be initialized in on_load") +} + +// ----------------------------------------------------------------------------- +// Commands: /spleef +// ----------------------------------------------------------------------------- + +struct SpleefRootHandler; +impl CommandHandler for SpleefRootHandler { + fn handle( + &self, + sender: CommandSender, + _server: Server, + _args: ConsumedArgs, + ) -> Result { + let help_text = "Spleef Commands:\n\ + /spleef join - Join the game queue\n\ + /spleef leave - Leave the queue or game\n\ + /spleef start - Force start the game\n\ + /spleef status - View game status\n\ + /spleef reset - Regenerate snow arena floor"; + sender.send_message(TextComponent::text(help_text)); + Ok(0) + } +} + +struct SpleefJoinHandler; +impl CommandHandler for SpleefJoinHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let player_name = sender.get_name(); + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + match session.state { + GameState::Active => { + sender.send_message(TextComponent::text("Game is already in progress! Please wait.")); + } + GameState::Waiting | GameState::Ended => { + if session.queued_players.insert(player_name.clone()) { + let count = session.queued_players.len(); + server.broadcast(&format!("{player_name} joined Spleef! ({count} players queued)")); + sender.send_message(TextComponent::text("You joined the queue! Use /spleef start when ready.")); + } else { + sender.send_message(TextComponent::text("You are already in the queue!")); + } + } + } + Ok(0) + } +} + +struct SpleefLeaveHandler; +impl CommandHandler for SpleefLeaveHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let player_name = sender.get_name(); + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + if session.queued_players.remove(&player_name) || session.alive_players.remove(&player_name) { + server.broadcast(&format!("{player_name} left Spleef.")); + sender.send_message(TextComponent::text("You left Spleef.")); + } else { + sender.send_message(TextComponent::text("You are not currently in a Spleef game.")); + } + Ok(0) + } +} + +struct SpleefStartHandler; +impl CommandHandler for SpleefStartHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + if session.state == GameState::Active { + sender.send_message(TextComponent::text("Game is already running!")); + return Ok(0); + } + + if session.queued_players.is_empty() { + session.queued_players.insert(sender.get_name()); + } + + session.alive_players = session.queued_players.clone(); + session.state = GameState::Active; + + let count = session.alive_players.len(); + server.broadcast(&format!("Spleef has STARTED with {count} players! Spleef your opponents!")); + + let spawn = session.config.spawn_pos; + for player_name in &session.alive_players { + if let Some(player) = server.get_player_by_name(player_name) { + let world = player.get_world(); + player.teleport(spawn, Some(0.0), Some(0.0), world); + player.show_title(TextComponent::text("SPLEEF!")); + player.show_subtitle(TextComponent::text("Break blocks beneath your rivals!")); + } + } + + Ok(0) + } +} + +struct SpleefStatusHandler; +impl CommandHandler for SpleefStatusHandler { + fn handle( + &self, + sender: CommandSender, + _server: Server, + _args: ConsumedArgs, + ) -> Result { + let session_arc = get_session(); + let session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + let status_msg = match session.state { + GameState::Waiting => format!("Spleef: WAITING ({} queued)", session.queued_players.len()), + GameState::Active => format!( + "Spleef: ACTIVE ({} alive: {:?})", + session.alive_players.len(), + session.alive_players + ), + GameState::Ended => "Spleef: ENDED (Use /spleef reset or /spleef start)".to_string(), + }; + + sender.send_message(TextComponent::text(&status_msg)); + Ok(0) + } +} + +struct SpleefResetHandler; +impl CommandHandler for SpleefResetHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + session.state = GameState::Waiting; + session.alive_players.clear(); + + let worlds = server.get_all_worlds(); + if let Some(world) = worlds.first() { + let flags = BlockFlags::NOTIFY_LISTENERS | BlockFlags::NOTIFY_NEIGHBORS; + for x in session.config.min_x..=session.config.max_x { + for z in session.config.min_z..=session.config.max_z { + let pos = BlockPos { x, y: session.config.floor_y, z }; + world.set_block_by_name(pos, "minecraft:snow_block", flags); + } + } + } + + server.broadcast("Spleef arena has been regenerated and reset!"); + sender.send_message(TextComponent::text("Spleef reset complete!")); + Ok(0) + } +} + +// ----------------------------------------------------------------------------- +// Event Listeners: BlockBreakEvent & PlayerMoveEvent +// ----------------------------------------------------------------------------- + +struct SpleefBreakListener; + +impl EventHandler for SpleefBreakListener { + fn handle(&self, _server: Server, mut event: BlockBreakEventData) -> BlockBreakEventData { + let session_arc = get_session(); + let session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + let pos = &event.block_pos; + + // If the block broken is on the Spleef floor: + if session.config.is_inside_floor(pos.x, pos.y, pos.z) { + // Only allow breaking during Active state by alive players + if session.state == GameState::Active { + if let Some(player) = &event.player { + if session.alive_players.contains(&player.get_name()) { + event.should_drop = false; // Don't drop cluttering snowballs + event.cancelled = false; + return event; + } + } + } + // Disallow breaking floor before start or by spectators + event.cancelled = true; + } + + event + } +} + +struct SpleefMoveListener; + +impl EventHandler for SpleefMoveListener { + fn handle(&self, server: Server, event: PlayerMoveEventData) -> PlayerMoveEventData { + let player_name = event.player.get_name(); + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + if session.state != GameState::Active || !session.alive_players.contains(&player_name) { + return event; + } + + let to = &event.to_position; + + // Elimination Check: player fell below elimination threshold + if to.1 < session.config.elimination_y { + session.alive_players.remove(&player_name); + server.broadcast(&format!("{player_name} was SPLEEFED and eliminated!")); + + let lobby = session.config.lobby_pos; + let world = event.player.get_world(); + event.player.teleport(lobby, Some(0.0), Some(0.0), world); + event.player.show_title(TextComponent::text("SPLEEFED!")); + + if session.alive_players.len() == 1 { + let winner_name = session.alive_players.iter().next().cloned().unwrap(); + server.broadcast(&format!("VICTORY! {winner_name} is the last player standing and WINS Spleef!")); + if let Some(winner) = server.get_player_by_name(&winner_name) { + winner.show_title(TextComponent::text("VICTORY!")); + winner.show_subtitle(TextComponent::text("You won Spleef!")); + } + session.state = GameState::Ended; + } else if session.alive_players.is_empty() { + server.broadcast("Game Over! No players remaining."); + session.state = GameState::Ended; + } + } + + event + } +} + +// ----------------------------------------------------------------------------- +// Plugin Lifecycle +// ----------------------------------------------------------------------------- + +pub struct SpleefPlugin; + +impl Plugin for SpleefPlugin { + fn new() -> Self { + SpleefPlugin + } + + fn metadata(&self) -> PluginMetadata { + PluginMetadata { + name: "spleef".into(), + version: env!("CARGO_PKG_VERSION").into(), + authors: vec!["Pumpkin Developer".into()], + description: "A complete Spleef minigame for Pumpkin Minecraft server.".into(), + dependencies: vec![], + permissions: vec![], + } + } + + fn on_load(&self, context: Context) -> Result<(), String> { + let config = SpleefConfig::default(); + let session = Arc::new(Mutex::new(SpleefSession::new(config))); + *SESSION.lock().unwrap_or_else(|e| e.into_inner()) = Some(session); + + let names = ["spleef".to_string(), "sp".to_string()]; + let command = Command::new(&names, "Spleef minigame management command") + .then(CommandNode::literal("join").execute(SpleefJoinHandler)) + .then(CommandNode::literal("leave").execute(SpleefLeaveHandler)) + .then(CommandNode::literal("start").execute(SpleefStartHandler)) + .then(CommandNode::literal("status").execute(SpleefStatusHandler)) + .then(CommandNode::literal("reset").execute(SpleefResetHandler)) + .execute(SpleefRootHandler); + + context.register_command(command, "pumpkin.command.spleef"); + + // Register BlockBreakEvent (blocking = true to allow/cancel break) + context.register_event_handler( + SpleefBreakListener, + EventPriority::High, + true, + )?; + + // Register PlayerMoveEvent for void detection + context.register_event_handler( + SpleefMoveListener, + EventPriority::Normal, + false, + )?; + + tracing::info!("Spleef minigame plugin successfully loaded!"); + Ok(()) + } + + fn on_unload(&self, _context: Context) -> Result<(), String> { + tracing::info!("Spleef plugin unloaded."); + Ok(()) + } +} + +register_plugin!(SpleefPlugin); diff --git a/rust/minigames/sumo/Cargo.toml b/rust/minigames/sumo/Cargo.toml new file mode 100644 index 0000000..8c75c1f --- /dev/null +++ b/rust/minigames/sumo/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "sumo" +version = "0.1.0" +edition = "2024" +authors = ["Pumpkin Developer"] +description = "A complete Sumo minigame for the Pumpkin Minecraft server" +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +pumpkin-plugin-api = "0.1.0-dev" +tracing = "0.1" diff --git a/rust/minigames/sumo/src/lib.rs b/rust/minigames/sumo/src/lib.rs new file mode 100644 index 0000000..f0dbff7 --- /dev/null +++ b/rust/minigames/sumo/src/lib.rs @@ -0,0 +1,408 @@ +//! # Sumo Minigame for Pumpkin +//! +//! A complete, runnable Sumo PvP minigame showcasing: +//! - State management (`Waiting`, `Active`, `Ended`). +//! - Commands: `/sumo join`, `/sumo leave`, `/sumo start`, `/sumo status`, `/sumo reset`. +//! - Arena protection: cancels all block breaking on the Sumo ring via [`BlockBreakEvent`]. +//! - Ring-out elimination: monitors [`PlayerMoveEvent`] to detect when a player is knocked off. +//! - Automatic win condition check (last player remaining on the platform). +//! - Platform regeneration (`/sumo reset`). + +use std::{ + collections::HashSet, + sync::{Arc, Mutex}, +}; + +use pumpkin_plugin_api::{ + command::{Command, CommandError, CommandNode, CommandSender, ConsumedArgs}, + commands::CommandHandler, + common::{BlockPos, Position}, + events::{ + EventHandler, EventPriority, + block::BlockBreakEvent, + player::PlayerMoveEvent, + }, + events_wit::{BlockBreakEventData, PlayerMoveEventData}, + world::BlockFlags, + Context, Plugin, PluginMetadata, register_plugin, Server, + text::TextComponent, +}; + +/// Sumo ring boundaries and platform coordinates. +#[derive(Clone, Debug)] +pub struct SumoConfig { + pub min_x: i32, + pub max_x: i32, + pub min_z: i32, + pub max_z: i32, + pub platform_y: i32, + pub elimination_y: f64, + pub spawn_pos_1: Position, + pub spawn_pos_2: Position, + pub lobby_pos: Position, +} + +impl Default for SumoConfig { + fn default() -> Self { + Self { + min_x: -6, + max_x: 6, + min_z: -6, + max_z: 6, + platform_y: 100, + elimination_y: 95.0, + spawn_pos_1: (-3.5, 101.0, 0.5), + spawn_pos_2: (3.5, 101.0, 0.5), + lobby_pos: (0.5, 115.0, 0.5), + } + } +} + +impl SumoConfig { + pub fn is_inside_platform(&self, x: i32, y: i32, z: i32) -> bool { + y == self.platform_y && x >= self.min_x && x <= self.max_x && z >= self.min_z && z <= self.max_z + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GameState { + Waiting, + Active, + Ended, +} + +pub struct SumoSession { + pub state: GameState, + pub config: SumoConfig, + pub queued_players: HashSet, + pub alive_players: HashSet, +} + +impl SumoSession { + pub fn new(config: SumoConfig) -> Self { + Self { + state: GameState::Waiting, + config, + queued_players: HashSet::new(), + alive_players: HashSet::new(), + } + } +} + +static SESSION: Mutex>>> = Mutex::new(None); + +fn get_session() -> Arc> { + SESSION + .lock() + .unwrap_or_else(|e| e.into_inner()) + .clone() + .expect("SumoSession must be initialized in on_load") +} + +// ----------------------------------------------------------------------------- +// Commands: /sumo +// ----------------------------------------------------------------------------- + +struct SumoRootHandler; +impl CommandHandler for SumoRootHandler { + fn handle( + &self, + sender: CommandSender, + _server: Server, + _args: ConsumedArgs, + ) -> Result { + let help_text = "Sumo Commands:\n\ + /sumo join - Join the Sumo queue\n\ + /sumo leave - Leave the queue or game\n\ + /sumo start - Force start the duel\n\ + /sumo status - View game status\n\ + /sumo reset - Regenerate arena ring"; + sender.send_message(TextComponent::text(help_text)); + Ok(0) + } +} + +struct SumoJoinHandler; +impl CommandHandler for SumoJoinHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let player_name = sender.get_name(); + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + match session.state { + GameState::Active => { + sender.send_message(TextComponent::text("A Sumo round is currently active! Please wait.")); + } + GameState::Waiting | GameState::Ended => { + if session.queued_players.insert(player_name.clone()) { + let count = session.queued_players.len(); + server.broadcast(&format!("{player_name} joined Sumo! ({count} fighters queued)")); + sender.send_message(TextComponent::text("You joined the queue! Use /sumo start when ready.")); + } else { + sender.send_message(TextComponent::text("You are already in the queue!")); + } + } + } + Ok(0) + } +} + +struct SumoLeaveHandler; +impl CommandHandler for SumoLeaveHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let player_name = sender.get_name(); + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + if session.queued_players.remove(&player_name) || session.alive_players.remove(&player_name) { + server.broadcast(&format!("{player_name} left Sumo.")); + sender.send_message(TextComponent::text("You left Sumo.")); + } else { + sender.send_message(TextComponent::text("You are not currently in a Sumo game.")); + } + Ok(0) + } +} + +struct SumoStartHandler; +impl CommandHandler for SumoStartHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + if session.state == GameState::Active { + sender.send_message(TextComponent::text("Sumo match is already running!")); + return Ok(0); + } + + if session.queued_players.is_empty() { + session.queued_players.insert(sender.get_name()); + } + + session.alive_players = session.queued_players.clone(); + session.state = GameState::Active; + + let count = session.alive_players.len(); + server.broadcast(&format!("Sumo duel has STARTED with {count} fighters! Knock your opponents off the ring!")); + + // Alternate spawn positions for players facing each other + let spawns = [session.config.spawn_pos_1, session.config.spawn_pos_2]; + for (idx, player_name) in session.alive_players.iter().enumerate() { + if let Some(player) = server.get_player_by_name(player_name) { + let world = player.get_world(); + let spawn = spawns[idx % spawns.len()]; + player.teleport(spawn, Some(0.0), Some(0.0), world); + player.show_title(TextComponent::text("SUMO!")); + player.show_subtitle(TextComponent::text("Knock the other players off the ring!")); + } + } + + Ok(0) + } +} + +struct SumoStatusHandler; +impl CommandHandler for SumoStatusHandler { + fn handle( + &self, + sender: CommandSender, + _server: Server, + _args: ConsumedArgs, + ) -> Result { + let session_arc = get_session(); + let session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + let status_msg = match session.state { + GameState::Waiting => format!("Sumo: WAITING ({} fighters queued)", session.queued_players.len()), + GameState::Active => format!( + "Sumo: ACTIVE ({} fighters on ring: {:?})", + session.alive_players.len(), + session.alive_players + ), + GameState::Ended => "Sumo: ENDED (Use /sumo reset or /sumo start)".to_string(), + }; + + sender.send_message(TextComponent::text(&status_msg)); + Ok(0) + } +} + +struct SumoResetHandler; +impl CommandHandler for SumoResetHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + session.state = GameState::Waiting; + session.alive_players.clear(); + + let worlds = server.get_all_worlds(); + if let Some(world) = worlds.first() { + let flags = BlockFlags::NOTIFY_LISTENERS | BlockFlags::NOTIFY_NEIGHBORS; + for x in session.config.min_x..=session.config.max_x { + for z in session.config.min_z..=session.config.max_z { + let pos = BlockPos { x, y: session.config.platform_y, z }; + world.set_block_by_name(pos, "minecraft:smooth_stone", flags); + } + } + } + + server.broadcast("Sumo ring has been regenerated and reset!"); + sender.send_message(TextComponent::text("Sumo reset complete!")); + Ok(0) + } +} + +// ----------------------------------------------------------------------------- +// Event Listeners: BlockBreakEvent & PlayerMoveEvent +// ----------------------------------------------------------------------------- + +struct SumoBreakListener; + +impl EventHandler for SumoBreakListener { + fn handle(&self, _server: Server, mut event: BlockBreakEventData) -> BlockBreakEventData { + let session_arc = get_session(); + let session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + let pos = &event.block_pos; + + // The Sumo arena platform is strictly unbreakable + if session.config.is_inside_platform(pos.x, pos.y, pos.z) { + event.cancelled = true; + if let Some(player) = &event.player { + player.send_system_message( + TextComponent::text("You cannot break the Sumo ring!"), + true, + ); + } + } + + event + } +} + +struct SumoMoveListener; + +impl EventHandler for SumoMoveListener { + fn handle(&self, server: Server, event: PlayerMoveEventData) -> PlayerMoveEventData { + let player_name = event.player.get_name(); + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + if session.state != GameState::Active || !session.alive_players.contains(&player_name) { + return event; + } + + let to = &event.to_position; + + // Ring-out check: knocked off the platform below elimination Y + if to.1 < session.config.elimination_y { + session.alive_players.remove(&player_name); + server.broadcast(&format!("{player_name} was KNOCKED OFF the ring and eliminated!")); + + let lobby = session.config.lobby_pos; + let world = event.player.get_world(); + event.player.teleport(lobby, Some(0.0), Some(0.0), world); + event.player.show_title(TextComponent::text("RING OUT!")); + + if session.alive_players.len() == 1 { + let winner_name = session.alive_players.iter().next().cloned().unwrap(); + server.broadcast(&format!("VICTORY! {winner_name} pushed everyone off and WINS Sumo!")); + if let Some(winner) = server.get_player_by_name(&winner_name) { + winner.show_title(TextComponent::text("VICTORY!")); + winner.show_subtitle(TextComponent::text("You won the Sumo match!")); + } + session.state = GameState::Ended; + } else if session.alive_players.is_empty() { + server.broadcast("Game Over! All fighters fell."); + session.state = GameState::Ended; + } + } + + event + } +} + +// ----------------------------------------------------------------------------- +// Plugin Lifecycle +// ----------------------------------------------------------------------------- + +pub struct SumoPlugin; + +impl Plugin for SumoPlugin { + fn new() -> Self { + SumoPlugin + } + + fn metadata(&self) -> PluginMetadata { + PluginMetadata { + name: "sumo".into(), + version: env!("CARGO_PKG_VERSION").into(), + authors: vec!["Pumpkin Developer".into()], + description: "A complete Sumo PvP minigame for Pumpkin Minecraft server.".into(), + dependencies: vec![], + permissions: vec![], + } + } + + fn on_load(&self, context: Context) -> Result<(), String> { + let config = SumoConfig::default(); + let session = Arc::new(Mutex::new(SumoSession::new(config))); + *SESSION.lock().unwrap_or_else(|e| e.into_inner()) = Some(session); + + let names = ["sumo".to_string(), "sm".to_string()]; + let command = Command::new(&names, "Sumo minigame management command") + .then(CommandNode::literal("join").execute(SumoJoinHandler)) + .then(CommandNode::literal("leave").execute(SumoLeaveHandler)) + .then(CommandNode::literal("start").execute(SumoStartHandler)) + .then(CommandNode::literal("status").execute(SumoStatusHandler)) + .then(CommandNode::literal("reset").execute(SumoResetHandler)) + .execute(SumoRootHandler); + + context.register_command(command, "pumpkin.command.sumo"); + + // Protect the ring platform from being broken + context.register_event_handler( + SumoBreakListener, + EventPriority::High, + true, + )?; + + // Monitor ring-out falls + context.register_event_handler( + SumoMoveListener, + EventPriority::Normal, + false, + )?; + + tracing::info!("Sumo minigame plugin successfully loaded!"); + Ok(()) + } + + fn on_unload(&self, _context: Context) -> Result<(), String> { + tracing::info!("Sumo plugin unloaded."); + Ok(()) + } +} + +register_plugin!(SumoPlugin); diff --git a/rust/minigames/tnt-run/Cargo.toml b/rust/minigames/tnt-run/Cargo.toml new file mode 100644 index 0000000..4037ca4 --- /dev/null +++ b/rust/minigames/tnt-run/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tnt-run" +version = "0.1.0" +edition = "2024" +authors = ["Pumpkin Developer"] +description = "A complete TNT Run minigame for the Pumpkin Minecraft server" +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +pumpkin-plugin-api = "0.1.0-dev" +tracing = "0.1" diff --git a/rust/minigames/tnt-run/src/lib.rs b/rust/minigames/tnt-run/src/lib.rs new file mode 100644 index 0000000..cca38ff --- /dev/null +++ b/rust/minigames/tnt-run/src/lib.rs @@ -0,0 +1,422 @@ +//! # TNT Run Minigame for Pumpkin +//! +//! A complete, runnable TNT Run minigame showcasing: +//! - State management (`Waiting`, `Active`, `Ended`). +//! - Commands: `/tntrun join`, `/tntrun leave`, `/tntrun start`, `/tntrun status`, `/tntrun reset`. +//! - Movement tracking with [`PlayerMoveEvent`]: +//! - Decaying blocks under players' feet with a delay. +//! - Detecting when a player falls below the arena elimination Y level. +//! - Automatic win condition check (last player standing). +//! - Clean floor regeneration when resetting. + +use std::{ + collections::HashSet, + sync::{Arc, Mutex}, +}; + +use pumpkin_plugin_api::{ + command::{Command, CommandError, CommandNode, CommandSender, ConsumedArgs}, + commands::CommandHandler, + common::{BlockPos, Position}, + events::{EventHandler, EventPriority, player::PlayerMoveEvent}, + events_wit::PlayerMoveEventData, + scheduler::SchedulerExt, + world::BlockFlags, + Context, Plugin, PluginMetadata, register_plugin, Server, + text::TextComponent, +}; + +/// Arena spatial configuration. +#[derive(Clone, Debug)] +pub struct ArenaConfig { + pub min_x: i32, + pub max_x: i32, + pub min_z: i32, + pub max_z: i32, + /// Y coordinates of the running floors (top to bottom) + pub floor_y_levels: Vec, + /// Any player falling below this Y level is eliminated + pub elimination_y: f64, + /// Spawn position where players start the game + pub spawn_pos: Position, + /// Lobby position where spectators / eliminated players wait + pub lobby_pos: Position, +} + +impl Default for ArenaConfig { + fn default() -> Self { + Self { + min_x: -15, + max_x: 15, + min_z: -15, + max_z: 15, + floor_y_levels: vec![100, 85, 70], + elimination_y: 60.0, + spawn_pos: (0.5, 101.0, 0.5), + lobby_pos: (0.5, 115.0, 0.5), + } + } +} + +impl ArenaConfig { + pub fn is_inside_xz(&self, x: f64, z: f64) -> bool { + let ix = x.floor() as i32; + let iz = z.floor() as i32; + ix >= self.min_x && ix <= self.max_x && iz >= self.min_z && iz <= self.max_z + } +} + +/// Lifecycle state of the TNT Run game. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GameState { + Waiting, + Active, + Ended, +} + +/// Shared game session state. +pub struct GameSession { + pub state: GameState, + pub config: ArenaConfig, + pub queued_players: HashSet, + pub alive_players: HashSet, + pub recently_decaying: HashSet<(i32, i32, i32)>, +} + +impl GameSession { + pub fn new(config: ArenaConfig) -> Self { + Self { + state: GameState::Waiting, + config, + queued_players: HashSet::new(), + alive_players: HashSet::new(), + recently_decaying: HashSet::new(), + } + } +} + +static SESSION: Mutex>>> = Mutex::new(None); + +fn get_session() -> Arc> { + SESSION + .lock() + .unwrap_or_else(|e| e.into_inner()) + .clone() + .expect("GameSession must be initialized in on_load") +} + +// ----------------------------------------------------------------------------- +// Commands: /tntrun +// ----------------------------------------------------------------------------- + +struct TntRunRootHandler; +impl CommandHandler for TntRunRootHandler { + fn handle( + &self, + sender: CommandSender, + _server: Server, + _args: ConsumedArgs, + ) -> Result { + let help_text = "TNT Run Commands:\n\ + /tntrun join - Join the game queue\n\ + /tntrun leave - Leave the queue or game\n\ + /tntrun start - Force start the game\n\ + /tntrun status - View game status\n\ + /tntrun reset - Regenerate arena floors"; + sender.send_message(TextComponent::text(help_text)); + Ok(0) + } +} + +struct TntRunJoinHandler; +impl CommandHandler for TntRunJoinHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let player_name = sender.get_name(); + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + match session.state { + GameState::Active => { + sender.send_message(TextComponent::text("Game is already in progress! Please wait.")); + } + GameState::Waiting | GameState::Ended => { + if session.queued_players.insert(player_name.clone()) { + let count = session.queued_players.len(); + server.broadcast(&format!("{player_name} joined TNT Run! ({count} players queued)")); + sender.send_message(TextComponent::text("You joined the queue! Use /tntrun start when ready.")); + } else { + sender.send_message(TextComponent::text("You are already in the queue!")); + } + } + } + Ok(0) + } +} + +struct TntRunLeaveHandler; +impl CommandHandler for TntRunLeaveHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let player_name = sender.get_name(); + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + if session.queued_players.remove(&player_name) || session.alive_players.remove(&player_name) { + server.broadcast(&format!("{player_name} left TNT Run.")); + sender.send_message(TextComponent::text("You left TNT Run.")); + } else { + sender.send_message(TextComponent::text("You are not currently in a TNT Run game.")); + } + Ok(0) + } +} + +struct TntRunStartHandler; +impl CommandHandler for TntRunStartHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + if session.state == GameState::Active { + sender.send_message(TextComponent::text("Game is already running!")); + return Ok(0); + } + + // If no one is queued, queue the sender if it's a player + if session.queued_players.is_empty() { + session.queued_players.insert(sender.get_name()); + } + + session.alive_players = session.queued_players.clone(); + session.state = GameState::Active; + session.recently_decaying.clear(); + + let count = session.alive_players.len(); + server.broadcast(&format!("TNT Run has STARTED with {count} players! Keep running!")); + + // Teleport all alive players to the spawn position + let spawn = session.config.spawn_pos; + for player_name in &session.alive_players { + if let Some(player) = server.get_player_by_name(player_name) { + let world = player.get_world(); + player.teleport(spawn, Some(0.0), Some(0.0), world); + player.show_title(TextComponent::text("TNT RUN!")); + player.show_subtitle(TextComponent::text("Don't stop moving!")); + } + } + + Ok(0) + } +} + +struct TntRunStatusHandler; +impl CommandHandler for TntRunStatusHandler { + fn handle( + &self, + sender: CommandSender, + _server: Server, + _args: ConsumedArgs, + ) -> Result { + let session_arc = get_session(); + let session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + let status_msg = match session.state { + GameState::Waiting => format!("TNT Run: WAITING ({} players queued)", session.queued_players.len()), + GameState::Active => format!( + "TNT Run: ACTIVE ({} alive: {:?})", + session.alive_players.len(), + session.alive_players + ), + GameState::Ended => "TNT Run: ENDED (Use /tntrun reset or /tntrun start)".to_string(), + }; + + sender.send_message(TextComponent::text(&status_msg)); + Ok(0) + } +} + +struct TntRunResetHandler; +impl CommandHandler for TntRunResetHandler { + fn handle( + &self, + sender: CommandSender, + server: Server, + _args: ConsumedArgs, + ) -> Result { + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + session.state = GameState::Waiting; + session.alive_players.clear(); + session.recently_decaying.clear(); + + // Regenerate floors using the first available world + let worlds = server.get_all_worlds(); + if let Some(world) = worlds.first() { + let flags = BlockFlags::NOTIFY_LISTENERS | BlockFlags::NOTIFY_NEIGHBORS; + for &y in &session.config.floor_y_levels { + for x in session.config.min_x..=session.config.max_x { + for z in session.config.min_z..=session.config.max_z { + let pos = BlockPos { x, y, z }; + world.set_block_by_name(pos, "minecraft:sand", flags); + let tnt_pos = BlockPos { x, y: y - 1, z }; + world.set_block_by_name(tnt_pos, "minecraft:tnt", flags); + } + } + } + } + + server.broadcast("TNT Run arena has been regenerated and reset!"); + sender.send_message(TextComponent::text("Arena reset complete!")); + Ok(0) + } +} + +// ----------------------------------------------------------------------------- +// Movement & Decay Event Listener +// ----------------------------------------------------------------------------- + +struct TntRunMoveListener; + +impl EventHandler for TntRunMoveListener { + fn handle(&self, server: Server, event: PlayerMoveEventData) -> PlayerMoveEventData { + let player_name = event.player.get_name(); + let session_arc = get_session(); + let mut session = session_arc.lock().unwrap_or_else(|e| e.into_inner()); + + // Only process movement if game is active and player is an alive participant + if session.state != GameState::Active || !session.alive_players.contains(&player_name) { + return event; + } + + let to = &event.to_position; + + // 1. Elimination Check: player fell below the elimination threshold + if to.1 < session.config.elimination_y { + session.alive_players.remove(&player_name); + server.broadcast(&format!("{player_name} fell into the void and was ELIMINATED!")); + + // Teleport eliminated player back to lobby + let lobby = session.config.lobby_pos; + let world = event.player.get_world(); + event.player.teleport(lobby, Some(0.0), Some(0.0), world); + event.player.show_title(TextComponent::text("ELIMINATED!")); + + // 2. Win Condition Check + if session.alive_players.len() == 1 { + let winner_name = session.alive_players.iter().next().cloned().unwrap(); + server.broadcast(&format!("VICTORY! {winner_name} is the last player standing and WINS TNT Run!")); + if let Some(winner) = server.get_player_by_name(&winner_name) { + winner.show_title(TextComponent::text("VICTORY!")); + winner.show_subtitle(TextComponent::text("You won TNT Run!")); + } + session.state = GameState::Ended; + } else if session.alive_players.is_empty() { + server.broadcast("Game Over! No players remaining."); + session.state = GameState::Ended; + } + + return event; + } + + // 3. Block Decay Mechanic: if inside arena XZ, decay the block under feet + if session.config.is_inside_xz(to.0, to.2) { + let bx = to.0.floor() as i32; + let by = (to.1 - 0.2).floor() as i32; + let bz = to.2.floor() as i32; + + // Only trigger decay if standing on one of the designated floor Y levels + if session.config.floor_y_levels.contains(&by) && session.recently_decaying.insert((bx, by, bz)) { + let world = event.player.get_world(); + let flags = BlockFlags::NOTIFY_LISTENERS | BlockFlags::NOTIFY_NEIGHBORS; + + // Turn sand and underlying TNT to air after delay + let session_clone = Arc::clone(&session_arc); + server.schedule_delayed_task(8, move |_| { + let pos = BlockPos { x: bx, y: by, z: bz }; + let tnt_pos = BlockPos { x: bx, y: by - 1, z: bz }; + world.set_block_by_name(pos, "minecraft:air", flags); + world.set_block_by_name(tnt_pos, "minecraft:air", flags); + + if let Ok(mut s) = session_clone.lock() { + s.recently_decaying.remove(&(bx, by, bz)); + } + }); + } + } + + event + } +} + +// ----------------------------------------------------------------------------- +// Plugin Lifecycle +// ----------------------------------------------------------------------------- + +pub struct TntRunPlugin; + +impl Plugin for TntRunPlugin { + fn new() -> Self { + TntRunPlugin + } + + fn metadata(&self) -> PluginMetadata { + PluginMetadata { + name: "tnt-run".into(), + version: env!("CARGO_PKG_VERSION").into(), + authors: vec!["Pumpkin Developer".into()], + description: "A complete TNT Run minigame for Pumpkin Minecraft server.".into(), + dependencies: vec![], + permissions: vec![], + } + } + + fn on_load(&self, context: Context) -> Result<(), String> { + let config = ArenaConfig::default(); + let session = Arc::new(Mutex::new(GameSession::new(config))); + *SESSION.lock().unwrap_or_else(|e| e.into_inner()) = Some(session); + + // Register /tntrun command tree + let names = ["tntrun".to_string(), "tr".to_string()]; + let command = Command::new(&names, "TNT Run minigame management command") + .then(CommandNode::literal("join").execute(TntRunJoinHandler)) + .then(CommandNode::literal("leave").execute(TntRunLeaveHandler)) + .then(CommandNode::literal("start").execute(TntRunStartHandler)) + .then(CommandNode::literal("status").execute(TntRunStatusHandler)) + .then(CommandNode::literal("reset").execute(TntRunResetHandler)) + .execute(TntRunRootHandler); + + context.register_command(command, "pumpkin.command.tntrun"); + + // Register PlayerMoveEvent listener + context.register_event_handler( + TntRunMoveListener, + EventPriority::Normal, + false, + )?; + + tracing::info!("TNT Run minigame plugin successfully loaded!"); + Ok(()) + } + + fn on_unload(&self, _context: Context) -> Result<(), String> { + tracing::info!("TNT Run plugin unloaded."); + Ok(()) + } +} + +register_plugin!(TntRunPlugin);