Skip to main content

pumpkinplus/
lib.rs

1//! PumpkinPlus is a [Pumpkin](https://github.com/Pumpkin-MC/Pumpkin) Minecraft plugin written in Rust
2//! that enhances the vanilla gameplay without replacing it.
3//!
4//! Every feature is modular and toggled via a JSON config file.
5//!
6//! ## Features
7//!
8//! | Category    | What it adds                                    |
9//! |-------------|-------------------------------------------------|
10//! | **Player**  | Custom join, leave, and kick messages           |
11//! | **Chat**    | Chat formatting and word filtering                |
12//! | **Tablist** | Dynamic tab list header/footer with placeholders |
13//! | **Locator** | Personalize locator bar color (`/locator`)        |
14//!
15//! ## Installation
16//!
17//! 1. Download the latest `pumpkinplus.wasm` from
18//!    [GitHub Releases](https://github.com/XodiumSoftware/PumpkinPlus/releases).
19//! 2. Drop it into your Pumpkin server's `plugins/` folder.
20//! 3. Start (or restart) the server.
21//!
22//! On first start, a `config.json` file is created in the plugin's data folder with all defaults.
23//! Edit it and restart to apply changes.
24//!
25//! ## Building
26//!
27//! ```bash
28//! cargo build --release --target wasm32-wasip2
29//! ```
30//!
31//! The output is at `target/wasm32-wasip2/release/pumpkinplus.wasm`.
32//!
33//! ## Viewing Documentation
34//!
35//! ```bash
36//! cargo doc --open
37//! ```
38//!
39//! # Configuration
40//!
41//! All settings live in `config.json` in the plugin's data folder.
42//! Each top-level key corresponds to one module.
43//!
44//! ## Placeholders
45//!
46//! String fields that are displayed as in-game messages support placeholders:
47//!
48//! | Placeholder | Replaced with              |
49//! |-------------|----------------------------|
50//! | `{player}`  | The player's in-game name  |
51//! | `{online}`  | Number of online players   |
52//! | `{tps}`     | Server TPS                 |
53//! | `{mspt}`    | Milliseconds per tick      |
54//! | `{message}` | The original chat message  |
55
56mod config;
57mod mirror_types {
58    pub mod entity_type;
59    pub mod gamemode;
60    pub mod interaction;
61}
62
63pub use mirror_types::entity_type::EntityType;
64pub use mirror_types::gamemode::GameMode;
65pub use mirror_types::interaction::InteractAction;
66
67mod modules {
68    pub mod recipes {
69        pub mod chainmail;
70        pub mod diamond_recycle;
71        pub mod painting;
72        pub mod recipe;
73        pub mod rotten_flesh;
74        pub mod wood_log;
75    }
76    pub mod mechanics {
77        pub mod mechanic;
78        pub mod entity {
79            pub mod bat;
80            pub mod griefing;
81            pub mod husk;
82            pub mod spawn_egg;
83            pub mod tameable;
84        }
85        pub mod world {
86            pub mod openable;
87        }
88        pub mod player {
89            pub mod enderchest;
90            pub mod head;
91            pub mod locator;
92            pub mod messages;
93            pub mod nickname;
94        }
95        pub mod server {
96            pub mod chat;
97            pub mod tablist;
98        }
99    }
100}
101
102pub use config::*;
103pub use modules::*;
104
105pub use modules::mechanics::entity::bat::BatConfig;
106pub use modules::mechanics::entity::griefing::GriefingConfig;
107pub use modules::mechanics::entity::husk::HuskConfig;
108pub use modules::mechanics::entity::spawn_egg::SpawnEggConfig;
109pub use modules::mechanics::entity::tameable::TameableConfig;
110pub use modules::mechanics::player::enderchest::EnderchestConfig;
111pub use modules::mechanics::player::head::HeadConfig;
112pub use modules::mechanics::player::messages::MessagesConfig;
113pub use modules::mechanics::player::nickname::NicknameConfig;
114pub use modules::mechanics::server::chat::ChatConfig;
115pub use modules::mechanics::server::tablist::TablistConfig;
116pub use modules::mechanics::world::openable::OpenableConfig;
117
118// use crate::mechanics::entity::bat::Bat;
119// use crate::mechanics::entity::griefing::Griefing;
120// use crate::mechanics::player::enderchest::Enderchest;
121use crate::mechanics::player::nickname::Nickname;
122// use crate::mechanics::player::locator::Locator;
123use crate::mechanics::player::messages::Messages;
124// use crate::mechanics::world::openable::Openable;
125use crate::mechanics::mechanic::Mechanic;
126use crate::mechanics::server::chat::Chat;
127use crate::mechanics::server::tablist::Tablist;
128use crate::modules::recipes::chainmail::Chainmail;
129use crate::modules::recipes::diamond_recycle::DiamondRecycle;
130use crate::modules::recipes::painting::Painting;
131use crate::modules::recipes::recipe::Recipe;
132use crate::modules::recipes::rotten_flesh::RottenFlesh;
133use crate::modules::recipes::wood_log::WoodLog;
134use pumpkin_plugin_api::{Context, Plugin, PluginMetadata};
135use std::time::Instant;
136use tracing::info;
137
138pub const PLUGIN_ID: &str = env!("CARGO_PKG_NAME");
139
140pub struct PumpkinPlus {}
141
142impl Plugin for PumpkinPlus {
143    fn new() -> Self {
144        PumpkinPlus {}
145    }
146
147    fn metadata(&self) -> PluginMetadata {
148        PluginMetadata {
149            name: PLUGIN_ID.into(),
150            version: env!("CARGO_PKG_VERSION").into(),
151            authors: env!("CARGO_PKG_AUTHORS")
152                .split(':')
153                .map(Into::into)
154                .collect(),
155            description: env!("CARGO_PKG_DESCRIPTION").into(),
156            dependencies: vec![],
157            permissions: vec![
158                pumpkin_plugin_api::permissions::FS_READ_DATA.into(),
159                pumpkin_plugin_api::permissions::FS_WRITE_DATA.into(),
160            ],
161        }
162    }
163
164    fn on_load(&mut self, context: Context) -> pumpkin_plugin_api::Result<()> {
165        let mut manager = ConfigManager::empty();
166
167        // manager.register::<BatConfig>();
168        // manager.register::<GriefingConfig>();
169        // manager.register::<HuskConfig>();
170        // manager.register::<SpawnEggConfig>();
171        // manager.register::<TameableConfig>();
172        // manager.register::<EnderchestConfig>();
173        // manager.register::<HeadConfig>();
174        manager.register::<NicknameConfig>();
175        // manager.register::<LocatorConfig>();
176        manager.register::<MessagesConfig>();
177        manager.register::<ChatConfig>();
178        manager.register::<TablistConfig>();
179        // manager.register::<OpenableConfig>();
180
181        manager.finalize(&context);
182
183        // let bat = Bat;
184        // let griefing = Griefing;
185        // let husk = Husk;
186        // let spawn_egg = SpawnEgg;
187        // let tameable = Tameable;
188        // let enderchest = Enderchest;
189        // let head = Head;
190        let nickname = Nickname;
191        // let locator = Locator;
192        let messages = Messages;
193        let chat = Chat;
194        let tablist = Tablist;
195        // let openable = Openable;
196        let modules: Vec<&dyn Mechanic> = vec![
197            // &bat,
198            // &griefing,
199            // &husk,
200            // &spawn_egg,
201            // &tameable,
202            // &enderchest,
203            // &head,
204            &nickname, // &locator,
205            &messages, &chat, &tablist,
206            // &openable,
207        ];
208        let enabled_count = modules.iter().filter(|m| m.enabled()).count();
209
210        let mut total_ms = 0u128;
211        for module in modules {
212            let start = Instant::now();
213            module.register(&context);
214            total_ms += start.elapsed().as_millis();
215        }
216
217        info!(
218            "Registered: {} module(s) | Took {}ms",
219            enabled_count, total_ms
220        );
221
222        // Recipe registration (no config toggles yet — always on)
223        let recipes: Vec<&dyn Recipe> = vec![
224            &Chainmail,
225            &DiamondRecycle,
226            &Painting,
227            &RottenFlesh,
228            &WoodLog,
229        ];
230
231        let mut _recipe_total_ms = 0u128;
232        for recipe in recipes {
233            let start = Instant::now();
234            recipe.register();
235            _recipe_total_ms += start.elapsed().as_millis();
236        }
237        info!("Pumpkin+ loaded. NICE TO CYA!");
238        Ok(())
239    }
240
241    fn on_unload(&mut self, _context: Context) -> pumpkin_plugin_api::Result<()> {
242        info!("Pumpkin+ unloaded. CYA NEXT TIME!");
243        Ok(())
244    }
245}
246
247pumpkin_plugin_api::register_plugin!(PumpkinPlus);