update: docs
This commit is contained in:
parent
091c33d073
commit
102bd0329c
@ -16,3 +16,4 @@ extend-exclude = [
|
||||
|
||||
[default.extend-words]
|
||||
Iinclude = "Iinclude"
|
||||
ser = "ser"
|
||||
|
||||
128
CONTRIBUTING.md
128
CONTRIBUTING.md
@ -1,43 +1,109 @@
|
||||
# Contributing to Pumpkin
|
||||
# Contributing to Pumpkin Documentation
|
||||
|
||||
We appreciate your interest in contributing to Pumpkin! This document outlines the guidelines for submitting bug reports, feature suggestions, and code changes.
|
||||
We appreciate your interest in contributing to the Pumpkin documentation! High-quality documentation helps developers, server administrators, and plugin creators understand and adopt Pumpkin.
|
||||
|
||||
> [!NOTE]
|
||||
> - **Looking to contribute to the Pumpkin server codebase?** Please consult the **[Pumpkin Core Developer & Contributing Guide](https://pumpkinmc.org/developer/contributing)** or the [`CONTRIBUTING.md`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/CONTRIBUTING.md) in the main Pumpkin repository.
|
||||
> - **Looking to contribute to documentation or translations?** You are in the right place! Read on below.
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
The easiest way to get started is by asking for help in [our Discord server](https://discord.gg/wT8XjrjKkf).
|
||||
Join our community and ask questions in [our Discord server](https://discord.gg/wT8XjrjKkf) in the `#development` channel.
|
||||
|
||||
### How to Contribute
|
||||
### Ways to Contribute
|
||||
|
||||
There are several ways you can contribute to Pumpkin:
|
||||
- **Fixing Typos & Improving Phrasing**: Clarify instructions, fix grammar, or fix dead links across any documentation page.
|
||||
- **Documenting New Features**: Add or update documentation as new features land in Pumpkin.
|
||||
- **Adding Examples**: Add code snippets, configuration examples, and tutorials.
|
||||
- **Adding or Updating Translations**: Help localize Pumpkin documentation for users worldwide.
|
||||
|
||||
- **Reporting Bugs**:
|
||||
If you encounter a bug, please search for existing issues on the issue tracker first.
|
||||
If you can't find a duplicate issue, open a new one.
|
||||
Provide a clear description of the bug, including steps to reproduce it if possible.
|
||||
Screenshots, logs, or code snippets can also be helpful.
|
||||
- **Suggesting Features**:
|
||||
Do you have an idea on how Pumpkin can be improved? Share your thoughts by opening an issue on the issue tracker.
|
||||
Describe the proposed feature in detail, including its benefits and potential implementation considerations.
|
||||
- **Submitting Pull Requests**:
|
||||
If you'd like to contribute code changes, fork the Pumpkin repository on GitHub.
|
||||
Install Rust at [rust-lang.org](https://www.rust-lang.org/).
|
||||
Make your changes on your local fork and create a pull request to the main repository.
|
||||
Ensure your code adheres to our project structure and style guidelines.
|
||||
Write clear and concise commit messages that describe your changes.
|
||||
---
|
||||
|
||||
### Adding a Translation
|
||||
## Local Development Setup
|
||||
|
||||
Below are instructions for adding a new language translation for the documentation.
|
||||
This documentation site is built with [VitePress](https://vitepress.dev/) and powered by Node.js / Bun.
|
||||
|
||||
1. Create a new locale file in `docs/.vitepress/`. (e.g. `fr.ts` for a French translation)
|
||||
2. Translate the content and update all links with the appropriate language code (refer to the Dutch translation for an example).
|
||||
3. Register the new locale in `docs/.vitepress/config.ts`.
|
||||
4. Duplicate English documentation folder (`docs/en/`) and rename it to your language's ISO code. (e.g. `en`,`nl`)
|
||||
5. Translate all documentation files in the newly created folder.
|
||||
6. Configure the typo check to skip the translation files in `.typos.toml`.
|
||||
### 1. Prerequisites
|
||||
|
||||
### Additional Information
|
||||
- [Node.js](https://nodejs.org/) (v18+) or [Bun](https://bun.sh/)
|
||||
- [Git](https://git-scm.com/)
|
||||
|
||||
We encourage you to comment on existing issues and pull requests to share your thoughts and provide feedback.
|
||||
Feel free to ask questions in the issue tracker or reach out to the project maintainers if you need assistance.
|
||||
Before submitting a large contribution, consider opening an issue or discussion, or talk with us on our Discord to discuss your approach.
|
||||
### 2. Setup & Running
|
||||
|
||||
```bash
|
||||
# Clone the documentation repository
|
||||
git clone https://github.com/Pumpkin-MC/Pumpkin-Docs.git
|
||||
cd Pumpkin-Docs
|
||||
|
||||
# Install dependencies
|
||||
bun install
|
||||
# or: npm install
|
||||
|
||||
# Start local development server with hot-reload
|
||||
bun run dev
|
||||
# or: npm run dev
|
||||
```
|
||||
|
||||
Visit the displayed localhost address (typically `http://localhost:5173`) in your browser.
|
||||
|
||||
### 3. Verification
|
||||
|
||||
Before submitting a pull request, verify that the documentation builds cleanly and has no typos:
|
||||
|
||||
```bash
|
||||
# Verify static build passes
|
||||
bun run build
|
||||
# or: npx vitepress build docs
|
||||
|
||||
# Check for spelling errors
|
||||
typos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding a Translation
|
||||
|
||||
To add a new language translation:
|
||||
|
||||
1. Create a new locale configuration file in `docs/.vitepress/` (e.g., `fr.ts` for French).
|
||||
2. Register the new locale in `docs/.vitepress/config.ts`.
|
||||
3. Duplicate the English documentation folder (`docs/en/`) and rename it to your language's ISO code (e.g., `docs/fr/`).
|
||||
4. Translate all markdown files in the newly created folder, updating links to preserve the locale prefix.
|
||||
5. Add the translation folder to the exclude list in `.typos.toml` if your language uses non-English words.
|
||||
6. Run `bun run build` to verify that there are no broken links or syntax errors.
|
||||
|
||||
---
|
||||
|
||||
## Pull Request & Commit Guidelines
|
||||
|
||||
All contributions submitted via Pull Requests to the Pumpkin documentation repository must follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
|
||||
|
||||
### Format
|
||||
|
||||
```text
|
||||
<type>(<optional scope>): <imperative summary>
|
||||
```
|
||||
|
||||
### Allowed Types
|
||||
|
||||
| Type | Purpose | Example |
|
||||
| :--- | :--- | :--- |
|
||||
| `docs` | Documentation additions, revisions, or expansions | `docs(entities): document mob goal selector` |
|
||||
| `fix` | Correcting typos, dead links, or factual errors | `fix(proxy): fix velocity forwarding port reference` |
|
||||
| `feat` | Adding a completely new guide, section, or interactive component | `feat(commands): add Brigadier execution context guide` |
|
||||
| `refactor` | Restructuring files or configs without content changes | `refactor(locales): add explicit ts extensions to imports` |
|
||||
| `chore` | Tooling, dependency, or build configuration updates | `chore(deps): update vitepress` |
|
||||
|
||||
### Key Rules
|
||||
|
||||
- **Use Imperative Mood**: Write `add`, not `added` or `adds`; `fix`, not `fixed` or `fixes`.
|
||||
- **Lowercase**: Start descriptions with a lowercase letter and omit trailing punctuation.
|
||||
- **PR Title**: Ensure your GitHub Pull Request title follows the Conventional Commits format, as it is used for automated squash commits and release changelogs.
|
||||
|
||||
---
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
import { en } from "./en";
|
||||
import { nl } from "./nl";
|
||||
import { pt } from "./pt";
|
||||
import { zhCN } from "./zh_cn";
|
||||
import { de } from "./de";
|
||||
import { trTR } from "./tr_tr";
|
||||
import { jaJP } from "./ja_jp";
|
||||
import { en } from "./en.ts";
|
||||
import { nl } from "./nl.ts";
|
||||
import { pt } from "./pt.ts";
|
||||
import { zhCN } from "./zh_cn.ts";
|
||||
import { de } from "./de.ts";
|
||||
import { trTR } from "./tr_tr.ts";
|
||||
import { jaJP } from "./ja_jp.ts";
|
||||
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: "Pumpkin",
|
||||
title: "Pumpkin Docs",
|
||||
|
||||
themeConfig: {
|
||||
siteTitle: "Pumpkin Docs",
|
||||
},
|
||||
|
||||
cleanUrls: true,
|
||||
rewrites: {
|
||||
@ -52,6 +56,10 @@ export default defineConfig({
|
||||
|
||||
["meta", { name: "apple-mobile-web-app-title", content: "Pumpkin" }],
|
||||
|
||||
["link", { rel: "preconnect", href: "https://fonts.googleapis.com" }],
|
||||
["link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: "" }],
|
||||
["link", { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" }],
|
||||
|
||||
["script", { async: '', src: "https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX" }],
|
||||
['script', {}, `window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
|
||||
@ -19,9 +19,7 @@ export const de = defineConfig({
|
||||
{
|
||||
text: "Über",
|
||||
items: [
|
||||
{ text: "Benchmarks", link: "/de/about/benchmarks" },
|
||||
{ text: "Helfen & Spenden", link: "/de/about/helping" },
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -5,13 +5,26 @@ export const en = defineConfig({
|
||||
description: "A High-performance Minecraft server software written in Rust",
|
||||
|
||||
themeConfig: {
|
||||
siteTitle: "Pumpkin Docs",
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
search: {
|
||||
provider: "local",
|
||||
},
|
||||
nav: [
|
||||
{
|
||||
text: "Home",
|
||||
text: "Administration",
|
||||
link: "/admin/introduction",
|
||||
},
|
||||
{
|
||||
text: "Contributors",
|
||||
link: "/developer/introduction",
|
||||
},
|
||||
{
|
||||
text: "Plugins",
|
||||
link: "/plugin-dev/introduction",
|
||||
},
|
||||
{
|
||||
text: "Website",
|
||||
link: "https://pumpkinmc.org/",
|
||||
},
|
||||
],
|
||||
@ -19,53 +32,128 @@ export const en = defineConfig({
|
||||
{
|
||||
text: "About",
|
||||
items: [
|
||||
{ text: "Benchmarks", link: "/about/benchmarks" },
|
||||
{ text: "Helping & Donating", link: "/about/helping" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Administration",
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: "Overview", link: "/admin/introduction" },
|
||||
{ text: "Migrating from Bukkit", link: "/admin/migrating-from-bukkit" },
|
||||
{
|
||||
text: "Configuration",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "Introduction", link: "/config/introduction" },
|
||||
{ text: "Basic", link: "/config/basic" },
|
||||
{ text: "World", link: "/config/world" },
|
||||
{ text: "Chat & Anti-Spam", link: "/config/chat" },
|
||||
{ text: "Commands", link: "/config/commands" },
|
||||
{ text: "Bedrock & NetherNet", link: "/config/bedrock" },
|
||||
{ text: "Proxy", link: "/config/proxy" },
|
||||
{ text: "Authentication", link: "/config/authentication" },
|
||||
{ text: "Packet Limiter", link: "/config/packet-limiter" },
|
||||
{ text: "Compression", link: "/config/compression" },
|
||||
{ text: "Resource Pack", link: "/config/resource-pack" },
|
||||
{ text: "Plugins", link: "/config/plugins" },
|
||||
{ text: "Server Links", link: "/config/server-links" },
|
||||
{ text: "Player Data", link: "/config/player-data" },
|
||||
{ text: "Logging", link: "/config/logging" },
|
||||
{ text: "Query", link: "/config/query" },
|
||||
{ text: "RCON", link: "/config/rcon" },
|
||||
{ text: "PVP", link: "/config/pvp" },
|
||||
{ text: "LAN Broadcast", link: "/config/lan-broadcast" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Configuration",
|
||||
text: "Contributors",
|
||||
items: [
|
||||
{ text: "Introduction", link: "/config/introduction" },
|
||||
{ text: "Basic", link: "/config/basic" },
|
||||
{ text: "World", link: "/config/world" },
|
||||
{ text: "Chat & Anti-Spam", link: "/config/chat" },
|
||||
{ text: "Commands", link: "/config/commands" },
|
||||
{ text: "Bedrock & NetherNet", link: "/config/bedrock" },
|
||||
{ text: "Proxy", link: "/config/proxy" },
|
||||
{ text: "Authentication", link: "/config/authentication" },
|
||||
{ text: "Packet Limiter", link: "/config/packet-limiter" },
|
||||
{ text: "Compression", link: "/config/compression" },
|
||||
{ text: "Resource Pack", link: "/config/resource-pack" },
|
||||
{ text: "Plugins", link: "/config/plugins" },
|
||||
{ text: "Server Links", link: "/config/server-links" },
|
||||
{ text: "Player Data", link: "/config/player-data" },
|
||||
{ text: "Logging", link: "/config/logging" },
|
||||
{ text: "Query", link: "/config/query" },
|
||||
{ text: "RCON", link: "/config/rcon" },
|
||||
{ text: "PVP", link: "/config/pvp" },
|
||||
{ text: "LAN Broadcast", link: "/config/lan-broadcast" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Developers",
|
||||
items: [
|
||||
{ text: "Contributing", link: "/developer/contributing" },
|
||||
{ text: "Introduction", link: "/developer/introduction" },
|
||||
{ text: "Contributing Guide", link: "/developer/contributing" },
|
||||
{ text: "Architecture", link: "/developer/architecture" },
|
||||
{ text: "Data Extractor", link: "/developer/extractor" },
|
||||
{ text: "Code Generation", link: "/developer/codegen" },
|
||||
{ text: "Porting Versions", link: "/developer/porting-versions" },
|
||||
{
|
||||
text: "Networking",
|
||||
link: "/developer/networking/networking",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Authentication",
|
||||
link: "/developer/networking/authentication",
|
||||
text: "Introduction",
|
||||
link: "/developer/networking/",
|
||||
},
|
||||
{
|
||||
text: "Metadata & Synced Data",
|
||||
link: "/developer/networking/metadata",
|
||||
},
|
||||
{
|
||||
text: "Java Edition",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Overview",
|
||||
link: "/developer/networking/java/overview",
|
||||
},
|
||||
{
|
||||
text: "Adding a Packet",
|
||||
link: "/developer/networking/java/adding-packets",
|
||||
},
|
||||
{
|
||||
text: "Authentication",
|
||||
link: "/developer/networking/java/authentication",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Bedrock Edition",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Overview",
|
||||
link: "/developer/networking/bedrock/overview",
|
||||
},
|
||||
{
|
||||
text: "Adding a Packet",
|
||||
link: "/developer/networking/bedrock/adding-packets",
|
||||
},
|
||||
{
|
||||
text: "Authentication",
|
||||
link: "/developer/networking/bedrock/authentication",
|
||||
},
|
||||
{
|
||||
text: "NetherNet",
|
||||
link: "/developer/networking/bedrock/nethernet",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Proxy Forwarding",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Overview",
|
||||
link: "/developer/networking/proxy/",
|
||||
},
|
||||
{
|
||||
text: "BungeeCord",
|
||||
link: "/developer/networking/proxy/bungeecord",
|
||||
},
|
||||
{
|
||||
text: "Velocity",
|
||||
link: "/developer/networking/proxy/velocity",
|
||||
},
|
||||
{
|
||||
text: "Vine",
|
||||
link: "/developer/networking/proxy/vine",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Query (GameSpy4)",
|
||||
link: "/developer/networking/query",
|
||||
},
|
||||
{
|
||||
text: "RCON",
|
||||
@ -73,8 +161,52 @@ export const en = defineConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
{ text: "World", link: "/developer/world" },
|
||||
{ text: "Mobile dev", link: "/developer/mobile" },
|
||||
{
|
||||
text: "Blocks",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "Overview", link: "/developer/blocks/" },
|
||||
{ text: "Adding a Block", link: "/developer/blocks/adding-blocks" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Items",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "Overview", link: "/developer/items/" },
|
||||
{ text: "Adding an Item", link: "/developer/items/adding-items" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Entities",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "Overview & Hierarchy", link: "/developer/entities/" },
|
||||
{ text: "Spawning & Tracking", link: "/developer/entities/spawning-and-tracking" },
|
||||
{ text: "Mob AI & Goals", link: "/developer/entities/ai-and-mobs" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Commands",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "Overview & Dispatcher", link: "/developer/commands/" },
|
||||
{ text: "Adding a Command", link: "/developer/commands/adding-commands" },
|
||||
{ text: "Arguments & Suggestions", link: "/developer/commands/arguments-and-suggestions" },
|
||||
{ text: "Execution & Context", link: "/developer/commands/execution-and-context" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Plugin Engine",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "Overview", link: "/developer/plugins/" },
|
||||
{ text: "Adding a Plugin Loader", link: "/developer/plugins/loaders" },
|
||||
{ text: "WASM Signing & Verification", link: "/developer/plugins/wasm-signing" },
|
||||
],
|
||||
},
|
||||
{ text: "World Engine", link: "/developer/world" },
|
||||
{ text: "Mobile Development", link: "/developer/mobile" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -86,7 +218,7 @@ export const en = defineConfig({
|
||||
},
|
||||
{
|
||||
text: "Migrating from Bukkit",
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Overview",
|
||||
@ -112,7 +244,7 @@ export const en = defineConfig({
|
||||
},
|
||||
{
|
||||
text: "Rust",
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Creating Project",
|
||||
@ -147,7 +279,7 @@ export const en = defineConfig({
|
||||
},
|
||||
{
|
||||
text: "Python",
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Quick Start",
|
||||
@ -169,7 +301,7 @@ export const en = defineConfig({
|
||||
},
|
||||
{
|
||||
text: "C#",
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Quick Start",
|
||||
@ -183,7 +315,7 @@ export const en = defineConfig({
|
||||
},
|
||||
{
|
||||
text: "C",
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Quick Start",
|
||||
@ -197,7 +329,7 @@ export const en = defineConfig({
|
||||
},
|
||||
{
|
||||
text: "Go",
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Quick Start",
|
||||
@ -215,7 +347,7 @@ export const en = defineConfig({
|
||||
},
|
||||
{
|
||||
text: "Kotlin",
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Quick Start",
|
||||
@ -234,7 +366,7 @@ export const en = defineConfig({
|
||||
items: [
|
||||
{
|
||||
text: "Common Issues",
|
||||
link: "/troubleshooting/common_issues.md",
|
||||
link: "/troubleshooting/common_issues",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -19,9 +19,7 @@ export const jaJP = defineConfig({
|
||||
{
|
||||
text: "概要",
|
||||
items: [
|
||||
{ text: "ベンチマーク", link: "/ja_jp/about/benchmarks" },
|
||||
{ text: "ヘルプと寄付", link: "/ja_jp/about/helping" },
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -19,9 +19,7 @@ export const nl = defineConfig({
|
||||
{
|
||||
text: "Over",
|
||||
items: [
|
||||
{ text: "Benchmarktests", link: "/nl/about/benchmarks" },
|
||||
{ text: "Helpen & Doneren", link: "/nl/about/helping" },
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -19,9 +19,7 @@ export const pt = defineConfig({
|
||||
{
|
||||
text: "Sobre",
|
||||
items: [
|
||||
{ text: "Benchmark", link: "/pt/about/benchmarks" },
|
||||
{ text: "Ajudar e Doar", link: "/pt/about/helping" },
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,107 +1,107 @@
|
||||
/**
|
||||
* Customize default theme styling by overriding CSS variables:
|
||||
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
|
||||
* Pumpkin Documentation Theme Styling
|
||||
* High-performance, clean developer theme for Pumpkin Minecraft Server.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Colors
|
||||
*
|
||||
* Each colors have exact same color scale system with 3 levels of solid
|
||||
* colors with different brightness, and 1 soft color.
|
||||
*
|
||||
* - `XXX-1`: The most solid color used mainly for colored text. It must
|
||||
* satisfy the contrast ratio against when used on top of `XXX-soft`.
|
||||
*
|
||||
* - `XXX-2`: The color used mainly for hover state of the button.
|
||||
*
|
||||
* - `XXX-3`: The color for solid background, such as bg color of the button.
|
||||
* It must satisfy the contrast ratio with pure white (#ffffff) text on
|
||||
* top of it.
|
||||
*
|
||||
* - `XXX-soft`: The color used for subtle background such as custom container
|
||||
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
|
||||
* on top of it.
|
||||
*
|
||||
* The soft color must be semi transparent alpha channel. This is crucial
|
||||
* because it allows adding multiple "soft" colors on top of each other
|
||||
* to create a accent, such as when having inline code block inside
|
||||
* custom containers.
|
||||
*
|
||||
* - `default`: The color used purely for subtle indication without any
|
||||
* special meanings attched to it such as bg color for menu hover state.
|
||||
*
|
||||
* - `brand`: Used for primary brand colors, such as link text, button with
|
||||
* brand theme, etc.
|
||||
*
|
||||
* - `tip`: Used to indicate useful information. The default theme uses the
|
||||
* brand color for this by default.
|
||||
*
|
||||
* - `warning`: Used to indicate warning to the users. Used in custom
|
||||
* container, badges, etc.
|
||||
*
|
||||
* - `danger`: Used to show error, or dangerous message to the users. Used
|
||||
* in custom container, badges, etc.
|
||||
* -------------------------------------------------------------------------- */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
|
||||
|
||||
:root {
|
||||
--vp-c-default-1: var(--vp-c-gray-1);
|
||||
--vp-c-default-2: var(--vp-c-gray-2);
|
||||
--vp-c-default-3: var(--vp-c-gray-3);
|
||||
--vp-c-default-soft: var(--vp-c-gray-soft);
|
||||
/* ==========================================================================
|
||||
Base Theme Variables & Brand Palette
|
||||
========================================================================== */
|
||||
|
||||
--vp-c-brand-1: #ff9800;
|
||||
--vp-c-brand-2: #fb8c00;
|
||||
--vp-c-brand-3: #f57c00;
|
||||
--vp-c-brand-soft: rgba(255, 152, 0, .16);
|
||||
:root {
|
||||
--vp-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
--vp-font-family-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
|
||||
/* Refined Pumpkin Brand Colors (Warm Amber & Tangerine) */
|
||||
--vp-c-brand-1: #f97316;
|
||||
--vp-c-brand-2: #ea580c;
|
||||
--vp-c-brand-3: #c2410c;
|
||||
--vp-c-brand-soft: rgba(249, 115, 22, 0.14);
|
||||
|
||||
--vp-c-tip-1: var(--vp-c-brand-1);
|
||||
--vp-c-tip-2: var(--vp-c-brand-2);
|
||||
--vp-c-tip-3: var(--vp-c-brand-3);
|
||||
--vp-c-tip-soft: var(--vp-c-brand-soft);
|
||||
|
||||
--vp-c-warning-1: var(--vp-c-yellow-1);
|
||||
--vp-c-warning-2: var(--vp-c-yellow-2);
|
||||
--vp-c-warning-3: var(--vp-c-yellow-3);
|
||||
--vp-c-warning-soft: var(--vp-c-yellow-soft);
|
||||
--vp-c-warning-1: #eab308;
|
||||
--vp-c-warning-2: #ca8a04;
|
||||
--vp-c-warning-3: #a16207;
|
||||
--vp-c-warning-soft: rgba(234, 179, 8, 0.14);
|
||||
|
||||
--vp-c-danger-1: var(--vp-c-red-1);
|
||||
--vp-c-danger-2: var(--vp-c-red-2);
|
||||
--vp-c-danger-3: var(--vp-c-red-3);
|
||||
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||
--vp-c-danger-1: #ef4444;
|
||||
--vp-c-danger-2: #dc2626;
|
||||
--vp-c-danger-3: #b91c1c;
|
||||
--vp-c-danger-soft: rgba(239, 68, 68, 0.14);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
/* ==========================================================================
|
||||
Deep Obsidian Dark Mode
|
||||
========================================================================== */
|
||||
|
||||
.dark {
|
||||
--vp-c-bg: #0c0d0e;
|
||||
--vp-c-bg-alt: #121316;
|
||||
--vp-c-bg-elv: #181a1f;
|
||||
--vp-c-bg-soft: rgba(255, 255, 255, 0.04);
|
||||
--vp-c-divider: rgba(255, 255, 255, 0.08);
|
||||
--vp-c-gutter: #0c0d0e;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Typography Polish
|
||||
========================================================================== */
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.vp-doc h1 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.vp-doc h2 {
|
||||
font-weight: 600;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.vp-doc h3 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Component: Button
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
--vp-button-brand-border: transparent;
|
||||
--vp-button-brand-text: var(--vp-c-white);
|
||||
--vp-button-brand-bg: var(--vp-c-brand-3);
|
||||
--vp-button-brand-text: #ffffff;
|
||||
--vp-button-brand-bg: var(--vp-c-brand-2);
|
||||
--vp-button-brand-hover-border: transparent;
|
||||
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
|
||||
--vp-button-brand-hover-text: #ffffff;
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-1);
|
||||
--vp-button-brand-active-border: transparent;
|
||||
--vp-button-brand-active-text: var(--vp-c-white);
|
||||
--vp-button-brand-active-bg: var(--vp-c-brand-1);
|
||||
--vp-button-brand-active-text: #ffffff;
|
||||
--vp-button-brand-active-bg: var(--vp-c-brand-3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Home
|
||||
* -------------------------------------------------------------------------- */
|
||||
/* ==========================================================================
|
||||
Component: Home Hero
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||
120deg,
|
||||
#ff9800 30%,
|
||||
#fb8c00
|
||||
#f97316 30%,
|
||||
#fb923c
|
||||
);
|
||||
|
||||
--vp-home-hero-image-background-image: linear-gradient(
|
||||
-45deg,
|
||||
#ff9800 50%,
|
||||
#fb8c00 50%
|
||||
#f97316 50%,
|
||||
#ea580c 50%
|
||||
);
|
||||
--vp-home-hero-image-filter: blur(44px);
|
||||
}
|
||||
@ -118,22 +118,100 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Custom Block
|
||||
* -------------------------------------------------------------------------- */
|
||||
/* ==========================================================================
|
||||
Structured Tables
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
--vp-custom-block-tip-border: transparent;
|
||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-indigo-soft);
|
||||
--vp-custom-block-tip-code-bg: var(--vp-c-indigo-soft);
|
||||
.vp-doc table {
|
||||
width: 100%;
|
||||
display: table;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
margin: 20px 0;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
font-size: 13.5px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Algolia
|
||||
* -------------------------------------------------------------------------- */
|
||||
.vp-doc th {
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.vp-doc td {
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
transition: background-color 0.15s ease;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.vp-doc tr:first-child td {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.vp-doc tr:hover td {
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Code Blocks & Inline Monospace
|
||||
========================================================================== */
|
||||
|
||||
.vp-doc :not(pre) > code {
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 0.85em;
|
||||
padding: 2.5px 6px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.vp-doc div[class*='language-'] {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vp-code-group .tabs {
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Rounded Pill Badges
|
||||
========================================================================== */
|
||||
|
||||
.VPBadge {
|
||||
border-radius: 9999px !important;
|
||||
font-size: 10.5px !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.04em !important;
|
||||
padding: 2px 8px !important;
|
||||
line-height: 16px !important;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Component: Custom Block / Callouts
|
||||
========================================================================== */
|
||||
|
||||
.vp-doc .custom-block {
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Component: Algolia / Local Search
|
||||
========================================================================== */
|
||||
|
||||
.DocSearch {
|
||||
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ export const trTR = defineConfig({
|
||||
{
|
||||
text: "Hakkında",
|
||||
items: [
|
||||
{ text: "Kıyaslamalar", link: "/tr_TR/about/benchmarks" },
|
||||
{ text: "Yardım & Bağış", link: "/tr_TR/about/helping" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -19,9 +19,7 @@ export const zhCN = defineConfig({
|
||||
{
|
||||
text: "关于",
|
||||
items: [
|
||||
{ text: "性能基准", link: "/zh_cn/about/benchmarks" },
|
||||
{ text: "帮助与捐赠", link: "/zh_cn/about/helping" },
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,187 +0,0 @@
|
||||
# Benchmarks
|
||||
|
||||
Hier wird gängige Minecraft‑Serversoftware mit Pumpkin verglichen.
|
||||
|
||||
> [!CAUTION]
|
||||
> **Dieser Vergleich ist nicht fair.** Pumpkin verfügt derzeit über deutlich weniger Features als andere Server, was den Ressourcenverbrauch geringer erscheinen lassen kann.
|
||||
> Es ist außerdem zu berücksichtigen, dass andere Server jahrelang optimiert wurden.
|
||||
> Vanilla‑Forks, die die gesamte Vanilla‑Logik nicht neu implementieren müssen, können sich ausschließlich auf Optimierungen konzentrieren.
|
||||
|
||||

|
||||
|
||||
## Spezifikationen
|
||||
|
||||
### Technik
|
||||
|
||||
#### Software
|
||||
|
||||
- Distribution: Manjaro Linux
|
||||
- Architektur: x86_64 (64‑bit)
|
||||
- Kernel‑Version: 6.11.3-arch1-1
|
||||
|
||||
#### Hardware
|
||||
|
||||
- Mainboard: MAG B650 TOMAHAWK WIFI
|
||||
- CPU: AMD Ryzen 7600X 6‑Kern
|
||||
- RAM: Corsair 2x16GB DDR5 6000Mhz
|
||||
- Speicher: Samsung 990 PRO 1TB PCIe 4.0 M.2 SSD
|
||||
- Kühlung: be quiet Dark Rock Elite
|
||||
|
||||
#### Rust
|
||||
|
||||
- Toolchain: stable-x86_64-unknown-linux-gnu (1.81.0)
|
||||
- Rust‑Compiler: rustc 1.81.0 (eeb90cda1 2024-09-04)
|
||||
|
||||
#### Java
|
||||
|
||||
- JDK‑Version: OpenJDK 23 64‑Bit 2024-09-17
|
||||
- JRE‑Version: OpenJDK Runtime Environment (Build 23+37)
|
||||
- Anbieter: Oracle
|
||||
|
||||
#### Spiel
|
||||
|
||||
- Minecraft‑Version: 1.21.1
|
||||
- Sichtweite: 10
|
||||
- Simulationsdistanz: 10
|
||||
- Online‑Modus: false
|
||||
- RCON: false
|
||||
|
||||
<sub><sup>Der Online‑Modus wurde für einfacheres Testen mit Non‑Premium‑Accounts deaktiviert.</sup></sub>
|
||||
|
||||
> [!NOTE]
|
||||
> Alle Tests wurden für genauere Ergebnisse mehrfach ausgeführt.
|
||||
> Alle Spieler blieben nach dem Spawnen stehen. Nur die initialen 8 Chunks wurden geladen.
|
||||
> Alle Server nutzten ihre eigene Terrain‑Generierung. Es wurde keine Welt vorab geladen.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `CPU Max` ist mit einem Spieler meist höher, da die initialen Chunks geladen werden.
|
||||
|
||||
## Pumpkin
|
||||
|
||||
Build: [8febc50](https://github.com/Snowiiii/Pumpkin/commit/8febc5035d5611558c13505b7724e6ca284e0ada)
|
||||
|
||||
Compile‑Args: `--release`
|
||||
|
||||
Run‑Args:
|
||||
|
||||
**Dateigröße:** <FmtNum :n=12.3 />MB
|
||||
|
||||
**Startzeit:** <FmtNum :n=8 />ms
|
||||
|
||||
**Stopzeit:** <FmtNum :n=0 />ms
|
||||
|
||||
| Spieler | RAM | CPU Idle | CPU Max |
|
||||
| ------- | ---------------------- | ----------------- | ------------------- |
|
||||
| 0 | <FmtNum :n=392.2 />KB | <FmtNum :n=0 />% | <FmtNum :n=0 />% |
|
||||
| 1 | <FmtNum :n=24.9 />MB | <FmtNum :n=0 />% | <FmtNum :n=4 />% |
|
||||
| 2 | <FmtNum :n=25.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=0.6 />% |
|
||||
| 5 | <FmtNum :n=26 />MB | <FmtNum :n=0 />% | <FmtNum :n=1 />% |
|
||||
| 10 | <FmtNum :n=27.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=1.5 />% |
|
||||
|
||||
<sub><sup>Pumpkin cached bereits geladene Chunks, wodurch außer Spielerdaten kein zusätzlicher RAM verbraucht wird und die CPU‑Last minimal bleibt.</sup></sub>
|
||||
|
||||
### Compile‑Zeit
|
||||
|
||||
Kompilieren von Grund auf:
|
||||
|
||||
**Debug:** <FmtNum :n=10.35 />s
|
||||
**Release:** <FmtNum :n=38.40 />s
|
||||
|
||||
Re‑Kompilierung (pumpkin‑Crate):
|
||||
|
||||
**Debug:** <FmtNum :n=1.82 />s
|
||||
**Release:** <FmtNum :n=28.68 />s
|
||||
|
||||
## Vanilla
|
||||
|
||||
Release: [1.21.1](https://piston-data.mojang.com/v1/objects/59353fb40c36d304f2035d51e7d6e6baa98dc05c/server.jar)
|
||||
|
||||
Compile‑Args:
|
||||
|
||||
Run‑Args: `nogui`
|
||||
|
||||
**Dateigröße:** <FmtNum :n=51.6 />MB
|
||||
|
||||
**Startzeit:** <FmtNum :n=7 />s
|
||||
|
||||
**Stopzeit:** <FmtNum :n=4 />s
|
||||
|
||||
| Spieler | RAM | CPU idle | CPU Max |
|
||||
| ------- | -------------------- | ----------------------------------------- | ------------------- |
|
||||
| 0 | <FmtNum n="860" />MB | <FmtNum n="0.1" /> - <FmtNum n="0.3" />% | <FmtNum n="51" />% |
|
||||
| 1 | <FmtNum n="1.5" />GB | <FmtNum n="0.9" /> - <FmtNum n="1" />% | <FmtNum n="41" />% |
|
||||
| 2 | <FmtNum n="1.6" />GB | <FmtNum n="1" /> - <FmtNum n="1.1" />% | <FmtNum n="10" />% |
|
||||
| 5 | <FmtNum n="1.8" />GB | <FmtNum n="2" />% | <FmtNum n="20" />% |
|
||||
| 10 | <FmtNum n="2.2" />GB | <FmtNum n="4" />% | <FmtNum n="24" />% |
|
||||
|
||||
## Paper
|
||||
|
||||
Build: [122](https://api.papermc.io/v2/projects/paper/versions/1.21.1/builds/122/downloads/paper-1.21.1-122.jar)
|
||||
|
||||
Compile‑Args:
|
||||
|
||||
Run‑Args: `nogui`
|
||||
|
||||
**Dateigröße:** <FmtNum :n=49.4 />MB
|
||||
|
||||
**Startzeit:** <FmtNum :n=7 />s
|
||||
|
||||
**Stopzeit:** <FmtNum :n=3 />s
|
||||
|
||||
| Spieler | RAM | CPU idle | CPU Max |
|
||||
| ------- | ------------------ | --------------------------------------- | ------------------ |
|
||||
| 0 | <FmtNum :n=1.1 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=36 />% |
|
||||
| 1 | <FmtNum :n=1.7 />GB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=47 />% |
|
||||
| 2 | <FmtNum :n=1.8 />GB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=10 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.5 />% | <FmtNum :n=15 />% |
|
||||
| 10 | <FmtNum :n=2 />GB | <FmtNum :n=3 />% | <FmtNum :n=20 />% |
|
||||
|
||||
## Purpur
|
||||
|
||||
Build: [2324](https://api.purpurmc.org/v2/purpur/1.21.1/2324/download)
|
||||
|
||||
Compile‑Args:
|
||||
|
||||
Run‑Args: `nogui`
|
||||
|
||||
**Dateigröße:** <FmtNum :n=53.1 />MB
|
||||
|
||||
**Startzeit:** <FmtNum :n=8 />s
|
||||
|
||||
**Stopzeit:** <FmtNum :n=4 />s
|
||||
|
||||
| Spieler | RAM | CPU idle | CPU Max |
|
||||
| ------- | ------------------ | --------------------------------------- | ------------------ |
|
||||
| 0 | <FmtNum :n=1.4 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=25 />% |
|
||||
| 1 | <FmtNum :n=1.6 />GB | <FmtNum :n=0.7 /> - <FmtNum :n=1.0 />% | <FmtNum :n=35 />% |
|
||||
| 2 | <FmtNum :n=1.7 />GB | <FmtNum :n=1.1 /> - <FmtNum :n=1.3 />% | <FmtNum :n=9 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.6 />% | <FmtNum :n=20 />% |
|
||||
| 10 | <FmtNum :n=2.2 />GB | <FmtNum :n=2 /> - <FmtNum :n=2.5 />% | <FmtNum :n=26 />% |
|
||||
|
||||
## Minestom
|
||||
|
||||
Commit: [0ca1dda2fe](https://github.com/Minestom/Minestom/commit/0ca1dda2fe11390a1b89a228bbe7bf78fefc73e1)
|
||||
|
||||
Compile‑Args:
|
||||
|
||||
Run‑Args:
|
||||
|
||||
**Sprache:** Benchmarks liefen mit Kotlin 2.0.0 (Minestom selbst ist in Java geschrieben)
|
||||
|
||||
**Dateigröße:** <FmtNum :n=2.8 />MB (Bibliothek)
|
||||
|
||||
**Startzeit:** <FmtNum :n=310 />ms
|
||||
|
||||
**Stopzeit:** <FmtNum :n=0 />ms
|
||||
|
||||
<sub>[Beispielcode verwendet von](https://minestom.net/docs/setup/your-first-server)</sub>
|
||||
|
||||
| Spieler | RAM | CPU idle | CPU Max |
|
||||
| ------- | ------------------ | --------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=228 />MB | <FmtNum :n=0.1 /> - <FmtNum :n=0.3 />% | <FmtNum :n=1 />% |
|
||||
| 1 | <FmtNum :n=365 />MB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=5 />% |
|
||||
| 2 | <FmtNum :n=371 />MB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=4 />% |
|
||||
| 5 | <FmtNum :n=390 />MB | <FmtNum :n=1.0 />% | <FmtNum :n=6 />% |
|
||||
| 10 | <FmtNum :n=421 />MB | <FmtNum :n=3 />% | <FmtNum :n=9 />% |
|
||||
|
||||
Benchmarks ausgeführt am <FmtDateTime :d="new Date('2024-10-15T16:34Z')" />
|
||||
@ -1,187 +0,0 @@
|
||||
# Benchmarks
|
||||
|
||||
Here, common Minecraft server software is compared against Pumpkin.
|
||||
|
||||
> [!CAUTION]
|
||||
> **This comparison is unfair.** Pumpkin currently has far fewer features than other servers, which might suggest it uses fewer resources.
|
||||
> It's also important to consider that other servers have had years to optimize.
|
||||
> Vanilla forks, which don’t need to rewrite the entire Vanilla logic, can focus exclusively on optimizations.
|
||||
|
||||

|
||||
|
||||
## Specifications
|
||||
|
||||
### Technical
|
||||
|
||||
#### Software
|
||||
|
||||
- Distribution: Manjaro Linux
|
||||
- Architecture: x86_64 (64-bit)
|
||||
- Kernel Version: 6.11.3-arch1-1
|
||||
|
||||
#### Hardware
|
||||
|
||||
- Motherboard: MAG B650 TOMAHAWK WIFI
|
||||
- CPU: AMD Ryzen 7600X 6-Core
|
||||
- RAM: Corsair 2x16GB DDR5 6000Mhz
|
||||
- Storage: Samsung 990 PRO 1TB PCIe 4.0 M.2 SSD
|
||||
- Cooling: be quiet Dark Rock Elite
|
||||
|
||||
#### Rust
|
||||
|
||||
- Toolchain: stable-x86_64-unknown-linux-gnu (1.81.0)
|
||||
- Rust Compiler: rustc 1.81.0 (eeb90cda1 2024-09-04)
|
||||
|
||||
#### Java
|
||||
|
||||
- JDK Version: OpenJDK 23 64-Bit 2024-09-17
|
||||
- JRE Version: OpenJDK Runtime Environment (build 23+37)
|
||||
- Vendor: Oracle
|
||||
|
||||
#### Game
|
||||
|
||||
- Minecraft version: 1.21.1
|
||||
- View distance: 10
|
||||
- Simulated distance: 10
|
||||
- Online mode: false
|
||||
- RCON: false
|
||||
|
||||
<sub><sup>Online mode was disabled for easier testing with non-premium accounts.</sup></sub>
|
||||
|
||||
> [!NOTE]
|
||||
> All tests have been ran multiple times for more accurate results.
|
||||
> All players did not move when spawning. Only the initial 8 chunks were loaded.
|
||||
> All servers used their own terrain generation. No world was pre-loaded.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `CPU Max` is usually higher with one player because the initial chunks are being loaded.
|
||||
|
||||
## Pumpkin
|
||||
|
||||
Build: [8febc50](https://github.com/Snowiiii/Pumpkin/commit/8febc5035d5611558c13505b7724e6ca284e0ada)
|
||||
|
||||
Compile args: `--release`
|
||||
|
||||
Run args:
|
||||
|
||||
**File Size:** <FmtNum :n=12.3 />MB
|
||||
|
||||
**Startup time:** <FmtNum :n=8 />ms
|
||||
|
||||
**Shutdown time:** <FmtNum :n=0 />ms
|
||||
|
||||
| Players | RAM | CPU Idle | CPU Max |
|
||||
| ------- | --------------------- | ---------------- | ------------------ |
|
||||
| 0 | <FmtNum :n=392.2 />KB | <FmtNum :n=0 />% | <FmtNum :n=0 />% |
|
||||
| 1 | <FmtNum :n=24.9 />MB | <FmtNum :n=0 />% | <FmtNum :n=4 />% |
|
||||
| 2 | <FmtNum :n=25.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=0.6 />% |
|
||||
| 5 | <FmtNum :n=26 />MB | <FmtNum :n=0 />% | <FmtNum :n=1 />% |
|
||||
| 10 | <FmtNum :n=27.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=1.5 />% |
|
||||
|
||||
<sub><sup>Pumpkin does cache already loaded chunks, resulting in no extra RAM usage besides player data and minimal CPU usage.</sup></sub>
|
||||
|
||||
### Compile time
|
||||
|
||||
Compiling from nothing:
|
||||
|
||||
**Debug:** <FmtNum :n=10.35 />sec
|
||||
**Release:** <FmtNum :n=38.40 />sec
|
||||
|
||||
Recompilation (pumpkin crate):
|
||||
|
||||
**Debug:** <FmtNum :n=1.82 />sec
|
||||
**Release:** <FmtNum :n=28.68 />sec
|
||||
|
||||
## Vanilla
|
||||
|
||||
Release: [1.21.1](https://piston-data.mojang.com/v1/objects/59353fb40c36d304f2035d51e7d6e6baa98dc05c/server.jar)
|
||||
|
||||
Compile args:
|
||||
|
||||
Run args: `nogui`
|
||||
|
||||
**File Size:** <FmtNum :n=51.6 />MB
|
||||
|
||||
**Startup time:** <FmtNum :n=7 />sec
|
||||
|
||||
**Shutdown time:** <FmtNum :n=4 />sec
|
||||
|
||||
| Players | RAM | CPU idle | CPU Max |
|
||||
| ------- | --------------------- | ---------------------------------------- | ------------------ |
|
||||
| 0 | <FmtNum n="860" />MB | <FmtNum n="0.1" /> - <FmtNum n="0.3" />% | <FmtNum n="51" />% |
|
||||
| 1 | <FmtNum n="1.5" />GB | <FmtNum n="0.9" /> - <FmtNum n="1" />% | <FmtNum n="41" />% |
|
||||
| 2 | <FmtNum n="1.6" />GB | <FmtNum n="1" /> - <FmtNum n="1.1" />% | <FmtNum n="10" />% |
|
||||
| 5 | <FmtNum n="1.8" />GB | <FmtNum n="2" />% | <FmtNum n="20" />% |
|
||||
| 10 | <FmtNum n="2.2" />GB | <FmtNum n="4" />% | <FmtNum n="24" />% |
|
||||
|
||||
## Paper
|
||||
|
||||
Build: [122](https://api.papermc.io/v2/projects/paper/versions/1.21.1/builds/122/downloads/paper-1.21.1-122.jar)
|
||||
|
||||
Compile args:
|
||||
|
||||
Run args: `nogui`
|
||||
|
||||
**File Size:** <FmtNum :n=49.4 />MB
|
||||
|
||||
**Startup time:** <FmtNum :n=7 />sec
|
||||
|
||||
**Shutdown time:** <FmtNum :n=3 />sec
|
||||
|
||||
| Players | RAM | CPU idle | CPU Max |
|
||||
| ------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.1 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=36 />% |
|
||||
| 1 | <FmtNum :n=1.7 />GB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=47 />% |
|
||||
| 2 | <FmtNum :n=1.8 />GB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=10 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.5 />% | <FmtNum :n=15 />% |
|
||||
| 10 | <FmtNum :n=2 />GB | <FmtNum :n=3 />% | <FmtNum :n=20 />% |
|
||||
|
||||
## Purpur
|
||||
|
||||
Build: [2324](https://api.purpurmc.org/v2/purpur/1.21.1/2324/download)
|
||||
|
||||
Compile args:
|
||||
|
||||
Run args: `nogui`
|
||||
|
||||
**File Size:** <FmtNum :n=53.1 />MB
|
||||
|
||||
**Startup time:** <FmtNum :n=8 />sec
|
||||
|
||||
**Shutdown time:** <FmtNum :n=4 />sec
|
||||
|
||||
| Players | RAM | CPU idle | CPU Max |
|
||||
| ------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.4 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=25 />% |
|
||||
| 1 | <FmtNum :n=1.6 />GB | <FmtNum :n=0.7 /> - <FmtNum :n=1.0 />% | <FmtNum :n=35 />% |
|
||||
| 2 | <FmtNum :n=1.7 />GB | <FmtNum :n=1.1 /> - <FmtNum :n=1.3 />% | <FmtNum :n=9 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.6 />% | <FmtNum :n=20 />% |
|
||||
| 10 | <FmtNum :n=2.2 />GB | <FmtNum :n=2 /> - <FmtNum :n=2.5 />% | <FmtNum :n=26 />% |
|
||||
|
||||
## Minestom
|
||||
|
||||
Commit: [0ca1dda2fe](https://github.com/Minestom/Minestom/commit/0ca1dda2fe11390a1b89a228bbe7bf78fefc73e1)
|
||||
|
||||
Compile args:
|
||||
|
||||
Run args:
|
||||
|
||||
**Language:** Benchmarks ran with Kotlin 2.0.0 (Minestom itself is made with Java)
|
||||
|
||||
**File Size:** <FmtNum :n=2.8 />MB (Library)
|
||||
|
||||
**Startup time:** <FmtNum :n=310 />ms
|
||||
|
||||
**Shutdown time:** <FmtNum :n=0 />ms
|
||||
|
||||
<sub>[Used example code from](https://minestom.net/docs/setup/your-first-server)</sub>
|
||||
|
||||
| Players | RAM | CPU idle | CPU Max |
|
||||
| ------- | ------------------- | -------------------------------------- | ---------------- |
|
||||
| 0 | <FmtNum :n=228 />MB | <FmtNum :n=0.1 /> - <FmtNum :n=0.3 />% | <FmtNum :n=1 />% |
|
||||
| 1 | <FmtNum :n=365 />MB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=5 />% |
|
||||
| 2 | <FmtNum :n=371 />MB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=4 />% |
|
||||
| 5 | <FmtNum :n=390 />MB | <FmtNum :n=1.0 />% | <FmtNum :n=6 />% |
|
||||
| 10 | <FmtNum :n=421 />MB | <FmtNum :n=3 />% | <FmtNum :n=9 />% |
|
||||
|
||||
Benchmarked at <FmtDateTime :d="new Date('2024-10-15T16:34Z')" />
|
||||
139
docs/en/developer/architecture.md
Normal file
139
docs/en/developer/architecture.md
Normal file
@ -0,0 +1,139 @@
|
||||
# Pumpkin Architecture & Crates
|
||||
|
||||
Pumpkin is organized as a modular Cargo workspace composed of specialized crates. Each crate has distinct responsibilities, strictly defined boundaries, and minimal cross-dependencies.
|
||||
|
||||
This architecture enables high parallel compilation speeds, isolated testing, zero-cost data sharing, and clean separation between network protocols, game logic, and world management.
|
||||
|
||||
---
|
||||
|
||||
## Workspace Crate Overview
|
||||
|
||||
### Workspace Architecture at a Glance
|
||||
|
||||
| Layer | Crates | Responsibilities |
|
||||
| :--- | :--- | :--- |
|
||||
| **Core Runtime** | `pumpkin` | Game loop, world ticking, player sessions, entity management, and event loops |
|
||||
| **Networking & Protocols** | `pumpkin-protocol`, `pumpkin-auth`, `pumpkin-nbt` | Java/Bedrock packet serialization, Yggdrasil & Xbox Live authentication, SNBT |
|
||||
| **World & Gameplay** | `pumpkin-world`, `pumpkin-inventory`, `pumpkin-command` | Region I/O (Anvil/Linear), terrain generation, block ticking, containers, Brigadier tree |
|
||||
| **Extensibility** | `pumpkin-plugin-runtime`, `pumpkin-host-bindings`, `pumpkin-plugin-api`, `pumpkin-plugin-wit` | WebAssembly plugin execution via Wasmtime, WIT host APIs, native and WASM loaders |
|
||||
| **Foundations** | `pumpkin-data`, `pumpkin-config`, `pumpkin-util` | Code-generated registries, typed configuration files, mathematical vectors, permissions |
|
||||
|
||||
---
|
||||
|
||||
## Detailed Crate Breakdown
|
||||
|
||||
### 1. `pumpkin` (Core Server)
|
||||
- **Path**: `crates/pumpkin`
|
||||
- **Purpose**: The main server binary and centralized orchestration engine.
|
||||
- **Responsibilities**:
|
||||
- Initializes configuration, logging (`tracing`), and telemetry.
|
||||
- Spawns Tokio network listeners for Minecraft: Java Edition, Bedrock Edition, RCON, Query, and LAN broadcast.
|
||||
- Manages the server tick loop (`Server::tick`), coordinating world updates, player actions, entity movements, and chunk broadcasting.
|
||||
- Dispatches incoming client packets through `crates/pumpkin/src/net/java/` and `net/bedrock/`.
|
||||
- Integrates the plugin manager, firing event handlers before and after packet processing.
|
||||
|
||||
### 2. `pumpkin-protocol` (Network Protocol)
|
||||
- **Path**: `crates/pumpkin-protocol`
|
||||
- **Purpose**: High-speed network wire format serialization and deserialization for Minecraft Java and Bedrock editions.
|
||||
- **Responsibilities**:
|
||||
- Implements the `ClientPacket` (serialization) and `ServerPacket` (deserialization) traits.
|
||||
- Handles protocol primitives: `VarInt`, `VarLong`, UUIDs, NBT compounds, and string codecs.
|
||||
- Implements packets across all states: `Handshake`, `Status`, `Login`, `Config`, and `Play`.
|
||||
- Provides packet compression (ZLib/Fast compression) and packet encryption (AES-128/CFB8).
|
||||
- Encapsulates RCON and GameSpy4 Query protocols.
|
||||
|
||||
### 3. `pumpkin-world` (World & Chunks)
|
||||
- **Path**: `crates/pumpkin-world`
|
||||
- **Purpose**: World loading, saving, terrain generation, and chunk management.
|
||||
- **Responsibilities**:
|
||||
- Supports multiple chunk storage formats:
|
||||
- **Vanilla Anvil (`.mca`)**: Reading and writing standard region files.
|
||||
- **Linear (`.linear`)**: High-efficiency zstd-compressed region format saving 50–95% disk space.
|
||||
- **Slime (`.slime`)**: Single-file world format.
|
||||
- **Pump**: Native Pumpkin chunk storage format.
|
||||
- Implements world generation pipelines: Perlin noise heightmaps, 3D noise routers, biome climate parameters, placed features, carvers, and superflat terrain.
|
||||
- Manages block lighting and sky lighting propagation.
|
||||
- Coordinates chunk loading, unloading, and streaming via cylindrical chunk iterators.
|
||||
|
||||
### 4. `pumpkin-data` (Minecraft Datasets & Registries)
|
||||
- **Path**: `crates/pumpkin-data`
|
||||
- **Purpose**: Compile-time static registries and data tables generated directly from vanilla Minecraft assets and reports.
|
||||
- **Responsibilities**:
|
||||
- Contains complete ID mappings, properties, and definitions for blocks, items, biomes, entities, sounds, particles, enchantments, and recipes.
|
||||
- Packet ID lookups across multiple Minecraft protocol versions.
|
||||
- Zero-cost lookup tables generated by `tools/pumpkin-codegen`.
|
||||
|
||||
### 5. `pumpkin-config` (Configuration Management)
|
||||
- **Path**: `crates/pumpkin-config`
|
||||
- **Purpose**: TOML configuration parsing, serialization, and validation.
|
||||
- **Responsibilities**:
|
||||
- Loads and writes `pumpkin.toml`.
|
||||
- Configures server ports, compression thresholds, proxy modes, resource packs, logging levels, world settings, and Bedrock integration.
|
||||
- Provides safe defaults and schema validations.
|
||||
|
||||
### 6. `pumpkin-inventory` (Inventory & Containers)
|
||||
- **Path**: `crates/pumpkin-inventory`
|
||||
- **Purpose**: Server-side container window handlers and inventory mechanics.
|
||||
- **Responsibilities**:
|
||||
- Manages player inventory, chest containers, crafting tables, furnaces, and villager trading screens.
|
||||
- Validates client slot clicks, drag operations, item stacking, and item splitting.
|
||||
- Coordinates window packet synchronization to ensure client inventory fidelity.
|
||||
|
||||
### 7. `pumpkin-command` (Command Dispatcher)
|
||||
- **Path**: `crates/pumpkin-command`
|
||||
- **Purpose**: Brigadier-inspired hierarchical command tree dispatcher and parser.
|
||||
- **Responsibilities**:
|
||||
- Constructs modular command trees with typed arguments (`Integer`, `Float`, `Entity`, `BlockPos`, etc.).
|
||||
- Executes permission-checked commands.
|
||||
- Provides real-time autocompletion suggestions sent to connected clients.
|
||||
|
||||
### 8. `pumpkin-auth` (Authentication & Session Verification)
|
||||
- **Path**: `crates/pumpkin-auth`
|
||||
- **Purpose**: Cryptographic authentication and player identity verification.
|
||||
- **Responsibilities**:
|
||||
- Validates client sessions with Mojang's Yggdrasil authentication servers.
|
||||
- Handles RSA keypair generation and shared secret exchange during the Login state.
|
||||
- Supports offline-mode UUID generation and custom authentication endpoints.
|
||||
- Implements proxy forwarding protocols: [BungeeCord](https://github.com/SpigotMC/BungeeCord), [BungeeGuard](https://github.com/lucko/BungeeGuard), and [Velocity](https://github.com/PaperMC/Velocity) modern forwarding with HMAC-SHA256 signatures.
|
||||
|
||||
### 9. `pumpkin-nbt` (Named Binary Tag Codec)
|
||||
- **Path**: `crates/pumpkin-nbt`
|
||||
- **Purpose**: High-throughput binary NBT parser and serializer.
|
||||
- **Responsibilities**:
|
||||
- Parses and encodes all standard NBT tag types (`Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `ByteArray`, `String`, `List`, `Compound`, `IntArray`, `LongArray`).
|
||||
- Supports compression layers: Gzip, Zlib, and uncompressed Network NBT.
|
||||
|
||||
### 10. `pumpkin-plugin-*` (Extensibility Infrastructure)
|
||||
- **Paths**:
|
||||
- `crates/pumpkin-plugin-api`: The Rust developer SDK for compiling native plugins.
|
||||
- `crates/pumpkin-plugin-runtime`: Wasmtime-powered WebAssembly execution host.
|
||||
- `crates/pumpkin-plugin-wit`: WebAssembly Interface Type (WIT) specifications.
|
||||
- `crates/pumpkin-host-bindings`: Auto-generated host bindings connecting Wasmtime and the server core.
|
||||
- **Purpose**: Enables safe, polyglot plugin execution (Rust, Python, C#, C, Go, Kotlin) with sandboxed memory and near-native performance.
|
||||
|
||||
### 11. `pumpkin-util` & `pumpkin-macros` (Foundations)
|
||||
- **Paths**: `crates/pumpkin-util`, `crates/pumpkin-macros`, `crates/pumpkin-api-macros`
|
||||
- **Purpose**: Foundational types, shared algorithms, and procedural macros.
|
||||
- **Responsibilities**:
|
||||
- Mathematical utilities: `Vector2`, `Vector3`, `BoundingBox`, and angle conversions.
|
||||
- Text and chat formatting: `TextComponent` and JSON component serialization.
|
||||
- Procedural macros such as `#[java_packet(...)]` that bind packet structs to protocol IDs and packet traits.
|
||||
|
||||
### 12. `tools/` (Developer Tooling)
|
||||
- **`tools/pumpkin-codegen`**: Automated code generator that parses Minecraft version jars, data reports, and asset archives to emit Rust source files into `pumpkin-data`.
|
||||
- **`tools/pumpkin-fuzzer`**: Fuzz testing suites for network packet parsers, NBT readers, and region file decoders to identify potential crash vectors or memory anomalies.
|
||||
|
||||
---
|
||||
|
||||
## Concurrency & Threading Model
|
||||
|
||||
| Component | Runtime / Pool | Primary Workload | Communication Mechanism |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Network & Socket I/O** | Tokio Async Runtime | TCP packet reads, Bedrock UDP packets, framing, session handshakes | Async tasks (`tokio::spawn`), non-blocking reactors |
|
||||
| **Heavy Compute** | Rayon Thread Pool | Terrain generation, noise routers, light propagation, chunk compression | Worker threads via crossbeam / Rayon iterators |
|
||||
| **Inter-Thread Bridge** | Tokio Channels | Dispatching completed chunks and encoded packets back to network tasks | Non-blocking `tokio::sync::mpsc` channels |
|
||||
|
||||
Pumpkin strictly adheres to this separation of concerns:
|
||||
- **I/O Bound**: Managed on Tokio asynchronous tasks.
|
||||
- **CPU Bound**: Managed on Rayon thread pools.
|
||||
- **Communication**: Coordinated via cross-thread channels (`tokio::sync::mpsc`) without ever blocking the Tokio reactor loop.
|
||||
182
docs/en/developer/blocks/adding-blocks.md
Normal file
182
docs/en/developer/blocks/adding-blocks.md
Normal file
@ -0,0 +1,182 @@
|
||||
# Adding a Block Behavior
|
||||
|
||||
This guide walks through implementing interactive behavior for a block in Pumpkin.
|
||||
|
||||
---
|
||||
|
||||
## When Is a Behavior Needed?
|
||||
|
||||
- **Non-Interactive Blocks** (e.g., stone, dirt, terracotta): Do **not** require any custom Rust code. Their breaking speeds, hardness, and placement properties are automatically derived from `pumpkin-data`.
|
||||
- **Interactive or Functional Blocks** (e.g., doors, chests, levers, crops, campfires, TNT): Require an implementation of the `BlockBehaviour` trait to respond to player clicks, redstone signals, physics steps, or timer ticks.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create the Behavior Struct
|
||||
|
||||
Create a new file under `crates/pumpkin/src/block/blocks/<category>/<block_name>.rs` (or directly in `crates/pumpkin/src/block/blocks/`).
|
||||
|
||||
Use the `#[pumpkin_block(...)]` macro to associate your behavior struct with one or more vanilla block identifiers:
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::block_properties::{DoorLikeProperties, Facing};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, NormalUseArgs, OnNeighborUpdateArgs, PlacedArgs, UseWithItemArgs,
|
||||
};
|
||||
use crate::world::World;
|
||||
|
||||
/// Associates this behavior struct with one or multiple block IDs.
|
||||
#[pumpkin_block([
|
||||
"minecraft:oak_door",
|
||||
"minecraft:iron_door",
|
||||
"minecraft:spruce_door",
|
||||
"minecraft:birch_door"
|
||||
])]
|
||||
pub struct DoorBlock;
|
||||
|
||||
impl BlockBehaviour for DoorBlock {
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let state_id = args.world.get_block_state(args.location).await;
|
||||
|
||||
// Iron doors require redstone power and cannot be opened manually by hand
|
||||
if args.block.id == pumpkin_data::Block::IRON_DOOR.id {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
|
||||
// Toggle open state
|
||||
let is_open = DoorLikeProperties::open(state_id);
|
||||
let new_state = DoorLikeProperties::with_open(state_id, !is_open);
|
||||
args.world.set_block_state(args.location, new_state).await;
|
||||
|
||||
BlockActionResult::Consume
|
||||
}
|
||||
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
// Handle neighbor redstone signals or support block destruction
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: `BlockBehaviour` Hook Reference
|
||||
|
||||
The `BlockBehaviour` trait exposes lifecycle hooks modeled on vanilla mechanics:
|
||||
|
||||
### Player & Item Interaction Hooks
|
||||
|
||||
| Hook | Signature | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `normal_use` | `(&self, args: NormalUseArgs<'_>) -> BlockActionResult` | Triggered when right-clicking the block with an empty hand. |
|
||||
| `use_with_item` | `(&self, args: UseWithItemArgs<'_>) -> BlockActionResult` | Triggered when right-clicking the block while holding an item. |
|
||||
| `get_screen_handler_factory` | `(&self, args: GetScreenHandlerFactoryArgs<'_>) -> Option<Box<dyn ScreenHandlerFactory>>` | Returns an inventory container factory to open a UI screen (e.g., Chests, Crafting Tables). |
|
||||
|
||||
### World Lifecycle & Physics Hooks
|
||||
|
||||
| Hook | Signature | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `placed` | `(&self, args: PlacedArgs<'_>)` | Triggered immediately after the block is placed in the world. |
|
||||
| `broken` | `(&self, args: BrokenArgs<'_>)` | Triggered when broken by a player or game mechanics. |
|
||||
| `on_entity_collision` | `(&self, args: OnEntityCollisionArgs<'_>)` | Triggered when an entity walks inside, steps on, or falls into the block. |
|
||||
| `on_neighbor_update` | `(&self, args: OnNeighborUpdateArgs<'_>)` | Triggered when an adjacent block state updates or is destroyed. |
|
||||
| `on_projectile_hit` | `(&self, args: OnProjectileHitArgs<'_>)` | Triggered when an arrow, snowball, or projectile strikes the block. |
|
||||
|
||||
### Redstone & Power Hooks
|
||||
|
||||
| Hook | Signature | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `emits_redstone_power` | `(&self, state_id: BlockStateId) -> bool` | Declares whether the block emits redstone power to neighbors. |
|
||||
| `get_weak_redstone_power` | `(&self, ...) -> u8` | Returns emitted weak power level (0–15). |
|
||||
| `get_strong_redstone_power` | `(&self, ...) -> u8` | Returns emitted strong direct power level (0–15). |
|
||||
|
||||
### Foliage & Bonemeal Hooks
|
||||
|
||||
| Hook | Signature | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `is_valid_bonemeal_target` | `(&self, args: BonemealArgs<'_>) -> bool` | Checks if bone meal can be applied (e.g., crop not fully grown). |
|
||||
| `is_bonemeal_success` | `(&self, args: BonemealArgs<'_>) -> bool` | Evaluates random chance roll for bone meal success. |
|
||||
| `perform_bonemeal` | `(&self, args: BonemealArgs<'_>)` | Advances growth stages or spawns adjacent flora. |
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Modifying Block States
|
||||
|
||||
All block properties are strongly typed in `pumpkin_data::block_properties`:
|
||||
|
||||
```rust
|
||||
use pumpkin_data::block_properties::{BedLikeProperties, Part};
|
||||
|
||||
// 1. Read typed properties from the numeric BlockStateId
|
||||
let part = BedLikeProperties::part(state_id); // Part::Head or Part::Foot
|
||||
let is_occupied = BedLikeProperties::occupied(state_id);
|
||||
|
||||
// 2. Generate a new BlockStateId with updated properties
|
||||
let updated_state = BedLikeProperties::with_occupied(state_id, true);
|
||||
|
||||
// 3. Commit the updated state to the world
|
||||
world.set_block_state(pos, updated_state).await;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Adding a Block Entity (Tile Entity)
|
||||
|
||||
If your block needs to store persistent custom state (inventories, custom text, timers):
|
||||
|
||||
1. Create a block entity struct in `crates/pumpkin/src/block/entities/`:
|
||||
```rust
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use crate::block::entities::BlockEntity;
|
||||
|
||||
pub struct CustomBlockEntity {
|
||||
pub pos: BlockPos,
|
||||
pub custom_counter: u32,
|
||||
}
|
||||
|
||||
impl BlockEntity for CustomBlockEntity {
|
||||
fn to_nbt(&self) -> pumpkin_nbt::compound::NbtCompound {
|
||||
let mut compound = pumpkin_nbt::compound::NbtCompound::new();
|
||||
compound.put_int("Counter", self.custom_counter as i32);
|
||||
compound
|
||||
}
|
||||
|
||||
fn from_nbt(&mut self, tag: &pumpkin_nbt::compound::NbtCompound) {
|
||||
if let Some(val) = tag.get_int("Counter") {
|
||||
self.custom_counter = val as u32;
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&mut self, world: &std::sync::Arc<crate::world::World>, pos: &BlockPos) {
|
||||
// Periodic logic per game tick
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Link the block entity in your `BlockBehaviour`:
|
||||
```rust
|
||||
impl BlockBehaviour for CustomBlock {
|
||||
fn create_block_entity(&self, pos: BlockPos) -> Option<Arc<Mutex<dyn BlockEntity>>> {
|
||||
Some(Arc::new(Mutex::new(CustomBlockEntity::new(pos))))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Registering the Block
|
||||
|
||||
1. Export your module in `crates/pumpkin/src/block/blocks/mod.rs`:
|
||||
```rust
|
||||
pub mod doors;
|
||||
```
|
||||
|
||||
2. Register the behavior struct in `crates/pumpkin/src/block/registry.rs`:
|
||||
```rust
|
||||
manager.register(DoorBlock);
|
||||
```
|
||||
|
||||
At startup, `manager.register` automatically reads the block IDs declared in `#[pumpkin_block(...)]` and routes interactions to your implementation.
|
||||
104
docs/en/developer/blocks/index.md
Normal file
104
docs/en/developer/blocks/index.md
Normal file
@ -0,0 +1,104 @@
|
||||
# Block Architecture & State System
|
||||
|
||||
Blocks form the static and dynamic foundation of Minecraft worlds. In Pumpkin, block handling is cleanly bifurcated into two distinct layers:
|
||||
|
||||
1. **Static Data Layer ([`pumpkin-data`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-data))**: Compile-time generated metadata including numeric block IDs, block state IDs, hardness, blast resistance, light emission, opacity, collision shapes, and block state properties generated by [`pumpkin-codegen`](/developer/codegen).
|
||||
2. **Dynamic Behavior Layer ([`pumpkin::block`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin/src/block))**: Server-side behavioral hooks—such as opening containers, toggling redstone signals, growing crops, breaking triggers, and collision effects—handled via the `BlockBehaviour` trait.
|
||||
|
||||
---
|
||||
|
||||
## Static Blocks vs. Interactive Behaviors
|
||||
|
||||
Because Minecraft contains thousands of block state combinations, Pumpkin avoids allocating runtime behavior structs for simple, non-interactive blocks:
|
||||
|
||||
| Category | Description | Examples | Runtime Behavior Required? |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Static Blocks** | Rigid solid voxels without interactive logic or custom physics | `stone`, `dirt`, `cobblestone`, `bedrock`, `obsidian` | No — Placement, breaking times, and tool requirements are handled entirely by `pumpkin-data`. |
|
||||
| **Interactive Blocks** | Blocks triggered by right-clicks or tool actions | `door`, `trapdoor`, `lever`, `button`, `crafting_table` | Yes — Implements `BlockBehaviour::normal_use` or `use_with_item`. |
|
||||
| **Collision Blocks** | Blocks that modify entity movement or apply forces | `honey_block`, `slime_block`, `soul_sand`, `powder_snow` | Yes — Implements `on_entity_collision` or `on_landed_upon`. |
|
||||
| **Redstone Components** | Power sources, conductors, and logic gates | `redstone_wire`, `repeater`, `comparator`, `observer` | Yes — Implements `emits_redstone_power` and `on_neighbor_update`. |
|
||||
| **Block Entities** | Blocks with persistent, serialized custom states | `chest`, `furnace`, `sign`, `decorated_pot`, `spawner` | Yes — Implements `create_block_entity` and links a `BlockEntity`. |
|
||||
|
||||
---
|
||||
|
||||
## Block IDs, States, and Properties
|
||||
|
||||
Minecraft distinguishes between a **Block** type and a specific **Block State**:
|
||||
|
||||
### 1. `BlockId` and `BlockStateId`
|
||||
|
||||
- **`BlockId`**: An identifier representing a base block type (e.g., `minecraft:oak_door`).
|
||||
- **`BlockStateId`**: A 16-bit integer representing a unique combination of block properties (e.g., `minecraft:oak_door[facing=north,half=lower,hinge=left,open=false,powered=false]`).
|
||||
|
||||
In Pumpkin, block data is looked up from precomputed lookup tables in `pumpkin-data`:
|
||||
|
||||
```rust
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::BlockStateId;
|
||||
|
||||
// Access static properties directly from generated data
|
||||
let door_block = Block::OAK_DOOR;
|
||||
let default_state_id: BlockStateId = door_block.default_state_id;
|
||||
|
||||
let hardness = door_block.hardness;
|
||||
let blast_resistance = door_block.blast_resistance;
|
||||
let is_air = door_block.is_air;
|
||||
```
|
||||
|
||||
### 2. Generated State Property Helpers
|
||||
|
||||
Rather than parsing raw strings or bitmasks manually, [`pumpkin-codegen`](/developer/codegen) generates strongly typed property helpers in `pumpkin_data::block_properties`:
|
||||
|
||||
```rust
|
||||
use pumpkin_data::block_properties::{DoorLikeProperties, DoubleBlockHalf, Facing};
|
||||
|
||||
// Read typed properties from a numeric BlockStateId
|
||||
let is_open = DoorLikeProperties::open(state_id);
|
||||
let half = DoorLikeProperties::half(state_id);
|
||||
let facing = DoorLikeProperties::facing(state_id);
|
||||
|
||||
// Generate a new BlockStateId with mutated properties
|
||||
let toggled_state_id = DoorLikeProperties::with_open(state_id, !is_open);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Physics, Bounding Boxes & Raycasting
|
||||
|
||||
Pumpkin calculates collision and raycasting bounding boxes directly using pre-baked shapes in `pumpkin-data`:
|
||||
|
||||
- **Solid Collisions**: During physics ticks, an entity's axis-aligned bounding box (AABB) is tested against surrounding block collision voxels.
|
||||
- **Raycasting**: When a player targets a block to place or break, client raycasts are validated against the server-side block outline shapes.
|
||||
- **Custom Movement Hooks**: Blocks like slime blocks or honey blocks override vertical rebound or horizontal velocity:
|
||||
- `bounce_entity_after_fall`: Dampens fall damage and multiplies rebound velocity.
|
||||
- `stop_vertical_movement_after_fall`: Zeros fall velocity upon contact.
|
||||
|
||||
---
|
||||
|
||||
## Block Entities (Tile Entities)
|
||||
|
||||
When a block must store arbitrary dynamic data that cannot fit into standard block state bits (such as container inventories, custom sign text, brewing timers, or mob spawner delays), it uses a **Block Entity**:
|
||||
|
||||
| Component | Responsibility | Implementation File |
|
||||
| :--- | :--- | :--- |
|
||||
| `BlockEntity` Trait | Defines NBT serialization (`to_nbt`, `from_nbt`) and tick execution | [`crates/pumpkin/src/block/entities/`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin/src/block/entities) |
|
||||
| `create_block_entity` | Hook in `BlockBehaviour` called when placing the block | Attached to relevant block behaviors |
|
||||
| Chunk Entity Storage | Manages active block entities and coordinates within chunk sections | [`crates/pumpkin-world`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-world) |
|
||||
|
||||
---
|
||||
|
||||
## The Block Registry & Dispatch System
|
||||
|
||||
Block interaction events are routed through the central `BlockRegistry` in [`crates/pumpkin/src/block/registry.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/block/registry.rs):
|
||||
|
||||
1. **Registration**: At server initialization, all behavior structs are registered into the `BlockManager`.
|
||||
2. **Mapping**: The manager maps numeric `BlockId` keys to thread-safe behavior instances (`Arc<dyn BlockBehaviour>`).
|
||||
3. **Dispatch**: When the network layer receives block action packets (such as `CUseItemOn` or block dig packets), the server looks up the behavior in $O(1)$ time and invokes the relevant hook.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
To implement a new interactive block behavior, continue to the practical guide:
|
||||
|
||||
- **[Adding a Block](/developer/blocks/adding-blocks)**: Complete walk-through of declaring attributes, implementing interaction hooks, creating block entities, and registering behaviors.
|
||||
66
docs/en/developer/codegen.md
Normal file
66
docs/en/developer/codegen.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Code Generation (`pumpkin-codegen`)
|
||||
|
||||
Minecraft is a massive game with thousands of block states, items, packet IDs, sound events, recipes, and biome parameters. These registries frequently change between Minecraft updates.
|
||||
|
||||
To maintain 100% fidelity without manually maintaining tens of thousands of error-prone constants, Pumpkin utilizes an automated code generation tool: [`tools/pumpkin-codegen`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/tools/pumpkin-codegen).
|
||||
|
||||
---
|
||||
|
||||
## Purpose & Workflow
|
||||
|
||||
`pumpkin-codegen` parses official Minecraft server data generator reports, asset files, and version specifications, and outputs zero-cost, strongly-typed Rust files directly into [`crates/pumpkin-data`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-data).
|
||||
|
||||
The pipeline operates in three distinct stages:
|
||||
1. **Ingestion**: Reads raw JSON assets and vanilla server reports from `assets/` and `assets/datapacks/`.
|
||||
2. **Transformation**: Normalizes states, calculates bitsets, processes ViaVersion remappings, and optimizes lookup tables.
|
||||
3. **Emission**: Emits compiled Rust constants and structs into [`crates/pumpkin-data/src/generated`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-data/src/generated).
|
||||
|
||||
---
|
||||
|
||||
## Generated Datasets
|
||||
|
||||
`pumpkin-codegen` generates the following components in `pumpkin-data`:
|
||||
|
||||
| Category | Description | Generated Source |
|
||||
| :--- | :--- | :--- |
|
||||
| **Packets** | Serverbound and clientbound packet IDs for all protocol states across supported Minecraft versions. | `packet.rs` |
|
||||
| **Blocks** | Block definitions, default states, state properties (facing, powered, waterlogged, etc.), and block collision boxes. | `block.rs` |
|
||||
| **Items** | Item definitions, stack sizes, durability, data components, food properties, and equipment slots. | `item.rs` |
|
||||
| **Registries** | Dynamic registries for biomes, dimensions, chat types, damage types, trim materials, and banner patterns. | `registry.rs` |
|
||||
| **Biomes & Noise** | Climate parameters, noise router configurations, 3D noise settings, and surface rules. | `biome.rs`, `noise_router.rs` |
|
||||
| **Entities** | Entity types, default bounding boxes, attributes, and tracked entity metadata schemas. | `entity_type.rs`, `tracked_data.rs` |
|
||||
| **Recipes & Crafting** | Shaped, shapeless, smelting, blasting, smoking, and stonecutting recipes. | `recipes.rs` |
|
||||
| **Tags** | Block tags, item tags, fluid tags, and entity type tags (e.g., `#minecraft:mineable/pickaxe`). | `tag.rs` |
|
||||
| **Enchantments & Potions** | Enchantment definitions, potion effects, and brewing stand recipes. | `enchantments.rs`, `potion.rs` |
|
||||
| **Translations** | Official localization keys for chat messages and item names. | `translations.rs` |
|
||||
| **Plugin WIT** | WebAssembly Component Model WIT files defining plugin interfaces. | `wit/` |
|
||||
|
||||
---
|
||||
|
||||
## Running Code Generation
|
||||
|
||||
To run the code generator locally:
|
||||
|
||||
```bash
|
||||
# Navigate to the workspace root
|
||||
cd Pumpkin
|
||||
|
||||
# Run the codegen tool
|
||||
cargo run -p pumpkin-codegen
|
||||
```
|
||||
|
||||
### When Should You Run Codegen?
|
||||
|
||||
- **Upgrading to a New Minecraft Version**: When porting Pumpkin to a new minor or major Minecraft release.
|
||||
- **Adding Protocol Mappings**: When adding support for an older or newer client protocol version.
|
||||
- **Updating WIT Definitions**: When adding new functions or types to the WebAssembly plugin API.
|
||||
|
||||
---
|
||||
|
||||
## Data Sources
|
||||
|
||||
`pumpkin-codegen` reads data from several primary sources:
|
||||
|
||||
1. **Minecraft Data Generator Reports**: Generated by running `java -DbundlerMainClass=net.minecraft.data.Main -jar server.jar --reports`.
|
||||
2. **Minecraft Client Assets**: Extracted from client `.jar` files for translation keys, model shapes, and sound mappings.
|
||||
3. **Minecraft Protocol Wiki**: Cross-referenced with protocol specifications for packet IDs.
|
||||
246
docs/en/developer/commands/adding-commands.md
Normal file
246
docs/en/developer/commands/adding-commands.md
Normal file
@ -0,0 +1,246 @@
|
||||
# Adding a Command
|
||||
|
||||
This guide explains how to implement and register a new command in the Pumpkin server codebase.
|
||||
|
||||
Commands in Pumpkin are implemented in the `crates/pumpkin/src/command/commands/` directory and registered into the central [`CommandDispatcher`](file:///home/alex/Documents/Development/Rust/Pumpkin/crates/pumpkin-command/src/node/dispatcher.rs) during server initialization.
|
||||
|
||||
---
|
||||
|
||||
## Anatomy of a Pumpkin Command
|
||||
|
||||
A typical command file contains:
|
||||
1. **Metadata Constants**: Command description, permission node identifier, and argument names.
|
||||
2. **Permission Registration**: Declaring default OP requirements in the server's [`PermissionRegistry`](file:///home/alex/Documents/Development/Rust/Pumpkin/crates/pumpkin-util/src/permission.rs).
|
||||
3. **Command Executors**: Structs implementing the [`CommandExecutor`](file:///home/alex/Documents/Development/Rust/Pumpkin/crates/pumpkin-command/src/node/mod.rs) trait for each terminal node or subcommand.
|
||||
4. **Registration Function**: Constructing the argument tree using fluent builders (`command`, `literal`, `argument`).
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Implementation: Creating `/heal`
|
||||
|
||||
Let's implement a complete `/heal` command that can heal either the executing player or a set of target players:
|
||||
|
||||
```
|
||||
/heal -> Heals the executing sender
|
||||
/heal <targets> -> Heals target player(s)
|
||||
/heal <targets> <amount> -> Heals target player(s) by a specified amount
|
||||
```
|
||||
|
||||
### 1. Create the File
|
||||
|
||||
Create `crates/pumpkin/src/command/commands/heal.rs`:
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::command::argument_builder::{ArgumentBuilder, argument, command};
|
||||
use crate::command::argument_types::core::float::FloatArgumentType;
|
||||
use crate::command::argument_types::entity::EntityArgumentType;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
use crate::entity::EntityBase;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
const DESCRIPTION: &str = "Restores health to target entities.";
|
||||
const PERMISSION: &str = "pumpkin:command.heal";
|
||||
|
||||
const ARG_TARGETS: &str = "targets";
|
||||
const ARG_AMOUNT: &str = "amount";
|
||||
|
||||
const ERROR_NOT_PLAYER: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::PERMISSIONS_REQUIRES_PLAYER,
|
||||
translation::java::PERMISSIONS_REQUIRES_PLAYER,
|
||||
);
|
||||
```
|
||||
|
||||
### 2. Implement the Executors
|
||||
|
||||
Command execution logic is encapsulated in structs implementing [`CommandExecutor`](file:///home/alex/Documents/Development/Rust/Pumpkin/crates/pumpkin-command/src/node/mod.rs):
|
||||
|
||||
```rust
|
||||
/// Heals the executing player to full health.
|
||||
struct SelfHealExecutor;
|
||||
|
||||
impl CommandExecutor for SelfHealExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let player = context
|
||||
.source
|
||||
.output
|
||||
.as_player()
|
||||
.ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?;
|
||||
|
||||
// Restore player health
|
||||
player.living_entity.set_health(20.0);
|
||||
|
||||
context.source.send_feedback(
|
||||
TextComponent::text("Fully restored your health!"),
|
||||
true, // Broadcast to operators if configured
|
||||
);
|
||||
|
||||
// Return number of affected entities
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Heals specified target players.
|
||||
struct TargetsHealExecutor {
|
||||
has_amount: bool,
|
||||
}
|
||||
|
||||
impl CommandExecutor for TargetsHealExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let targets = EntityArgumentType::get_players(context, ARG_TARGETS)?;
|
||||
|
||||
let amount = if self.has_amount {
|
||||
FloatArgumentType::get(context, ARG_AMOUNT)?
|
||||
} else {
|
||||
20.0
|
||||
};
|
||||
|
||||
let count = targets.len();
|
||||
for target in &targets {
|
||||
let current = target.living_entity.health.load();
|
||||
target.living_entity.set_health((current + amount).min(20.0));
|
||||
}
|
||||
|
||||
let msg = if count == 1 {
|
||||
TextComponent::text(format!("Healed {} by {} HP", targets[0].gameprofile.name, amount))
|
||||
} else {
|
||||
TextComponent::text(format!("Healed {} players by {} HP", count, amount))
|
||||
};
|
||||
|
||||
context.source.send_feedback(msg, true);
|
||||
|
||||
Ok(count as i32)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Build the Command Tree
|
||||
|
||||
Use the fluent builder functions `command`, `literal`, and `argument` to assemble the syntax tree and register the permission:
|
||||
|
||||
```rust
|
||||
pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistry) {
|
||||
// 1. Register permission node with default requirement (OP level 2)
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
DESCRIPTION,
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
));
|
||||
|
||||
// 2. Assemble command tree
|
||||
dispatcher.register(
|
||||
command("heal", DESCRIPTION)
|
||||
.requires(PERMISSION)
|
||||
.executes(SelfHealExecutor)
|
||||
.then(
|
||||
argument(ARG_TARGETS, EntityArgumentType::Players)
|
||||
.executes(TargetsHealExecutor { has_amount: false })
|
||||
.then(
|
||||
argument(ARG_AMOUNT, FloatArgumentType::new(1.0, 100.0))
|
||||
.executes(TargetsHealExecutor { has_amount: true }),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Command Structure Patterns
|
||||
|
||||
### Adding Subcommand Literals
|
||||
Use `literal("subcommand_name")` to create keyword branches:
|
||||
|
||||
```rust
|
||||
use crate::command::argument_builder::{argument, command, literal};
|
||||
|
||||
dispatcher.register(
|
||||
command("weather", "Set the world weather")
|
||||
.requires("minecraft:command.weather")
|
||||
.then(literal("clear").executes(ClearWeatherExecutor))
|
||||
.then(literal("rain").executes(RainWeatherExecutor))
|
||||
.then(literal("thunder").executes(ThunderWeatherExecutor))
|
||||
);
|
||||
```
|
||||
|
||||
### Cross-Platform Translation Feedback
|
||||
Pumpkin servers serve both Java and Bedrock Edition players simultaneously. Whenever possible, use vanilla translation keys with `TextComponent::translate_cross` so messages render localized on the client:
|
||||
|
||||
```rust
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
let msg = TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_KILL_SUCCESS_SINGLE,
|
||||
translation::bedrock::COMMANDS_KILL_SUCCESSFUL,
|
||||
[target.get_display_name()],
|
||||
);
|
||||
|
||||
context.source.send_feedback(msg, true);
|
||||
```
|
||||
|
||||
### Error Handling in Executors
|
||||
Executors must return `CommandExecutorResult`, which resolves to `Result<i32, CommandSyntaxError>`. If validation fails, return an error cleanly without panicking:
|
||||
|
||||
```rust
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
|
||||
const ERROR_INVALID_VALUE: CommandErrorType<0> = CommandErrorType::new(
|
||||
"Amount must be greater than zero.",
|
||||
"Amount must be greater than zero.",
|
||||
);
|
||||
|
||||
if amount <= 0.0 {
|
||||
return Err(ERROR_INVALID_VALUE.create_without_context());
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Registering in `default_dispatcher`
|
||||
|
||||
Once your command is implemented, register it in `crates/pumpkin/src/command/commands/mod.rs`:
|
||||
|
||||
1. **Declare the Module**:
|
||||
```rust
|
||||
// In crates/pumpkin/src/command/commands/mod.rs
|
||||
mod heal;
|
||||
```
|
||||
|
||||
2. **Invoke `register` in `default_dispatcher`**:
|
||||
```rust
|
||||
pub fn default_dispatcher(
|
||||
permission_manager: &PermissionManager,
|
||||
commands_config: &CommandsConfig,
|
||||
) -> CommandDispatcher {
|
||||
let registry = &permission_manager.registry;
|
||||
let mut dispatcher = CommandDispatcher::new();
|
||||
|
||||
// ... existing registrations ...
|
||||
heal::register(&mut dispatcher, registry);
|
||||
|
||||
dispatcher
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices Checklist
|
||||
|
||||
- [ ] **No Panics or Unwraps**: Use `?` or return descriptive `CommandSyntaxError`s when arguments are invalid.
|
||||
- [ ] **Permission Nodes**: Prefix permission strings with appropriate namespaces (`minecraft:command.<name>` for vanilla commands, `pumpkin:command.<name>` for server-specific commands).
|
||||
- [ ] **Return Success Counts**: The returned `i32` represents the command's exit code or number of affected objects for redstone command blocks and `/execute store result`.
|
||||
- [ ] **Respect GameRules**: When notifying players other than the sender, check if `send_command_feedback` is enabled:
|
||||
```rust
|
||||
let server = context.source.server();
|
||||
if server.level_info.load().game_rules.send_command_feedback {
|
||||
target.send_system_message(&feedback);
|
||||
}
|
||||
```
|
||||
159
docs/en/developer/commands/arguments-and-suggestions.md
Normal file
159
docs/en/developer/commands/arguments-and-suggestions.md
Normal file
@ -0,0 +1,159 @@
|
||||
# Argument Types & Suggestions
|
||||
|
||||
Pumpkin provides a comprehensive suite of type-safe command arguments through `pumpkin-command` and the main `pumpkin` server crate. Arguments deserialize raw token streams into structured Rust types, validate ranges, and furnish tab-completion suggestions to clients.
|
||||
|
||||
---
|
||||
|
||||
## Standard Argument Types
|
||||
|
||||
Argument parsers implement the [`ArgumentType<S>`](file:///home/alex/Documents/Development/Rust/Pumpkin/crates/pumpkin-command/src/argument_types/argument_type.rs) trait. The following built-in types are available for command builders:
|
||||
|
||||
### 1. Primitive & Scalar Types
|
||||
|
||||
| Argument Type | Import Path | Deserialized Type | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`IntegerArgumentType`** | `...::core::integer` | `i32` | Standard 32-bit integer with optional `[min, max]` bounds. |
|
||||
| **`FloatArgumentType`** | `...::core::float` | `f32` | 32-bit floating point number with bounds. |
|
||||
| **`DoubleArgumentType`** | `...::core::double` | `f64` | 64-bit floating point number with bounds. |
|
||||
| **`LongArgumentType`** | `...::core::long` | `i64` | 64-bit integer with bounds. |
|
||||
| **`BoolArgumentType`** | `...::core::bool` | `bool` | Boolean value (`true` or `false`). |
|
||||
| **`StringArgumentType`** | `...::core::string` | `String` | String tokens based on quoting rules. |
|
||||
|
||||
#### String Argument Behaviors
|
||||
- **`StringArgumentType::Word`**: Parses a single word without whitespace.
|
||||
- **`StringArgumentType::Quotable`**: Parses either a single word or a quoted phrase (`"hello world"`).
|
||||
- **`StringArgumentType::Greedy`**: Consumes all remaining characters in the input stream (ideal for chat/broadcast commands like `/say` and `/me`).
|
||||
|
||||
---
|
||||
|
||||
## Coordinate Argument Types
|
||||
|
||||
Minecraft coordinates support absolute positions, relative offsets (`~`), and local caret directions (`^`):
|
||||
|
||||
```rust
|
||||
use crate::command::argument_builder::argument;
|
||||
use crate::command::argument_types::coordinates::block_pos::BlockPosArgumentType;
|
||||
use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType;
|
||||
|
||||
// Block position (integer BlockPos)
|
||||
argument("pos", BlockPosArgumentType)
|
||||
|
||||
// Precise entity coordinates (f64 Vector3)
|
||||
argument("location", Vec3ArgumentType)
|
||||
```
|
||||
|
||||
### Retrieving Parsed Coordinates
|
||||
Inside an executor, coordinate arguments resolve relative offsets against the sender's current position and orientation in the [`CommandSource`](file:///home/alex/Documents/Development/Rust/Pumpkin/crates/pumpkin/src/command/context/command_source.rs):
|
||||
|
||||
```rust
|
||||
// Extracts centered f64 coordinates (handling ~ and ^ relative to sender)
|
||||
let pos: Vector3<f64> = Vec3ArgumentType::get_coordinates(context, "location")?;
|
||||
|
||||
// Extracts integer block position
|
||||
let block_pos: BlockPos = BlockPosArgumentType::get_block_pos(context, "pos")?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entity Selectors (`EntityArgumentType`)
|
||||
|
||||
Entity selectors allow players and command blocks to target entities using player names, UUIDs, or vanilla target selectors (`@p`, `@r`, `@a`, `@e`, `@s`).
|
||||
|
||||
In Pumpkin, entity selectors are accessed via [`EntityArgumentType`](file:///home/alex/Documents/Development/Rust/Pumpkin/crates/pumpkin/src/command/argument_types/entity.rs):
|
||||
|
||||
```rust
|
||||
pub enum EntityArgumentType {
|
||||
/// Matches exactly one player
|
||||
Player,
|
||||
/// Matches zero or more players
|
||||
Players,
|
||||
/// Matches exactly one entity
|
||||
Entity,
|
||||
/// Matches zero or more entities
|
||||
Entities,
|
||||
}
|
||||
```
|
||||
|
||||
### Resolution in Executors
|
||||
|
||||
```rust
|
||||
// 1. Target single player
|
||||
let player: Arc<Player> = EntityArgumentType::get_player(context, "target")?;
|
||||
|
||||
// 2. Target multiple players
|
||||
let players: Vec<Arc<Player>> = EntityArgumentType::get_players(context, "targets")?;
|
||||
|
||||
// 3. Target single living entity or mob
|
||||
let entity: Arc<dyn EntityBase> = EntityArgumentType::get_entity(context, "target")?;
|
||||
|
||||
// 4. Target multiple entities
|
||||
let entities: Vec<Arc<dyn EntityBase>> = EntityArgumentType::get_entities(context, "targets")?;
|
||||
```
|
||||
|
||||
### Supported Selector Syntaxes
|
||||
Pumpkin's entity selector parser (`crates/pumpkin/src/command/argument_types/entity_selector/`) parses and validates:
|
||||
- `@p`: Nearest player relative to execution coordinates.
|
||||
- `@r`: Random player.
|
||||
- `@a`: All online players.
|
||||
- `@e`: All entities in loaded world chunks.
|
||||
- `@s`: Executing entity (`context.source.entity`).
|
||||
- **Bracket Arguments**: Filter queries like `@e[type=zombie,distance=..10,limit=5,sort=nearest]`.
|
||||
|
||||
---
|
||||
|
||||
## Registry & Game Mechanic Types
|
||||
|
||||
Pumpkin provides specialized argument parsers for Minecraft data types:
|
||||
|
||||
- **`GameModeArgumentType`**: Resolves `survival`, `creative`, `adventure`, `spectator` or numeric IDs `0`–`3`.
|
||||
- **`BlockArgumentType`**: Parses block identifiers and block states (e.g., `minecraft:chest[facing=north]`).
|
||||
- **`ItemArgumentType`**: Parses item identifiers and optional data components (e.g., `minecraft:diamond_sword`).
|
||||
- **`NbtArgumentType`**: Parses SNBT (Stringified NBT) tags into Pumpkin's NBT representation.
|
||||
- **`TimeArgumentType`**: Parses durations with suffixes (e.g., `10s`, `20t`, `1d`).
|
||||
|
||||
---
|
||||
|
||||
## Suggestion Providers & Tab Completion
|
||||
|
||||
Arguments can supply dynamic tab-completion candidates to connected players using the [`SuggestionProvider`](file:///home/alex/Documents/Development/Rust/Pumpkin/crates/pumpkin-command/src/suggestion/mod.rs) trait:
|
||||
|
||||
```rust
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::suggestion::provider::SuggestionProvider;
|
||||
use pumpkin_command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
struct CustomSuggestionProvider;
|
||||
|
||||
impl SuggestionProvider for CustomSuggestionProvider {
|
||||
fn suggest(
|
||||
&self,
|
||||
context: &CommandContext,
|
||||
mut builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
// Retrieve already typed token prefix
|
||||
let remaining = builder.remaining();
|
||||
|
||||
let candidates = ["alpha", "bravo", "charlie"];
|
||||
for candidate in candidates {
|
||||
if candidate.starts_with(remaining) {
|
||||
builder.suggest(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
builder.build()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Attaching Suggestions to Arguments
|
||||
Use the `.suggests(...)` method on `RequiredArgumentBuilder`:
|
||||
|
||||
```rust
|
||||
argument("code", StringArgumentType::Word)
|
||||
.suggests(CustomSuggestionProvider)
|
||||
.executes(MyExecutor)
|
||||
```
|
||||
|
||||
### Client Completion Protocol
|
||||
- **Java Edition**: When a player presses `Tab`, the client transmits a `ServerboundCommandSuggestion` packet. Pumpkin matches the cursor position within the `CommandDispatcher`, executes the target node's `SuggestionProvider`, and responds with `ClientboundCommandSuggestions` containing completion ranges and tooltip tooltips.
|
||||
- **Bedrock Edition**: Suggestion options for literals and enums are serialized ahead of time in the `CAvailableCommands` packet as dynamic enum value tables.
|
||||
118
docs/en/developer/commands/execution-and-context.md
Normal file
118
docs/en/developer/commands/execution-and-context.md
Normal file
@ -0,0 +1,118 @@
|
||||
# Execution & Context
|
||||
|
||||
In Pumpkin, command execution separates the **physical origin** of a command from its **runtime context**. This design enables complex command routing, dimension shifts, conditional execution, and vanilla-compliant `/execute` chaining.
|
||||
|
||||
---
|
||||
|
||||
## `CommandSender` vs. `CommandSource`
|
||||
|
||||
Understanding the distinction between `CommandSender` and `CommandSource` is fundamental to developing commands in Pumpkin:
|
||||
|
||||
| Attribute | `CommandSender` | `CommandSource` |
|
||||
| :--- | :--- | :--- |
|
||||
| **Concept** | The physical origin and network destination | The mutable runtime execution environment |
|
||||
| **Mutability** | Immutable throughout command execution | Cloned and modified across `/execute` branches |
|
||||
| **Key Fields** | Terminal socket, connection handle, RCON buffer | World, position coordinates, rotation, entity, silent flag |
|
||||
| **Creation** | Instantiated on network connection or terminal input | Constructed via `sender.into_source(&server)` |
|
||||
|
||||
### 1. `CommandSender` (Physical Caller)
|
||||
Represents the entity or socket that initiated the command and receives its output:
|
||||
|
||||
```rust
|
||||
pub enum CommandSender {
|
||||
Console,
|
||||
Rcon(Arc<std::sync::Mutex<Vec<String>>>),
|
||||
Player(Arc<Player>),
|
||||
CommandBlock(Arc<CommandBlockEntity>, Arc<World>),
|
||||
Dummy,
|
||||
}
|
||||
```
|
||||
|
||||
- **Output Target**: Determines where messages formatted with `send_message` are routed (e.g., standard out, RCON buffer, player packet, or block entity NBT).
|
||||
- **Hard Permissions**: Holds fixed base permissions (e.g., Console has level 4 bypass; Command Blocks have level 2).
|
||||
|
||||
### 2. `CommandSource` (Execution Environment)
|
||||
Represents the mutable runtime state during command evaluation:
|
||||
|
||||
```rust
|
||||
pub struct CommandSource {
|
||||
pub output: CommandSender,
|
||||
pub world: Option<Arc<World>>,
|
||||
pub entity: Option<Arc<dyn EntityBase>>,
|
||||
pub position: Vector3<f64>,
|
||||
pub rotation: Vector2<f32>,
|
||||
pub name: String,
|
||||
pub display_name: TextComponent,
|
||||
pub server: Option<Arc<Server>>,
|
||||
pub silent: bool,
|
||||
pub command_result_taker: ResultValueTaker,
|
||||
pub entity_anchor: EntityAnchor,
|
||||
}
|
||||
```
|
||||
|
||||
A sender converts into a `CommandSource` via `sender.into_source(&server)`.
|
||||
|
||||
---
|
||||
|
||||
## The `/execute` Command & Context Redirection
|
||||
|
||||
The `/execute` command manipulates `CommandSource` properties before passing control to a downstream command.
|
||||
|
||||
### Branching Modifiers
|
||||
- **`as <targets>`**: Changes `context.source.entity` to target entities without altering position or rotation.
|
||||
- **`at <targets>`**: Updates `context.source.world`, `position`, and `rotation` to match the target entity.
|
||||
- **`positioned <pos>`**: Modifies `context.source.position` to new coordinates.
|
||||
- **`in <dimension>`**: Changes `context.source.world` to the specified dimension (Overworld, Nether, The End).
|
||||
- **`anchored (feet|eyes)`**: Adjusts coordinate offsets for line-of-sight and facing checks.
|
||||
|
||||
### Context Forking & Chaining
|
||||
When `/execute` matches multiple targets (e.g., `/execute as @e run ...`), `pumpkin-command` uses **forking**:
|
||||
1. The dispatcher forks the execution context once per matched target.
|
||||
2. The child command executes independently for each cloned `CommandSource`.
|
||||
3. The cumulative success count is aggregated across all executions.
|
||||
|
||||
---
|
||||
|
||||
## Result Values & Success Counts
|
||||
|
||||
Vanilla commands communicate results back through integer exit codes and return values.
|
||||
|
||||
### The Return Value Callback
|
||||
Executors return `CommandExecutorResult` (`Result<i32, CommandSyntaxError>`). The returned integer signifies:
|
||||
- **0**: Failure or zero entities affected.
|
||||
- **> 0**: Number of entities affected, blocks changed, or items cleared.
|
||||
|
||||
For Command Blocks, this value is persisted into the block entity's `SuccessCount`:
|
||||
|
||||
```rust
|
||||
pub fn set_success_count(&self, count: u32) {
|
||||
if let Self::CommandBlock(c, _) = self {
|
||||
c.success_count.store(count, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `/execute store`
|
||||
When chained with `/execute store (result|success) ...`, Pumpkin intercepts the return value via `ReturnValueCallback` and writes it to scoreboards, bossbars, or entity NBT tags.
|
||||
|
||||
---
|
||||
|
||||
## Feedback & GameRules Integration
|
||||
|
||||
Sending output to the command sender is governed by server configuration and world game rules:
|
||||
|
||||
```rust
|
||||
context.source.send_feedback(
|
||||
TextComponent::text("Command executed successfully!"),
|
||||
true, // Broadcast to server operators
|
||||
);
|
||||
```
|
||||
|
||||
### Output Rules Matrix
|
||||
|
||||
| Condition | Feedback Behavior |
|
||||
| :--- | :--- |
|
||||
| `silent == true` | Output is suppressed completely. |
|
||||
| `send_command_feedback == false` | Player senders do not receive success feedback (errors are still displayed). |
|
||||
| `command_block_output == false` | Command block execution output is not broadcast to online operators. |
|
||||
| `broadcast_console_to_ops == true` | Server console and RCON commands emit a copy of their feedback to all online OP level 2+ players. |
|
||||
142
docs/en/developer/commands/index.md
Normal file
142
docs/en/developer/commands/index.md
Normal file
@ -0,0 +1,142 @@
|
||||
# Commands Architecture & Dispatcher
|
||||
|
||||
Pumpkin features a high-performance, pure-Rust implementation of the **Brigadier** command tree specification through the dedicated `pumpkin-command` crate. It coordinates command parsing, requirement resolution, type-safe argument deserialization, execution branching, and dual-edition client tree synchronization for both **Minecraft: Java Edition** and **Minecraft: Bedrock Edition**.
|
||||
|
||||
### Command Processing Pipeline
|
||||
|
||||
Every command executed in Pumpkin flows through the following discrete stages:
|
||||
|
||||
1. **Input Normalization**: Strips single leading slashes while preserving plugin multi-slash conventions (such as `//set`).
|
||||
2. **Availability Check**: Rejects commands disabled via server configuration with standard `unknown command` syntax errors.
|
||||
3. **Permission Evaluation**: Validates requirements (`PermissionLvl` or custom registry nodes) against the executing sender.
|
||||
4. **Tokenization & Parsing**: `StringReader` recursively walks the `Tree<S>`, matching literal keywords and parsing typed argument parameters.
|
||||
5. **Context Dispatch**: Invokes the matched `CommandExecutor` callback with a typed `CommandContext<S>`.
|
||||
6. **Feedback & Result Consumption**: Formats cross-platform translation components, notifies observers, and writes exit codes back to the caller.
|
||||
|
||||
---
|
||||
|
||||
## The `pumpkin-command` Crate
|
||||
|
||||
Pumpkin decouples command parsing and tree representation from the main server runtime into the modular `pumpkin-command` crate (`crates/pumpkin-command`).
|
||||
|
||||
Key components of `pumpkin-command` include:
|
||||
- **`Tree<S>`**: Contiguous vector-backed storage of attached command nodes (`AttachedNode`).
|
||||
- **`CommandDispatcher<S>`**: The central registry and dispatch engine. It processes raw command strings, parses tokens with `StringReader`, traverses the command tree, and executes target callbacks.
|
||||
- **`ArgumentBuilder`**: Fluent builder API (`command`, `literal`, `argument`) modeled after Mojang's Brigadier library.
|
||||
- **`CommandSource`**: Generic execution state representing the current position, rotation, dimension, entity, and feedback output stream.
|
||||
|
||||
---
|
||||
|
||||
## The Command Node Hierarchy
|
||||
|
||||
Commands in Pumpkin are modeled as directed graphs of nodes. To optimize memory allocations and avoid circular reference issues during building, the system separates nodes into **detached** and **attached** states:
|
||||
|
||||
### 1. Detached Nodes (`DetachedNode`)
|
||||
Constructed using fluent builders (`CommandArgumentBuilder`, `LiteralArgumentBuilder`, `RequiredArgumentBuilder`) before registration:
|
||||
- **`CommandDetachedNode`**: Represents a root command entrypoint with an identifier and a description.
|
||||
- **`LiteralDetachedNode`**: Represents a literal keyword in a subcommand branch (e.g., `set` in `/gamemode creative`).
|
||||
- **`ArgumentDetachedNode`**: Represents a typed parameter parsed by an `ArgumentType` (e.g., coordinates, entity selectors, integer ranges).
|
||||
|
||||
### 2. Attached Nodes (`AttachedNode`)
|
||||
Once registered into the `CommandDispatcher`, detached trees are flattened into an indexed `Tree<S>` structure:
|
||||
```rust
|
||||
pub enum AttachedNode<S: CommandSource = DummySource> {
|
||||
Root(RootAttachedNode<S>),
|
||||
Literal(LiteralAttachedNode<S>),
|
||||
Argument(ArgumentAttachedNode<S>),
|
||||
Command(CommandAttachedNode<S>),
|
||||
}
|
||||
```
|
||||
Each attached node has:
|
||||
- **`NodeId`**: A zero-cost typed index (`NonZero<usize>`) into the contiguous node arena.
|
||||
- **`children`**: Fast lookup map (`FxHashMap<String, NodeId>`) for literals and child arguments.
|
||||
- **`requirements`**: Predicates determining whether a sender has permission to view or execute the node.
|
||||
- **`command`**: Optional `CommandExecutor` callback invoked when parsing terminates on this node.
|
||||
- **`redirect`**: Optional `Redirection` pointer to loop back or redirect to another sub-tree (used heavily by `/execute`).
|
||||
|
||||
---
|
||||
|
||||
## Dispatch & Execution Pipeline
|
||||
|
||||
When a command arrives at the server, `CommandDispatcher::handle_command` processes it through several stages:
|
||||
|
||||
```rust
|
||||
pub fn handle_command<'a>(&'a self, source: &S, mut input: &'a str) {
|
||||
// 1. Strip single leading slash (preserving double-slash commands like //set)
|
||||
if let Some(sliced) = input.strip_prefix('/') {
|
||||
let first_token = input.split_whitespace().next().unwrap_or("");
|
||||
let sliced_token = sliced.split_whitespace().next().unwrap_or("");
|
||||
if !self.has_command(first_token)
|
||||
&& (self.has_command(sliced_token) || !first_token.starts_with("//"))
|
||||
{
|
||||
input = sliced;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check disabled commands from configuration
|
||||
if self.is_disabled(Self::command_name(input)) {
|
||||
let reader = StringReader::new(input);
|
||||
Self::send_error_to_source(source, DISPATCHER_UNKNOWN_COMMAND.create(&reader), input);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Parse and execute
|
||||
let output = self.execute_input(input, source);
|
||||
if let Err(error) = output {
|
||||
Self::send_error_to_source(source, error, input);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Syntax Error Formatting
|
||||
When parsing encounters invalid syntax, unexpected tokens, or out-of-range arguments, Pumpkin constructs a `CommandSyntaxError`. If context is available, the dispatcher formats a visual pointer highlighting exactly where the parser failed:
|
||||
|
||||
```
|
||||
Unknown or incomplete command, see below for error
|
||||
/gamemode creatve<--[HERE]
|
||||
```
|
||||
|
||||
In the Java Edition client, this error text is annotated with a `ClickEvent::SuggestCommand`, allowing the player to click the error in chat to copy the failed command into their input box for quick editing.
|
||||
|
||||
---
|
||||
|
||||
## Dual-Edition Network Synchronization
|
||||
|
||||
Unlike vanilla servers which only serialize command graphs for Java Edition, Pumpkin dynamically synchronizes the registered command tree across both Java and Bedrock clients:
|
||||
|
||||
### 1. Java Edition: `CCommands` Packet
|
||||
When a player joins or their permission level updates (e.g., via `/op` or `/deop`), Pumpkin builds and sends a `CCommands` packet (`pumpkin_protocol::java::client::play::CCommands`):
|
||||
- Converts `AttachedNode`s into a flat array of `ProtoNode` entries.
|
||||
- Filters out disabled commands and nodes where `requirements.evaluate(source)` returns `false`.
|
||||
- Serializes argument parsers (`brigadier:string`, `brigadier:integer`, `minecraft:entity`, etc.) and client-side suggestion identifiers.
|
||||
|
||||
### 2. Bedrock Edition: `CAvailableCommands` Packet
|
||||
Bedrock Edition uses an overload-and-enum format rather than Brigadier's recursive graph. In `client_suggestions.rs`, Pumpkin traverses the `AttachedNode` tree to synthesize:
|
||||
- **`CommandData`**: Root command name, description, permission flags, and aliases.
|
||||
- **`OverloadData`**: Flattened permutations of argument chains leading to executable endpoints.
|
||||
- **`ParamData`**: Bedrock parameter types, bitflags (`ARG_FLAG_VALID`, `ARG_FLAG_ENUM`), and dynamically collected enum value tables.
|
||||
|
||||
---
|
||||
|
||||
## Universal Execution Targets
|
||||
|
||||
Pumpkin commands can be executed across diverse senders:
|
||||
|
||||
| Sender | Source Representation | Output Destination | Default Permission |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Player** | `CommandSender::Player(Arc<Player>)` | Client system chat packet | Loaded from permission level / permissions file |
|
||||
| **Server Console** | `CommandSender::Console` | Standard stdout (`tracing::info!`) | Level 4 (Full administrative bypass) |
|
||||
| **RCON Console** | `CommandSender::Rcon(Arc<Mutex<Vec<String>>>)` | Network response packet payload | Level 4 |
|
||||
| **Command Block** | `CommandSender::CommandBlock(Arc<CommandBlockEntity>, Arc<World>)` | NBT `LastOutput` field | Level 2 |
|
||||
| **Datapack Function** | `CommandSender` inherited from caller | Inherited | Inherited |
|
||||
| **WASM Plugin** | Invoked through WIT `execute_command` host call | Captured via `ResultDeferrer` | Configurable |
|
||||
|
||||
---
|
||||
|
||||
## Command Configuration & Disabling
|
||||
|
||||
Pumpkin allows server administrators to disable individual commands via `config/commands.toml` (`CommandsConfig`). When a command is disabled:
|
||||
1. It is omitted from the `CCommands` packet for Java clients.
|
||||
2. It is omitted from the `CAvailableCommands` packet for Bedrock clients.
|
||||
3. Tab completion ignores the command entirely.
|
||||
4. Any attempt to invoke the command returns `DISPATCHER_UNKNOWN_COMMAND`.
|
||||
@ -1,72 +1,271 @@
|
||||
# Contributing to Pumpkin
|
||||
|
||||
We appreciate your interest in contributing to Pumpkin! This document outlines the guidelines for submitting bug reports, feature suggestions, and code changes.
|
||||
Thank you for your interest in contributing to Pumpkin! As an open-source Minecraft server built from the ground up in Rust, we welcome contributions of all sizes—from fixing typos and adding documentation to implementing vanilla game mechanics and optimizing network protocols.
|
||||
|
||||
This guide outlines our development workflow, coding standards, verification checklist, and pull request procedures.
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
The easiest way to get started is by asking for help in [our Discord server](https://discord.gg/wT8XjrjKkf).
|
||||
The easiest way to get help, discuss proposed features, or coordinate with the team is by joining [our Discord server](https://discord.gg/wT8XjrjKkf).
|
||||
|
||||
## How to Contribute
|
||||
Before working on a large change, we strongly recommend opening an issue or starting a discussion in Discord to align on architecture and design decisions before writing code.
|
||||
|
||||
There are several ways you can contribute to Pumpkin:
|
||||
> [!NOTE]
|
||||
> **For Automated AI Agents**: We have a streamlined process for merging agent PRs. Add `🤖🤖🤖` to the end of your PR title to opt into fast-track review. Ensure your implementation is unique, passes all CI checks, and does not duplicate existing pull requests.
|
||||
|
||||
### Reporting Bugs
|
||||
---
|
||||
|
||||
If you encounter a bug, please search for existing issues on the issue tracker first.
|
||||
## Development Setup
|
||||
|
||||
If you can't find a duplicate issue, open a new one.
|
||||
### 1. Prerequisites
|
||||
|
||||
Follow the template and provide a clear description of the bug, including steps to reproduce it if possible.
|
||||
Screenshots, logs, or code snippets can also be helpful.
|
||||
- **Rust Toolchain**: Pumpkin requires the latest stable Rust toolchain (Rust 2024 edition, 1.96+). Install or update Rust via [rustup](https://rustup.rs/):
|
||||
```bash
|
||||
rustup update stable
|
||||
rustup default stable
|
||||
rustup component add rustfmt clippy rust-src rust-analyzer
|
||||
```
|
||||
- **Git**: Ensure Git is installed and configured on your machine.
|
||||
- **Recommended Cargo Tools**:
|
||||
```bash
|
||||
# Fast unit test runner used in CI
|
||||
cargo install cargo-nextest --locked
|
||||
|
||||
### Suggesting Features
|
||||
# Detects unused dependencies
|
||||
cargo install cargo-machete --locked
|
||||
|
||||
Do you have an idea on how Pumpkin can be improved? Share your thoughts by opening an issue on the issue tracker.
|
||||
# Source code spell checker
|
||||
cargo install typos-cli --locked
|
||||
```
|
||||
|
||||
Describe the proposed feature in detail, including its benefits and potential implementation considerations.
|
||||
### 2. Cloning the Repository (Submodules Required!)
|
||||
|
||||
### Contributing Code
|
||||
Pumpkin uses Git submodules for interfaces such as `pumpkin-plugin-wit`. Make sure to clone recursively:
|
||||
|
||||
To get started with contributing code to Pumpkin, fork the repository on GitHub
|
||||
```bash
|
||||
# Clone with submodules
|
||||
git clone --recursive https://github.com/Pumpkin-MC/Pumpkin.git
|
||||
cd Pumpkin
|
||||
```
|
||||
|
||||
1. First, create a GitHub account if you don't already have one
|
||||
If you have already cloned without `--recursive`, initialize the submodules manually:
|
||||
|
||||
2. Go to Pumpkin's official [GitHub Organization](https://github.com/Pumpkin-MC) and press fork
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
> Creating a fork means you now have your own copy of the Pumpkin source code (this does not mean you own the copyright).
|
||||
### 3. Building and Running
|
||||
|
||||
Now that you have a copy that you can edit, you will need a few tools.
|
||||
3. Install [git](https://git-scm.com/downloads) for your operating system
|
||||
You can compile and launch the server in debug mode:
|
||||
|
||||
- To get started with git, visit [Getting started with Git](https://docs.github.com/en/get-started/getting-started-with-git)
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
- Optional: If you want a graphical tool to interact with GitHub, install [GitHub-Desktop](https://desktop.github.com/download/)
|
||||
To build an optimized release binary (with LTO and single codegen units):
|
||||
|
||||
> GitHub Desktop may be easier if you are not used to the command line, but it is not for everyone
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
- To get started with GitHub Desktop, visit [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/overview/getting-started-with-github-desktop)
|
||||
---
|
||||
|
||||
- If you are wanting to contribute code, install Rust at [rust-lang.org](https://www.rust-lang.org/).
|
||||
## Coding Guidelines & Standards
|
||||
|
||||
- If you are wanting to contribute to documentation, install [NodeJS](https://nodejs.org/en)
|
||||
Pumpkin maintains high code quality and strict performance criteria. Our workspace enforces stringent Clippy lints, and CI will reject code that fails these checks.
|
||||
|
||||
### Decompiling Minecraft's code
|
||||
### 1. The Zero-Panic Policy
|
||||
|
||||
When working at Pumpkin, we heavily rely on the official Minecraft client and utilize existing server logic. We often refer to Minecraft's official code.
|
||||
The easiest way to decompile Minecraft is by using Fabric Yarn:
|
||||
Pumpkin is designed to run 24/7 without crashing under unexpected inputs. Therefore, panic-inducing constructs are denied at the compiler level:
|
||||
|
||||
```shell
|
||||
- **No `unwrap()` or `expect()`**: Never call `.unwrap()` or `.expect()` in production code. Use the question mark operator `?`, pattern matching (`match` / `if let`), `.unwrap_or()`, or `.unwrap_or_default()`.
|
||||
```rust
|
||||
// ❌ FORBIDDEN: Will fail CI
|
||||
let chunk = level.get_chunk(pos).unwrap();
|
||||
|
||||
// ✅ CORRECT: Handle Option or propagate error
|
||||
let Some(chunk) = level.get_chunk(pos) else {
|
||||
return Ok(());
|
||||
};
|
||||
```
|
||||
- **No `panic!()`, `todo!()`, or `unimplemented!()`**: Do not leave unhandled stubs or panic statements in committed code.
|
||||
- **No `println!()` or `eprintln!()`**: Always use the structured logging framework [`tracing`](https://docs.rs/tracing/):
|
||||
```rust
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
// ❌ FORBIDDEN: Will fail CI
|
||||
println!("Player joined: {}", player.gameprofile.name);
|
||||
|
||||
// ✅ CORRECT
|
||||
info!("Player {} joined the server", player.gameprofile.name);
|
||||
```
|
||||
|
||||
### 2. Error Handling
|
||||
|
||||
We use [`thiserror`](https://docs.rs/thiserror/) across our crates and unified error types like `PumpkinError`. Functions should return descriptive `Result<T, E>` types rather than suppressing errors silently.
|
||||
|
||||
### 3. Concurrency Architecture: Tokio vs. Rayon
|
||||
|
||||
Pumpkin uses a hybrid concurrency model:
|
||||
|
||||
1. **Tokio Runtime (Asynchronous I/O)**: Handles TCP/UDP network connections, incoming/outgoing packet streams, authentication HTTP requests, and timers.
|
||||
2. **Rayon Thread Pool (CPU-Intensive Tasks)**: Handles heavy, CPU-bound computations such as:
|
||||
- Chunk generation and terrain carving
|
||||
- Lighting propagation and recalculation
|
||||
- Packet compression and world chunk serialization
|
||||
- Complex pathfinding and physics
|
||||
|
||||
> [!WARNING]
|
||||
> **Never block Tokio worker threads with CPU-heavy work!**
|
||||
> Do not call long-running synchronous code inside async tasks. Instead, offload heavy work to Rayon via `rayon::spawn` and communicate results back using asynchronous channels like `tokio::sync::mpsc`.
|
||||
>
|
||||
> Refer to `pumpkin_world::level::Level::fetch_chunks` for an architectural example.
|
||||
|
||||
### 4. Unsafe Code Policy
|
||||
|
||||
All `unsafe` blocks must be strictly justified and documented:
|
||||
|
||||
```rust
|
||||
// SAFETY: The buffer is guaranteed to contain at least 4 valid bytes
|
||||
// as verified by the bounds check on line 42.
|
||||
unsafe {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
Any undocumented `unsafe` block triggers a denied lint in CI (`undocumented_unsafe_blocks = "deny"`).
|
||||
|
||||
---
|
||||
|
||||
## Pre-Commit Verification Checklist
|
||||
|
||||
Before submitting a Pull Request, run the following commands locally to ensure your changes will pass our GitHub Actions CI pipeline:
|
||||
|
||||
```bash
|
||||
# 1. Format code according to rustfmt rules
|
||||
cargo fmt --check
|
||||
|
||||
# 2. Run debug lints
|
||||
cargo clippy --all-targets --all-features
|
||||
|
||||
# 3. Run release lints
|
||||
cargo clippy --release --all-targets --all-features
|
||||
|
||||
# 4. Run tests with nextest (or cargo test)
|
||||
cargo nextest run --verbose
|
||||
|
||||
# 5. Run documentation tests
|
||||
cargo test --doc --verbose
|
||||
|
||||
# 6. Check for unused dependencies
|
||||
cargo machete
|
||||
|
||||
# 7. Check for spelling typos
|
||||
typos
|
||||
```
|
||||
|
||||
### Benchmarking Performance
|
||||
|
||||
If you are modifying performance-sensitive hot paths (such as packet serialization, chunk loading, or entity ticking), run benchmarks with [Criterion](https://github.com/bheisler/criterion.rs):
|
||||
|
||||
```bash
|
||||
cargo bench
|
||||
```
|
||||
|
||||
Consider adding a new benchmark in `benches/` when introducing complex algorithms.
|
||||
|
||||
---
|
||||
|
||||
## Decompiling Minecraft for Reference
|
||||
|
||||
When implementing vanilla Minecraft mechanics, packet layouts, or game logic, referencing official behavior is essential. Pumpkin is a clean-room Rust implementation, and understanding vanilla Minecraft logic helps ensure 1:1 compatibility.
|
||||
|
||||
### Decompiling with Fabric Yarn
|
||||
|
||||
The easiest and cleanest way to decompile the Minecraft server/client for reference is using [Fabric Yarn](https://github.com/FabricMC/yarn) with the Vineflower decompiler:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FabricMC/yarn.git
|
||||
cd yarn
|
||||
./gradlew decompileVineflower
|
||||
```
|
||||
|
||||
After decompiling, you can find the source code located in `build/namedSrc`.
|
||||
After decompilation completes, the mapped source code will be available in:
|
||||
```
|
||||
build/namedSrc
|
||||
```
|
||||
|
||||
### Additional Information
|
||||
### Useful Protocol & Data Resources
|
||||
|
||||
We encourage you to comment on existing issues and pull requests to share your thoughts and provide feedback.
|
||||
- **[Minecraft Wiki Protocol Reference](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol)**: Complete reference of packet structures, fields, and IDs across game states.
|
||||
- **[Mojang Data Reports](https://github.com/Arcensoth/mc-data)**: Raw JSON dumps of registries, blocks, items, commands, and packet IDs.
|
||||
- **`tools/pumpkin-codegen`**: Our internal tool for parsing Minecraft game data and generating `pumpkin-data` code.
|
||||
|
||||
Feel free to ask questions in the issue tracker or reach out to the project maintainers if you need assistance.
|
||||
> [!IMPORTANT]
|
||||
> **Clean-Room Etiquette**: Do not copy-paste decompiled Java code directly into Pumpkin. Study the algorithm and behavior, then write clean, idiomatic, safe Rust that fits Pumpkin's architecture and licensing (GPLv3).
|
||||
|
||||
Before submitting a large contribution, consider opening an issue or discussion, or talk with us on our Discord to discuss your approach.
|
||||
---
|
||||
|
||||
## Submitting Pull Requests
|
||||
|
||||
When your code is tested, formatted, and verified locally:
|
||||
|
||||
1. **Fork the Repository**: Fork [Pumpkin-MC/Pumpkin](https://github.com/Pumpkin-MC/Pumpkin) on GitHub.
|
||||
2. **Create a Feature Branch**:
|
||||
```bash
|
||||
git checkout -b feature/my-new-feature
|
||||
```
|
||||
3. **Commit Your Changes**: Follow the **Conventional Commits** specification (detailed below).
|
||||
4. **Push and Open a Pull Request**:
|
||||
- **PR Title**: Ensure your pull request title strictly conforms to the Conventional Commits specification.
|
||||
- **Description**: Use our PR template to explain:
|
||||
- **What** was changed?
|
||||
- **Why** was it changed?
|
||||
- **How** was it tested?
|
||||
- Are there any known limitations or edge cases?
|
||||
- **AI Agents**: Suffix your PR title with `🤖🤖🤖` if submitting via an automated AI agent.
|
||||
5. **CI & Code Review**: GitHub Actions will automatically verify formatting, clippy lints, tests, and dependencies. Maintainers will review your implementation and provide feedback.
|
||||
|
||||
---
|
||||
|
||||
## Conventional Commits Specification
|
||||
|
||||
All commit messages and pull request titles in Pumpkin must strictly follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification. This convention ensures consistent commit history and automated release changelog generation.
|
||||
|
||||
### Commit & PR Title Structure
|
||||
|
||||
```text
|
||||
<type>(<optional scope>): <imperative summary>
|
||||
|
||||
[optional detailed body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
### Commit Types
|
||||
|
||||
| Type | Purpose | Example |
|
||||
| :--- | :--- | :--- |
|
||||
| `feat` | Introduces a new feature or vanilla game mechanic | `feat(entity): implement drowned swimming goal` |
|
||||
| `fix` | Resolves a bug, glitch, or protocol issue | `fix(protocol): resolve creative slot index offset` |
|
||||
| `docs` | Documentation additions or updates | `docs(developer): add porting versions guide` |
|
||||
| `perf` | Code modifications that improve execution speed or memory usage | `perf(world): optimize chunk section block iteration` |
|
||||
| `refactor` | Code changes that neither fix bugs nor add features | `refactor(command): unify argument parsing helpers` |
|
||||
| `test` | Adding missing tests or correcting existing tests | `test(inventory): add crafting table recipe tests` |
|
||||
| `chore` | Routine maintenance, dependency bumps, or toolchain updates | `chore(deps): bump tokio to 1.43` |
|
||||
| `ci` | Modifications to CI workflows, scripts, or Docker containers | `ci(github): add machete dependency verification` |
|
||||
|
||||
### Common Scopes
|
||||
|
||||
Scopes indicate the crate or subsystem being modified:
|
||||
|
||||
- **Crates**: `protocol`, `world`, `entity`, `command`, `inventory`, `data`, `util`, `macros`, `config`
|
||||
- **Subsystems**: `bedrock`, `java`, `network`, `proxy`, `query`, `auth`, `plugin`, `linear`, `anvil`
|
||||
|
||||
### Format Guidelines
|
||||
|
||||
- **Imperative Mood**: Use imperative present tense in summaries (`add`, not `added` or `adds`; `fix`, not `fixed` or `fixes`).
|
||||
- **Lowercase**: Start summaries with lowercase characters and omit trailing punctuation.
|
||||
- **Breaking Changes**: Mark breaking API or protocol changes with an exclamation mark before the colon (`feat(protocol)!: overhaul packet codec`) or include `BREAKING CHANGE:` in the commit footer.
|
||||
- **PR Title Uniformity**: Because PR titles are squashed into the main branch history on merge, PR titles must also follow this specification.
|
||||
|
||||
111
docs/en/developer/entities/ai-and-mobs.md
Normal file
111
docs/en/developer/entities/ai-and-mobs.md
Normal file
@ -0,0 +1,111 @@
|
||||
# Mob AI & Goal System
|
||||
|
||||
Pumpkin implements a modular, priority-driven Artificial Intelligence (AI) goal system for computer-controlled mobs, located in [`crates/pumpkin/src/entity/ai/`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin/src/entity/ai) and [`crates/pumpkin/src/entity/mob/`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin/src/entity/mob).
|
||||
|
||||
---
|
||||
|
||||
## The Goal System Architecture
|
||||
|
||||
Mob behaviors are decomposed into independent modular tasks called **Goals**. Each mob possesses a **GoalSelector** that maintains an ordered collection of goals evaluated on every server tick.
|
||||
|
||||
### Goal Priority Hierarchy
|
||||
|
||||
Goals are assigned an integer priority (where lower numbers represent higher precedence). During each tick, higher-priority actions preempt lower-priority background tasks:
|
||||
|
||||
| Priority | Goal Type | Evaluation Condition | Behavior |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **1 (Highest)** | `SwimGoal` | Entity is submerged in water | Jump repeatedly to swim to the water surface and avoid drowning. |
|
||||
| **2** | `MeleeAttackGoal` | Valid hostile target in sight/range | Navigate towards target entity and execute melee attacks. |
|
||||
| **3** | `WanderAroundGoal` | No active combat target | Pick a random nearby navigable voxel and walk to it. |
|
||||
| **4 (Lowest)** | `LookAtEntityGoal` | Player or mob within observation radius | Rotate head and body pitch/yaw towards the target entity. |
|
||||
|
||||
### Execution Flow
|
||||
|
||||
1. **Evaluation**: On every game tick, `GoalSelector::tick()` iterates through registered goals in order of priority.
|
||||
2. **Activation**: If an inactive goal satisfies `can_start()`, it is activated via `start()`.
|
||||
3. **Execution**: Active goals run their per-tick logic in `tick()`.
|
||||
4. **Preemption & Termination**: If `should_continue()` returns `false`, or a mutually exclusive goal with higher priority activates, `stop()` is called and the goal resets.
|
||||
|
||||
### The `Goal` Lifecycle
|
||||
|
||||
Every AI behavior implements the `Goal` trait:
|
||||
|
||||
```rust
|
||||
pub trait Goal: Send + Sync {
|
||||
/// Evaluates preconditions to determine if this goal can activate.
|
||||
fn can_start(&self) -> bool;
|
||||
|
||||
/// Called once when the goal transitions from inactive to active.
|
||||
fn start(&mut self) {}
|
||||
|
||||
/// Evaluates whether the goal should continue executing on subsequent ticks.
|
||||
fn should_continue(&self) -> bool {
|
||||
self.can_start()
|
||||
}
|
||||
|
||||
/// Executes the continuous behavior on each server tick.
|
||||
fn tick(&mut self) {}
|
||||
|
||||
/// Called when the goal completes, fails, or is preempted by a higher priority goal.
|
||||
fn stop(&mut self) {}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Standard Built-in Goals
|
||||
|
||||
Pumpkin provides standard vanilla AI behaviors in `crates/pumpkin/src/entity/ai/`:
|
||||
|
||||
| Goal | Description | Target Mobs |
|
||||
| :--- | :--- | :--- |
|
||||
| **`SwimGoal`** | Jumps repeatedly while submerged in water to prevent drowning. | Almost all land mobs |
|
||||
| **`MeleeAttackGoal`** | Navigates towards an attack target and executes a strike when within reach. | Zombies, Spiders, Iron Golems |
|
||||
| **`RangedAttackGoal`** | Stays at range and shoots projectiles (arrows, snowballs). | Skeletons, Pillagers, Blazes |
|
||||
| **`FleeSunGoal`** | Seeks shade or water during daytime to prevent combustion. | Zombies, Skeletons |
|
||||
| **`PanicGoal`** | Sprints in random directions when taking damage. | Cows, Pigs, Sheep, Chickens |
|
||||
| **`WanderAroundGoal`** | Selects random accessible coordinates nearby when idle. | Passive & neutral mobs |
|
||||
| **`LookAtEntityGoal`** | Rotates head to face nearby players within sensory range. | Villagers, Mobs |
|
||||
|
||||
---
|
||||
|
||||
## Navigation & Pathfinding
|
||||
|
||||
Mobs navigate the block world using Pumpkin's 3D grid pathfinder:
|
||||
|
||||
1. **Path Calculation**: Resolves a path from the mob's origin to target coordinates using an A* algorithm optimized for Minecraft block geometries.
|
||||
2. **Obstacle Avoidance**:
|
||||
- Avoids hazardous blocks (lava, fire, sweet berry bushes).
|
||||
- Prevents walking off ledges higher than the mob's fall threshold.
|
||||
- Computes jumping steps over 1-block obstacles.
|
||||
3. **Waypoint Steering**: The navigation engine updates the mob's velocity vectors per tick to smoothly guide the entity along waypoints.
|
||||
|
||||
---
|
||||
|
||||
## Entity Attributes System
|
||||
|
||||
Mob capabilities and stats are governed by the **Attributes** system in [`crates/pumpkin/src/entity/attributes.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/entity/attributes.rs):
|
||||
|
||||
```rust
|
||||
use pumpkin_data::entity::attribute::EntityAttribute;
|
||||
|
||||
// Common vanilla attributes
|
||||
EntityAttribute::GENERIC_MAX_HEALTH; // Base hit points (e.g. 20.0 for Zombies)
|
||||
EntityAttribute::GENERIC_MOVEMENT_SPEED; // Base walking velocity (e.g. 0.23)
|
||||
EntityAttribute::GENERIC_ATTACK_DAMAGE; // Melee attack damage (e.g. 3.0)
|
||||
EntityAttribute::GENERIC_FOLLOW_RANGE; // Sensory detection range in blocks (e.g. 35.0)
|
||||
EntityAttribute::GENERIC_KNOCKBACK_RESISTANCE; // Resistance against knockback (0.0 to 1.0)
|
||||
```
|
||||
|
||||
### Modifiers
|
||||
Attributes support dynamic modifiers with additive or multiplicative calculations (e.g., speed bonuses from Swiftness potions or armor attribute boosts).
|
||||
|
||||
---
|
||||
|
||||
## Ageable & Passive Entities
|
||||
|
||||
Passive animals (cows, sheep, pigs) implement the `Ageable` trait:
|
||||
|
||||
- **Baby State**: Renders with smaller collision boxes and oversized heads.
|
||||
- **Growing Age**: A negative tick timer counting upward to `0`. When it reaches `0`, the entity matures into an adult.
|
||||
- **Breeding Cooldown**: Positive tick timer tracking cooldowns between successful matings.
|
||||
77
docs/en/developer/entities/index.md
Normal file
77
docs/en/developer/entities/index.md
Normal file
@ -0,0 +1,77 @@
|
||||
# Entity Architecture & Hierarchy
|
||||
|
||||
Entities in Minecraft represent all dynamic, living and non-living objects that exist within a world—ranging from dropped item stacks and arrows to zombies, villagers, and players.
|
||||
|
||||
In Pumpkin, entity systems are implemented in [`crates/pumpkin/src/entity/`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin/src/entity). The architecture leverages object-oriented trait composition on top of concurrency-safe, cache-friendly data structures.
|
||||
|
||||
---
|
||||
|
||||
## Entity Trait Hierarchy
|
||||
|
||||
Pumpkin structures entities into an inheritance hierarchy using Rust traits:
|
||||
|
||||
| Trait / Type | Parent Trait | Core Capabilities & State |
|
||||
| :--- | :--- | :--- |
|
||||
| `Entity` | *(None / Base Struct)* | Entity ID, UUID, atomic position and velocity vectors, bounding box, pose |
|
||||
| `EntityBase` | *(Root Trait)* | Dynamic runtime downcasting (`get_living_entity`, etc.), world tick, damage, teleportation |
|
||||
| `LivingEntity` | `EntityBase` | Health, absorption, potion effects, equipment slots, attribute containers |
|
||||
| `Mob` | `LivingEntity` | AI goal selector, navigation, pathfinding, target tracking, death loot |
|
||||
| `Player` | `LivingEntity` | Network client session, inventory, crafting screens, hunger, advancements |
|
||||
|
||||
### 1. `Entity` (Base Struct)
|
||||
Contains fundamental physics, geometry, and spatial state common to all entities:
|
||||
- **`entity_id`**: Fast 32-bit unique integer identifier used across network packets.
|
||||
- **`uuid`**: 128-bit persistent UUID.
|
||||
- **`position` & `velocity`**: Thread-safe vectors (`AtomicCell<Vector3<f64>>`) allowing concurrent reading without lock contention.
|
||||
- **`bounding_box`**: 3D axis-aligned bounding box (AABB) used for collision resolution and raycast targeting.
|
||||
- **`pose`**: Current pose (`Standing`, `Crouching`, `Sleeping`, `FallFlying`, etc.).
|
||||
|
||||
### 2. `EntityBase` (Foundation Trait)
|
||||
The root dynamic trait implemented by every entity in the server:
|
||||
- Supports safe runtime downcasting via helper methods (`get_living_entity()`, `get_mob()`, `get_player()`).
|
||||
- Provides hooks for world interaction: `tick()`, `damage()`, `teleport()`, `on_lightning_strike()`, `is_immune_to_explosion()`.
|
||||
|
||||
### 3. `LivingEntity` (Biological Trait)
|
||||
Extends `EntityBase` for entities with health, combat, and animation states:
|
||||
- Manages health bars, absorption, invulnerability ticks, and fall damage.
|
||||
- Tracks active potion effects (`crates/pumpkin/src/entity/effect/`).
|
||||
- Handles equipment slots (`MainHand`, `OffHand`, `Helmet`, `Chestplate`, `Leggings`, `Boots`).
|
||||
- Encapsulates attribute containers (max health, movement speed, knockback resistance).
|
||||
|
||||
### 4. `Mob` (AI Trait)
|
||||
Extends `LivingEntity` for computer-controlled mobs:
|
||||
- Manages AI goal selectors (attacking, wandering, fleeing, looking around).
|
||||
- Controls navigation and pathfinding over blocks.
|
||||
- Spawns configured loot tables upon death.
|
||||
|
||||
### 5. `Player` (Human Client)
|
||||
Represents a human player connected through a game client:
|
||||
- Links to network socket handlers (`JavaClient` or `BedrockClient`).
|
||||
- Manages container screen handlers, ender chests, player inventory, and crafting.
|
||||
- Tracks hunger, saturation, exhaustion, experience levels, and advancement criteria.
|
||||
|
||||
---
|
||||
|
||||
## Physics & Movement Simulation
|
||||
|
||||
On every server tick, Pumpkin simulates entity physics through a deterministic sequence:
|
||||
|
||||
1. **Apply Velocity & Gravity**: Evaluates current velocity and adds downward gravitational acceleration unless `has_gravity()` is false or the entity is flying.
|
||||
2. **Resolve Block Collisions**: Checks the entity's 3D axis-aligned bounding box (`bounding_box`) against solid block voxels in the surrounding chunk; velocity along colliding axes is dampened or zeroed.
|
||||
3. **Apply Fluid Drag & Buoyancy**: Evaluates whether the entity intersects water or lava fluids, applying drag coefficients and buoyant upward force.
|
||||
4. **Synchronize & Broadcast**: Persists the updated coordinates in atomic position cells and broadcasts relative delta movement packets to observing players.
|
||||
5. **Void Death**: Non-player entities falling below $Y = -128$ are automatically despawned.
|
||||
|
||||
---
|
||||
|
||||
## Combat & Damage System
|
||||
|
||||
Damage calculations reside in [`crates/pumpkin/src/entity/combat.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/entity/combat.rs):
|
||||
|
||||
1. **Damage Types**: Damage is categorized by `DamageType` (e.g., `GENERIC`, `PLAYER_ATTACK`, `FALL`, `FIRE`, `MAGIC`, `EXPLOSION`, `LIGHTNING_BOLT`).
|
||||
2. **Damage Scaling**:
|
||||
- Armor defense points mitigate incoming physical damage according to the vanilla formula:
|
||||
$$\text{damage} = \text{damage} \times \left(1 - \frac{\min(20, \max(\text{armor} / 5, \text{armor} - \frac{\text{damage}}{2 + \text{toughness} / 4}))}{25}\right)$$
|
||||
- Resistance status effects and Protection enchantments apply secondary reductions.
|
||||
3. **Knockback**: When damage is dealt, a directional impulse vector is added to the victim's velocity, modulated by their knockback resistance attribute.
|
||||
4. **Hurt Animations**: Triggers hurt sounds and broadcasts red-tint flash metadata to all observers.
|
||||
95
docs/en/developer/entities/spawning-and-tracking.md
Normal file
95
docs/en/developer/entities/spawning-and-tracking.md
Normal file
@ -0,0 +1,95 @@
|
||||
# Spawning & Network Tracking
|
||||
|
||||
To maintain high tick rates with hundreds of active entities, Pumpkin utilizes a chunk-based entity tracking and streaming system.
|
||||
|
||||
Entities are held in chunk-level collections inside [`crates/pumpkin-world`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-world) and synchronized across clients via edition-specific network packets.
|
||||
|
||||
---
|
||||
|
||||
## The Spawning Pipeline
|
||||
|
||||
Spawning an entity involves creating an instance, allocating its network IDs, registering it with the world, and broadcasting spawn packets to chunk observers:
|
||||
|
||||
1. **Construct Entity Instance**: Instantiate the specific entity struct (such as `ZombieEntity`, `ArrowEntity`, or `ItemEntity`) initialized with its starting world and spatial coordinates.
|
||||
2. **Allocate Unique Network Identifiers**: Assign an atomic 32-bit `entity_id` for network protocol packets and a 128-bit persistent UUID.
|
||||
3. **Register in World & Chunk Map**: Insert the entity into the world's entity storage and index it within its corresponding chunk coordinate entry.
|
||||
4. **Query Chunk Observers**: Look up all connected players within tracking range whose view distance includes the containing chunk.
|
||||
5. **Broadcast Edition-Specific Spawn Packets**: Transmit the relevant spawn and metadata packets tailored to each observing client's edition (Java or Bedrock).
|
||||
|
||||
### Spawning via the World API
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use crate::entity::Entity;
|
||||
use crate::entity::mob::zombie::ZombieEntity;
|
||||
|
||||
// 1. Create base entity with target world and position
|
||||
let position = Vector3::new(100.5, 64.0, -200.5);
|
||||
let base_entity = Entity::new(world.clone(), position, &EntityType::ZOMBIE);
|
||||
|
||||
// 2. Wrap in specific entity implementation
|
||||
let zombie = Arc::new(ZombieEntity::new(base_entity));
|
||||
|
||||
// 3. Register into the world
|
||||
world.spawn_entity(zombie);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Network Packet Synchronization
|
||||
|
||||
When a player enters a chunk containing entities—or when an entity moves into an observed chunk—Pumpkin dispatches spawn packets tailored to the client's edition:
|
||||
|
||||
### Java Edition Spawning
|
||||
1. **`CSpawnEntity`**: Declares the entity ID, UUID, entity type, position, and initial pitch/yaw.
|
||||
2. **`CSetEntityMetadata`**: Sends initial visual states (custom name tags, burning flags, baby/adult state).
|
||||
3. **`CEntityVelocity`**: Synchronizes initial momentum.
|
||||
4. **`CUpdateEntityRot` & `CHeadRot`**: Synchronizes head orientation independently from body yaw.
|
||||
|
||||
### Bedrock Edition Spawning
|
||||
1. **`CAddActor`**: Declares the runtime ID, actor type identifier, position, rotation, and embedded `SyncedActorDataList`.
|
||||
2. **`CSetActorMotion`**: Sets initial velocity vector.
|
||||
|
||||
---
|
||||
|
||||
## Movement & Position Synchronization
|
||||
|
||||
Transmitting absolute 64-bit coordinates every tick would congest network bandwidth. Instead, Pumpkin dynamically switches between compact relative delta updates and authoritative full synchronization based on movement distance:
|
||||
|
||||
| Metric / Scenario | Threshold | Java Protocol Packet | Bedrock Protocol Packet | Bandwidth Impact |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **Incremental Movement** | $\Delta < 8$ blocks per tick | `CUpdateEntityPos` / `CUpdateEntityPosRot` | `CMoveActorDelta` | Minimal (fixed-point byte deltas) |
|
||||
| **Teleport / Long Distance** | $\Delta \ge 8$ blocks or chunk warp | `CTeleportEntity` / `CEntityPositionSync` | `CMoveActorAbsolute` | Full 64-bit float coordinate payload |
|
||||
| **Rotation Only** | Position unchanged | `CUpdateEntityRot` & `CHeadRot` | `CMoveActorDelta` (rot flags only) | Minimal (byte angles) |
|
||||
|
||||
### 1. Relative Delta Movement
|
||||
If the entity moved less than 8 blocks in a single tick, Pumpkin sends delta packets using fixed-point encoded increments:
|
||||
- **Java**: `CUpdateEntityPos` (XYZ delta), `CUpdateEntityRot` (yaw/pitch delta), or `CUpdateEntityPosRot` (both).
|
||||
- **Bedrock**: `CMoveActorDelta` with bitflags indicating which coordinates changed (`MOVE_ACTOR_DELTA_FLAG_HAS_X`, `HAS_Y`, `HAS_Z`).
|
||||
|
||||
### 2. Full Position Synchronization
|
||||
When an entity teleports, crosses chunk boundaries, or accumulates floating-point drift:
|
||||
- **Java**: Sends `CEntityPositionSync` containing absolute coordinates, velocities, and on-ground flags.
|
||||
- **Bedrock**: Sends `CMoveActorAbsolute`.
|
||||
|
||||
---
|
||||
|
||||
## Passenger & Vehicle Mounting
|
||||
|
||||
When an entity mounts another entity (e.g., a player mounting a horse, boat, or minecart):
|
||||
|
||||
- **Java Edition**: Sends `CSetPassengers` containing the vehicle's `VarInt` entity ID and an array of passenger entity IDs.
|
||||
- **Bedrock Edition**: Sends `CSetActorLink` defining the rider and vehicle runtime IDs and link type (`Rider`, `Passenger`).
|
||||
|
||||
---
|
||||
|
||||
## Despawning & Entity Removal
|
||||
|
||||
When an entity dies, is removed from the world, or steps out of a player's tracking radius:
|
||||
|
||||
- **Java Edition**: Sends `CRemoveEntities` with an array of entity IDs.
|
||||
- **Bedrock Edition**: Sends `CRemoveActor` with the entity's runtime ID.
|
||||
|
||||
The client immediately frees its local rendering and audio memory for the removed entity.
|
||||
144
docs/en/developer/extractor.md
Normal file
144
docs/en/developer/extractor.md
Normal file
@ -0,0 +1,144 @@
|
||||
# Vanilla Data Extractor
|
||||
|
||||
The [Pumpkin Extractor](https://github.com/Pumpkin-MC/Extractor) is a dedicated Fabric mod written in Kotlin that introspects a running vanilla Minecraft server or client to dump authoritative game registries and runtime metadata into structured JSON files.
|
||||
|
||||
These JSON dumps are consumed by [`pumpkin-codegen`](/developer/codegen) to generate strongly-typed Rust code in `pumpkin-data` and `pumpkin-world`.
|
||||
|
||||
---
|
||||
|
||||
## Datapacks vs. Extractor: Our Data Philosophy
|
||||
|
||||
Before creating a new extractor, it is crucial to understand Pumpkin's data hierarchy:
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **We preferably use vanilla datapacks whenever possible.**
|
||||
> If vanilla Minecraft already exposes a registry or system through official datapack JSON files, we consume the datapack directly rather than writing custom extractor code.
|
||||
|
||||
### When to Use Datapacks vs. When to Use the Extractor
|
||||
|
||||
| Source | When to Use | Examples |
|
||||
| :--- | :--- | :--- |
|
||||
| **Vanilla Datapacks** *(Preferred)* | Any data officially serialized as JSON by Mojang in `data/minecraft/`. | Recipes, tags (`blocks`, `items`, `fluids`), biomes, dimension types, damage types, trim materials, worldgen noise parameters. |
|
||||
| **Fabric Extractor** *(Only when necessary)* | Game data **hardcoded in Java bytecode** that cannot be exported from raw datapack files. | Block collision shapes, AABB bounding boxes, block hardness, packet ID-to-state mappings, entity tracked data IDs, fuel burn durations, composter chances, and flower pot conversion tables. |
|
||||
|
||||
---
|
||||
|
||||
## Extractor Mod Architecture
|
||||
|
||||
The Extractor runs on top of the Fabric loader using official Fabric Yarn mappings:
|
||||
|
||||
- **Mod Entry Point (`src/main/kotlin/de/snowii/extractor/Extractor.kt`)**: Registers with Fabric's `ServerLifecycleEvents.SERVER_STARTED` event. When the headless server finishes booting, it invokes all registered extractors and dumps files to `run/pumpkin_extractor_output/`.
|
||||
- **Extractor Interface**: Every data extractor implements the `Extractor.Extractor` interface:
|
||||
```kotlin
|
||||
interface Extractor {
|
||||
fun fileName(): String
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun extract(server: MinecraftServer): JsonElement
|
||||
|
||||
// Returns true if output belongs in tests/ rather than the root output folder
|
||||
fun isTest(): Boolean = false
|
||||
}
|
||||
```
|
||||
- **Output Directory**: JSON files are saved to `pumpkin_extractor_output/` encoded in UTF-8 without HTML escaping.
|
||||
|
||||
---
|
||||
|
||||
## How to Add a New Extractor
|
||||
|
||||
Follow this step-by-step workflow when you need to extract new hardcoded vanilla mechanics:
|
||||
|
||||
### Step 1: Verify the Data Cannot Be Sourced from Datapacks
|
||||
Inspect the official Minecraft client/server JAR or Mojang's data reports:
|
||||
- Check `data/minecraft/` for existing registries, tags, or JSON definitions.
|
||||
- If the data is present in the datapack, add it directly to `Pumpkin/assets/datapacks/` instead of writing an extractor.
|
||||
|
||||
### Step 2: Create the Extractor Class
|
||||
If the data is hardcoded in Java methods, create a new Kotlin class in:
|
||||
```text
|
||||
src/main/kotlin/de/snowii/extractor/extractors/<ExtractorName>.kt
|
||||
```
|
||||
|
||||
Implement the `Extractor.Extractor` interface:
|
||||
|
||||
```kotlin
|
||||
package de.snowii.extractor.extractors
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import de.snowii.extractor.Extractor
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
class Fuels : Extractor.Extractor {
|
||||
override fun fileName(): String {
|
||||
return "fuels.json"
|
||||
}
|
||||
|
||||
override fun extract(server: MinecraftServer): JsonElement {
|
||||
val fuelsJson = JsonObject()
|
||||
|
||||
// Query vanilla runtime methods
|
||||
server.fuelValues().fuelItems().forEach { fuel ->
|
||||
val itemId = BuiltInRegistries.ITEM.getKey(fuel).toString()
|
||||
val burnDuration = server.fuelValues().burnDuration(ItemStack(fuel))
|
||||
fuelsJson.add(itemId, JsonPrimitive(burnDuration))
|
||||
}
|
||||
|
||||
return fuelsJson
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Register the Extractor
|
||||
Open `src/main/kotlin/de/snowii/extractor/Extractor.kt` and add an instance of your class to the `extractors` array:
|
||||
|
||||
```kotlin
|
||||
val extractors = arrayOf(
|
||||
// ... existing extractors ...
|
||||
Blocks(),
|
||||
Items(),
|
||||
Entities(),
|
||||
Fuels(), // [!code ++]
|
||||
)
|
||||
```
|
||||
|
||||
### Step 4: Run the Extractor
|
||||
Execute the headless server task in the Extractor repository:
|
||||
|
||||
```bash
|
||||
./gradlew runServer
|
||||
```
|
||||
|
||||
Once the extraction finishes, locate your new JSON file in:
|
||||
```text
|
||||
run/pumpkin_extractor_output/<your_file>.json
|
||||
```
|
||||
|
||||
### Step 5: Integrate with `pumpkin-codegen`
|
||||
1. Move the generated JSON file into Pumpkin:
|
||||
```bash
|
||||
cp run/pumpkin_extractor_output/fuels.json ../Pumpkin/assets/
|
||||
```
|
||||
2. In the `Pumpkin` repository, open `tools/pumpkin-codegen`:
|
||||
- Create a parser for `<your_file>.json`.
|
||||
- Write a code generation template to produce the corresponding Rust structs or static lookup tables in `crates/pumpkin-data`.
|
||||
3. Run code generation:
|
||||
```bash
|
||||
cargo run -p pumpkin-codegen
|
||||
```
|
||||
4. Verify that `pumpkin-data` compiles and passes all unit tests:
|
||||
```bash
|
||||
cargo test -p pumpkin-data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices for Extractor Contributions
|
||||
|
||||
- **Sort Keys Deterministically**: When generating JSON objects or lists, sort entries by numeric registry ID or alphabetically by identifier (`minecraft:stone`, `minecraft:dirt`). This prevents noisy git diffs when re-running extraction across updates.
|
||||
- **Prefer Standard Types**: Use vanilla identifiers (`net.minecraft.resources.Identifier`) as string keys instead of arbitrary integers where possible.
|
||||
- **Access Wideners**: If private vanilla fields or methods need to be accessed, declare an access widener in `src/main/resources/extractor.accesswidener` rather than resorting to brittle runtime reflection.
|
||||
@ -1,9 +1,57 @@
|
||||
# Introduction
|
||||
# Contributor Documentation
|
||||
|
||||
Welcome to the Pumpkin Documentation!
|
||||
Welcome to the internal contributor and developer documentation for Pumpkin!
|
||||
|
||||
Whether you're an internal Pumpkin developer or working on a Pumpkin plugin, this documentation is your resource for everything Pumpkin.
|
||||
Pumpkin is an ultra-high-performance, extensible Minecraft server software written entirely in modern **Rust**. Designed from the ground up for concurrency, memory safety, and vanilla fidelity, Pumpkin supports both **Minecraft: Java Edition** and **Minecraft: Bedrock Edition**.
|
||||
|
||||
## Plugin Development
|
||||
This section provides comprehensive documentation for contributors working on the core Pumpkin server, its internal crates, and supporting tooling.
|
||||
|
||||
For a better documentation structure, plugin development docs have been moved to a [separate category](/plugin-dev/introduction).
|
||||
---
|
||||
|
||||
## Core Philosophy & Design Principles
|
||||
|
||||
Pumpkin is built on several foundational engineering principles:
|
||||
|
||||
- **Zero-Cost Abstractions & Raw Performance**: We leverage Rust's zero-cost abstractions, efficient memory layouts, and cache-friendly data structures to minimize CPU and RAM overhead.
|
||||
- **Strict Concurrency Model**:
|
||||
- **Asynchronous I/O via Tokio**: Network socket handling, client handshakes, packets streaming, and file I/O run efficiently on an asynchronous runtime.
|
||||
- **Parallel Compute via Rayon**: Heavy, CPU-bound computations—such as terrain generation, lighting calculations, chunk serialization, and physics—are dispatched to Rayon thread pools to prevent blocking the async network loop.
|
||||
- **Thread-safe Communication**: Non-blocking channels (`tokio::sync::mpsc`, crossbeam) coordinate work seamlessly between async tasks and thread pools.
|
||||
- **Robust Quality & Panic Freedom**: Pumpkin enforces strict Clippy rules. In production server code, panics, `unwrap()`, `expect()`, and unhandled errors are denied at compile time.
|
||||
- **Modular Workspace Architecture**: Functionality is divided cleanly into focused crates (`pumpkin-protocol`, `pumpkin-world`, `pumpkin-inventory`, `pumpkin-command`, `pumpkin-data`, etc.). Each crate has specific responsibilities and clear dependency boundaries.
|
||||
- **Protocol Fidelity**: We strive for strict vanilla compatibility while providing modern features like Linear region format support, Bedrock cross-play, and a polyglot WebAssembly plugin runtime.
|
||||
|
||||
---
|
||||
|
||||
## Documentation Roadmap
|
||||
|
||||
Explore the contributor documentation to understand how Pumpkin works and how to contribute effectively:
|
||||
|
||||
| Section | Description |
|
||||
| :--- | :--- |
|
||||
| **[Contributing Guide](/developer/contributing)** | Step-by-step instructions for environment setup, building, testing, clippy rules, and PR standards. |
|
||||
| **[Architecture & Crates](/developer/architecture)** | Deep dive into Pumpkin's multi-crate workspace structure, crate responsibilities, and data flow. |
|
||||
| **[Data Extractor](/developer/extractor)** | Extracting hardcoded vanilla Minecraft registries, bounding boxes, and packet structures via our Fabric mod. |
|
||||
| **[Networking Overview](/developer/networking/)** | Core networking architecture, transports, and multi-edition support. |
|
||||
| **[Java Edition Networking](/developer/networking/java/overview)** | Protocol states, [adding packets](/developer/networking/java/adding-packets), and [authentication](/developer/networking/java/authentication). |
|
||||
| **[Bedrock Edition Networking](/developer/networking/bedrock/overview)** | RakNet/NetherNet transport, [adding packets](/developer/networking/bedrock/adding-packets), [authentication](/developer/networking/bedrock/authentication), and [NetherNet](/developer/networking/bedrock/nethernet). |
|
||||
| **[Proxy Forwarding](/developer/networking/proxy/)** | Secure player info forwarding for [BungeeCord](/developer/networking/proxy/bungeecord), [Velocity](/developer/networking/proxy/velocity), and [Vine](/developer/networking/proxy/vine). |
|
||||
| **[Query Protocol](/developer/networking/query)** | GameSpy4 UDP query implementation for external monitoring bots and server lists. |
|
||||
| **[Blocks](/developer/blocks/)** | Block architecture, states, collisions, properties, and [adding block behaviors](/developer/blocks/adding-blocks). |
|
||||
| **[Items](/developer/items/)** | Item architecture, data components, food, cooldowns, and [adding item behaviors](/developer/items/adding-items). |
|
||||
| **[Entities & Mobs](/developer/entities/)** | Entity trait hierarchy, [spawning & tracking](/developer/entities/spawning-and-tracking), and [mob AI & pathfinding](/developer/entities/ai-and-mobs). |
|
||||
| **[Commands](/developer/commands/)** | Pure-Rust Brigadier command tree, [adding commands](/developer/commands/adding-commands), [arguments & suggestions](/developer/commands/arguments-and-suggestions), and [execution context](/developer/commands/execution-and-context). |
|
||||
| **[Plugin Engine](/developer/plugins/)** | Internal plugin engine architecture, [adding plugin loaders](/developer/plugins/loaders), and [WASM signing & licensing](/developer/plugins/wasm-signing). |
|
||||
| **[World Engine & Formats](/developer/world)** | Chunk loading, saving, Anvil/Linear/Slime/Pump formats, terrain generation, and lighting. |
|
||||
| **[Code Generation](/developer/codegen)** | How `tools/pumpkin-codegen` processes vanilla Minecraft data to generate `pumpkin-data`. |
|
||||
| **[Porting Minecraft Versions](/developer/porting-versions)** | End-to-end guide to upgrading Pumpkin: data extraction, assets & datapacks, codegen, compiler fixes, new block/item/entity logic, and ViaVersion remapping. |
|
||||
| **[Mobile Development](/developer/mobile)** | Developing and compiling Pumpkin on mobile environments like Android (Termux). |
|
||||
|
||||
---
|
||||
|
||||
## Plugin Development vs. Core Development
|
||||
|
||||
> [!NOTE]
|
||||
> If you are looking to build third-party plugins that extend Pumpkin rather than contributing to the server itself, please check out the dedicated **[Plugin Development](/plugin-dev/introduction)** section.
|
||||
>
|
||||
> Pumpkin supports writing plugins in **Rust**, **Python**, **C#**, **C**, **Go**, and **Kotlin** powered by our WebAssembly (Wasmtime) runtime and WIT interfaces.
|
||||
|
||||
121
docs/en/developer/items/adding-items.md
Normal file
121
docs/en/developer/items/adding-items.md
Normal file
@ -0,0 +1,121 @@
|
||||
# Adding an Item Behavior
|
||||
|
||||
This guide details how to implement custom interactive mechanics for an item in Pumpkin.
|
||||
|
||||
---
|
||||
|
||||
## When Is an Item Behavior Needed?
|
||||
|
||||
- **Passive Items** (e.g., diamonds, gold ingots, sticks): Do **not** require custom Rust code. Their stack sizes, tool categories, and crafting recipes are handled automatically by `pumpkin-data` and `pumpkin-inventory`.
|
||||
- **Interactive Items** (e.g., snowballs, ender pearls, hoes, bows, potions): Require an implementation of `ItemBehaviour` to handle right-click events, cooldowns, animations, and entity spawns.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Define the Item Struct & Metadata
|
||||
|
||||
Create a new file under `crates/pumpkin/src/item/items/<item_name>.rs` (e.g., `items/snowball.rs`):
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use crate::entity::Entity;
|
||||
use crate::entity::player::Player;
|
||||
use crate::entity::projectile::snowball::SnowballEntity;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
|
||||
pub struct SnowBallItem;
|
||||
|
||||
// 1. Declare the associated numeric Item ID(s)
|
||||
impl ItemMetadata for SnowBallItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
[Item::SNOWBALL.id].into()
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Implement the interaction behavior
|
||||
impl ItemBehaviour for SnowBallItem {
|
||||
fn normal_use(&self, _item: &Item, player: &Player) {
|
||||
let position = player.position();
|
||||
let world = player.world();
|
||||
|
||||
// Play throw sound effect
|
||||
world.play_sound(
|
||||
Sound::EntitySnowballThrow,
|
||||
SoundCategory::Neutral,
|
||||
&position,
|
||||
);
|
||||
|
||||
// Construct and launch the projectile entity
|
||||
let entity = Entity::new(world.clone(), position, &EntityType::SNOWBALL);
|
||||
let snowball = SnowballEntity::new_shot(entity, player.get_entity());
|
||||
let (yaw, pitch) = player.rotation();
|
||||
snowball.thrown.set_velocity_from(pitch, yaw, 0.0, 1.5, 1.0);
|
||||
world.spawn_entity(Arc::new(snowball));
|
||||
|
||||
// Safely decrement held item unless in Creative mode
|
||||
let mut held = player.inventory.held_item();
|
||||
held.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
player.inventory.set_held_item(held);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: `ItemBehaviour` Hook Reference
|
||||
|
||||
The `ItemBehaviour` trait provides comprehensive hooks for different interaction models:
|
||||
|
||||
### Instant Single-Use Hooks
|
||||
|
||||
| Hook | Signature | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `normal_use` | `(&self, item: &Item, player: &Player)` | Triggered on right-clicking into open air. |
|
||||
| `normal_use_with_rotation` | `(&self, item: &Item, player: &Player, yaw: f32, pitch: f32)` | Provides client-reported view angles for precise raycasting and aiming. |
|
||||
| `use_on_block` | `(&self, item: &mut ItemStack, player: &Player, location: BlockPos, face: BlockDirection, cursor_pos: Vector3<f32>, block: &Block, server: &Server) -> BlockActionResult` | Triggered when right-clicking a block face (e.g., stripping logs with an axe, tilling farmland with a hoe). |
|
||||
| `use_on_entity` | `(&self, item: &mut ItemStack, player: &Player, entity: Arc<dyn EntityBase>)` | Triggered when right-clicking another living entity (e.g., shearing sheep, nametags). |
|
||||
|
||||
### Channeled & Timed Hooks
|
||||
|
||||
| Hook | Signature | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `get_use_duration` | `(&self) -> i32` | Duration in game ticks required to complete the action (e.g., `32` ticks for food, `72000` ticks for bows). |
|
||||
| `on_use_tick` | `(&self, stack: &ItemStack, player: &Player, remaining_use_ticks: i32)` | Executed on each tick while right-click is held. |
|
||||
| `on_stopped_using` | `(&self, stack: &ItemStack, player: &Player)` | Triggered when the player releases right-click (e.g., firing a charged bow). |
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Durability & Cooldowns
|
||||
|
||||
### Decrementing Stacks Safely
|
||||
Always use `decrement_unless_creative` to ensure survival players consume items while creative players retain them:
|
||||
|
||||
```rust
|
||||
item_stack.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
```
|
||||
|
||||
### Applying Cooldowns
|
||||
To prevent action spamming (e.g., Ender Pearls, Chorus Fruit):
|
||||
|
||||
```rust
|
||||
// Start a 20-tick (1-second) cooldown on Ender Pearls
|
||||
player.start_cooldown(Item::ENDER_PEARL.registry_key.to_string(), 20);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Registering the Item
|
||||
|
||||
1. Export your module in `crates/pumpkin/src/item/items/mod.rs`:
|
||||
```rust
|
||||
pub mod snowball;
|
||||
```
|
||||
|
||||
2. Register the struct in `crates/pumpkin/src/item/registry.rs`:
|
||||
```rust
|
||||
items.register(SnowBallItem);
|
||||
```
|
||||
|
||||
When the server initializes, `items.register` maps the IDs declared in `ItemMetadata::ids()` to your behavior instance.
|
||||
71
docs/en/developer/items/index.md
Normal file
71
docs/en/developer/items/index.md
Normal file
@ -0,0 +1,71 @@
|
||||
# Item Architecture & Data Components
|
||||
|
||||
Items represent portable game objects held in player inventories, hotbars, armor slots, and world containers. In Pumpkin, item handling is divided into two primary subsystems:
|
||||
|
||||
1. **Static Data Layer ([`pumpkin-data`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-data))**: Compile-time generated metadata including item IDs, max stack sizes, max durability, tool tier properties, food nutrition, and data components generated by [`pumpkin-codegen`](/developer/codegen).
|
||||
2. **Dynamic Behavior Layer ([`pumpkin::item`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin/src/item))**: Interactive right-click actions, projectile launching, eating durations, bow charging, and block interactions handled via the `ItemBehaviour` trait.
|
||||
|
||||
---
|
||||
|
||||
## Static Items vs. Actionable Items
|
||||
|
||||
| Item Type | Description | Examples | Runtime Behavior Struct Needed? |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Passive Items** | Items without active right-click effects | `diamond`, `stick`, `iron_ingot`, `feather` | No — Stacking, moving, crafting, and dropping are handled automatically by `pumpkin-inventory`. |
|
||||
| **Standard Block Items** | Items corresponding directly to placeable blocks | `stone`, `oak_planks`, `glass` | No — Placement logic queries the target block's data. |
|
||||
| **Projectiles & Throwables** | Items spawned into physical entities when thrown | `snowball`, `egg`, `ender_pearl`, `splash_potion` | Yes — Implements `ItemBehaviour::normal_use`. |
|
||||
| **Channeled / Charged Items** | Items with use durations or charging animations | `bow`, `crossbow`, `spyglass`, `trident` | Yes — Implements `get_use_duration`, `on_use_tick`, `on_stopped_using`. |
|
||||
| **Tools with Block Interactions**| Items modifying existing blocks in the world | `hoe` (tilling), `axe` (stripping), `shovel` (pathmaking) | Yes — Implements `ItemBehaviour::use_on_block`. |
|
||||
|
||||
---
|
||||
|
||||
## The `ItemStack` Structure
|
||||
|
||||
An `ItemStack` represents an instance of an item in an inventory container. It encapsulates:
|
||||
|
||||
- **`item`**: Reference to the static [`Item`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin-data/src/item.rs) definition.
|
||||
- **`item_count`**: Current stack size ($1 \le \text{count} \le \text{max\_stack\_size}$).
|
||||
- **Data Components**: Dynamic properties attached to the item stack (custom names, lore, damage values, enchantments, potion effects).
|
||||
|
||||
```rust
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
|
||||
// Create a new item stack
|
||||
let mut stack = ItemStack::new(1, Item::DIAMOND_SWORD);
|
||||
|
||||
// Safely decrement stack count, respecting creative mode
|
||||
stack.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Components System
|
||||
|
||||
Modern Minecraft represents item properties via modular **Data Components**:
|
||||
|
||||
| Component | Responsibility | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `FoodImpl` | Nutrition & Saturation | Controls hunger points restored and saturation ratio. |
|
||||
| `ConsumableImpl` | Consumption Mechanics | Controls eating duration, sound effects, and animation types. |
|
||||
| `Damageable` | Durability | Tracks current damage points and breaks when max durability is reached. |
|
||||
| `UseCooldown` | Cooldown Group | Prevents item re-use for a specified duration in game ticks. |
|
||||
| `Enchantments` | Enchantment Levels | Key-value mapping of applied enchantments and levels. |
|
||||
|
||||
---
|
||||
|
||||
## The Item Registry & Dispatch Architecture
|
||||
|
||||
Item interactions are managed by the `ItemRegistry` in [`crates/pumpkin/src/item/registry.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/item/registry.rs):
|
||||
|
||||
1. **Registration**: At server startup, all `ItemBehaviour` implementations are registered along with their target numeric item IDs.
|
||||
2. **Interaction Routing**: When a client sends use packets (`CUseItem`, `CUseItemOn`), Pumpkin evaluates cooldowns and routes execution to the registered `ItemBehaviour`.
|
||||
3. **Cooldown System**: Players track active cooldowns keyed by cooldown group strings. If an item is on cooldown, interactions are canceled before calling the behavior.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
To implement interactive mechanics for a new item, proceed to the practical guide:
|
||||
|
||||
- **[Adding an Item](/developer/items/adding-items)**: Step-by-step instructions for implementing `ItemMetadata`, `ItemBehaviour`, managing cooldowns, and registering items.
|
||||
@ -18,7 +18,7 @@ By default, `online_mode` is enabled in the configuration (`[networking.java.onl
|
||||
|
||||
## Custom Authentication Server
|
||||
|
||||
Pumpkin supports custom authentication servers. You can replace the authentication URL in `features.toml`.
|
||||
Pumpkin supports custom authentication servers and fallbacks configured in `pumpkin.toml` under `[networking.java.authentication]`.
|
||||
|
||||
### How Pumpkin Authentication Works
|
||||
|
||||
|
||||
182
docs/en/developer/networking/bedrock/adding-packets.md
Normal file
182
docs/en/developer/networking/bedrock/adding-packets.md
Normal file
@ -0,0 +1,182 @@
|
||||
# Adding a Bedrock Edition Packet
|
||||
|
||||
This guide explains how to implement, encode, decode, and handle a new Minecraft: Bedrock Edition packet in Pumpkin.
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
- **Clientbound packets** (Server → Client) must start with **`C`** (e.g., `CChunkRadiusUpdated`, `CContainerOpen`).
|
||||
- **Serverbound packets** (Client → Server) must start with **`S`** (e.g., `SRequestChunkRadius`, `SPlayerAction`).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Identifying the Packet ID
|
||||
|
||||
In the Bedrock protocol, packet IDs are fixed integer constants assigned by Mojang. Verify the numeric ID from the official protocol documentation or network packet traces.
|
||||
|
||||
For example:
|
||||
- `Disconnect` packet ID: `5`
|
||||
- `RequestChunkRadius` packet ID: `69`
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implementing a Clientbound Packet
|
||||
|
||||
Clientbound packets implement the `PacketWrite` trait from `pumpkin-protocol::serial`.
|
||||
|
||||
### 1. Create the Packet File
|
||||
Place the file in `crates/pumpkin-protocol/src/bedrock/client/<packet_name>.rs`:
|
||||
|
||||
```rust
|
||||
use std::io::{Error, Write};
|
||||
use pumpkin_macros::packet;
|
||||
use crate::{codec::var_int::VarInt, serial::PacketWrite};
|
||||
|
||||
#[packet(5)]
|
||||
pub struct CDisconnect {
|
||||
pub reason: VarInt,
|
||||
pub skip_message: bool,
|
||||
pub message: String,
|
||||
pub filtered_message: String,
|
||||
}
|
||||
|
||||
impl CDisconnect {
|
||||
#[must_use]
|
||||
pub const fn new(reason: i32, message: String) -> Self {
|
||||
Self {
|
||||
reason: VarInt(reason),
|
||||
skip_message: message.is_empty(),
|
||||
message,
|
||||
filtered_message: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketWrite for CDisconnect {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
self.reason.write(writer)?;
|
||||
self.skip_message.write(writer)?;
|
||||
if !self.skip_message {
|
||||
self.message.write(writer)?;
|
||||
self.filtered_message.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Export the Module
|
||||
In `crates/pumpkin-protocol/src/bedrock/client/mod.rs`:
|
||||
|
||||
```rust
|
||||
pub mod disconnect;
|
||||
pub use disconnect::*;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Implementing a Serverbound Packet
|
||||
|
||||
Serverbound packets implement the `PacketRead` trait. Pumpkin provides a procedural derive macro `#[derive(PacketRead)]` for automatic deserialization when fields implement `PacketRead`.
|
||||
|
||||
### 1. Create the Packet File
|
||||
Place the file in `crates/pumpkin-protocol/src/bedrock/server/<packet_name>.rs`:
|
||||
|
||||
```rust
|
||||
use pumpkin_macros::packet;
|
||||
use crate::{codec::var_int::VarInt, serial::PacketRead};
|
||||
|
||||
#[derive(PacketRead, Debug)]
|
||||
#[packet(69)]
|
||||
pub struct SRequestChunkRadius {
|
||||
pub chunk_radius: VarInt,
|
||||
pub max_chunk_radius: u8,
|
||||
}
|
||||
```
|
||||
|
||||
If a packet requires custom parsing logic, implement `PacketRead` manually:
|
||||
|
||||
```rust
|
||||
use std::io::{Error, Read};
|
||||
use crate::serial::PacketRead;
|
||||
|
||||
impl PacketRead for SMyCustomPacket {
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||
let chunk_radius = VarInt::read(reader)?;
|
||||
let max_chunk_radius = u8::read(reader)?;
|
||||
Ok(Self { chunk_radius, max_chunk_radius })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Export the Module
|
||||
In `crates/pumpkin-protocol/src/bedrock/server/mod.rs`:
|
||||
|
||||
```rust
|
||||
pub mod request_chunk_radius;
|
||||
pub use request_chunk_radius::*;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Dispatching Incoming Packets in `pumpkin`
|
||||
|
||||
Incoming packets are dispatched in `crates/pumpkin/src/net/bedrock/mod.rs`:
|
||||
|
||||
```rust
|
||||
// Inside BedrockClient::handle_game_packet
|
||||
match packet.id {
|
||||
SRequestChunkRadius::PACKET_ID => {
|
||||
let packet = SRequestChunkRadius::read(reader)?;
|
||||
self.handle_request_chunk_radius(player, &packet);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Implementing the Handler Function
|
||||
|
||||
Implement the handler logic in `crates/pumpkin/src/net/bedrock/play/<packet_name>.rs`:
|
||||
|
||||
```rust
|
||||
use super::*;
|
||||
use pumpkin_protocol::bedrock::server::request_chunk_radius::SRequestChunkRadius;
|
||||
|
||||
impl BedrockClient {
|
||||
pub fn handle_request_chunk_radius(&self, player: &Arc<Player>, packet: &SRequestChunkRadius) {
|
||||
let requested_radius = packet.chunk_radius.0;
|
||||
|
||||
// Clamp view distance within server configuration limits
|
||||
let clamped = requested_radius.clamp(2, 16);
|
||||
|
||||
// Notify client that view radius was accepted
|
||||
self.try_enqueue_client_packet(&CChunkRadiusUpdated {
|
||||
chunk_radius: VarInt(clamped),
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Remember to register the new module in `crates/pumpkin/src/net/bedrock/play/mod.rs`:
|
||||
|
||||
```rust
|
||||
pub mod request_chunk_radius;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Sending Outgoing Packets
|
||||
|
||||
To send a Bedrock packet to a connected client:
|
||||
|
||||
```rust
|
||||
// Enqueues the packet into the client's outgoing batch queue
|
||||
self.try_enqueue_client_packet(&CChunkRadiusUpdated {
|
||||
chunk_radius: VarInt(view_distance),
|
||||
});
|
||||
```
|
||||
|
||||
Batched packets are automatically compressed, framed with `0xfe`, and transmitted over the RakNet/NetherNet socket.
|
||||
61
docs/en/developer/networking/bedrock/authentication.md
Normal file
61
docs/en/developer/networking/bedrock/authentication.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Bedrock Edition Authentication
|
||||
|
||||
Minecraft: Bedrock Edition implements a certificate chain authentication protocol based on JSON Web Tokens (JWT) cryptographically anchored to Microsoft / Xbox Live root public keys.
|
||||
|
||||
Authentication handling is powered by the [`pumpkin-auth`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-auth) crate and processed during the Bedrock login handshake in `crates/pumpkin/src/net/bedrock/login/`.
|
||||
|
||||
---
|
||||
|
||||
## The Handshake Flow
|
||||
|
||||
When a Bedrock client initiates connection, it sends an `SLogin` packet containing the client's protocol version, an **Identity Chain**, and **Client Data**:
|
||||
|
||||
1. **Token Acquisition**: The Bedrock client signs into Xbox Live, obtaining a chained JSON Web Token (JWT) certificate signed by Microsoft's CA.
|
||||
2. **Login Transmission**: The client packages this identity chain alongside a separate `ClientData` JWT into `SLogin`.
|
||||
3. **Chain Validation**: Pumpkin walks the certificate chain, verifying each token's signature against Mojang's public root keys and extracting the player's Xbox User ID (XUID), Gamertag, and ephemeral session public key.
|
||||
4. **Hardware Verification**: Pumpkin verifies the `ClientData` payload using the client's ephemeral public key, extracting geometry, skins, and device OS telemetry.
|
||||
5. **Session Authorization**: Once validated, Pumpkin emits `CPlayStatus` (Login Success) and progresses to resource pack negotiation.
|
||||
|
||||
---
|
||||
|
||||
## Token Payloads
|
||||
|
||||
### 1. Identity Chain (`Chain`)
|
||||
An array of JWT tokens forming an unbroken chain of trust:
|
||||
- **Root Token**: Cryptographically signed by Mojang's official public root certificate authority.
|
||||
- **Intermediate Tokens**: Issued by Xbox Live services, verifying the player's Xbox Live User ID (**XUID**), gamertag, and generating an ephemeral ECDSA public key for the session.
|
||||
- **Claims**: Includes `extraData` with `displayName`, `identity` (UUID), `XUID`, and `titleId`.
|
||||
|
||||
### 2. Client Data (`ClientData`)
|
||||
A separate JWT signed with the client's ephemeral private key corresponding to the public key in the identity chain:
|
||||
- **Hardware & Environment**: `DeviceOS`, `DeviceModel`, `CurrentInputMode`, `DefaultInputMode`, `ClientRandomId`.
|
||||
- **Game Version**: `GameVersion` string.
|
||||
- **Player Cosmetics**: Full skin texture bitmaps, cape data, geometry names, and persona definitions.
|
||||
|
||||
---
|
||||
|
||||
## Verification in `pumpkin-auth`
|
||||
|
||||
Pumpkin verifies token signatures using **NIST P-384 ECDSA** cryptography:
|
||||
|
||||
```rust
|
||||
// In crates/pumpkin/src/net/bedrock/login/mod.rs
|
||||
match AuthPayload::verify(&jwt_payload) {
|
||||
Ok(profile) => {
|
||||
// Successfully verified against Mojang public keys
|
||||
info!("Authenticated Bedrock player: {} (XUID: {})", profile.name, profile.xuid);
|
||||
}
|
||||
Err(LoginError::ChainValidationFailed(err)) => {
|
||||
warn!("Bedrock JWT chain validation failed: {err}");
|
||||
self.try_kick(DisconnectReason::Kicked, "Authentication failed".into());
|
||||
}
|
||||
Err(LoginError::SelfSignedNotAllowed) => {
|
||||
warn!("Rejected unauthenticated self-signed Bedrock client");
|
||||
self.try_kick(DisconnectReason::Kicked, "Server enforces Xbox Live authentication".into());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Online vs. Offline Mode
|
||||
- **Online Mode** (`online_mode = true` in `pumpkin.toml`): The chain must successfully validate against Mojang's public keys. Self-signed or unsigned tokens are immediately rejected.
|
||||
- **Offline Mode** (`online_mode = false`): Pumpkin accepts self-signed client chains, generating offline UUIDs while extracting cosmetic skins and device metadata.
|
||||
61
docs/en/developer/networking/bedrock/nethernet.md
Normal file
61
docs/en/developer/networking/bedrock/nethernet.md
Normal file
@ -0,0 +1,61 @@
|
||||
# NetherNet WebRTC Transport
|
||||
|
||||
**NetherNet** is Mojang's modern, WebRTC-based transport protocol for Minecraft: Bedrock Edition. Designed for high-resilience peer-to-peer and relayed communication, NetherNet enables seamless cross-platform multiplayer (mobile, console, desktop) without requiring server operators or players to configure router port-forwarding.
|
||||
|
||||
Pumpkin provides a native, zero-dependency NetherNet implementation in [`crates/pumpkin/src/net/bedrock/nethernet.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/net/bedrock/nethernet.rs).
|
||||
|
||||
---
|
||||
|
||||
## Architectural Layers
|
||||
|
||||
| Layer | Implementation | Responsibility |
|
||||
| :--- | :--- | :--- |
|
||||
| **HTTP Signaling** | Axum HTTP Router (`ice_router.rs`) | Handles SDP Offer (`POST /offer`), SDP Answer (`GET /answer`), and ICE Candidates (`POST /candidate`) |
|
||||
| **NAT Traversal** | ICE Agent | Coordinates STUN/TURN binding discovery and hole punching |
|
||||
| **Transport Security** | DTLS Handshake | Generates ephemeral session keys and secures the WebRTC transport using ECDSA P-384 keys |
|
||||
| **Data Delivery** | WebRTC DataChannels | Unordered/ordered datagram channels streaming standard Bedrock game batches to the dispatcher |
|
||||
|
||||
---
|
||||
|
||||
## Core Components in Pumpkin
|
||||
|
||||
Pumpkin's NetherNet subsystem is divided into four distinct modules:
|
||||
|
||||
### 1. Cryptographic Identity (`nethernet-key.der`)
|
||||
- Pumpkin generates and loads a **NIST P-384 ECDSA private key** (`nethernet-key.der`).
|
||||
- Used to cryptographically sign session descriptions (SDP offers and answers), authenticating the server's identity to connecting clients.
|
||||
- Encoded using standard PKCS#8 DER formatting.
|
||||
|
||||
### 2. HTTP Signaling Router (`ice_router.rs`)
|
||||
Signaling is handled over HTTP via an [Axum](https://github.com/tokio-rs/axum) router:
|
||||
|
||||
- **`POST /offer`**: The client submits an SDP offer describing its WebRTC codecs, transport profiles, and network interfaces.
|
||||
- **`GET /answer`**: Pumpkin generates a corresponding local SDP answer containing its negotiated capabilities.
|
||||
- **`POST /candidate`**: Both endpoints exchange trickle ICE candidates to discover the fastest direct network path (host, server-reflexive, or relay).
|
||||
|
||||
### 3. Local Network Discovery (`discovery.rs`)
|
||||
- Listens and broadcasts availability on the local subnet via UDP multicast.
|
||||
- Allows Bedrock clients on the same Wi-Fi or LAN to discover and connect to the Pumpkin server automatically without entering an IP address.
|
||||
|
||||
### 4. DataChannel Packet Streaming
|
||||
- Once the WebRTC peer connection transitions to the `Connected` state, an ordered, reliable **WebRTC DataChannel** is opened.
|
||||
- Bedrock batch packets (`0xfe`) flow bidirectionally through the DataChannel directly into Pumpkin's `incoming_game_packet_send` channel.
|
||||
|
||||
---
|
||||
|
||||
## Configuration in `pumpkin.toml`
|
||||
|
||||
NetherNet is configured under `[networking.bedrock.nethernet]` in `pumpkin.toml`:
|
||||
|
||||
```toml
|
||||
[networking.bedrock]
|
||||
enabled = true
|
||||
address = "0.0.0.0:19132"
|
||||
|
||||
[networking.bedrock.nethernet]
|
||||
enabled = true
|
||||
signaling_address = "0.0.0.0:19133"
|
||||
ice_servers = [
|
||||
"stun:stun.l.google.com:19302"
|
||||
]
|
||||
```
|
||||
80
docs/en/developer/networking/bedrock/overview.md
Normal file
80
docs/en/developer/networking/bedrock/overview.md
Normal file
@ -0,0 +1,80 @@
|
||||
# Bedrock Edition Networking Overview
|
||||
|
||||
Pumpkin features native support for **Minecraft: Bedrock Edition** clients (iOS, Android, Windows, Xbox, PlayStation, Switch).
|
||||
|
||||
Bedrock protocol structures and wire codecs reside in `pumpkin-protocol::bedrock`, while connection management and gameplay handlers reside in `pumpkin::net::bedrock`.
|
||||
|
||||
---
|
||||
|
||||
## Transport Layers: RakNet & NetherNet
|
||||
|
||||
Unlike Java Edition, which communicates exclusively over TCP streams, Bedrock Edition operates over UDP using two primary transport layers:
|
||||
|
||||
1. **RakNet (Port 19132)**:
|
||||
- A reliable, sequenced UDP networking library originally developed by Jenkins Software.
|
||||
- Provides connection handshakes, packet acknowledgment (ACK/NACK), MTU negotiation, and packet splitting for payloads larger than the network MTU.
|
||||
2. **NetherNet**:
|
||||
- Modern peer-to-peer and relayed transport protocol used by modern Bedrock versions.
|
||||
- Integrated in Pumpkin via `crates/pumpkin/src/net/bedrock/nethernet.rs`.
|
||||
|
||||
---
|
||||
|
||||
## Packet Framing & Batching (`0xfe`)
|
||||
|
||||
Bedrock does not send individual game packets over the network directly. Instead, multiple game packets are bundled into a single **batch packet**:
|
||||
|
||||
1. **Header Byte**: Every Bedrock game packet batch starts with `0xfe`.
|
||||
2. **Compression**: The payload following `0xfe` is compressed using **Deflate** (or ZLib) above a negotiated threshold.
|
||||
3. **Sub-packet Framing**: When decompressed, the batch contains one or more sequential game packets, each prefixed by its length encoded as a `VarUInt`.
|
||||
|
||||
In Pumpkin, batch decoding and assembly are managed by `BedrockBatchDecoder` and `BedrockBatchEncoder` in `crates/pumpkin-protocol/src/bedrock/`.
|
||||
|
||||
---
|
||||
|
||||
## Connection Lifecycle & State Progression
|
||||
|
||||
Bedrock does not have explicit protocol states like Java Edition. Instead, it follows a strict sequence of packets:
|
||||
|
||||
| Step | Packet Exchanged | Purpose |
|
||||
| :--- | :--- | :--- |
|
||||
| **1. Network Settings** | `SRequestNetworkSettings` -> `CNetworkSettings` | Negotiates compression algorithm (Deflate/Snappy) and payload threshold |
|
||||
| **2. Authentication** | `SLogin` -> `CPlayStatus` | Transmits JWT identity chain and client hardware data; verifies Xbox Live tokens |
|
||||
| **3. Resource Negotiation** | `CResourcePacksInfo` -> `SResourcePackClientResponse` | Negotiates server resource packs and download behavior |
|
||||
| **4. World Initialization** | `CStartGame` + `CItemRegistry` + `CCreativeContent` | Streams world spawn position, dimension settings, runtime player entity ID, and item palettes |
|
||||
| **5. Ready to Spawn** | `SSetLocalPlayerAsInitialized` -> `CPlayStatus` | Signals client loading completion and transitions to active in-game play |
|
||||
|
||||
---
|
||||
|
||||
## Packet Dispatching in `pumpkin`
|
||||
|
||||
Inside `crates/pumpkin/src/net/bedrock/mod.rs`, decompressed sub-packets are dispatched based on their numeric packet ID:
|
||||
|
||||
```rust
|
||||
match packet.id {
|
||||
SPlayerAuthInput::PACKET_ID => {
|
||||
let packet = SPlayerAuthInput::read(reader)?;
|
||||
self.handle_player_auth_input(player, packet, server);
|
||||
}
|
||||
SRequestChunkRadius::PACKET_ID => {
|
||||
let packet = SRequestChunkRadius::read(reader)?;
|
||||
self.handle_request_chunk_radius(player, &packet);
|
||||
}
|
||||
SInventoryTransaction::PACKET_ID => {
|
||||
let packet = SInventoryTransaction::read(reader)?;
|
||||
self.handle_inventory_action(player, packet);
|
||||
}
|
||||
_ => {
|
||||
trace!("Unhandled Bedrock packet ID: {}", packet.id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authentication & NetherNet
|
||||
|
||||
- **JWT Authentication**: Bedrock verifies player identities via certificate chains signed by Microsoft / Xbox Live root authorities.
|
||||
- **NetherNet WebRTC**: High-performance peer-to-peer transport using WebRTC DataChannels, ICE NAT traversal, and P-384 ECDSA keys.
|
||||
|
||||
For complete implementation details, see **[Bedrock Authentication & NetherNet](/developer/networking/bedrock/authentication)**.
|
||||
|
||||
49
docs/en/developer/networking/index.md
Normal file
49
docs/en/developer/networking/index.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Networking Overview
|
||||
|
||||
Pumpkin is built with a high-performance, asynchronous networking stack capable of servicing both **Minecraft: Java Edition** and **Minecraft: Bedrock Edition** clients simultaneously.
|
||||
|
||||
All protocol definitions, serialization traits, and wire-level codecs are isolated in the [`pumpkin-protocol`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-protocol) crate, while client connection management, session state machines, and gameplay packet dispatching are managed in [`pumpkin/src/net`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin/src/net).
|
||||
|
||||
---
|
||||
|
||||
## Supported Editions
|
||||
|
||||
Pumpkin separates network implementations by edition due to their fundamentally distinct transport layers, packet encodings, and state models:
|
||||
|
||||
| Feature | Minecraft: Java Edition | Minecraft: Bedrock Edition |
|
||||
| :--- | :--- | :--- |
|
||||
| **Transport Layer** | TCP socket streams | UDP via RakNet & NetherNet |
|
||||
| **Framing** | VarInt length-prefixed frames | Batched packets encapsulated in `0xfe` headers |
|
||||
| **Encryption** | AES-128 / CFB8 stream cipher | Cryptographic session handshake |
|
||||
| **Compression** | Per-packet ZLib (above configurable threshold) | Per-batch Deflate / ZLib compression |
|
||||
| **State Model** | Explicit states: Handshake, Status, Login, Config, Play | Packet-driven state progression |
|
||||
| **Protocol Crate** | `pumpkin-protocol::java` | `pumpkin-protocol::bedrock` |
|
||||
| **Server Net Module** | `pumpkin::net::java` | `pumpkin::net::bedrock` |
|
||||
|
||||
---
|
||||
|
||||
## Networking Architecture
|
||||
|
||||
The networking pipeline routes incoming connections through specialized transport and codec layers before delegating to the unified server game loop:
|
||||
|
||||
1. **Transport Listeners**: Independent listeners bind to their respective ports—Tokio TCP for Java Edition (`:25565`) and UDP/RakNet/NetherNet for Bedrock Edition (`:19132`).
|
||||
2. **Session Abstraction**: Each accepted connection spawns an isolated session task (`JavaClient` or `BedrockClient`) managing wire framing and encryption.
|
||||
3. **Codec Deserialization**: Wire bytes are deserialized into typed packet structs via `pumpkin-protocol::java` (`ClientPacket`/`ServerPacket`) and `pumpkin-protocol::bedrock` (`PacketWrite`/`PacketRead`).
|
||||
4. **Unified Gameplay Dispatch**: Decoded packets are dispatched into the unified game loop, updating player entity states and streaming world chunks.
|
||||
|
||||
---
|
||||
|
||||
## Navigating the Networking Docs
|
||||
|
||||
Explore the documentation for each edition:
|
||||
|
||||
- **[Entity Metadata & Synced Data](/developer/networking/metadata)**: Synchronization of visual entity states (DataWatchers in Java, Actor Data in Bedrock).
|
||||
- **[Java Edition](/developer/networking/java/overview)**:
|
||||
- **[Architecture & Lifecycle](/developer/networking/java/overview)**: State machine, `JavaClient` vs. `Player`, and multi-version negotiation.
|
||||
- **[How to Add a Packet](/developer/networking/java/adding-packets)**: Guide to defining, serializing, and dispatching Java Edition packets.
|
||||
- **[Authentication](/developer/networking/java/authentication)**: Mojang Yggdrasil validation, GameProfiles, and proxy forwarding.
|
||||
- **[Bedrock Edition](/developer/networking/bedrock/overview)**:
|
||||
- **[Architecture & Transports](/developer/networking/bedrock/overview)**: RakNet and NetherNet integration, batching, and session handling.
|
||||
- **[How to Add a Packet](/developer/networking/bedrock/adding-packets)**: Guide to defining and handling Bedrock Edition packets.
|
||||
- **[Authentication & NetherNet](/developer/networking/bedrock/authentication)**: JWT certificate chain verification, WebRTC DataChannels, P-384 keys, and signaling.
|
||||
- **[RCON](/developer/networking/rcon)**: Implementation of Valve's Remote Console protocol for administrative management.
|
||||
205
docs/en/developer/networking/java/adding-packets.md
Normal file
205
docs/en/developer/networking/java/adding-packets.md
Normal file
@ -0,0 +1,205 @@
|
||||
# Adding a Java Edition Packet
|
||||
|
||||
This guide walks you through implementing, serializing, dispatching, and sending a new Java Edition packet in Pumpkin.
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
To keep packet definitions uniform across the codebase, follow these naming conventions:
|
||||
|
||||
- **Clientbound packets** (Server → Client) must start with **`C`** (e.g., `CBlockUpdate`, `CPlayDisconnect`).
|
||||
- **Serverbound packets** (Client → Server) must start with **`S`** (e.g., `SChatCommand`, `SPlayerPosition`).
|
||||
- If a packet exists in multiple states, prefix the state name before the packet action (e.g., `CLoginDisconnect`, `CConfigDisconnect`, and `CPlayDisconnect`).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Find the Packet ID in `pumpkin-data`
|
||||
|
||||
Packet IDs across all supported Minecraft versions are maintained in `pumpkin-data`. Import the corresponding constant:
|
||||
|
||||
```rust
|
||||
// Clientbound Play state disconnect
|
||||
use pumpkin_data::packet::clientbound::play::DISCONNECT;
|
||||
|
||||
// Serverbound Play state player position
|
||||
use pumpkin_data::packet::serverbound::play::MOVE_PLAYER_POS;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implementing a Clientbound Packet
|
||||
|
||||
Clientbound packets are sent from Pumpkin to the Minecraft client. They implement the `ClientPacket` trait from `pumpkin-protocol`.
|
||||
|
||||
### 1. Create the Struct File
|
||||
Place the file in `crates/pumpkin-protocol/src/java/client/<state>/<packet_name>.rs` (e.g., `play/disconnect.rs`):
|
||||
|
||||
```rust
|
||||
use std::io::Write;
|
||||
use pumpkin_data::packet::clientbound::play::DISCONNECT;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_protocol::ClientPacket;
|
||||
use pumpkin_protocol::ser::{NetworkWriteExt, WritingError};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
|
||||
/// Displays a disconnect screen with a message to the player in Play state.
|
||||
#[java_packet(DISCONNECT)]
|
||||
pub struct CPlayDisconnect<'a> {
|
||||
pub reason: &'a TextComponent,
|
||||
}
|
||||
|
||||
impl<'a> CPlayDisconnect<'a> {
|
||||
#[must_use]
|
||||
pub const fn new(reason: &'a TextComponent) -> Self {
|
||||
Self { reason }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CPlayDisconnect<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
mut write: impl Write,
|
||||
version: &JavaMinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
// Use NetworkWriteExt methods to serialize fields
|
||||
write.write_component(self.reason, version)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Export the Module
|
||||
In `crates/pumpkin-protocol/src/java/client/<state>/mod.rs`:
|
||||
|
||||
```rust
|
||||
mod disconnect;
|
||||
pub use disconnect::*;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Implementing a Serverbound Packet
|
||||
|
||||
Serverbound packets are received by Pumpkin from the game client. They implement the `ServerPacket` trait from `pumpkin-protocol`.
|
||||
|
||||
### 1. Create the Struct File
|
||||
Place the file in `crates/pumpkin-protocol/src/java/server/<state>/<packet_name>.rs` (e.g., `play/player_position.rs`):
|
||||
|
||||
```rust
|
||||
use pumpkin_data::packet::serverbound::play::MOVE_PLAYER_POS;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_protocol::ServerPacket;
|
||||
use pumpkin_protocol::ser::{NetworkReadExt, ReadingError};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
|
||||
#[java_packet(MOVE_PLAYER_POS)]
|
||||
pub struct SPlayerPosition {
|
||||
pub position: Vector3<f64>,
|
||||
pub collision: u8,
|
||||
}
|
||||
|
||||
impl<'a> ServerPacket<'a> for SPlayerPosition {
|
||||
fn read(bytebuf: &mut &'a [u8], version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
|
||||
let x = bytebuf.get_f64_be()?;
|
||||
let y = bytebuf.get_f64_be()?;
|
||||
|
||||
// Handle version-specific fields if necessary
|
||||
if *version <= JavaMinecraftVersion::V_1_7_6 {
|
||||
let _stance = bytebuf.get_f64_be()?;
|
||||
}
|
||||
|
||||
let z = bytebuf.get_f64_be()?;
|
||||
let collision = bytebuf.get_u8()?;
|
||||
|
||||
Ok(Self {
|
||||
position: Vector3::new(x, y, z),
|
||||
collision,
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Export the Module
|
||||
In `crates/pumpkin-protocol/src/java/server/<state>/mod.rs`:
|
||||
|
||||
```rust
|
||||
mod player_position;
|
||||
pub use player_position::*;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Network Codec Extension Helpers
|
||||
|
||||
`NetworkWriteExt` and `NetworkReadExt` provide helper methods for standard Minecraft types:
|
||||
|
||||
| Data Type | Reading Method | Writing Method |
|
||||
| :--- | :--- | :--- |
|
||||
| **`VarInt`** | `bytebuf.get_var_int()?` | `write.write_var_int(val)?` |
|
||||
| **`VarLong`** | `bytebuf.get_var_long()?` | `write.write_var_long(val)?` |
|
||||
| **`String`** | `bytebuf.get_string()?` | `write.write_string(val)?` |
|
||||
| **`UUID`** | `bytebuf.get_uuid()?` | `write.write_uuid(&val)?` |
|
||||
| **`TextComponent`** | `bytebuf.get_component()?` | `write.write_component(val, version)?` |
|
||||
| **`Position`** | `bytebuf.get_block_pos()?` | `write.write_block_pos(val)?` |
|
||||
| **`NBT`** | `bytebuf.get_nbt()?` | `write.write_nbt(&val)?` |
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Handling Incoming Packets in `pumpkin`
|
||||
|
||||
Incoming serverbound packets must be handled in the appropriate state handler in `crates/pumpkin/src/net/java/`.
|
||||
|
||||
For packets in the **Play state**, open `crates/pumpkin/src/net/java/mod.rs` and update `handle_play_packet`:
|
||||
|
||||
```rust
|
||||
// In crates/pumpkin/src/net/java/mod.rs
|
||||
pub fn handle_play_packet(
|
||||
&self,
|
||||
player: &Arc<Player>,
|
||||
server: &Arc<Server>,
|
||||
packet: &RawPacket,
|
||||
) -> Result<(), Box<dyn PumpkinError>> {
|
||||
let version = self.version.load();
|
||||
let mut payload = &packet.payload[..];
|
||||
|
||||
match packet.id {
|
||||
// Match against version-specific packet ID
|
||||
id if id == SPlayerPosition::to_id(version) => {
|
||||
let packet = SPlayerPosition::read(&mut payload, &version)?;
|
||||
self.handle_player_position(player, &packet);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Then implement the handler function:
|
||||
|
||||
```rust
|
||||
impl JavaClient {
|
||||
pub fn handle_player_position(&self, player: &Arc<Player>, packet: &SPlayerPosition) {
|
||||
// Validate coordinates, update player position, check collisions
|
||||
player.set_position(packet.position);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Sending Outgoing Packets
|
||||
|
||||
To send a clientbound packet to a player:
|
||||
|
||||
```rust
|
||||
// 1. Send directly to a player's connection
|
||||
player.client.send_packet(&CPlayDisconnect::new(&reason));
|
||||
|
||||
// 2. Broadcast to all players watching a chunk
|
||||
world.broadcast_packet_except(
|
||||
&[player.gameprofile.id],
|
||||
&CBlockUpdate::new(block_pos, block_id),
|
||||
).await;
|
||||
```
|
||||
83
docs/en/developer/networking/java/authentication.md
Normal file
83
docs/en/developer/networking/java/authentication.md
Normal file
@ -0,0 +1,83 @@
|
||||
# Java Edition Authentication
|
||||
|
||||
Authentication verifies the identity of players connecting to Pumpkin to prevent username spoofing and account impersonation.
|
||||
|
||||
Pumpkin handles authentication in the [`pumpkin-auth`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-auth) crate and orchestrates it during the `Login` state in `crates/pumpkin/src/net/java/login/`.
|
||||
|
||||
---
|
||||
|
||||
## Why Authentication Matters
|
||||
|
||||
Offline (non-authenticated) accounts generate player UUIDs strictly based on their offline username string (`UUID::new_v3(...)`). Without verification, any user can join using the username of an operator or administrator and gain privileged access.
|
||||
|
||||
In online mode (`online_mode = true` in `pumpkin.toml`), Pumpkin cryptographically verifies with Mojang's session servers that the connecting client owns the requested account.
|
||||
|
||||
---
|
||||
|
||||
## How Yggdrasil Authentication Works
|
||||
|
||||
The authentication exchange between the client, Pumpkin, and Mojang's Yggdrasil servers follows these steps:
|
||||
|
||||
1. **Initiation**: The client sends `SLoginStart` declaring its desired username and optional offline UUID.
|
||||
2. **Key Exchange**: Pumpkin replies with `CEncryptionBegin`, providing a random 4-byte verify token, the server ID string, and Pumpkin's 1024-bit RSA public key.
|
||||
3. **Session Join**: The client generates a random 16-byte shared secret (`AES-128`), computes a SHA-1 server hash, and calls Mojang's session server (`POST /session/minecraft/join`) using its authenticated Microsoft access token.
|
||||
4. **Verification**: The client sends `SEncryptionResponse` containing the RSA-encrypted shared secret and encrypted verify token. Pumpkin decrypts the token with its RSA private key, recalculates the SHA-1 digest, and queries Mojang (`GET /session/minecraft/hasJoined`).
|
||||
5. **Profile Retrieval**: Mojang validates that the client and server hashes match, returning the authenticated player's UUID, username, and signed skin/cape texture properties (`GameProfile`).
|
||||
6. **Encryption Activation**: Pumpkin activates full-stream symmetric encryption (`AES-128/CFB8`) and completes the login sequence by sending `CLoginSuccess`.
|
||||
|
||||
---
|
||||
|
||||
## Game Profile Structure
|
||||
|
||||
When Mojang's `hasJoined` endpoint validates a session, it returns a `GameProfile` containing the player's authenticated UUID, username, and signed texture properties:
|
||||
|
||||
```rust
|
||||
pub struct GameProfile {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub properties: Vec<Property>,
|
||||
pub profile_actions: Option<Vec<ProfileAction>>,
|
||||
}
|
||||
|
||||
pub struct Property {
|
||||
pub name: String,
|
||||
pub value: String, // Base64 encoded texture data
|
||||
pub signature: Option<String>, // Cryptographic signature verifying authenticity
|
||||
}
|
||||
```
|
||||
|
||||
Pumpkin verifies that skin and cape signatures are cryptographically signed with Mojang's public key to prevent clients from injecting malicious payloads or unauthorized textures.
|
||||
|
||||
---
|
||||
|
||||
## Custom Authentication Endpoints
|
||||
|
||||
For private networks or enterprise environments running custom Yggdrasil-compatible authentication servers, endpoints can be customized in `pumpkin.toml`:
|
||||
|
||||
```toml
|
||||
[networking.java.authentication]
|
||||
enabled = true
|
||||
fallbacks = []
|
||||
connect_timeout = 5000
|
||||
read_timeout = 5000
|
||||
prevent_proxy_connections = false
|
||||
|
||||
[networking.java.authentication.player_profile]
|
||||
allow_banned_players = false
|
||||
allowed_actions = ["FORCED_NAME_CHANGE", "USING_BANNED_SKIN"]
|
||||
|
||||
[networking.java.authentication.textures]
|
||||
enabled = true
|
||||
allowed_url_schemes = ["http", "https"]
|
||||
allowed_url_domains = [".minecraft.net", ".mojang.com"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Proxy Authentication Forwarding
|
||||
|
||||
When running Pumpkin behind proxies, client connections terminate at the proxy. Pumpkin supports the three primary proxy forwarding standards:
|
||||
|
||||
- **Velocity Modern Forwarding**: Validates player data forwarded with HMAC-SHA256 secret tokens (`[networking.proxy.velocity]`).
|
||||
- **BungeeGuard**: Token-based authentication header validation for BungeeCord setups.
|
||||
- **Legacy BungeeCord**: Handshake forwarding appending IP, UUID, and properties to the server host string.
|
||||
74
docs/en/developer/networking/java/overview.md
Normal file
74
docs/en/developer/networking/java/overview.md
Normal file
@ -0,0 +1,74 @@
|
||||
# Java Edition Networking Overview
|
||||
|
||||
The Java Edition network stack handles standard Minecraft PC clients connecting over TCP. Wire-level structures and serialization logic reside in `pumpkin-protocol::java`, while connection management and gameplay packet handlers reside in `pumpkin::net::java`.
|
||||
|
||||
---
|
||||
|
||||
## The Five Protocol States
|
||||
|
||||
The Minecraft Java protocol operates as an explicit finite state machine. Every connection begins in the `Handshake` state and transitions according to client requests and server responses:
|
||||
|
||||
| State | Transition Trigger | Purpose & Next State |
|
||||
| :--- | :--- | :--- |
|
||||
| **Handshake** | Client initiates connection | Inspects protocol version and transitions to `Status`, `Login`, or `Transfer` |
|
||||
| **Status** | `NextState = 1` | Streams MOTD, active player counts, favicon, and ping response |
|
||||
| **Login** | `NextState = 2` | Authenticates account (Yggdrasil), negotiates encryption (AES-128), transitions to `Config` |
|
||||
| **Transfer** | `NextState = 3` | Modern proxy / server-transfer negotiation |
|
||||
| **Config** | Authentication success | Exchanges dynamic registries, data tags, feature flags, and resource packs |
|
||||
| **Play** | Configuration acknowledged | Primary bidirectional gameplay loop (chunks, movement, combat, chat) |
|
||||
|
||||
1. **Handshake (`0x00`)**: Always the first packet sent by the client. It declares the client's protocol version, server address, port, and desired next state (`Status`, `Login`, or `Transfer`).
|
||||
2. **Status**: Lightweight query state. The client requests a server list ping, and Pumpkin responds with the server version, player counts, favicon, and MOTD.
|
||||
3. **Login**: Authenticates the player. If `online_mode` is enabled, Pumpkin performs an encryption handshake (AES-128/CFB8) and verifies the player's account with Mojang's Yggdrasil session servers.
|
||||
4. **Config**: Introduced in modern Minecraft (1.20.2+). The server sends feature flags, known registries, resource packs, and custom server links before the player joins the physical world.
|
||||
5. **Play**: The primary gameplay state. Used for all in-game actions including block breaking, entity movement, combat, chat, and chunk streaming.
|
||||
|
||||
---
|
||||
|
||||
## Connection Representation: `JavaClient` vs. `Player`
|
||||
|
||||
To keep memory usage minimal and prevent unnecessary allocations, Pumpkin cleanly separates pre-play connections from active players:
|
||||
|
||||
### `JavaClient`
|
||||
- **Scope**: Exists across `Handshake`, `Status`, `Login`, and `Config` states.
|
||||
- **Location**: `crates/pumpkin/src/net/java/mod.rs`.
|
||||
- **Characteristics**:
|
||||
- Contains connection metadata: client protocol version, remote address, encryption/compression state.
|
||||
- Has no world coordinates, no physics, no inventory, and no living entity.
|
||||
- Very low memory footprint.
|
||||
|
||||
### `Player`
|
||||
- **Scope**: Exists solely during the `Play` state.
|
||||
- **Location**: `crates/pumpkin/src/entity/player/`.
|
||||
- **Characteristics**:
|
||||
- Implements `EntityBase`, representing a living entity inside a `World` and `Level`.
|
||||
- Holds inventory screens, health, hunger, velocities, and chunk view subscription queues.
|
||||
- Contains an `Arc<JavaClient>` reference for network transmission.
|
||||
|
||||
---
|
||||
|
||||
## Multi-Version Support (`JavaMinecraftVersion`)
|
||||
|
||||
Pumpkin supports multiple Minecraft client versions. Protocol differences are handled cleanly via `pumpkin_util::version::JavaMinecraftVersion`:
|
||||
|
||||
- Packet IDs may differ across Minecraft versions. The `#[java_packet(ID)]` macro works with `pumpkin-data` to resolve the correct ID for the target version.
|
||||
- When reading or writing packets, methods accept a `&JavaMinecraftVersion` reference to conditionally include or omit fields that changed in specific Minecraft updates.
|
||||
|
||||
```rust
|
||||
if *version <= JavaMinecraftVersion::V_1_7_6 {
|
||||
// Read legacy stance coordinate present in early Minecraft versions
|
||||
let _stance = bytebuf.get_f64_be()?;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The Packet Pipeline
|
||||
|
||||
Incoming Java packets pass through a structured pipeline:
|
||||
|
||||
1. **Length Framing**: `tokio_util::codec` decodes VarInt frame lengths.
|
||||
2. **Decompression & Decryption**: If enabled, the raw buffer is decrypted via AES/CFB8 and decompressed via ZLib.
|
||||
3. **Plugin Event Interception**: A `PacketReceivedEvent` is dispatched to the plugin manager. Plugins can inspect or cancel the packet before server processing.
|
||||
4. **ID Dispatching**: The packet ID is matched against version-specific constants in `crates/pumpkin/src/net/java/mod.rs` (or the respective state module).
|
||||
5. **Handling**: The packet struct is deserialized and processed by state-specific handler functions.
|
||||
123
docs/en/developer/networking/metadata.md
Normal file
123
docs/en/developer/networking/metadata.md
Normal file
@ -0,0 +1,123 @@
|
||||
# Entity Metadata & Synced Data
|
||||
|
||||
Minecraft synchronizes persistent entity visual states, animation flags, poses, and properties via **Entity Metadata** (historically called *DataWatchers* in Java Edition and *Actor Data* in Bedrock Edition).
|
||||
|
||||
Instead of transmitting full state packets whenever an entity crouches, catches fire, or takes damage, the server broadcasts compact metadata updates to nearby observers.
|
||||
|
||||
---
|
||||
|
||||
## Edition Comparison
|
||||
|
||||
| Feature | Minecraft: Java Edition | Minecraft: Bedrock Edition |
|
||||
| :--- | :--- | :--- |
|
||||
| **Packet Name** | `CSetEntityMetadata` (`SET_ENTITY_DATA`) | `CSetActorData` (Packet ID `39`) |
|
||||
| **Storage Structure** | `TrackedData` list terminating with `0xFF` | `SyncedActorDataList` (`HashMap<u32, MetadataValue>`) |
|
||||
| **Key Type** | Index (`u8`) mapped to typed `MetaDataType` | Enum keys (`entity_data_key::*`) |
|
||||
| **Flags** | Bitfield byte (fire, crouching, sprinting, glowing) | Bitfield int64 (`entity_data_flag::*`) |
|
||||
| **Dynamic Properties** | N/A (baked into tracked indices) | `PropertySyncData` (client/server sync) |
|
||||
| **Crate Location** | `crates/pumpkin-protocol/src/java/client/play/entity_metadata.rs` | `crates/pumpkin-protocol/src/bedrock/client/set_actor_data.rs` |
|
||||
|
||||
---
|
||||
|
||||
## 1. Java Edition: `CSetEntityMetadata`
|
||||
|
||||
In Java Edition, entity metadata consists of numbered index entries. Each entry defines an index, a `MetaDataType` ID, and the encoded value. The list terminates with a single `0xFF` byte.
|
||||
|
||||
### Key Index Hierarchy
|
||||
|
||||
Entity metadata follows an object-oriented inheritance model:
|
||||
|
||||
1. **Entity (Base)**:
|
||||
- `0`: State bitfield (flags: bit `0` = on fire, bit `1` = crouching, bit `3` = sprinting, bit `5` = invisible, bit `6` = glowing).
|
||||
- `1`: Air supply (`VarInt`).
|
||||
- `2`: Custom name (optional `TextComponent`).
|
||||
- `3`: Custom name visible (`bool`).
|
||||
- `4`: Silent (`bool`).
|
||||
- `5`: No gravity (`bool`).
|
||||
- `6`: Pose (`Standing`, `FallFlying`, `Sleeping`, `Swimming`, `SpinAttack`, `Crouching`, `Dying`).
|
||||
2. **LivingEntity (extends Entity)**:
|
||||
- `8`: Hand states (using item, active hand).
|
||||
- `9`: Health (`f32`).
|
||||
- `10`: Potion effect ambient color.
|
||||
3. **Player (extends LivingEntity)**:
|
||||
- Additional hearts, skin customization bitmask, main hand indicator.
|
||||
|
||||
### Sending Metadata Updates in Pumpkin
|
||||
|
||||
Pumpkin encodes entity metadata in `pumpkin-protocol`:
|
||||
|
||||
```rust
|
||||
use pumpkin_protocol::VarInt;
|
||||
use pumpkin_protocol::java::client::play::CSetEntityMetadata;
|
||||
|
||||
// Construct metadata update for entity with ID 42
|
||||
let packet = CSetEntityMetadata::new(
|
||||
VarInt(entity.entity_id),
|
||||
serialized_metadata_bytes,
|
||||
);
|
||||
|
||||
// Broadcast to chunk observers
|
||||
world.broadcast_packet_all(&packet).await;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Bedrock Edition: `CSetActorData`
|
||||
|
||||
Bedrock Edition uses `CSetActorData` (`packet(39)`), storing properties in a dictionary mapping `u32` keys to strongly-typed `MetadataValue` variants.
|
||||
|
||||
### Key & Flag Constants
|
||||
|
||||
Bedrock exposes flags as a 64-bit integer bitfield under `entity_data_key::FLAGS`:
|
||||
|
||||
- `entity_data_flag::ON_FIRE` (`0`): Entity is burning.
|
||||
- `entity_data_flag::SNEAKING` (`1`): Player is sneaking.
|
||||
- `entity_data_flag::RIDING` (`2`): Entity is mounted on a vehicle.
|
||||
- `entity_data_flag::SPRINTING` (`3`): Player is sprinting.
|
||||
- `entity_data_flag::USING_ITEM` (`4`): Player is actively charging a bow, eating food, or using a shield.
|
||||
- `entity_data_flag::INVISIBLE` (`5`): Entity is invisible.
|
||||
- `entity_data_flag::GLOWING` (`26`): Entity has the glowing effect outline.
|
||||
|
||||
### Constructing and Updating Actor Data in Pumpkin
|
||||
|
||||
In `crates/pumpkin/src/entity/living.rs`:
|
||||
|
||||
```rust
|
||||
use pumpkin_protocol::bedrock::client::set_actor_data::{
|
||||
CSetActorData, MetadataValue, PropertySyncData, SyncedActorDataList,
|
||||
entity_data_flag, entity_data_key,
|
||||
};
|
||||
|
||||
let mut actor_data = SyncedActorDataList::new();
|
||||
|
||||
// Set entity flags (e.g. actively using an item)
|
||||
let mut flags: i64 = 0;
|
||||
if is_using_item {
|
||||
flags |= 1i64 << entity_data_flag::USING_ITEM;
|
||||
}
|
||||
actor_data.set(entity_data_key::FLAGS, MetadataValue::Int64(flags));
|
||||
|
||||
// Set air supply
|
||||
actor_data.set(entity_data_key::AIR_SUPPLY, MetadataValue::Short(air as i16));
|
||||
|
||||
// Set custom name tag
|
||||
actor_data.set(entity_data_key::NAME, MetadataValue::String(name));
|
||||
|
||||
let packet = CSetActorData {
|
||||
target_runtime_id: VarULong(entity_id),
|
||||
actor_data,
|
||||
synced_properties: PropertySyncData::default(),
|
||||
tick: VarULong(current_tick),
|
||||
};
|
||||
|
||||
client.try_enqueue_client_packet(&packet);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multi-Version Compatibility
|
||||
|
||||
Because Mojang frequently shifts metadata indices between Minecraft releases, `pumpkin-protocol` leverages translation tables from `pumpkin-data`:
|
||||
|
||||
- For Java Edition, `pumpkin_data::meta_data_type::MetaDataType` remaps indices dynamically based on the client's `JavaMinecraftVersion`.
|
||||
- For Bedrock Edition, `SyncedActorDataList` validates keys against the active Bedrock protocol version to prevent client crashes.
|
||||
@ -1,240 +1,210 @@
|
||||
### Networking
|
||||
# Networking & Protocol
|
||||
|
||||
Most of the networking code in Pumpkin can be found in the [pumpkin-protocol](https://github.com/Pumpkin-MC/Pumpkin/tree/master/pumpkin-protocol) crate.
|
||||
Pumpkin includes a high-performance network stack supporting both **Minecraft: Java Edition** and **Minecraft: Bedrock Edition**.
|
||||
|
||||
Serverbound: Client→Server
|
||||
Most protocol definitions, codecs, and serialization logic reside in the [`pumpkin-protocol`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-protocol) crate, while connection handling and gameplay packet dispatching reside in the [`pumpkin`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin/src/net) crate.
|
||||
|
||||
Clientbound: Server→Client
|
||||
> [!NOTE]
|
||||
> Detailed guides are available in the dedicated subcategories:
|
||||
> - **[Networking Introduction](/developer/networking/)**
|
||||
> - **Java Edition**: **[Architecture](/developer/networking/java/overview)**, **[Adding a Java Packet](/developer/networking/java/adding-packets)**, and **[Authentication](/developer/networking/java/authentication)**
|
||||
> - **Bedrock Edition**: **[Architecture](/developer/networking/bedrock/overview)** and **[Adding a Bedrock Packet](/developer/networking/bedrock/adding-packets)**
|
||||
|
||||
### Structure
|
||||
---
|
||||
|
||||
Packets in the Pumpkin protocol are organized by functionality and state.
|
||||
## Direction & Protocol Terminology
|
||||
|
||||
`server`: Contains definitions for serverbound packets.
|
||||
- **Serverbound (Client → Server)**: Packets sent by the game client and received by Pumpkin. Prefixed with `S` (e.g., `SChatMessage`, `SPlayerPosition`).
|
||||
- **Clientbound (Server → Client)**: Packets sent by Pumpkin to the game client. Prefixed with `C` (e.g., `CPlayDisconnect`, `CBlockUpdate`).
|
||||
|
||||
`client`: Contains definitions for clientbound packets.
|
||||
---
|
||||
|
||||
### States
|
||||
## Protocol States
|
||||
|
||||
**Handshake**: Always the first packet being sent from the client. This also determines the next state, usually to indicate if the player wants to perform a status request, join the server, or wants to be transferred.
|
||||
The Minecraft Java protocol operates as a finite state machine with five distinct states:
|
||||
|
||||
**Status**: Indicates the client wants to see a status response (MOTD).
|
||||
1. **Handshake**: The initial packet sent when a client connects. It specifies the protocol version, server address, and the target next state (`Status`, `Login`, or `Transfer`).
|
||||
2. **Status**: Lightweight query state used for the multiplayer server list (MOTD, online player count, latency ping).
|
||||
3. **Login**: Authenticates the player (via Mojang Yggdrasil or offline mode), sets up encryption (AES-128/CFB8), and activates packet compression if enabled.
|
||||
4. **Config**: Introduced in modern Minecraft versions to configure client settings, negotiation of features, resource packs, and server links prior to gameplay.
|
||||
5. **Play**: The primary gameplay state where entities, world chunks, inventory interactions, chat, and movement are processed.
|
||||
|
||||
**Login**: The login sequence. Indicates the client wants to join the server.
|
||||
---
|
||||
|
||||
**Config**: A sequence of configuration packets is mostly sent from the server to the client (features, resource pack, server links, etc.).
|
||||
## Defining Packets in `pumpkin-protocol`
|
||||
|
||||
**Play**: The final state, which indicates the player is now ready to join, is also used to handle all other gameplay packets.
|
||||
Packets in `pumpkin-protocol` are organized by edition (`java`, `bedrock`), direction (`client`, `server`), and state (`handshake`, `status`, `login`, `config`, `play`).
|
||||
|
||||
### Minecraft Protocol
|
||||
### 1. Clientbound Packets (`ClientPacket`)
|
||||
|
||||
You can find all Minecraft Java packets at <https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol>. There you also can see which [state](#states) they are in.
|
||||
You can also see all the information the packets have, which we can either read or write depending on whether they are serverbound or clientbound.
|
||||
To create a clientbound packet:
|
||||
|
||||
### Adding a Packet
|
||||
|
||||
1. Adding a packet is easy. First, derive:
|
||||
1. Retrieve the packet ID constant from `pumpkin-data`.
|
||||
2. Annotate the struct with `#[java_packet(PACKET_CONSTANT)]`.
|
||||
3. Implement `ClientPacket`, writing the packet fields into the byte stream using `NetworkWriteExt`.
|
||||
|
||||
```rust
|
||||
// For clientbound packets:
|
||||
#[derive(Serialize)]
|
||||
use std::io::Write;
|
||||
use pumpkin_data::packet::clientbound::play::DISCONNECT;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_protocol::ClientPacket;
|
||||
use pumpkin_protocol::ser::{NetworkWriteExt, WritingError};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
|
||||
// For serverbound packets:
|
||||
#[derive(Deserialize)]
|
||||
```
|
||||
|
||||
2. Next, you have to make it known that your struct represents a packet. This automatically gets the packet ID from the JSON packets file.
|
||||
|
||||
```rust
|
||||
use pumpkin_data::packet::clientbound::PLAY_DISCONNECT;
|
||||
|
||||
#[packet(PLAY_DISCONNECT)]
|
||||
```
|
||||
|
||||
3. Now you can create the `struct`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Please start the packet name with "C" or "S" for "Clientbound" or "Serverbound".
|
||||
> Also, if it's a packet that can be sent in multiple [states](#states), please add the state to the name. For example, there are 3 different disconnect packets.
|
||||
>
|
||||
> - `CLoginDisconnect`
|
||||
> - `CConfigDisconnect`
|
||||
> - `CPlayDisconnect`
|
||||
|
||||
Create fields within your packet structure to represent the data that will be sent.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Use descriptive field names and appropriate data types.
|
||||
|
||||
Examples:
|
||||
|
||||
```rust
|
||||
pub struct CPlayDisconnect {
|
||||
reason: TextComponent,
|
||||
// more fields...
|
||||
/// Disconnects the player while in the Play state.
|
||||
#[java_packet(DISCONNECT)]
|
||||
pub struct CPlayDisconnect<'a> {
|
||||
pub reason: &'a TextComponent,
|
||||
}
|
||||
|
||||
pub struct SPlayerPosition {
|
||||
pub x: f64,
|
||||
pub feet_y: f64,
|
||||
pub z: f64,
|
||||
pub ground: bool,
|
||||
}
|
||||
```
|
||||
|
||||
4. (Clientbound packets only) `impl` a `new` function so we can actually create them by putting in the values.
|
||||
|
||||
```rust
|
||||
impl CPlayDisconnect {
|
||||
pub fn new(reason: TextComponent) -> Self {
|
||||
Self { reason }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. In the end, everything should come together.
|
||||
|
||||
```rust
|
||||
#[derive(Serialize)]
|
||||
#[packet(PLAY_DISCONNECT)]
|
||||
pub struct CPlayDisconnect {
|
||||
reason: TextComponent,
|
||||
}
|
||||
|
||||
impl CPlayDisconnect {
|
||||
pub fn new(reason: TextComponent) -> Self {
|
||||
impl<'a> CPlayDisconnect<'a> {
|
||||
pub const fn new(reason: &'a TextComponent) -> Self {
|
||||
Self { reason }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[packet(PLAY_MOVE_PLAYER_POS)]
|
||||
pub struct SPlayerPosition {
|
||||
pub x: f64,
|
||||
pub feet_y: f64,
|
||||
pub z: f64,
|
||||
pub ground: bool,
|
||||
impl ClientPacket for CPlayDisconnect<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
mut write: impl Write,
|
||||
version: &JavaMinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
// Write the text component formatted for the target client version
|
||||
write.write_component(self.reason, version)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. You can also serialize/deserialize the packet manually, which can be useful if the packet is more complex.
|
||||
### 2. Serverbound Packets (`ServerPacket`)
|
||||
|
||||
```diff
|
||||
-#[derive(Serialize)]
|
||||
To create a serverbound packet:
|
||||
|
||||
+ impl ClientPacket for CPlayDisconnect {
|
||||
+ fn write(&self, bytebuf: &mut BytesMut) {
|
||||
+ bytebuf.put_slice(&self.reason.encode());
|
||||
+ }
|
||||
|
||||
-#[derive(Deserialize)]
|
||||
|
||||
+ impl ServerPacket for SPlayerPosition {
|
||||
+ fn read(bytebuf: &mut Bytes) -> Result<Self, ReadingError> {
|
||||
+ Ok(Self {
|
||||
+ x: bytebuf.try_get_f64()?,
|
||||
+ feet_y: bytebuf.try_get_f64()?,
|
||||
+ z: bytebuf.try_get_f64()?,
|
||||
+ ground: bytebuf.try_get_bool()?,
|
||||
+ })
|
||||
+ }
|
||||
```
|
||||
|
||||
7. You can now send the clientbound packet (see [Sending Packets](#sending-packets)) or listen for the serverbound packet (see [Receiving Packets](#receiving-packets)).
|
||||
|
||||
### Client
|
||||
|
||||
Pumpkin categorizes `Client`s and `Player`s separately. Everything that is not in the play state is a simple `Client`. Here are the differences:
|
||||
|
||||
#### Client
|
||||
|
||||
- Can only be the states: Status, Login, Transfer, Config
|
||||
- Is not a living entity
|
||||
- Has small resource consumption
|
||||
|
||||
#### Player
|
||||
|
||||
- Can only be in the Play state
|
||||
- Is a living entity in a world
|
||||
- Has more data and consumes more resources
|
||||
|
||||
#### Sending Packets
|
||||
|
||||
Example:
|
||||
1. Retrieve the packet ID constant from `pumpkin-data`.
|
||||
2. Annotate the struct with `#[java_packet(PACKET_CONSTANT)]`.
|
||||
3. Implement `ServerPacket<'a>`, reading fields from the incoming byte slice with `NetworkReadExt`.
|
||||
|
||||
```rust
|
||||
// Works only in the Status state
|
||||
client.send_packet(&CStatusResponse::new("{ description: "A Description"}"));
|
||||
```
|
||||
use pumpkin_data::packet::serverbound::play::MOVE_PLAYER_POS;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_protocol::ServerPacket;
|
||||
use pumpkin_protocol::ser::{NetworkReadExt, ReadingError};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
|
||||
#### Receiving Packets
|
||||
|
||||
For `Client`s:
|
||||
`src/client/mod.rs`
|
||||
|
||||
```diff
|
||||
// Put the packet into the right state
|
||||
fn handle_mystate_packet(
|
||||
&self,
|
||||
server: &Arc<Server>,
|
||||
packet: &mut RawPacket,
|
||||
) -> Result<(), ReadingError> {
|
||||
let bytebuf = &mut packet.bytebuf;
|
||||
match packet.id.0 {
|
||||
SStatusRequest::PACKET_ID => {
|
||||
self.handle_status_request(server, SStatusRequest::read(bytebuf)?)
|
||||
.await;
|
||||
}
|
||||
+ MyPacket::PACKET_ID => {
|
||||
+ self.handle_my_packet(MyPacket::read(bytebuf)?)
|
||||
+ .await;
|
||||
}
|
||||
_ => {
|
||||
log::error!(
|
||||
"Failed to handle packet id {} while in ... state",
|
||||
packet.id.0
|
||||
);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
#[java_packet(MOVE_PLAYER_POS)]
|
||||
pub struct SPlayerPosition {
|
||||
pub position: Vector3<f64>,
|
||||
pub collision: u8,
|
||||
}
|
||||
```
|
||||
|
||||
For `Player`s:
|
||||
`src/entity/player.rs`
|
||||
|
||||
```diff
|
||||
// Players only have the Play state
|
||||
fn handle_play_packet(
|
||||
&self,
|
||||
server: &Arc<Server>,
|
||||
packet: &mut RawPacket,
|
||||
) -> Result<(), ReadingError> {
|
||||
let bytebuf = &mut packet.bytebuf;
|
||||
match packet.id.0 {
|
||||
SChatMessage::PACKET_ID => {
|
||||
self.handle_chat_message(SChatMessage::read(bytebuf)?).await;
|
||||
impl<'a> ServerPacket<'a> for SPlayerPosition {
|
||||
fn read(bytebuf: &mut &'a [u8], version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
|
||||
let x = bytebuf.get_f64_be()?;
|
||||
let y = bytebuf.get_f64_be()?;
|
||||
if *version <= JavaMinecraftVersion::V_1_7_6 {
|
||||
let _stance = bytebuf.get_f64_be()?;
|
||||
}
|
||||
MyPacket::PACKET_ID => {
|
||||
+ self.handle_mypacket(server, MyPacket::read(bytebuf)?).await;
|
||||
let z = bytebuf.get_f64_be()?;
|
||||
let collision = bytebuf.get_u8()?;
|
||||
|
||||
Ok(Self {
|
||||
position: Vector3::new(x, y, z),
|
||||
collision,
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Useful Serialization Helpers (`NetworkWriteExt` & `NetworkReadExt`)
|
||||
|
||||
Pumpkin provides extension traits on byte streams for standard Minecraft primitives:
|
||||
|
||||
- `write_var_int(i32)` / `get_var_int()`: Variable-length 32-bit integers.
|
||||
- `write_var_long(i64)` / `get_var_long()`: Variable-length 64-bit integers.
|
||||
- `write_string(&str)` / `get_string()`: UTF-8 string prefixed with a VarInt length.
|
||||
- `write_uuid(&Uuid)` / `get_uuid()`: 128-bit player or entity UUID.
|
||||
- `write_component(&TextComponent)`: Serializes formatted chat/UI text.
|
||||
- `write_nbt(&NBTTag)` / `get_nbt()`: Serializes/deserializes binary NBT tags.
|
||||
|
||||
---
|
||||
|
||||
## Packet Flow & Handling in `pumpkin`
|
||||
|
||||
Incoming packets pass through several stages before modifying game state:
|
||||
|
||||
1. **Frame Decoding**: The Tokio TCP stream decodes the VarInt length prefix and decrypts the frame if encryption is enabled.
|
||||
2. **Event Interception**: Emits `PacketReceivedEvent` to registered plugins. If cancelled, the packet is immediately dropped.
|
||||
3. **Version-Aware Dispatch**: Maps the wire packet ID according to the client's negotiated protocol version.
|
||||
4. **State Handling**: Routes the parsed packet struct to its corresponding handler (e.g., `handle_play_packet`).
|
||||
5. **State Mutation**: Mutates player session, world chunks, or entity states.
|
||||
|
||||
### 1. `Client` vs `Player`
|
||||
|
||||
Pumpkin maintains a strict distinction between pre-play connections and active players:
|
||||
|
||||
- **`Client`**: Represents a connection in `Handshake`, `Status`, `Login`, or `Config` state. Lightweight, not linked to a world, and has no physical entity. Handled in `crates/pumpkin/src/net/java/mod.rs`.
|
||||
- **`Player`**: Represents a living entity in an active `World` during the `Play` state. Holds inventory, health, velocity, and chunk subscription queues. Handled in `crates/pumpkin/src/entity/player/`.
|
||||
|
||||
### 2. Packet Dispatching (`handle_play_packet`)
|
||||
|
||||
Inside `crates/pumpkin/src/net/java/mod.rs`, packets are dispatched by matching against their version-specific packet IDs:
|
||||
|
||||
```rust
|
||||
pub fn handle_play_packet(
|
||||
&self,
|
||||
player: &Arc<Player>,
|
||||
server: &Arc<Server>,
|
||||
packet: &RawPacket,
|
||||
) -> Result<(), Box<dyn PumpkinError>> {
|
||||
let version = self.version.load();
|
||||
|
||||
// Fire plugin event to allow plugins to observe or cancel packets
|
||||
let mut event = PacketReceivedEvent::new(player.clone(), packet.id, packet.payload.clone());
|
||||
server.plugin_manager.fire_blocking(server, &mut event);
|
||||
if event.cancelled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut payload = &event.payload[..];
|
||||
match event.packet_id {
|
||||
id if id == SConfirmTeleport::to_id(version) => {
|
||||
self.handle_confirm_teleport(player, &SConfirmTeleport::read(&mut payload, &version)?);
|
||||
}
|
||||
id if id == SChatCommand::to_id(version) => {
|
||||
let packet = SChatCommand::read(&mut payload, &version)?;
|
||||
self.handle_chat_command(player, server, &packet);
|
||||
}
|
||||
id if id == SPlayerPosition::to_id(version) => {
|
||||
let packet = SPlayerPosition::read(&mut payload, &version)?;
|
||||
self.handle_player_position(player, &packet);
|
||||
}
|
||||
_ => {
|
||||
log::error!(
|
||||
"Failed to handle packet id {} while in ... state",
|
||||
packet.id.0
|
||||
);
|
||||
trace!("Unhandled play packet id: {:#04x}", event.packet_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Compression
|
||||
### 3. Sending Packets
|
||||
|
||||
Minecraft packets **can** use ZLib compression for decoding/encoding. There is usually a threshold set when compression is applied; this most often affects chunk packets.
|
||||
To send a packet to a client:
|
||||
|
||||
### Porting
|
||||
```rust
|
||||
// Directly send to a single player
|
||||
player.client.send_packet(&CPlayDisconnect::new(&reason));
|
||||
|
||||
To port to a new minecraft version, you can compare differences in the protocol on the [minecraft.wiki Protocol reference](https://minecraft.wiki/w/Java_Edition_protocol).
|
||||
// Broadcast to nearby players in the world
|
||||
world.broadcast_packet_except(
|
||||
&[player.gameprofile.id],
|
||||
&CBlockUpdate::new(block_pos, block_id),
|
||||
).await;
|
||||
```
|
||||
|
||||
Also, change the `CURRENT_MC_PROTOCOL` in `src/lib.rs`.
|
||||
---
|
||||
|
||||
## Compression & Encryption
|
||||
|
||||
- **Compression**: Once a client passes the Login state, if `compression_threshold` is enabled in `pumpkin.toml`, packets exceeding the threshold size are compressed using ZLib/fast compression.
|
||||
- **Encryption**: In online mode, 128-bit AES/CFB8 encryption is activated immediately after shared secret exchange in the Login state.
|
||||
|
||||
73
docs/en/developer/networking/proxy/bungeecord.md
Normal file
73
docs/en/developer/networking/proxy/bungeecord.md
Normal file
@ -0,0 +1,73 @@
|
||||
# BungeeCord Forwarding
|
||||
|
||||
Pumpkin provides native support for BungeeCord and Waterfall proxy networks, including **BungeeGuard** authentication token verification.
|
||||
|
||||
Implementation is located in [`crates/pumpkin/src/net/proxy/bungeecord.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/net/proxy/bungeecord.rs).
|
||||
|
||||
---
|
||||
|
||||
## How BungeeCord Forwarding Operates
|
||||
|
||||
Unlike modern protocols that use custom login channels, BungeeCord operates by encoding client metadata directly into the `server_address` field of the initial `SHandshake` packet.
|
||||
|
||||
The string uses null character (`\0`) delimiters:
|
||||
|
||||
```text
|
||||
[original_host]\0[player_ip:port]\0[player_uuid]\0[properties_json]
|
||||
```
|
||||
|
||||
1. **`original_host`**: The domain name the player originally typed into their client.
|
||||
2. **`player_ip:port`**: The genuine IP address and ephemeral source port of the client.
|
||||
3. **`player_uuid`**: The player's Mojang UUID (32 hex characters without hyphens or 36 characters with hyphens).
|
||||
4. **`properties_json`**: A JSON array containing skin textures, cape textures, and Mojang cryptographic signatures.
|
||||
|
||||
---
|
||||
|
||||
## The Security Risk: Proxy Bypasses
|
||||
|
||||
Because BungeeCord's handshake string is unauthenticated plain text, an attacker could connect directly to an exposed backend port, inject an arbitrary UUID and operator username, and gain full server control.
|
||||
|
||||
Traditionally, this required strict Linux firewall rules (`iptables` / `ufw`) to restrict port access exclusively to the proxy IP.
|
||||
|
||||
---
|
||||
|
||||
## BungeeGuard Protection
|
||||
|
||||
To eliminate proxy bypasses without relying solely on network firewalls, Pumpkin integrates **BungeeGuard** token verification:
|
||||
|
||||
1. The BungeeCord proxy injects a shared secret into the player's profile properties under the key `bungeeguard-token`.
|
||||
2. When Pumpkin processes the handshake in `bungeecord_login`:
|
||||
- It checks whether `secret` is configured in `pumpkin.toml`.
|
||||
- It inspects the forwarded properties for `bungeeguard-token`.
|
||||
- If the token matches, Pumpkin **strips the secret property** from the player's profile (so other plugins cannot read it) and completes the login.
|
||||
- If the token is missing or incorrect, Pumpkin rejects the connection with `BungeeCordError::MissingToken` or `BungeeCordError::InvalidToken`.
|
||||
|
||||
```rust
|
||||
// In crates/pumpkin/src/net/proxy/bungeecord.rs
|
||||
if !secret.is_empty() {
|
||||
let Some(token_index) = properties.iter().position(|p| p.name == BUNGEEGUARD_TOKEN_PROPERTY) else {
|
||||
return Err(BungeeCordError::MissingToken);
|
||||
};
|
||||
|
||||
if properties[token_index].value != secret {
|
||||
return Err(BungeeCordError::InvalidToken);
|
||||
}
|
||||
properties.remove(token_index);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration in `pumpkin.toml`
|
||||
|
||||
To enable BungeeCord forwarding with BungeeGuard:
|
||||
|
||||
```toml
|
||||
[networking.proxy]
|
||||
enabled = true
|
||||
|
||||
[networking.proxy.bungeecord]
|
||||
enabled = true
|
||||
# Optional: Configure the BungeeGuard shared secret to block direct connections
|
||||
secret = "YOUR_BUNGEEGUARD_SECRET_HERE"
|
||||
```
|
||||
46
docs/en/developer/networking/proxy/index.md
Normal file
46
docs/en/developer/networking/proxy/index.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Proxy Forwarding Overview
|
||||
|
||||
In multi-server Minecraft networks, players connect through a front-facing reverse proxy (such as Velocity, BungeeCord, or Vine) that routes connections between lobbies and sub-servers without requiring players to reconnect.
|
||||
|
||||
Because backend servers receive TCP connections directly from the proxy machine rather than the player, the server must be informed of the player's genuine **IP address**, **UUID**, **username**, and **skin properties**.
|
||||
|
||||
Pumpkin provides native support for all three major proxy forwarding protocols in [`crates/pumpkin/src/net/proxy/`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin/src/net/proxy).
|
||||
|
||||
---
|
||||
|
||||
## The Proxy Forwarding Problem
|
||||
|
||||
Without secure proxy forwarding:
|
||||
1. **IP Loss**: Every connected player appears with the proxy's loopback or internal IP (breaking IP bans and geo-location).
|
||||
2. **UUID Spoofing & Bypasses**: Malicious users could connect directly to the backend server's port with any spoofed username or operator identity.
|
||||
|
||||
To prevent proxy bypasses, Pumpkin validates cryptographic secrets, tokens, or digital signatures sent by the proxy.
|
||||
|
||||
---
|
||||
|
||||
## Comparison of Supported Protocols
|
||||
|
||||
| Protocol | Protocol Transport | Security / Cryptography | Replay Protection | Crate Module |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **[BungeeCord](/developer/networking/proxy/bungeecord)** | `SHandshake` server address string | Optional token in profile properties (`bungeeguard-token`) | None | `pumpkin::net::proxy::bungeecord` |
|
||||
| **[Velocity](/developer/networking/proxy/velocity)** | Login plugin message (`velocity:player_info`) | Symmetric HMAC-SHA256 signature | None | `pumpkin::net::proxy::velocity` |
|
||||
| **[Vine](/developer/networking/proxy/vine)** | Login plugin message (`vine:player_info`) | Asymmetric **Ed25519** digital signatures | 16-byte nonce challenge + 30s timestamp window | `pumpkin::net::proxy::vine` |
|
||||
|
||||
---
|
||||
|
||||
## Lifecycle Architecture
|
||||
|
||||
1. **Player Authentication**: The player connects to the proxy (BungeeCord, Velocity, or Vine) and authenticates against Mojang's session servers.
|
||||
2. **Backend Connection**: The proxy connects to Pumpkin over TCP and sends `SHandshake`.
|
||||
3. **Data Forwarding**:
|
||||
- **BungeeCord Mode**: The proxy injects the player's remote IP, UUID, and properties directly into the `SHandshake` hostname string (separated by null bytes `\0`).
|
||||
- **Modern Mode (Velocity / Vine)**: Pumpkin sends `CLoginPluginRequest` on a dedicated channel (`velocity:player_info` or `vine:player_info`). The proxy signs the player profile using HMAC-SHA256 (Velocity) or Ed25519 (Vine) and replies with `SLoginPluginResponse`.
|
||||
4. **Validation & Session Initialization**: Pumpkin validates the signature, binds the real IP and GameProfile to the `JavaClient`, and sends `CLoginSuccess`.
|
||||
|
||||
---
|
||||
|
||||
## Proxy Documentation
|
||||
|
||||
- **[BungeeCord](/developer/networking/proxy/bungeecord)**: Legacy host string injection and optional BungeeGuard token validation.
|
||||
- **[Velocity Modern Forwarding](/developer/networking/proxy/velocity)**: HMAC-SHA256 signed player info during the Login state.
|
||||
- **[Vine Modern Forwarding](/developer/networking/proxy/vine)**: Next-generation proxy forwarding with Ed25519 signatures and replay attack prevention.
|
||||
88
docs/en/developer/networking/proxy/velocity.md
Normal file
88
docs/en/developer/networking/proxy/velocity.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Velocity Modern Forwarding
|
||||
|
||||
[Velocity](https://papermc.io/software/velocity) is a high-performance proxy created by PaperMC. It uses modern plugin messaging during the `Login` state to securely transmit player data with **HMAC-SHA256** message authentication codes.
|
||||
|
||||
Pumpkin implements Velocity modern forwarding in [`crates/pumpkin/src/net/proxy/velocity.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/net/proxy/velocity.rs).
|
||||
|
||||
---
|
||||
|
||||
## How Velocity Forwarding Operates
|
||||
|
||||
Unlike BungeeCord, which injects unauthenticated data into the handshake string, Velocity operates during the **Login** state over a dedicated plugin channel:
|
||||
|
||||
1. **Handshake & Login**: The player authenticates with Velocity. Velocity initiates a standard TCP connection to Pumpkin and transmits `SHandshake` and `SLoginStart`.
|
||||
2. **Channel Query**: Recognizing that Velocity modern forwarding is enabled, Pumpkin pauses the login sequence and dispatches a `CLoginPluginRequest` on channel `velocity:player_info`.
|
||||
3. **Payload Generation**: Velocity bundles the player's remote address, UUID, username, and skin textures. It calculates a 32-byte HMAC-SHA256 signature across this payload using the shared secret.
|
||||
4. **Signature Verification**: Velocity sends `SLoginPluginResponse`. Pumpkin validates the HMAC-SHA256 digest against its configured secret. If valid, Pumpkin overwrites the connection's network address and GameProfile and transitions to `Config` / `Play`.
|
||||
|
||||
---
|
||||
|
||||
## Wire Payload Structure
|
||||
|
||||
The payload in `SLoginPluginResponse` is structured as follows:
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 32-byte HMAC-SHA256 Signature │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Forwarding Protocol Version (VarInt: 1 - 4) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Client Remote IP Address (String / IPv4 / IPv6) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Player UUID (128-bit / UUID) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Player Username (String) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Profile Properties Count (VarInt) │
|
||||
│ ├─ Property Name (String) │
|
||||
│ ├─ Property Value (String) │
|
||||
│ └─ Optional Signature (String) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cryptographic Validation in Pumpkin
|
||||
|
||||
Inside `crates/pumpkin/src/net/proxy/velocity.rs`:
|
||||
|
||||
```rust
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use sha2::Sha256;
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
// Extract signature and payload
|
||||
let (signature, data_to_verify) = data.split_at(32);
|
||||
|
||||
// Initialize HMAC with secret from pumpkin.toml
|
||||
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
|
||||
.map_err(|_| VelocityError::FailedVerifyIntegrity)?;
|
||||
|
||||
mac.update(data_to_verify);
|
||||
|
||||
// Constant-time verification prevents timing attacks
|
||||
mac.verify_slice(signature)
|
||||
.map_err(|_| VelocityError::FailedVerifyIntegrity)?;
|
||||
```
|
||||
|
||||
If the signature does not match (e.g., if an attacker attempts to connect directly to the backend without knowing the secret), Pumpkin drops the connection immediately.
|
||||
|
||||
---
|
||||
|
||||
## Configuration in `pumpkin.toml`
|
||||
|
||||
1. Open `velocity.toml` on your Velocity proxy and set:
|
||||
```toml
|
||||
player-info-forwarding-mode = "modern"
|
||||
forwarding-secret-file = "forwarding.secret"
|
||||
```
|
||||
2. Copy the generated secret string from `forwarding.secret` into `pumpkin.toml`:
|
||||
```toml
|
||||
[networking.proxy]
|
||||
enabled = true
|
||||
|
||||
[networking.proxy.velocity]
|
||||
enabled = true
|
||||
secret = "YOUR_VELOCITY_FORWARDING_SECRET_HERE"
|
||||
```
|
||||
112
docs/en/developer/networking/proxy/vine.md
Normal file
112
docs/en/developer/networking/proxy/vine.md
Normal file
@ -0,0 +1,112 @@
|
||||
# Vine Modern Forwarding
|
||||
|
||||
**Vine** is a next-generation, high-performance Minecraft proxy written in Rust. Designed for ultra-low latency, multi-threading, and enterprise-grade security, Vine introduces modern cryptographic forwarding based on **Ed25519 asymmetric signatures** and **replay attack protection**.
|
||||
|
||||
Pumpkin provides native support for Vine forwarding in [`crates/pumpkin/src/net/proxy/vine.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/net/proxy/vine.rs).
|
||||
|
||||
---
|
||||
|
||||
## Why Vine Forwarding?
|
||||
|
||||
While Velocity's HMAC-SHA256 forwarding represents an improvement over legacy BungeeCord, it relies on a **symmetric shared secret**. If one backend server in a network is compromised, an attacker obtains the shared secret and can forge authenticated player logins across the entire server cluster.
|
||||
|
||||
Vine solves this with:
|
||||
1. **Asymmetric Public-Key Cryptography (Ed25519)**: The proxy signs player data using its private key. Backend servers only store the proxy's **Public Key** (`public_key`). Even if a backend server is compromised, the attacker cannot forge identities to other servers.
|
||||
2. **Replay Attack Protection (16-byte Challenge Nonce)**: Pumpkin generates a cryptographic random 16-byte nonce for each connection. The proxy must sign and return this nonce. Replaying a captured login packet is impossible.
|
||||
3. **Timestamp Drift Window**: Transmits a Unix timestamp that must be within ±30 seconds of the backend's system clock (`MAX_TIMESTAMP_DRIFT_SECS = 30`).
|
||||
|
||||
---
|
||||
|
||||
## The Handshake Sequence
|
||||
|
||||
1. **Connection**: The player connects and authenticates with Vine. Vine opens a backend connection to Pumpkin and sends `SHandshake` and `SLoginStart`.
|
||||
2. **Challenge Query**: Pumpkin generates a cryptographically random 16-byte nonce and sends `CLoginPluginRequest` on the `vine:player_info` channel.
|
||||
3. **Asymmetric Signing**: Vine serializes the version, timestamp, the returned 16-byte nonce, the client IP address, and the player GameProfile. It signs this byte stream with its Ed25519 private key.
|
||||
4. **Tri-fold Validation**: Vine replies with `SLoginPluginResponse`. Pumpkin verifies:
|
||||
- **Clock Skew**: The embedded timestamp is within ±30 seconds of UTC time.
|
||||
- **Nonce Check**: The signed nonce exactly matches the 16-byte challenge generated in step 2.
|
||||
- **Ed25519 Signature**: The signature is cryptographically valid against the configured `public_key`.
|
||||
5. **Session Success**: If all three checks pass, Pumpkin applies the verified identity and emits `CLoginSuccess`.
|
||||
|
||||
---
|
||||
|
||||
## Wire Payload Structure
|
||||
|
||||
The `SLoginPluginResponse` data layout:
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 64-byte Ed25519 Digital Signature │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Forwarding Protocol Version (i32: 1) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Unix Timestamp in Seconds (i64) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 16-byte Challenge Nonce │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Client Remote IP Address (String / IPv4 / IPv6) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Player UUID (128-bit / UUID) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Player Username (String) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Profile Properties Count (VarInt) │
|
||||
│ ├─ Property Name (String) │
|
||||
│ ├─ Property Value (String) │
|
||||
│ └─ Optional Signature (String) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification in Pumpkin
|
||||
|
||||
In `crates/pumpkin/src/net/proxy/vine.rs`:
|
||||
|
||||
```rust
|
||||
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
|
||||
|
||||
// 1. Validate payload length (64-byte signature + 25-byte headers minimum)
|
||||
if data.len() < 89 {
|
||||
return Err(VineError::DataTooShort);
|
||||
}
|
||||
|
||||
// 2. Extract signature and signed payload
|
||||
let (sig_bytes, signed_data) = data.split_at(64);
|
||||
let signature = Signature::from_slice(sig_bytes)
|
||||
.map_err(|_| VineError::InvalidSignature)?;
|
||||
|
||||
// 3. Verify timestamp skew (30 second maximum drift)
|
||||
let skew = (server_now - timestamp).abs();
|
||||
if skew > MAX_TIMESTAMP_DRIFT_SECS {
|
||||
return Err(VineError::TimestampExpired(skew, MAX_TIMESTAMP_DRIFT_SECS));
|
||||
}
|
||||
|
||||
// 4. Verify challenge nonce matches connection state
|
||||
if received_nonce != expected_nonce {
|
||||
return Err(VineError::ChallengeMismatch);
|
||||
}
|
||||
|
||||
// 5. Verify Ed25519 signature with proxy's public key
|
||||
verifying_key.verify(signed_data, &signature)
|
||||
.map_err(|_| VineError::InvalidSignature)?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration in `pumpkin.toml`
|
||||
|
||||
Configure Vine forwarding in `pumpkin.toml`:
|
||||
|
||||
```toml
|
||||
[networking.proxy]
|
||||
enabled = true
|
||||
|
||||
[networking.proxy.vine]
|
||||
enabled = true
|
||||
# 64-character hex-encoded Ed25519 public key of the Vine proxy
|
||||
public_key = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
|
||||
# Alternatively, if secret is supplied, the public key is derived automatically
|
||||
secret = ""
|
||||
```
|
||||
97
docs/en/developer/networking/query.md
Normal file
97
docs/en/developer/networking/query.md
Normal file
@ -0,0 +1,97 @@
|
||||
# GameSpy4 Query Protocol
|
||||
|
||||
Pumpkin includes native support for the **GameSpy4 Query** protocol over UDP. External monitoring systems, Discord bots, and server listing platforms use Query to poll real-time player counts, MOTD, server versions, and active player lists with minimal network overhead.
|
||||
|
||||
Query wire encoding is implemented in [`crates/pumpkin-protocol/src/query.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin-protocol/src/query.rs), and the asynchronous UDP listener is implemented in [`crates/pumpkin/src/net/query.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/net/query.rs).
|
||||
|
||||
---
|
||||
|
||||
## Why Query? (UDP vs. Status Ping)
|
||||
|
||||
While standard Minecraft clients ping servers via the TCP `Status` state (SLP), Query operates over stateless **UDP**:
|
||||
|
||||
- **No TCP Handshake**: Avoids TCP connection overhead and connection state tables.
|
||||
- **Detailed Server State**: Returns comprehensive key-value metadata and a full list of connected player usernames (not available via basic TCP ping).
|
||||
- **Amplification Attack Protection**: Enforces cryptographic challenge tokens so malicious actors cannot abuse the UDP port for denial-of-service reflection attacks.
|
||||
|
||||
---
|
||||
|
||||
## The Handshake & Challenge Token Flow
|
||||
|
||||
To prevent UDP spoofing and reflection DDoS attacks, Query requires a two-step handshake:
|
||||
|
||||
1. **Handshake Challenge**: The client transmits `SHandshake` (`0xFEFD`, type `0x09`, and a 32-bit session ID). Pumpkin generates a cryptographic 32-bit integer challenge token tied to the client's `SocketAddr` and responds with `CHandshake`.
|
||||
2. **Status Query**: The client sends `SStatusRequest` containing the received challenge token:
|
||||
- **Basic Status** (no padding): Returns MOTD, game type (`SMP`), map name, online player count, and max player capacity.
|
||||
- **Full Status** (4-byte padding): Returns detailed server metadata key-value pairs (`hostname`, `gametype`, `game_id`, `version`, `plugins`, `map`, `numplayers`, `maxplayers`, `hostport`, `hostip`) followed by an array of online player usernames.
|
||||
|
||||
### Challenge Token Invalidation
|
||||
In `crates/pumpkin/src/net/query.rs`, challenge tokens are stored in a thread-safe map (`Arc<RwLock<HashMap<SocketAddr, i32>>>`). A background Tokio timer purges all challenge tokens every 30 seconds to prevent replay abuse:
|
||||
|
||||
```rust
|
||||
// Tokens are strictly bound to the client's IP and port
|
||||
let valid_challenge_tokens = Arc::new(RwLock::new(HashMap::new()));
|
||||
|
||||
// Background expiration loop
|
||||
tokio::spawn(async move {
|
||||
let mut interval = time::interval(Duration::from_secs(30));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
valid_challenge_tokens.write().await.clear();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Packet Formats
|
||||
|
||||
Every incoming Query packet starts with the 2-byte magic header `0xFEFD` followed by a 1-byte packet type:
|
||||
|
||||
| Type Byte | Name | Direction | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `0x09` | Handshake Request | Client → Server | Client asks for a challenge token. |
|
||||
| `0x09` | Handshake Response | Server → Client | Returns the numeric token encoded as an ASCII string. |
|
||||
| `0x00` | Stat Request | Client → Server | Requests basic or full status with challenge token. |
|
||||
| `0x00` | Basic Stat Response | Server → Client | 11-byte short summary. |
|
||||
| `0x00` | Full Stat Response | Server → Client | Key-value dictionary + null-separated player list. |
|
||||
|
||||
---
|
||||
|
||||
## Response Payloads
|
||||
|
||||
### 1. Basic Status (`CBasicStatus`)
|
||||
Contains essential server fields:
|
||||
- **`motd`**: Server message of the day (stripped of color codes).
|
||||
- **`gametype`**: Fixed string `"SMP"`.
|
||||
- **`map`**: Active world/level name (e.g., `"world"`).
|
||||
- **`numplayers`**: Online player count.
|
||||
- **`maxplayers`**: Maximum configured player capacity.
|
||||
- **`hostport`**: Port number (`u16` Little Endian).
|
||||
- **`hostip`**: Server IP string.
|
||||
|
||||
### 2. Full Status (`CFullStatus`)
|
||||
Returns two null-terminated sections:
|
||||
|
||||
1. **Key-Value Metadata Map**:
|
||||
- `hostname`: Server description / MOTD.
|
||||
- `gametype`: `"SMP"`.
|
||||
- `game_id`: `"MINECRAFT"`.
|
||||
- `version`: Server protocol / Minecraft version string.
|
||||
- `plugins`: Installed server software and plugin names.
|
||||
- `map`: Level name.
|
||||
- `numplayers` / `maxplayers`: Capacity stats.
|
||||
- `hostport` / `hostip`: Network bindings.
|
||||
2. **Player List**:
|
||||
- An array of null-terminated UTF-8 strings containing the usernames of all currently connected players.
|
||||
- Concludes with a terminal null byte (`\0`).
|
||||
|
||||
---
|
||||
|
||||
## Configuration in `pumpkin.toml`
|
||||
|
||||
```toml
|
||||
[networking.query]
|
||||
enabled = true
|
||||
address = "0.0.0.0:25565"
|
||||
```
|
||||
56
docs/en/developer/plugins/index.md
Normal file
56
docs/en/developer/plugins/index.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Plugin Engine Architecture
|
||||
|
||||
Pumpkin features a modular, multi-runtime plugin engine designed for performance, safety, and cross-language interoperability.
|
||||
|
||||
Unlike traditional Minecraft servers that rely on a single JVM class loader, Pumpkin isolates plugin execution through dedicated loaders supporting both **native high-performance Rust binaries** and **sandboxed WebAssembly components** compiled from Rust, Python, C#, C, Go, or Kotlin.
|
||||
|
||||
---
|
||||
|
||||
## Architectural Overview
|
||||
|
||||
### Architectural Breakdown
|
||||
|
||||
| Subsystem | Primary Crate | Responsibility |
|
||||
| :--- | :--- | :--- |
|
||||
| **Plugin Manager** | `crates/pumpkin/src/plugin` | Lifecycle orchestration (load, enable, disable), event dispatching, and loader registration |
|
||||
| **Native Loader** | `crates/pumpkin/src/plugin/loader/native` | Direct dynamic library loading (`.so`, `.dll`, `.dylib`) for zero-overhead Rust extensions |
|
||||
| **WASM Loader** | `crates/pumpkin/src/plugin/loader/wasm` | Wasmtime execution engine for sandboxed components compiled from Rust, Python, Go, C#, C, and Kotlin |
|
||||
| **Interface Specifications** | `crates/pumpkin-plugin-wit` | WebAssembly Interface Types (WIT) defining typed functions, types, and host imports |
|
||||
| **Security & Verification** | `crates/pumpkin/src/plugin/loader/wasm/sign` | Cryptographic signature verification via W3C wasmsign2 / Ed25519 public keys |
|
||||
|
||||
---
|
||||
|
||||
## The Dual Execution Model
|
||||
|
||||
| Feature | Native Plugins | WebAssembly (Wasmtime) Plugins |
|
||||
| :--- | :--- | :--- |
|
||||
| **File Extension** | `.so` (Linux), `.dll` (Windows), `.dylib` (macOS) | `.wasm` (Component Model) |
|
||||
| **Languages** | Rust (compiled dynamically) | Rust, Python, C#, C, Go, Kotlin |
|
||||
| **Performance** | Native CPU execution (zero overhead) | JIT-compiled with near-native speed |
|
||||
| **Sandboxing** | None (runs in host address space) | Memory-isolated sandboxed execution |
|
||||
| **Security & Signing** | OS-level permissions | Cryptographic W3C Wasm-Sign verification |
|
||||
| **Hot Unloading** | Limited (OS shared library constraints) | Fully supported memory reclamation |
|
||||
|
||||
---
|
||||
|
||||
## Event Lifecycle & Dispatching
|
||||
|
||||
Plugins interact with Pumpkin primarily through events:
|
||||
|
||||
1. **Event Dispatch**:
|
||||
- `server.plugin_manager.fire_blocking(&server, &mut event)`: Dispatches events synchronously on the executing thread (e.g., cancelling player block placement or modifying packet payloads).
|
||||
- Asynchronous events run on non-blocking Tokio tasks.
|
||||
2. **Cancellable Events**:
|
||||
- Handlers can set `event.cancelled = true` to abort the default game logic (such as cancelling player movement, interactions, or packet processing).
|
||||
|
||||
---
|
||||
|
||||
## Plugin Engine Documentation
|
||||
|
||||
Explore the internal mechanics of Pumpkin's plugin system:
|
||||
|
||||
- **[Adding a Plugin Loader](/developer/plugins/loaders)**: Step-by-step guide on implementing the `PluginLoader` trait for new scripting languages and runtimes.
|
||||
- **[WASM Signing & Verification](/developer/plugins/wasm-signing)**: Detailed specification of Ed25519 cryptographic signing, W3C `wasmsign2` custom sections, and marketplace license verification.
|
||||
|
||||
> [!NOTE]
|
||||
> Looking to develop third-party plugins rather than working on Pumpkin's internal plugin engine? Check out the **[Plugin Development Guide](/plugin-dev/introduction)**.
|
||||
161
docs/en/developer/plugins/loaders.md
Normal file
161
docs/en/developer/plugins/loaders.md
Normal file
@ -0,0 +1,161 @@
|
||||
# Adding a Plugin Loader
|
||||
|
||||
Pumpkin's plugin architecture is runtime-agnostic. The server does not assume all plugins run in WebAssembly or native shared libraries; instead, all execution runtimes implement the unified [`PluginLoader`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/plugin/loader/mod.rs) trait.
|
||||
|
||||
This guide explains how `PluginLoader` operates and demonstrates how to implement a custom loader for new script runtimes or virtual machines (such as Lua, JavaScript/Boa, or GraalVM).
|
||||
|
||||
---
|
||||
|
||||
## The `PluginLoader` Trait
|
||||
|
||||
All plugin loaders implement the `PluginLoader` trait located in `crates/pumpkin/src/plugin/loader/mod.rs`:
|
||||
|
||||
```rust
|
||||
use std::{any::Any, path::Path, pin::Pin, sync::Arc};
|
||||
use crate::plugin::{PluginMetadata, api::Plugin};
|
||||
|
||||
pub type PluginLoadFuture<'a> = Pin<
|
||||
Box<
|
||||
dyn Future<
|
||||
Output = Result<
|
||||
(Arc<dyn Plugin>, PluginMetadata, Box<dyn Any + Send + Sync>),
|
||||
LoaderError,
|
||||
>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>;
|
||||
|
||||
pub type PluginUnloadFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = Result<(), LoaderError>> + Send + 'a>>;
|
||||
|
||||
pub trait PluginLoader: Send + Sync {
|
||||
/// Determines whether this loader handles the target file.
|
||||
fn can_load(&self, path: &Path) -> bool;
|
||||
|
||||
/// Loads, parses, and initializes a plugin instance asynchronously.
|
||||
fn load<'a>(&'a self, path: &'a Path) -> PluginLoadFuture<'a>;
|
||||
|
||||
/// Unloads the plugin and reclaims associated resources.
|
||||
fn unload(&self, data: Box<dyn Any + Send + Sync>) -> PluginUnloadFuture<'_>;
|
||||
|
||||
/// Indicates whether the runtime supports safe unloading without memory leaks.
|
||||
fn can_unload(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Existing Loaders in Pumpkin
|
||||
|
||||
Pumpkin currently provides two official loaders:
|
||||
|
||||
1. **`NativePluginLoader` (`loader/native.rs`)**:
|
||||
- Matches dynamic library files (`.so` on Linux, `.dll` on Windows, `.dylib` on macOS).
|
||||
- Dynamically links the library using [`libloading`](https://docs.rs/libloading/).
|
||||
- Locates and invokes the extern entrypoint function (`pumpkin_plugin_init`).
|
||||
- `can_unload()` returns `false` due to OS-level shared library unloading hazards.
|
||||
2. **`WasmPluginLoader` (`loader/wasm/mod.rs`)**:
|
||||
- Matches `.wasm` files.
|
||||
- Verifies cryptographic signatures (see [WASM Signing](/developer/plugins/wasm-signing)).
|
||||
- JIT-compiles WebAssembly components using [Wasmtime](https://wasmtime.dev/).
|
||||
- Links host WIT exports (`pumpkin-host-bindings`).
|
||||
- `can_unload()` returns `true` because Wasmtime safely drops guest memory.
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step: Adding a Custom Plugin Loader
|
||||
|
||||
Here is how to create a custom loader (e.g., for a scripting runtime):
|
||||
|
||||
### Step 1: Define the Loader Struct
|
||||
Create a new file in `crates/pumpkin/src/plugin/loader/my_loader.rs`:
|
||||
|
||||
```rust
|
||||
use std::{any::Any, path::Path, sync::Arc};
|
||||
use crate::plugin::loader::{LoaderError, PluginLoadFuture, PluginLoader, PluginUnloadFuture};
|
||||
use crate::plugin::{PluginMetadata, api::Plugin};
|
||||
|
||||
pub struct MyCustomLoader;
|
||||
|
||||
impl MyCustomLoader {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Implement File Recognition (`can_load`)
|
||||
Check the file extension or inspect header bytes:
|
||||
|
||||
```rust
|
||||
impl PluginLoader for MyCustomLoader {
|
||||
fn can_load(&self, path: &Path) -> bool {
|
||||
path.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("script"))
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Implement Asynchronous Loading (`load`)
|
||||
The `load` method reads the script, initializes the VM, registers server host callbacks, and returns a tuple containing:
|
||||
1. `Arc<dyn Plugin>`: The generic plugin handle.
|
||||
2. `PluginMetadata`: Name, author, version, description.
|
||||
3. `Box<dyn Any + Send + Sync>`: Loader-specific context (e.g., VM state) retained for unloading.
|
||||
|
||||
```rust
|
||||
fn load<'a>(&'a self, path: &'a Path) -> PluginLoadFuture<'a> {
|
||||
Box::pin(async move {
|
||||
let script_content = tokio::fs::read_to_string(path).await
|
||||
.map_err(|e| LoaderError::InitializationFailed(e.to_string()))?;
|
||||
|
||||
// 1. Parse plugin metadata from header or accompanying manifest
|
||||
let metadata = PluginMetadata {
|
||||
name: "ExampleScript".into(),
|
||||
version: "1.0.0".into(),
|
||||
authors: vec!["Author".into()],
|
||||
description: Some("Custom script plugin".into()),
|
||||
};
|
||||
|
||||
// 2. Initialize runtime instance
|
||||
let plugin_instance = Arc::new(MyScriptPluginWrapper::new(script_content));
|
||||
let runtime_state: Box<dyn Any + Send + Sync> = Box::new(());
|
||||
|
||||
Ok((plugin_instance, metadata, runtime_state))
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Implement Clean Unloading (`unload` & `can_unload`)
|
||||
If your runtime can cleanly terminate without leaking resources:
|
||||
|
||||
```rust
|
||||
fn can_unload(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn unload(&self, data: Box<dyn Any + Send + Sync>) -> PluginUnloadFuture<'_> {
|
||||
Box::pin(async move {
|
||||
// Drop runtime state and disconnect event listeners
|
||||
drop(data);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Register the Loader in `PluginManager`
|
||||
|
||||
Open `crates/pumpkin/src/plugin/mod.rs` and register your loader in `PluginManager::new()`:
|
||||
|
||||
```rust
|
||||
// In crates/pumpkin/src/plugin/mod.rs
|
||||
let mut loaders: Vec<Box<dyn PluginLoader>> = Vec::new();
|
||||
|
||||
loaders.push(Box::new(NativePluginLoader::new()));
|
||||
loaders.push(Box::new(WasmPluginLoader::new()));
|
||||
loaders.push(Box::new(MyCustomLoader::new())); // Add custom loader
|
||||
```
|
||||
|
||||
During server startup, Pumpkin iterates through all registered loaders in sequence when scanning the `plugins/` folder, dispatching each file to the first loader whose `can_load` returns `true`.
|
||||
86
docs/en/developer/plugins/wasm-signing.md
Normal file
86
docs/en/developer/plugins/wasm-signing.md
Normal file
@ -0,0 +1,86 @@
|
||||
# WASM Plugin Signing & Verification
|
||||
|
||||
Pumpkin implements a cryptographic signing and license verification system for WebAssembly plugins. This ensures plugin integrity, authenticates author identity, and protects verified or marketplace plugins against unauthorized tampering.
|
||||
|
||||
The signing subsystem is implemented in [`crates/pumpkin/src/plugin/loader/wasm/wasm_host/signature.rs`](https://github.com/Pumpkin-MC/Pumpkin/blob/master/crates/pumpkin/src/plugin/loader/wasm/wasm_host/signature.rs) based on the **W3C `wasmsign2`** specification using custom WebAssembly sections.
|
||||
|
||||
---
|
||||
|
||||
## Why Sign WebAssembly Plugins?
|
||||
|
||||
- **Tamper Protection**: Ensures that compiled bytecode has not been injected with malicious logic after compilation.
|
||||
- **Author Identity**: Authenticates that a plugin was built and released by a verified developer.
|
||||
- **Marketplace Licensing**: Validates license keys and entitlements for plugins distributed through the official [Pumpkin Marketplace](https://market.pumpkinmc.org).
|
||||
|
||||
---
|
||||
|
||||
## Custom Sections in the WASM Binary
|
||||
|
||||
Pumpkin embeds signing and verification metadata directly into two standard WebAssembly custom sections:
|
||||
- **`pumpkin.metadata`**: Stores developer, publication, and license claims in JSON format.
|
||||
- **`wasm_signature`**: Stores the cryptographic signature envelope compliant with the W3C `wasmsign2` standard.
|
||||
|
||||
### 1. `pumpkin.metadata` Section
|
||||
Contains a JSON string holding publication and license claims:
|
||||
|
||||
```json
|
||||
{
|
||||
"marketplace_url": "https://market.pumpkinmc.org",
|
||||
"plugin_id": 1024,
|
||||
"plugin_name": "EconomyPlus",
|
||||
"version": "1.2.0",
|
||||
"dev_id": 42,
|
||||
"dev_name": "DevName",
|
||||
"is_paid": true,
|
||||
"user_id": 1001,
|
||||
"license_key": "XXXX-XXXX-XXXX-XXXX",
|
||||
"issued_at": "2026-09-14T05:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. `wasm_signature` Section
|
||||
Follows the W3C `wasmsign2` envelope structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"algorithm": "Ed25519",
|
||||
"public_key_hex": "4a7b...",
|
||||
"signature_hex": "8e1c..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The Signing Pipeline
|
||||
|
||||
The signing process guarantees integrity by hashing and signing the clean WebAssembly bytecode combined with the metadata claims:
|
||||
|
||||
### Signing Algorithm Steps
|
||||
1. **Sanitization (`strip_pumpkin_sections`)**: Uses [`wasmparser`](https://docs.rs/wasmparser/) to strip any preexisting `pumpkin.metadata` or `wasm_signature` custom sections to avoid recursion.
|
||||
2. **Payload Construction**:
|
||||
```rust
|
||||
let mut sign_payload = Vec::new();
|
||||
sign_payload.extend_from_slice(&clean_wasm);
|
||||
sign_payload.extend_from_slice(&metadata_json);
|
||||
```
|
||||
3. **Ed25519 Signature**: The payload is signed using [`ed25519-dalek`](https://docs.rs/ed25519-dalek/) with the developer's 32-byte secret key.
|
||||
4. **Encoding**: Uses [`wasm-encoder`](https://docs.rs/wasm-encoder/) to append the `pumpkin.metadata` and `wasm_signature` custom sections to the clean binary.
|
||||
|
||||
---
|
||||
|
||||
## The Verification Pipeline
|
||||
|
||||
When Pumpkin boots and loads a WebAssembly plugin, the verification sequence executes before the plugin is instantiated:
|
||||
|
||||
1. **Custom Section Extraction**: The `WasmPluginLoader` scans the binary for `pumpkin.metadata` and `wasm_signature`.
|
||||
2. **Key Retrieval**:
|
||||
- If the plugin is marked as marketplace-verified, Pumpkin verifies the public key against the Marketplace Public Key Endpoint (`https://market.pumpkinmc.org/api/v1/rest/public-key`).
|
||||
- If signed by an independent developer, the key is matched against trusted developer keys configured in `pumpkin.toml`.
|
||||
3. **Bytecode Verification**:
|
||||
- Pumpkin strips the custom sections from the loaded binary to recover the original `clean_wasm`.
|
||||
- Reconstructs the payload `(clean_wasm + metadata_json)`.
|
||||
- Verifies the signature using `verifying_key.verify(&payload, &signature)`.
|
||||
4. **License & Integrity Validation**:
|
||||
- If verification succeeds, Wasmtime instantiates the plugin safely.
|
||||
- If verification fails or the signature is invalid, loading is aborted with an error, protecting the server from running compromised code.
|
||||
287
docs/en/developer/porting-versions.md
Normal file
287
docs/en/developer/porting-versions.md
Normal file
@ -0,0 +1,287 @@
|
||||
# Porting to Newer Minecraft Versions
|
||||
|
||||
Upgrading Pumpkin to support a newer Minecraft release—whether a minor patch (e.g., `1.21.4` to `1.21.5`) or a major update (e.g., `1.21` to `26.1`/`26.2`)—is a structured, multi-step process.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **The Extractor mod must be ported first!**
|
||||
> Pumpkin cannot be ported in isolation. The primary source for Pumpkin's assets (`blocks.json`, `items.json`, `entities.json`, `packets.json`, `tracked_data.json`, `synced_registries.json`, etc.) is our dedicated Fabric mod: [Pumpkin-MC/Extractor](https://github.com/Pumpkin-MC/Extractor). You **must first port and run the Extractor on the target Minecraft version** before running `pumpkin-codegen` or updating server crates.
|
||||
|
||||
### Porting Lifecycle at a Glance
|
||||
|
||||
| Stage | Responsibility | Primary Crates / Tools |
|
||||
| :--- | :--- | :--- |
|
||||
| **1. Port Extractor** | Extract vanilla block states, entities, items, packets, and registries | `Pumpkin-MC/Extractor` (Fabric mod) |
|
||||
| **2. Update Datapacks** | Ingest new data packs, tags, recipes, and Bedrock mappings | `assets/datapacks/<ver>`, `assets/bedrock` |
|
||||
| **3. Version Declarations** | Register protocol version and ViaVersion/ViaBackwards remap nodes | `pumpkin-util`, `tools/pumpkin-codegen` |
|
||||
| **4. Code Generation** | Regenerate zero-cost Rust data tables and registries | `pumpkin-codegen` -> `pumpkin-data` |
|
||||
| **5. Compiler Fixes** | Update packet layouts, codecs, and data components | `pumpkin-protocol`, `pumpkin-inventory` |
|
||||
| **6. Gameplay Logic** | Implement new block behaviors, item mechanics, and mob AI | `crates/pumpkin/src/block`, `item`, `entity` |
|
||||
| **7. Multi-Version QA** | Validate backward compatibility, test suites, and client joins | `cargo test`, `pumpkin-gametest` |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Port the Extractor Mod First (`Pumpkin-MC/Extractor`)
|
||||
|
||||
The [Extractor](https://github.com/Pumpkin-MC/Extractor) is a Kotlin-based Fabric mod that boots an actual Minecraft client or server instance to introspect live game registries and extract data into the exact JSON formats Pumpkin expects.
|
||||
|
||||
### 1. Clone or Update the Extractor Repository
|
||||
```bash
|
||||
git clone https://github.com/Pumpkin-MC/Extractor
|
||||
cd Extractor
|
||||
```
|
||||
|
||||
### 2. Update `gradle.properties`
|
||||
Bump the dependency versions to match the target Minecraft version, Yarn mappings, and Fabric ecosystem:
|
||||
|
||||
```properties
|
||||
minecraft_version=26.2
|
||||
yarn_mappings=26.2+build.1:v2
|
||||
loader_version=0.16.10
|
||||
fabric_version=0.115.0+26.2
|
||||
```
|
||||
|
||||
### 3. Resolve Extractor Compilation Errors
|
||||
Minecraft and Fabric internal APIs often change between releases. Check and fix any broken extractors in `src/main/kotlin/de/snowii/extractor/extractors/`:
|
||||
- **`Blocks.kt`**: Extracts block states, hardness, resistance, luminance, and collision box offsets.
|
||||
- **`Items.kt`**: Extracts item IDs, max stack sizes, and default components.
|
||||
- **`Entities.kt`**: Extracts entity IDs, dimensions (width, height), and eye heights.
|
||||
- **`Packets.kt`**: Extracts packet IDs for handshake, status, login, configuration, and play states.
|
||||
- **`SyncedRegistries.kt`**: Extracts dynamic registries synchronized to clients.
|
||||
- **`TrackedData.kt`**: Extracts entity tracked data indices and serializers.
|
||||
- **`MultiNoise.kt` & `Biome.kt`**: Extracts world generation biome trees and noise parameters.
|
||||
|
||||
### 4. Run the Extractor
|
||||
Execute the server runner (or client runner for translation/screen dumps):
|
||||
|
||||
```bash
|
||||
./gradlew runServer
|
||||
```
|
||||
|
||||
Once the extraction finishes, all dumped JSON files will be located in `run/pumpkin_extractor_output/`.
|
||||
|
||||
### 5. Replace the Old Asset Files in Pumpkin
|
||||
Copy the newly generated JSON files from `run/pumpkin_extractor_output/` and use them to **replace the old asset files** in Pumpkin:
|
||||
- Replace the core game assets in `Pumpkin/assets/` (`blocks.json`, `items.json`, `entities.json`, `packets.json`, `properties.json`, `synced_registries.json`, `tracked_data.json`, `en_us.json`, etc.).
|
||||
- Replace the world generation test files in `Pumpkin/crates/pumpkin-world/assets/`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Update Assets & Datapacks
|
||||
|
||||
Once the raw extractor assets are in place, update the versioned datapacks and supplemental files in Pumpkin:
|
||||
|
||||
### 1. Add the Versioned Datapack
|
||||
Extract the internal vanilla data pack from the new Minecraft server JAR and place it under `assets/datapacks/<version>/`:
|
||||
|
||||
```
|
||||
assets/datapacks/<version>/
|
||||
└── data/
|
||||
├── minecraft/
|
||||
│ ├── advancement/
|
||||
│ ├── chat_type/
|
||||
│ ├── damage_type/
|
||||
│ ├── dimension_type/
|
||||
│ ├── enchantment/
|
||||
│ ├── item_modifier/
|
||||
│ ├── loot_table/
|
||||
│ ├── recipe/
|
||||
│ ├── tags/
|
||||
│ └── worldgen/
|
||||
└── c/
|
||||
└── tags/ (Conventional / Common Tags)
|
||||
```
|
||||
|
||||
### 2. Update Versioned Assets
|
||||
Update or generate the corresponding versioned metadata files:
|
||||
- `assets/registry/<version>_synced_registries.json`: Dynamic registries streamed to the client during the `Configuration` network state.
|
||||
- `assets/tags/<version>_tags.json`: Tag collections.
|
||||
- `assets/tracked_data/<version>_tracked_data.json`: Entity metadata indices.
|
||||
|
||||
### 3. Update Bedrock Mappings
|
||||
If porting Bedrock Edition support simultaneously, update the files in `assets/bedrock/` using the latest mappings from [GeyserMC](https://github.com/GeyserMC/Geyser) and Bedrock Dedicated Server (BDS).
|
||||
|
||||
### 4. Decompile Sources for Reverse Engineering (Optional but Recommended)
|
||||
For complex changes—such as worldgen noise router formulas and custom block collision shapes—decompile the matching vanilla JAR into `minecraft-sources-<version>/` (using Vineflower or Fabric Loom with official Mojang mappings) for reference.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Register Version Constants & ViaVersion Mappings
|
||||
|
||||
Pumpkin supports multi-version clients through automated protocol remapping powered by **ViaBackwards** / **ViaRewind** mapping tables.
|
||||
|
||||
### 1. Declare Version in `pumpkin-util`
|
||||
Open `crates/pumpkin-util/src/version.rs` and add the new version to [`JavaMinecraftVersion`](file:///home/alex/Documents/Development/Rust/Pumpkin/crates/pumpkin-util/src/version.rs):
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum JavaMinecraftVersion {
|
||||
// ... existing versions ...
|
||||
V_1_21_4,
|
||||
V_1_21_11,
|
||||
V_26_2, // New target version
|
||||
}
|
||||
|
||||
impl JavaMinecraftVersion {
|
||||
pub const fn protocol_version(&self) -> i32 {
|
||||
match self {
|
||||
// ...
|
||||
Self::V_26_2 => 770, // Protocol version number
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn version_name(&self) -> &'static str {
|
||||
match self {
|
||||
// ...
|
||||
Self::V_26_2 => "26.2",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Declare Version in `pumpkin-codegen`
|
||||
Open `tools/pumpkin-codegen/src/version.rs` and add the identical enum variant to the codegen AST.
|
||||
|
||||
### 3. Add ViaVersion / ViaBackwards Mappings
|
||||
To enable clients on older versions to connect to the updated server (e.g., a `1.21.4` client connecting to a `26.2` server):
|
||||
1. Obtain the NBT mapping file from the [ViaBackwards repository](https://github.com/ViaVersion/ViaBackwards):
|
||||
`mappings-<new_version>to<previous_version>.nbt`
|
||||
2. Save it to:
|
||||
`assets/viabackwards/data/mappings-<new_version>to<previous_version>.nbt`
|
||||
3. In `tools/pumpkin-codegen/src/remap/mod.rs`, link the new node into the `remap_nodes!` macro:
|
||||
|
||||
```rust
|
||||
let node_26_2 = $crate::remap::MappingNode {
|
||||
version: $crate::version::JavaMinecraftVersion::V_26_2,
|
||||
value: "../../assets/viabackwards/data/mappings-26.2to26.1.nbt",
|
||||
child: Some(&node_26_1),
|
||||
};
|
||||
$remapper.process(&node_26_2)
|
||||
```
|
||||
|
||||
4. Check `tools/pumpkin-codegen/src/remap/` to ensure specific remap builders (`block_state.rs`, `item_id.rs`, `entity_id.rs`, `sound_id.rs`, `argument_type.rs`) correctly process any new remapping structures introduced by ViaVersion.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Run Code Generation (`pumpkin-codegen`)
|
||||
|
||||
With all extractor assets, datapacks, and mapping nodes in place, execute the codegen tool from the workspace root:
|
||||
|
||||
```bash
|
||||
cargo run -p pumpkin-codegen
|
||||
```
|
||||
|
||||
### What Gets Regenerated?
|
||||
`pumpkin-codegen` generates zero-cost Rust data tables into `crates/pumpkin-data/src/generated/`:
|
||||
- **`block.rs`**: All block IDs, properties, state masks, and collision shapes.
|
||||
- **`item.rs`**: Items, default components, and tool tiers.
|
||||
- **`entity_type.rs` & `tracked_data.rs`**: Entity type IDs, default bounding boxes, and metadata serializers.
|
||||
- **`packet.rs`**: Serverbound and clientbound packet ID constants across protocol states.
|
||||
- **`recipes.rs`**: All crafting, cooking, and stonecutting recipes parsed from the new datapack.
|
||||
- **`tag.rs`**: Tag lookups (`#minecraft:mineable/axe`, `#c:iron_ingots`).
|
||||
- **`remap/*.rs`**: State remap lookup tables (`block_state_remap.rs`, `item_id_remap.rs`, `entity_id_remap.rs`, `sound_id_remap.rs`).
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Resolve Workspace Compilation Errors
|
||||
|
||||
After regenerating `pumpkin-data`, the Rust compiler will flag breaking changes across the workspace. Run:
|
||||
|
||||
```bash
|
||||
cargo check --workspace
|
||||
```
|
||||
|
||||
### Common Compiler Breakages & Fixes:
|
||||
|
||||
#### 1. Packet ID & Layout Changes (`pumpkin-protocol`)
|
||||
Mojang frequently adds, moves, or alters packet IDs and payloads:
|
||||
- Update `pumpkin-protocol/src/java/client/` and `pumpkin-protocol/src/java/server/`.
|
||||
- Inspect packets like `LoginSuccess`, `PlayLogin`, `Respawn`, and `PlayerSpawnData` for newly added fields (e.g., new game mode flags, dimension attributes, or teleportation IDs).
|
||||
- For Bedrock Edition, check `StartGame`, `NetworkItem`, and container transaction packets in `pumpkin-protocol/src/bedrock/`.
|
||||
|
||||
#### 2. Data Component Changes
|
||||
If items switched from older NBT structures to new Data Components (or if components were renamed/restructured):
|
||||
- Update `crates/pumpkin-data/src/data_component.rs`.
|
||||
- Update serialization and deserialization in `pumpkin-inventory`.
|
||||
|
||||
#### 3. Registry Synchronization
|
||||
During the `Configuration` phase, servers send dynamic registry contents to Java clients. If new registries were added to vanilla Minecraft (e.g., trim materials, banner patterns, instrument sounds):
|
||||
- Verify `crates/pumpkin-data/src/generated/registry.rs`.
|
||||
- Ensure `pumpkin/src/net/java/config.rs` streams all required registry codecs to the client.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Implement Logic for New Blocks, Items & Entities
|
||||
|
||||
Once the server compiles, any newly introduced gameplay content must be implemented.
|
||||
|
||||
### 1. New Blocks
|
||||
1. Locate new block identifiers in `pumpkin-data::Block`.
|
||||
2. Create a behavior module under `crates/pumpkin/src/block/blocks/<block_name>.rs` implementing [`BlockBehaviour`](/developer/blocks/adding-blocks):
|
||||
```rust
|
||||
pub struct NewBlockBehaviour;
|
||||
|
||||
impl BlockBehaviour for NewBlockBehaviour {
|
||||
fn on_use(&self, block: &Block, player: &Arc<Player>, location: BlockPos, world: &Arc<World>) {
|
||||
// Interaction logic
|
||||
}
|
||||
}
|
||||
```
|
||||
3. Register the behavior in the block registry in `crates/pumpkin/src/block/mod.rs`.
|
||||
|
||||
### 2. New Items
|
||||
1. Locate new item identifiers in `pumpkin-data::Item`.
|
||||
2. If the item has unique right-click or use behavior (e.g., weapons, projectiles, instruments), create an item behavior in `crates/pumpkin/src/item/items/<item_name>.rs` implementing [`ItemBehaviour`](/developer/items/adding-items).
|
||||
3. If the item places entities (e.g., spawn eggs, boats, minecarts), configure entity spawning logic in `pumpkin/src/item/items/spawn_egg.rs`.
|
||||
4. Register the behavior in `crates/pumpkin/src/item/mod.rs`.
|
||||
|
||||
### 3. New Entities
|
||||
1. Check `pumpkin-data::entity_type::EntityType` for new entity IDs.
|
||||
2. Create an entity struct in `crates/pumpkin/src/entity/mob/` or `living/` extending [`LivingEntity`](/developer/entities/).
|
||||
3. Implement `TrackedData` fields to match vanilla client expectations (e.g., poses, animations, glowing states).
|
||||
4. Assign AI goals and navigation tasks using the [Mob AI & Goals system](/developer/entities/ai-and-mobs).
|
||||
5. Add spawn packet construction in `crates/pumpkin/src/entity/spawning.rs`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Multi-Version & ViaVersion Verification
|
||||
|
||||
Ensure that older clients can still connect seamlessly:
|
||||
|
||||
1. **Verify Generated Remap Tables**:
|
||||
Check `crates/pumpkin-data/src/generated/block_state_remap.rs` and `item_id_remap.rs`. Ensure that novel block states fall back gracefully to compatible equivalents on older client versions (e.g., new wood variants mapping to oak or spruce).
|
||||
2. **Packet Translation**:
|
||||
Verify that packet writers in `pumpkin-protocol` apply `remap` functions when serializing data for a connection whose negotiated `protocol_version` is older than the server's native version.
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Testing & Verification Checklist
|
||||
|
||||
Before opening a Pull Request for a version port, complete the following QA checklist:
|
||||
|
||||
- [ ] **Automated Tests**:
|
||||
```bash
|
||||
cargo test --workspace
|
||||
```
|
||||
- [ ] **Game Test Suite**:
|
||||
```bash
|
||||
cargo test -p pumpkin-gametest
|
||||
```
|
||||
- [ ] **Strict Linter Rules**:
|
||||
```bash
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
```
|
||||
- [ ] **Spellcheck**:
|
||||
```bash
|
||||
typos
|
||||
```
|
||||
- [ ] **In-Game Java Client Test**:
|
||||
- Connect with the native new Minecraft client version.
|
||||
- Test login, world loading, chunk generation, and player movement.
|
||||
- Place and break new blocks; verify collision boxes and redstone reactivity.
|
||||
- Use and equip new items; test attack speed, durability, and animations.
|
||||
- Spawn and fight new entities; test animations, hurt sounds, and drops.
|
||||
- [ ] **In-Game Older Version Client Test**:
|
||||
- Connect with an older supported client version (e.g., 1.21.x).
|
||||
- Verify that blocks, items, and entities translate without causing client crashes or desyncs.
|
||||
- [ ] **In-Game Bedrock Client Test**:
|
||||
- Verify that Bedrock clients can authenticate, join, load world chunks, and view custom items.
|
||||
@ -1,83 +1,105 @@
|
||||
# World Formats
|
||||
# World Engine & Formats
|
||||
|
||||
## Region File Format
|
||||
The world engine is managed primarily within the [`pumpkin-world`](https://github.com/Pumpkin-MC/Pumpkin/tree/master/crates/pumpkin-world) crate. It handles world storage formats, chunk reading and writing, terrain generation, lighting calculations, and block ticking.
|
||||
|
||||
Minecraft Beta 1.3 to Release 1.2 used a Minecraft format known as the "Region file format".
|
||||
---
|
||||
|
||||
The files stored in this format are `.mcr` files, each storing a group of 32x32 chunks called a region.
|
||||
## Supported World Formats
|
||||
|
||||
More details can be found on the [Minecraft Wiki](https://minecraft.wiki/w/Region_file_format).
|
||||
Pumpkin is designed to support multiple region and world formats, giving server administrators flexibility between vanilla compatibility and modern high-performance compression.
|
||||
|
||||
## Anvil File Format
|
||||
### 1. Anvil File Format (`.mca`)
|
||||
|
||||
Replacing the Region File Format after Minecraft Release 1.2, this is the file format used to store modern Vanilla Minecraft: Java Edition worlds.
|
||||
The Anvil format is the standard format used by vanilla Minecraft: Java Edition.
|
||||
|
||||
The files stored in this format are `.mca` files. While using the same region logic, there were a number of changes. The notable changes include an increase
|
||||
to a 256 height limit, then to 320, as well as a higher number of block IDs.
|
||||
- **Structure**: Chunks are grouped into 32×32 chunk regions stored in `.mca` files (`r.{x}.{z}.mca`).
|
||||
- **Data Layers**: Each chunk contains block state palettes, biome palettes, heightmaps, tile entities, and light arrays.
|
||||
- **Compression**: Individual chunks within the region file are compressed using ZLib or LZ4.
|
||||
- **Support**: Pumpkin supports **both loading and saving** Anvil region files.
|
||||
|
||||
More details can be found on the [Minecraft Wiki](https://minecraft.wiki/w/Anvil_file_format).
|
||||
### 2. Linear Region Format (`.linear`)
|
||||
|
||||
## Linear File Format
|
||||
Linear is a modern alternative to Anvil that drastically reduces world save sizes and speeds up disk I/O by utilizing **Zstandard (zstd)** compression across the entire region rather than per-chunk zlib blocks.
|
||||
|
||||
There is a more modern file format known as the Linear region file format. It saves on disk space and uses the zstd library instead of zlib. This is beneficial as zlib is extremely old and
|
||||
outdated.
|
||||
- **Savings**: Reduces disk usage by ~50% in the Overworld and Nether, and up to ~95% in the End.
|
||||
- **Performance**: High decompression speeds and reduced disk writes make it ideal for high-throughput production servers.
|
||||
- **Support**: Native read/write support is built into `pumpkin-world`.
|
||||
|
||||
The files stored in this format are `.linear` files, and it saves about 50% of disk space in the Overworld and the Nether, and saves 95% in the End.
|
||||
### 3. Slime World Format (`.slime`)
|
||||
|
||||
More details can be found at the GitHub page for [LinearRegionFileFormatTools](https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools).
|
||||
Originally developed by Hypixel, the Slime format packages entire worlds into a single, compact, easily duplicable binary file. It is optimized for minigames and temporary instance-based worlds.
|
||||
|
||||
## Slime File Format
|
||||
### 4. Pump Format
|
||||
|
||||
Developed by Hypixel to fix many of the pitfalls of the Anvil file format, Slime also replaces zlib and saves space compared to Anvil. It saves the entire world in a single save
|
||||
file, and allows that file to be loaded into multiple instances.
|
||||
Pumpkin's native chunk storage format designed specifically for rapid serialization without the legacy overhead of the Anvil format.
|
||||
|
||||
The files stored in this format are `.slime` files.
|
||||
---
|
||||
|
||||
More details can be found on the GitHub page for [Slime World Manager](https://github.com/cijaaimee/Slime-World-Manager#:~:text=Slime%20World%20Manager%20is%20a,worlds%20faster%20and%20save%20space.), as well as on [Dev Blog #5](https://hypixel.net/threads/dev-blog-5-storing-your-skyblock-island.2190753/) for Hypixel.
|
||||
The world engine organizes chunk management and ticking into hierarchical layers:
|
||||
|
||||
## Schematic File Format
|
||||
### Hierarchy
|
||||
|
||||
Unlike the other file formats listed, the Schematic File Format is not used for storing Minecraft worlds, but instead used within 3rd party programs such as MCEdit, WorldEdit, and Schematica.
|
||||
1. **`World`**: Manages dimensions (`overworld`, `the_nether`, `the_end`), coordinates players within that dimension, and broadcasts updates.
|
||||
2. **`Level`**: The core data structure holding chunk tables, height bounds (-64 to 320 for vanilla 1.18+ worlds), block registries, and tick schedulers.
|
||||
3. **`Chunker`**: Manages view distances and player subscriptions. Uses `CylindricalChunkIterator` to compute which chunks to stream to players as they move.
|
||||
|
||||
The files stored in this format are `.schematic` files, and are stored in the NBT format.
|
||||
### Asynchronous Chunk Pipeline
|
||||
|
||||
More details can be found on the [Minecraft Wiki](https://minecraft.wiki/w/Schematic_file_format)
|
||||
To maintain steady 20 TPS tick rates:
|
||||
|
||||
### World Generation
|
||||
- Chunks requested by players are checked in the memory cache.
|
||||
- Uncached chunks are loaded from disk or generated via **Rayon** background tasks without stalling the main game tick loop.
|
||||
- Completed chunks are dispatched through asynchronous channels to the player's connection stream.
|
||||
|
||||
When the server is starting up, it checks if there is a save present, also known as the "world".
|
||||
---
|
||||
|
||||
Pumpkin then calls for world generation:
|
||||
## World Generation Pipeline
|
||||
|
||||
#### Save Present
|
||||
When a world does not yet have saved chunks, or when players explore past generated boundaries, Pumpkin generates new chunks through a 6-stage pipeline:
|
||||
|
||||
`AnvilChunkReader` is called to process the region files for the given save
|
||||
1. **Noise Routers**: Multi-octave 3D Perlin and Simplex noise algorithms compute continentalness, erosion, and peaks/valleys.
|
||||
2. **Density Shaping**: Transforms continuous 3D noise values into terrain density and solid vs. air voxel thresholds.
|
||||
3. **Biome Climate Mapping**: Samples temperature, humidity, continentalness, and erosion to assign biome IDs.
|
||||
4. **Surface Rules**: Evaluates layered rules to place grass, dirt, sand, sandstone, or deepslate depending on exposure and altitude.
|
||||
5. **Carvers**: Carves 3D cave tunnels, ravines, and aquifers into solid rock.
|
||||
6. **Features & Decoration**: Places ore veins, vegetation, trees, structures, and surface foliage.
|
||||
|
||||
- As stated above, region files store 32x32 chunks
|
||||
> Each region file is named corresponding to coordinates of where it is in the world
|
||||
### 1. Noise Routers & Density Functions
|
||||
|
||||
> r.{}.{}.mca
|
||||
Pumpkin implements Minecraft's multi-octave 3D Perlin and Simplex noise algorithms:
|
||||
|
||||
- The location table is read from the save file, representing the chunk coordinates
|
||||
- The timestamp table is read from the save file, representing the last time the chunk was modified
|
||||
- Calculates continentalness, erosion, peaks & valleys, and 3D density.
|
||||
- Generates natural terrain shapes including overhangs, floating islands, mountains, and deep caves.
|
||||
|
||||
#### No Save Present
|
||||
### 2. Biome Climate Parameters
|
||||
|
||||
The world seed is set to "0". In the future it will be set to the value in the "basic" configuration.
|
||||
Biomes are resolved dynamically based on multi-dimensional climate coordinates:
|
||||
|
||||
`PlainsGenerator` is called, as so far `Plains` is the only biome that has been implemented.
|
||||
- Temperature
|
||||
- Humidity
|
||||
- Continentalness
|
||||
- Erosion
|
||||
- Depth
|
||||
- Weirdness
|
||||
|
||||
- `PerlinTerrainGenerator` is called to set chunk height
|
||||
- Stone height is set 5 below chunk height
|
||||
- Dirt height is set to 2 below chunk height
|
||||
- Grass blocks appear at the top of dirt
|
||||
- Bedrock is set at y = -64
|
||||
- Flowers and short grass are scattered about randomly
|
||||
The parameters match vanilla Minecraft biome palettes and generate smooth biome transitions.
|
||||
|
||||
`SuperflatGenerator` is also available, but is not currently callable.
|
||||
### 3. Generators
|
||||
|
||||
- Bedrock is set at y = -64
|
||||
- Dirt is set two blocks up
|
||||
- Grass blocks are set one more block up
|
||||
- **Vanilla-like Terrain Generator**: Full 3D noise generation matching modern Minecraft world limits (Y = -64 to Y = 320).
|
||||
- **Superflat Generator**: Configurable flat layers (e.g., Bedrock at Y=-64, Dirt, Grass Block top layer).
|
||||
|
||||
Blocks are able to be placed and broken, but changes are not able to be saved in any world format. Anvil worlds are currently read only.
|
||||
---
|
||||
|
||||
## Lighting & Ticking
|
||||
|
||||
### 1. Lighting Engine
|
||||
|
||||
- **Block Light**: Propagated dynamically from light-emitting blocks (torches, glowstone, lava) up to level 15.
|
||||
- **Sky Light**: Propagated vertically from the sky down through transparent and semi-transparent blocks (leaves, water).
|
||||
- Light arrays are computed and serialized into clientbound chunk packets so clients render accurate shadows.
|
||||
|
||||
### 2. Block & Chunk Ticking
|
||||
|
||||
- **Scheduled Ticks**: Blocks requesting delayed execution (water flow, falling sand, redstone repeaters).
|
||||
- **Random Ticks**: Random blocks selected per sub-chunk each tick to simulate plant growth, ice melting, and crop maturation.
|
||||
|
||||
@ -4,19 +4,7 @@ Moving from **Bukkit / Spigot / Paper** plugin development to Pumpkin involves s
|
||||
|
||||
---
|
||||
|
||||
## High-Level Architectural Differences
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Bukkit / Paper
|
||||
A[Java Source] --> B[Java Bytecode .jar]
|
||||
B --> C[JVM / Main Server Thread]
|
||||
end
|
||||
subgraph Pumpkin
|
||||
D[Rust / Python / C# / Go / C] --> E[WASM Component .wasm]
|
||||
E --> F[WASM Sandbox / Multithreaded Engine]
|
||||
end
|
||||
```
|
||||
The table below contrasts the legacy Bukkit/Paper paradigm with Pumpkin's modern WebAssembly architecture:
|
||||
|
||||
| Concept | Bukkit / Spigot / Paper | Pumpkin |
|
||||
| :--- | :--- | :--- |
|
||||
|
||||
@ -1,187 +0,0 @@
|
||||
# ベンチマーク
|
||||
|
||||
ここでは、一般的な Minecraft サーバーソフトウェアと Pumpkin を比較しています。
|
||||
|
||||
> [!CAUTION]
|
||||
> **この比較は公平ではありません。** Pumpkin は現時点で他のサーバーよりもはるかに少ない機能しか持っていないため、リソース使用量が少なく見えるかもしれません。
|
||||
> また、他のサーバーが何年もかけて最適化されてきたことも考慮する必要があります。
|
||||
> バニラのフォークは、バニラのロジック全体を書き直す必要がないため、最適化のみに集中できます。
|
||||
|
||||

|
||||
|
||||
## 仕様
|
||||
|
||||
### 技術仕様
|
||||
|
||||
#### ソフトウェア
|
||||
|
||||
- ディストリビューション: Manjaro Linux
|
||||
- アーキテクチャ: x86_64 (64ビット)
|
||||
- カーネルバージョン: 6.11.3-arch1-1
|
||||
|
||||
#### ハードウェア
|
||||
|
||||
- マザーボード: MAG B650 TOMAHAWK WIFI
|
||||
- CPU: AMD Ryzen 7600X 6コア
|
||||
- RAM: Corsair 2x16GB DDR5 6000Mhz
|
||||
- ストレージ: Samsung 990 PRO 1TB PCIe 4.0 M.2 SSD
|
||||
- 冷却: be quiet Dark Rock Elite
|
||||
|
||||
#### Rust
|
||||
|
||||
- ツールチェイン: stable-x86_64-unknown-linux-gnu (1.81.0)
|
||||
- Rust コンパイラ: rustc 1.81.0 (eeb90cda1 2024-09-04)
|
||||
|
||||
#### Java
|
||||
|
||||
- JDK バージョン: OpenJDK 23 64-Bit 2024-09-17
|
||||
- JRE バージョン: OpenJDK Runtime Environment (build 23+37)
|
||||
- ベンダー: Oracle
|
||||
|
||||
#### ゲーム
|
||||
|
||||
- Minecraft バージョン: 1.21.1
|
||||
- 描画距離: 10
|
||||
- シミュレーション距離: 10
|
||||
- オンラインモード: false
|
||||
- RCON: false
|
||||
|
||||
<sub><sup>非プレミアムアカウントでのテストを容易にするため、オンラインモードは無効にしました。</sup></sub>
|
||||
|
||||
> [!NOTE]
|
||||
> より正確な結果を得るため、すべてのテストは複数回実行されています。
|
||||
> すべてのプレイヤーはスポーン時に移動しませんでした。最初の 8 チャンクのみがロードされました。
|
||||
> すべてのサーバーは独自の地形生成を使用しました。事前にロードされたワールドはありません。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `CPU Max` は通常、最初のチャンクがロードされるため、プレイヤーが 1 人の時に高くなります。
|
||||
|
||||
## Pumpkin
|
||||
|
||||
ビルド: [8febc50](https://github.com/Snowiiii/Pumpkin/commit/8febc5035d5611558c13505b7724e6ca284e0ada)
|
||||
|
||||
コンパイル引数: `--release`
|
||||
|
||||
実行引数:
|
||||
|
||||
**ファイルサイズ:** <FmtNum :n=12.3 />MB
|
||||
|
||||
**起動時間:** <FmtNum :n=8 />ms
|
||||
|
||||
**シャットダウン時間:** <FmtNum :n=0 />ms
|
||||
|
||||
| プレイヤー数 | RAM | CPU アイドル | CPU 最大 |
|
||||
| ------- | --------------------- | ---------------- | ------------------ |
|
||||
| 0 | <FmtNum :n=392.2 />KB | <FmtNum :n=0 />% | <FmtNum :n=0 />% |
|
||||
| 1 | <FmtNum :n=24.9 />MB | <FmtNum :n=0 />% | <FmtNum :n=4 />% |
|
||||
| 2 | <FmtNum :n=25.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=0.6 />% |
|
||||
| 5 | <FmtNum :n=26 />MB | <FmtNum :n=0 />% | <FmtNum :n=1 />% |
|
||||
| 10 | <FmtNum :n=27.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=1.5 />% |
|
||||
|
||||
<sub><sup>Pumpkin はロード済みのチャンクをキャッシュしているので、プレイヤーデータ以外の追加 RAM 使用量がほぼなく、CPU 使用率も最小限に抑えられます。</sup></sub>
|
||||
|
||||
### コンパイル時間
|
||||
|
||||
ゼロからのコンパイル:
|
||||
|
||||
**Debug:** <FmtNum :n=10.35 />秒
|
||||
**Release:** <FmtNum :n=38.40 />秒
|
||||
|
||||
再コンパイル (pumpkin crate):
|
||||
|
||||
**Debug:** <FmtNum :n=1.82 />秒
|
||||
**Release:** <FmtNum :n=28.68 />秒
|
||||
|
||||
## バニラ
|
||||
|
||||
リリース: [1.21.1](https://piston-data.mojang.com/v1/objects/59353fb40c36d304f2035d51e7d6e6baa98dc05c/server.jar)
|
||||
|
||||
コンパイル引数:
|
||||
|
||||
実行引数: `nogui`
|
||||
|
||||
**ファイルサイズ:** <FmtNum :n=51.6 />MB
|
||||
|
||||
**起動時間:** <FmtNum :n=7 />秒
|
||||
|
||||
**シャットダウン時間:** <FmtNum :n=4 />秒
|
||||
|
||||
| プレイヤー数 | RAM | CPU アイドル | CPU 最大 |
|
||||
| ------- | --------------------- | ---------------------------------------- | ------------------ |
|
||||
| 0 | <FmtNum n="860" />MB | <FmtNum n="0.1" /> - <FmtNum n="0.3" />% | <FmtNum n="51" />% |
|
||||
| 1 | <FmtNum n="1.5" />GB | <FmtNum n="0.9" /> - <FmtNum n="1" />% | <FmtNum n="41" />% |
|
||||
| 2 | <FmtNum n="1.6" />GB | <FmtNum n="1" /> - <FmtNum n="1.1" />% | <FmtNum n="10" />% |
|
||||
| 5 | <FmtNum n="1.8" />GB | <FmtNum n="2" />% | <FmtNum n="20" />% |
|
||||
| 10 | <FmtNum n="2.2" />GB | <FmtNum n="4" />% | <FmtNum n="24" />% |
|
||||
|
||||
## Paper
|
||||
|
||||
ビルド: [122](https://api.papermc.io/v2/projects/paper/versions/1.21.1/builds/122/downloads/paper-1.21.1-122.jar)
|
||||
|
||||
コンパイル引数:
|
||||
|
||||
実行引数: `nogui`
|
||||
|
||||
**ファイルサイズ:** <FmtNum :n=49.4 />MB
|
||||
|
||||
**起動時間:** <FmtNum :n=7 />秒
|
||||
|
||||
**シャットダウン時間:** <FmtNum :n=3 />秒
|
||||
|
||||
| プレイヤー数 | RAM | CPU アイドル | CPU 最大 |
|
||||
| ------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.1 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=36 />% |
|
||||
| 1 | <FmtNum :n=1.7 />GB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=47 />% |
|
||||
| 2 | <FmtNum :n=1.8 />GB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=10 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.5 />% | <FmtNum :n=15 />% |
|
||||
| 10 | <FmtNum :n=2 />GB | <FmtNum :n=3 />% | <FmtNum :n=20 />% |
|
||||
|
||||
## Purpur
|
||||
|
||||
ビルド: [2324](https://api.purpurmc.org/v2/purpur/1.21.1/2324/download)
|
||||
|
||||
コンパイル引数:
|
||||
|
||||
実行引数: `nogui`
|
||||
|
||||
**ファイルサイズ:** <FmtNum :n=53.1 />MB
|
||||
|
||||
**起動時間:** <FmtNum :n=8 />秒
|
||||
|
||||
**シャットダウン時間:** <FmtNum :n=4 />秒
|
||||
|
||||
| プレイヤー数 | RAM | CPU アイドル | CPU 最大 |
|
||||
| ------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.4 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=25 />% |
|
||||
| 1 | <FmtNum :n=1.6 />GB | <FmtNum :n=0.7 /> - <FmtNum :n=1.0 />% | <FmtNum :n=35 />% |
|
||||
| 2 | <FmtNum :n=1.7 />GB | <FmtNum :n=1.1 /> - <FmtNum :n=1.3 />% | <FmtNum :n=9 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.6 />% | <FmtNum :n=20 />% |
|
||||
| 10 | <FmtNum :n=2.2 />GB | <FmtNum :n=2 /> - <FmtNum :n=2.5 />% | <FmtNum :n=26 />% |
|
||||
|
||||
## Minestom
|
||||
|
||||
コミット: [0ca1dda2fe](https://github.com/Minestom/Minestom/commit/0ca1dda2fe11390a1b89a228bbe7bf78fefc73e1)
|
||||
|
||||
コンパイル引数:
|
||||
|
||||
実行引数:
|
||||
|
||||
**言語:** ベンチマークは Kotlin 2.0.0 で実行 (Minestom 自体は Java 製)
|
||||
|
||||
**ファイルサイズ:** <FmtNum :n=2.8 />MB (ライブラリ)
|
||||
|
||||
**起動時間:** <FmtNum :n=310 />ms
|
||||
|
||||
**シャットダウン時間:** <FmtNum :n=0 />ms
|
||||
|
||||
<sub>[使用したサンプルコード](https://minestom.net/docs/setup/your-first-server)</sub>
|
||||
|
||||
| プレイヤー数 | RAM | CPU アイドル | CPU 最大 |
|
||||
| ------- | ------------------- | -------------------------------------- | ---------------- |
|
||||
| 0 | <FmtNum :n=228 />MB | <FmtNum :n=0.1 /> - <FmtNum :n=0.3 />% | <FmtNum :n=1 />% |
|
||||
| 1 | <FmtNum :n=365 />MB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=5 />% |
|
||||
| 2 | <FmtNum :n=371 />MB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=4 />% |
|
||||
| 5 | <FmtNum :n=390 />MB | <FmtNum :n=1.0 />% | <FmtNum :n=6 />% |
|
||||
| 10 | <FmtNum :n=421 />MB | <FmtNum :n=3 />% | <FmtNum :n=9 />% |
|
||||
|
||||
ベンチマーク実行日時: <FmtDateTime :d="new Date('2024-10-15T16:34Z')" />
|
||||
@ -1,189 +0,0 @@
|
||||
# Benchmarktests
|
||||
|
||||
Hier worden veelvoorkomende Minecraft-servers vergeleken met Pumpkin.
|
||||
|
||||
> [!CAUTION]
|
||||
> **Deze vergelijking is oneerlijk.** Pumpkin heeft momenteel veel minder functies dan andere servers, wat zou kunnen suggereren dat het minder middelen gebruikt.
|
||||
> Het is ook belangrijk om te overwegen dat andere servers jaren hebben gehad voor optimalisatie.
|
||||
> Vanilla forks, die de volledige vanilla-logica niet hoeven te herschrijven, kunnen zich uitsluitend richten op optimalisaties.
|
||||
|
||||

|
||||
|
||||
## Specificaties
|
||||
|
||||
#### Technisch
|
||||
|
||||
**Software**
|
||||
|
||||
- Distributie: Manjaro Linux
|
||||
- Architectuur: x86_64 (64-bit)
|
||||
- Kernelversie: 6.11.3-arch1-1
|
||||
|
||||
**Hardware**
|
||||
|
||||
- Moederbord: MAG B650 TOMAHAWK WIFI
|
||||
- CPU: AMD Ryzen 7600X 6-Core
|
||||
- RAM: Corsair 2x16GB DDR5 6000Mhz
|
||||
- Opslag: Samsung 990 PRO 1TB PCIe 4.0 M.2 SSD
|
||||
- Koeling: be quiet Dark Rock Elite
|
||||
|
||||
**Rust**
|
||||
|
||||
- Toolchain: stable-x86_64-unknown-linux-gnu (1.81.0)
|
||||
- Rust Compiler: rustc 1.81.0 (eeb90cda1 2024-09-04)
|
||||
|
||||
**Java**
|
||||
|
||||
- JDK Versie: OpenJDK 23 64-Bit 2024-09-17
|
||||
- JRE Versie: OpenJDK Runtime Environment (build 23+37)
|
||||
- Leverancier: Oracle
|
||||
|
||||
#### Spel
|
||||
|
||||
- Minecraft versie: 1.21.1
|
||||
- Zichtafstand: 10
|
||||
- Gesimuleerde afstand: 10
|
||||
- Online modus: false
|
||||
- Rcon: false
|
||||
|
||||
<sub><sup>online modus was uitgeschakeld voor eenvoudiger testen met niet-premium accounts</sup></sub>
|
||||
|
||||
> [!NOTE]
|
||||
> Alle tests zijn meerdere keren uitgevoerd om consistente resultaten te garanderen.
|
||||
> Alle spelers bewogen niet bij het spawnen, alleen de eerste 8 chunks werden geladen.
|
||||
> Alle servers gebruikten hun eigen terrein generatie, geen wereld was vooraf geladen.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `CPU Max` is meestal hoger met één speler omdat de eerste chunks worden geladen.
|
||||
|
||||
## Pumpkin
|
||||
|
||||
Build: [8febc50](https://github.com/Snowiiii/Pumpkin/commit/8febc5035d5611558c13505b7724e6ca284e0ada)
|
||||
|
||||
Compileer args: `--release`
|
||||
|
||||
Run args:
|
||||
|
||||
**Bestandsgrootte:** <FmtNum :n=12.3 />MB
|
||||
|
||||
**Opstarttijd:** <FmtNum :n=8 />ms
|
||||
|
||||
**Afsluitingstijd:** <FmtNum :n=0 />ms
|
||||
|
||||
| Spelers | RAM | CPU Idle | CPU Max |
|
||||
| ------- | --------------------- | ---------------- | ------------------ |
|
||||
| 0 | <FmtNum :n=392.2 />KB | <FmtNum :n=0 />% | <FmtNum :n=0 />% |
|
||||
| 1 | <FmtNum :n=24.9 />MB | <FmtNum :n=0 />% | <FmtNum :n=4 />% |
|
||||
| 2 | <FmtNum :n=25.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=0.6 />% |
|
||||
| 5 | <FmtNum :n=26 />MB | <FmtNum :n=0 />% | <FmtNum :n=1 />% |
|
||||
| 10 | <FmtNum :n=27.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=1.5 />% |
|
||||
|
||||
<sub><sup>Pumpkin cachet al geladen chunks, wat resulteert in geen extra RAM-gebruik behalve spelergegevens en minimaal CPU-gebruik.</sup></sub>
|
||||
|
||||
#### Compileertijd
|
||||
Compileren vanaf niets:
|
||||
|
||||
**Debug:** <FmtNum :n=10.35 />sec
|
||||
**Release:** <FmtNum :n=38.40 />sec
|
||||
|
||||
Hercompilatie (pumpkin crate):
|
||||
|
||||
**Debug:** <FmtNum :n=1.82 />sec
|
||||
**Release:** <FmtNum :n=28.68 />sec
|
||||
|
||||
## Vanilla
|
||||
|
||||
Release: [1.21.1](https://piston-data.mojang.com/v1/objects/59353fb40c36d304f2035d51e7d6e6baa98dc05c/server.jar)
|
||||
|
||||
Compileer args:
|
||||
|
||||
Run args: `nogui`
|
||||
|
||||
**Bestandsgrootte:** <FmtNum :n=51.6 />MB
|
||||
|
||||
**Opstarttijd:** <FmtNum :n=7 />sec
|
||||
|
||||
**Afsluitingstijd:** <FmtNum :n=4 />sec
|
||||
|
||||
| Spelers | RAM | CPU idle | CPU Max |
|
||||
| ------- | --------------------- | ---------------------------------------- | ------------------ |
|
||||
| 0 | <FmtNum n="860" />MB | <FmtNum n="0.1" /> - <FmtNum n="0.3" />% | <FmtNum n="51" />% |
|
||||
| 1 | <FmtNum n="1.5" />GB | <FmtNum n="0.9" /> - <FmtNum n="1" />% | <FmtNum n="41" />% |
|
||||
| 2 | <FmtNum n="1.6" />GB | <FmtNum n="1" /> - <FmtNum n="1.1" />% | <FmtNum n="10" />% |
|
||||
| 5 | <FmtNum n="1.8" />GB | <FmtNum n="2" />% | <FmtNum n="20" />% |
|
||||
| 10 | <FmtNum n="2.2" />GB | <FmtNum n="4" />% | <FmtNum n="24" />% |
|
||||
|
||||
## Paper
|
||||
|
||||
Build: [122](https://api.papermc.io/v2/projects/paper/versions/1.21.1/builds/122/downloads/paper-1.21.1-122.jar)
|
||||
|
||||
Compileer args:
|
||||
|
||||
Run args: `nogui`
|
||||
|
||||
**Bestandsgrootte:** <FmtNum :n=49.4 />MB
|
||||
|
||||
**Opstarttijd:** <FmtNum :n=7 />sec
|
||||
|
||||
**Afsluitingstijd:** <FmtNum :n=3 />sec
|
||||
|
||||
| Spelers | RAM | CPU idle | CPU Max |
|
||||
| ------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.1 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=36 />% |
|
||||
| 1 | <FmtNum :n=1.7 />GB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=47 />% |
|
||||
| 2 | <FmtNum :n=1.8 />GB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=10 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.5 />% | <FmtNum :n=15 />% |
|
||||
| 10 | <FmtNum :n=2 />GB | <FmtNum :n=3 />% | <FmtNum :n=20 />% |
|
||||
|
||||
|
||||
## Purpur
|
||||
|
||||
Build: [2324](https://api.purpurmc.org/v2/purpur/1.21.1/2324/download)
|
||||
|
||||
Compileer args:
|
||||
|
||||
Run args: `nogui`
|
||||
|
||||
**Bestandsgrootte:** <FmtNum :n=53.1 />MB
|
||||
|
||||
**Opstarttijd:** <FmtNum :n=8 />sec
|
||||
|
||||
**Afsluitingstijd:** <FmtNum :n=4 />sec
|
||||
|
||||
| Spelers | RAM | CPU idle | CPU Max |
|
||||
| ------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.4 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=25 />% |
|
||||
| 1 | <FmtNum :n=1.6 />GB | <FmtNum :n=0.7 /> - <FmtNum :n=1.0 />% | <FmtNum :n=35 />% |
|
||||
| 2 | <FmtNum :n=1.7 />GB | <FmtNum :n=1.1 /> - <FmtNum :n=1.3 />% | <FmtNum :n=9 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.6 />% | <FmtNum :n=20 />% |
|
||||
| 10 | <FmtNum :n=2.2 />GB | <FmtNum :n=2 /> - <FmtNum :n=2.5 />% | <FmtNum :n=26 />% |
|
||||
|
||||
|
||||
## Minestom
|
||||
|
||||
Commit: [0ca1dda2fe](https://github.com/Minestom/Minestom/commit/0ca1dda2fe11390a1b89a228bbe7bf78fefc73e1)
|
||||
|
||||
Compileer args:
|
||||
|
||||
Run args:
|
||||
|
||||
**Taal:** Benchmarks uitgevoerd met Kotlin 2.0.0 (Minestom zelf is gemaakt met Java)
|
||||
|
||||
**Bestandsgrootte:** <FmtNum :n=2.8 />MB (Library)
|
||||
|
||||
**Opstarttijd:** <FmtNum :n=310 />ms
|
||||
|
||||
**Afsluitingstijd:** <FmtNum :n=0 />ms
|
||||
|
||||
<sub>[Gebruikte voorbeeldcode](https://minestom.net/docs/setup/your-first-server)</sub>
|
||||
|
||||
| Spelers | RAM | CPU idle | CPU Max |
|
||||
| ------- | ------------------- | -------------------------------------- | ---------------- |
|
||||
| 0 | <FmtNum :n=228 />MB | <FmtNum :n=0.1 /> - <FmtNum :n=0.3 />% | <FmtNum :n=1 />% |
|
||||
| 1 | <FmtNum :n=365 />MB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=5 />% |
|
||||
| 2 | <FmtNum :n=371 />MB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=4 />% |
|
||||
| 5 | <FmtNum :n=390 />MB | <FmtNum :n=1.0 />% | <FmtNum :n=6 />% |
|
||||
| 10 | <FmtNum :n=421 />MB | <FmtNum :n=3 />% | <FmtNum :n=9 />% |
|
||||
|
||||
|
||||
Benchmark uitgevoerd op <FmtDateTime :d="new Date('2024-10-15T16:34Z')" />
|
||||
@ -1,187 +0,0 @@
|
||||
# Benchmarks
|
||||
|
||||
Aqui, softwares de servidores Minecraft comuns são comparados com o Pumpkin.
|
||||
|
||||
> [!CAUTION] CUIDADO
|
||||
> **Esta comparação é injusta.** Pumpkin atualmente tem muito menos recursos que outros servidores, o que pode sugerir que ele usa menos recursos.
|
||||
> Também é importante considerar que outros servidores tiveram anos para otimizar.
|
||||
> Forks do Vanilla, que não precisam reescrever toda a lógica do Vanilla, podem se concentrar exclusivamente em otimizações.
|
||||
|
||||

|
||||
|
||||
## Especificações
|
||||
|
||||
#### Técnicas
|
||||
|
||||
**Software**
|
||||
|
||||
- Distribuição: Manjaro Linux
|
||||
- Arquitetura: x86_64 (64-bit)
|
||||
- Versão do Kernel: 6.11.3-arch1-1
|
||||
|
||||
**Hardware**
|
||||
|
||||
- Placa-mãe: MAG B650 TOMAHAWK WIFI
|
||||
- CPU: AMD Ryzen 7600X 6-Core
|
||||
- RAM: Corsair 2x16GB DDR5 6000Mhz
|
||||
- Armazenamento: Samsung 990 PRO 1TB PCIe 4.0 M.2 SSD
|
||||
- Refrigeração: be quiet Dark Rock Elite
|
||||
|
||||
**Rust**
|
||||
|
||||
- Toolchain: stable-x86_64-unknown-linux-gnu (1.81.0)
|
||||
- Compilador Rust: rustc 1.81.0 (eeb90cda1 2024-09-04)
|
||||
|
||||
**Java**
|
||||
|
||||
- Versão do JDK: OpenJDK 23 64-Bit 2024-09-17
|
||||
- Versão do JRE: OpenJDK Runtime Environment (build 23+37)
|
||||
- Fornecedor: Oracle
|
||||
|
||||
#### Jogo
|
||||
|
||||
- Versão do Minecraft: 1.21.1
|
||||
- Distância de visão: 10
|
||||
- Distância simulada: 10
|
||||
- Modo online: falso
|
||||
- Rcon: falso
|
||||
|
||||
<sub><sup>O modo online foi desativado para facilitar os testes com contas não premium.</sup></sub>
|
||||
|
||||
> [!NOTE] NOTA
|
||||
> Todos os testes foram realizados várias vezes para obter resultados mais precisos.
|
||||
> Todos os jogadores não se moveram ao nascer. Apenas os 8 primeiros chunks foram carregados.
|
||||
> Todos os servidores usaram sua própria geração de terreno. Nenhum mundo foi pré-carregado.
|
||||
|
||||
> [!IMPORTANT] IMPORTANTE
|
||||
> `CPU Max` geralmente é maior com um jogador, pois os chunks iniciais estão sendo carregados.
|
||||
|
||||
## Pumpkin
|
||||
|
||||
Build: [8febc50](https://github.com/Snowiiii/Pumpkin/commit/8febc5035d5611558c13505b7724e6ca284e0ada)
|
||||
|
||||
Argumentos de compilação: `--release`
|
||||
|
||||
Argumentos de execução:
|
||||
|
||||
**Tamanho do arquivo:** <FmtNum :n=12.3 />MB
|
||||
|
||||
**Tempo de inicialização:** <FmtNum :n=8 />ms
|
||||
|
||||
**Tempo de desligamento:** <FmtNum :n=0 />ms
|
||||
|
||||
| Jogadores | RAM | CPU Ocioso | CPU Máx |
|
||||
| --------- | --------------------- | ---------------- | ------------------ |
|
||||
| 0 | <FmtNum :n=392.2 />KB | <FmtNum :n=0 />% | <FmtNum :n=0 />% |
|
||||
| 1 | <FmtNum :n=24.9 />MB | <FmtNum :n=0 />% | <FmtNum :n=4 />% |
|
||||
| 2 | <FmtNum :n=25.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=0.6 />% |
|
||||
| 5 | <FmtNum :n=26 />MB | <FmtNum :n=0 />% | <FmtNum :n=1 />% |
|
||||
| 10 | <FmtNum :n=27.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=1.5 />% |
|
||||
|
||||
<sub><sup>Pumpkin faz cache dos chunks já carregados, resultando em nenhum uso extra de RAM além dos dados do jogador e uso mínimo de CPU.</sup></sub>
|
||||
|
||||
#### Tempo de compilação
|
||||
|
||||
Compilando do zero:
|
||||
|
||||
**Debug:** <FmtNum :n=10.35 />sec
|
||||
**Release:** <FmtNum :n=38.40 />sec
|
||||
|
||||
Recompilação (pumpkin crate):
|
||||
|
||||
**Debug:** <FmtNum :n=1.82 />sec
|
||||
**Release:** <FmtNum :n=28.68 />sec
|
||||
|
||||
## Vanilla
|
||||
|
||||
Release: [1.21.1](https://piston-data.mojang.com/v1/objects/59353fb40c36d304f2035d51e7d6e6baa98dc05c/server.jar)
|
||||
|
||||
Argumentos de compilação:
|
||||
|
||||
Argumentos de execução: `nogui`
|
||||
|
||||
**Tamanho do arquivo:** <FmtNum :n=51.6 />MB
|
||||
|
||||
**Tempo de inicialização:** <FmtNum :n=7 />sec
|
||||
|
||||
**Tempo de desligamento:** <FmtNum :n=4 />sec
|
||||
|
||||
| Jogadores | RAM | CPU ocioso | CPU Máx |
|
||||
| --------- | -------------------- | ---------------------------------------- | ------------------ |
|
||||
| 0 | <FmtNum n="860" />MB | <FmtNum n="0.1" /> - <FmtNum n="0.3" />% | <FmtNum n="51" />% |
|
||||
| 1 | <FmtNum n="1.5" />GB | <FmtNum n="0.9" /> - <FmtNum n="1" />% | <FmtNum n="41" />% |
|
||||
| 2 | <FmtNum n="1.6" />GB | <FmtNum n="1" /> - <FmtNum n="1.1" />% | <FmtNum n="10" />% |
|
||||
| 5 | <FmtNum n="1.8" />GB | <FmtNum n="2" />% | <FmtNum n="20" />% |
|
||||
| 10 | <FmtNum n="2.2" />GB | <FmtNum n="4" />% | <FmtNum n="24" />% |
|
||||
|
||||
## Paper
|
||||
|
||||
Build: [122](https://api.papermc.io/v2/projects/paper/versions/1.21.1/builds/122/downloads/paper-1.21.1-122.jar)
|
||||
|
||||
Argumentos de compilação:
|
||||
|
||||
Argumentos de execução: `nogui`
|
||||
|
||||
**Tamanho do arquivo:** <FmtNum :n=49.4 />MB
|
||||
|
||||
**Tempo de inicialização:** <FmtNum :n=7 />sec
|
||||
|
||||
**Tempo de desligamento:** <FmtNum :n=3 />sec
|
||||
|
||||
| Jogadores | RAM | CPU ocioso | CPU Máx |
|
||||
| --------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.1 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=36 />% |
|
||||
| 1 | <FmtNum :n=1.7 />GB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=47 />% |
|
||||
| 2 | <FmtNum :n=1.8 />GB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=10 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.5 />% | <FmtNum :n=15 />% |
|
||||
| 10 | <FmtNum :n=2 />GB | <FmtNum :n=3 />% | <FmtNum :n=20 />% |
|
||||
|
||||
## Purpur
|
||||
|
||||
Build: [2324](https://api.purpurmc.org/v2/purpur/1.21.1/2324/download)
|
||||
|
||||
Argumentos de compilação:
|
||||
|
||||
Argumentos de execução: `nogui`
|
||||
|
||||
**Tamanho do arquivo:** <FmtNum :n=53.1 />MB
|
||||
|
||||
**Tempo de inicialização:** <FmtNum :n=8 />sec
|
||||
|
||||
**Tempo de desligamento:** <FmtNum :n=4 />sec
|
||||
|
||||
| Jogadores | RAM | CPU ocioso | CPU Máx |
|
||||
| --------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.4 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=25 />% |
|
||||
| 1 | <FmtNum :n=1.6 />GB | <FmtNum :n=0.7 /> - <FmtNum :n=1.0 />% | <FmtNum :n=35 />% |
|
||||
| 2 | <FmtNum :n=1.7 />GB | <FmtNum :n=1.1 /> - <FmtNum :n=1.3 />% | <FmtNum :n=9 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.6 />% | <FmtNum :n=20 />% |
|
||||
| 10 | <FmtNum :n=2.2 />GB | <FmtNum :n=2 /> - <FmtNum :n=2.5 />% | <FmtNum :n=26 />% |
|
||||
|
||||
## Minestom
|
||||
|
||||
Commit: [0ca1dda2fe](https://github.com/Minestom/Minestom/commit/0ca1dda2fe11390a1b89a228bbe7bf78fefc73e1)
|
||||
|
||||
Argumentos de compilação:
|
||||
|
||||
Argumentos de execução:
|
||||
|
||||
**Idioma:** Benchmarks executados com Kotlin 2.0.0 (Minestom é feito com Java)
|
||||
|
||||
**Tamanho do arquivo:** <FmtNum :n=2.8 />MB (Biblioteca)
|
||||
|
||||
**Tempo de inicialização:** <FmtNum :n=310 />ms
|
||||
|
||||
**Tempo de desligamento:** <FmtNum :n=0 />ms
|
||||
|
||||
<sub>[Usado o código do exemplo de](https://minestom.net/docs/setup/your-first-server)</sub>
|
||||
|
||||
| Jogadores | RAM | CPU ocioso | CPU Máx |
|
||||
| --------- | ------------------- | -------------------------------------- | ---------------- |
|
||||
| 0 | <FmtNum :n=228 />MB | <FmtNum :n=0.1 /> - <FmtNum :n=0.3 />% | <FmtNum :n=1 />% |
|
||||
| 1 | <FmtNum :n=365 />MB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=5 />% |
|
||||
| 2 | <FmtNum :n=371 />MB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=4 />% |
|
||||
| 5 | <FmtNum :n=390 />MB | <FmtNum :n=1.0 />% | <FmtNum :n=6 />% |
|
||||
| 10 | <FmtNum :n=421 />MB | <FmtNum :n=3 />% | <FmtNum :n=9 />% |
|
||||
|
||||
Benchmarks realizados em <FmtDateTime :d="new Date('2024-10-15T16:34Z')" />
|
||||
@ -1,187 +0,0 @@
|
||||
# Kıyaslamalar
|
||||
|
||||
Burada, yaygın Minecraft sunucu yazılımları Pumpkin ile karşılaştırılıyor.
|
||||
|
||||
> [!CAUTION]
|
||||
> **Bu karşılaştırma adil değil.** Pumpkin şu anda diğer sunuculara göre çok daha az özelliğe sahip, bu da daha az kaynak kullandığı izlenimi verebilir.
|
||||
> Ayrıca diğer sunucuların yıllardır optimizasyon yaptığını da göz önünde bulundurmak gerekir.
|
||||
> Tüm Vanilla mantığını yeniden yazmak zorunda olmayan Vanilla fork'ları, yalnızca optimizasyonlara odaklanabilir.
|
||||
|
||||

|
||||
|
||||
## Özellikler
|
||||
|
||||
### Teknik
|
||||
|
||||
#### Yazılım
|
||||
|
||||
- Dağıtım: Manjaro Linux
|
||||
- Mimari: x86_64 (64-bit)
|
||||
- Çekirdek Sürümü: 6.11.3-arch1-1
|
||||
|
||||
#### Donanım
|
||||
|
||||
- Anakart: MAG B650 TOMAHAWK WIFI
|
||||
- CPU: AMD Ryzen 7600X 6-Core
|
||||
- RAM: Corsair 2x16GB DDR5 6000Mhz
|
||||
- Depolama: Samsung 990 PRO 1TB PCIe 4.0 M.2 SSD
|
||||
- Soğutma: be quiet Dark Rock Elite
|
||||
|
||||
#### Rust
|
||||
|
||||
- Araç zinciri: stable-x86_64-unknown-linux-gnu (1.81.0)
|
||||
- Rust Derleyicisi: rustc 1.81.0 (eeb90cda1 2024-09-04)
|
||||
|
||||
#### Java
|
||||
|
||||
- JDK Sürümü: OpenJDK 23 64-Bit 2024-09-17
|
||||
- JRE Sürümü: OpenJDK Runtime Environment (build 23+37)
|
||||
- Sağlayıcı: Oracle
|
||||
|
||||
#### Oyun
|
||||
|
||||
- Minecraft sürümü: 1.21.1
|
||||
- Görüş mesafesi: 10
|
||||
- Simülasyon mesafesi: 10
|
||||
- Çevrimiçi mod: false
|
||||
- RCON: false
|
||||
|
||||
<sub><sup>Premium olmayan hesaplarla daha kolay test için çevrimiçi mod devre dışı bırakıldı.</sup></sub>
|
||||
|
||||
> [!NOTE]
|
||||
> Tüm testler daha doğru sonuçlar için birden fazla kez çalıştırıldı.
|
||||
> Oyuncular doğduklarında hareket etmedi. Yalnızca ilk 8 chunk yüklendi.
|
||||
> Tüm sunucular kendi arazi üretimini kullandı. Önceden dünya yüklenmedi.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `CPU Max`, başlangıçtaki chunk'lar yüklendiği için genellikle tek oyuncuda daha yüksektir.
|
||||
|
||||
## Pumpkin
|
||||
|
||||
Derleme: [8febc50](https://github.com/Snowiiii/Pumpkin/commit/8febc5035d5611558c13505b7724e6ca284e0ada)
|
||||
|
||||
Derleme argümanları: `--release`
|
||||
|
||||
Çalıştırma argümanları:
|
||||
|
||||
**Dosya Boyutu:** <FmtNum :n=12.3 />MB
|
||||
|
||||
**Başlatma süresi:** <FmtNum :n=8 />ms
|
||||
|
||||
**Kapanma süresi:** <FmtNum :n=0 />ms
|
||||
|
||||
| Oyuncular | RAM | CPU Boşta | CPU Maksimum |
|
||||
| ------- | --------------------- | ---------------- | ------------------ |
|
||||
| 0 | <FmtNum :n=392.2 />KB | <FmtNum :n=0 />% | <FmtNum :n=0 />% |
|
||||
| 1 | <FmtNum :n=24.9 />MB | <FmtNum :n=0 />% | <FmtNum :n=4 />% |
|
||||
| 2 | <FmtNum :n=25.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=0.6 />% |
|
||||
| 5 | <FmtNum :n=26 />MB | <FmtNum :n=0 />% | <FmtNum :n=1 />% |
|
||||
| 10 | <FmtNum :n=27.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=1.5 />% |
|
||||
|
||||
<sub><sup>Pumpkin, zaten yüklenmiş chunk'ları önbelleğe alır; bu da oyuncu verileri dışında ek RAM kullanımı olmaması ve minimum CPU kullanımı anlamına gelir.</sup></sub>
|
||||
|
||||
### Derleme süresi
|
||||
|
||||
Sıfırdan derleme:
|
||||
|
||||
**Debug:** <FmtNum :n=10.35 />sec
|
||||
**Release:** <FmtNum :n=38.40 />sec
|
||||
|
||||
Yeniden derleme (pumpkin crate):
|
||||
|
||||
**Debug:** <FmtNum :n=1.82 />sec
|
||||
**Release:** <FmtNum :n=28.68 />sec
|
||||
|
||||
## Vanilla
|
||||
|
||||
Sürüm: [1.21.1](https://piston-data.mojang.com/v1/objects/59353fb40c36d304f2035d51e7d6e6baa98dc05c/server.jar)
|
||||
|
||||
Derleme argümanları:
|
||||
|
||||
Çalıştırma argümanları: `nogui`
|
||||
|
||||
**Dosya Boyutu:** <FmtNum :n=51.6 />MB
|
||||
|
||||
**Başlatma süresi:** <FmtNum :n=7 />sec
|
||||
|
||||
**Kapanma süresi:** <FmtNum :n=4 />sec
|
||||
|
||||
| Oyuncular | RAM | CPU Boşta | CPU Maksimum |
|
||||
| ------- | --------------------- | ---------------------------------------- | ------------------ |
|
||||
| 0 | <FmtNum n="860" />MB | <FmtNum n="0.1" /> - <FmtNum n="0.3" />% | <FmtNum n="51" />% |
|
||||
| 1 | <FmtNum n="1.5" />GB | <FmtNum n="0.9" /> - <FmtNum n="1" />% | <FmtNum n="41" />% |
|
||||
| 2 | <FmtNum n="1.6" />GB | <FmtNum n="1" /> - <FmtNum n="1.1" />% | <FmtNum n="10" />% |
|
||||
| 5 | <FmtNum n="1.8" />GB | <FmtNum n="2" />% | <FmtNum n="20" />% |
|
||||
| 10 | <FmtNum n="2.2" />GB | <FmtNum n="4" />% | <FmtNum n="24" />% |
|
||||
|
||||
## Paper
|
||||
|
||||
Derleme: [122](https://api.papermc.io/v2/projects/paper/versions/1.21.1/builds/122/downloads/paper-1.21.1-122.jar)
|
||||
|
||||
Derleme argümanları:
|
||||
|
||||
Çalıştırma argümanları: `nogui`
|
||||
|
||||
**Dosya Boyutu:** <FmtNum :n=49.4 />MB
|
||||
|
||||
**Başlatma süresi:** <FmtNum :n=7 />sec
|
||||
|
||||
**Kapanma süresi:** <FmtNum :n=3 />sec
|
||||
|
||||
| Oyuncular | RAM | CPU Boşta | CPU Maksimum |
|
||||
| ------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.1 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=36 />% |
|
||||
| 1 | <FmtNum :n=1.7 />GB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=47 />% |
|
||||
| 2 | <FmtNum :n=1.8 />GB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=10 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.5 />% | <FmtNum :n=15 />% |
|
||||
| 10 | <FmtNum :n=2 />GB | <FmtNum :n=3 />% | <FmtNum :n=20 />% |
|
||||
|
||||
## Purpur
|
||||
|
||||
Derleme: [2324](https://api.purpurmc.org/v2/purpur/1.21.1/2324/download)
|
||||
|
||||
Derleme argümanları:
|
||||
|
||||
Çalıştırma argümanları: `nogui`
|
||||
|
||||
**Dosya Boyutu:** <FmtNum :n=53.1 />MB
|
||||
|
||||
**Başlatma süresi:** <FmtNum :n=8 />sec
|
||||
|
||||
**Kapanma süresi:** <FmtNum :n=4 />sec
|
||||
|
||||
| Oyuncular | RAM | CPU Boşta | CPU Maksimum |
|
||||
| ------- | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.4 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=25 />% |
|
||||
| 1 | <FmtNum :n=1.6 />GB | <FmtNum :n=0.7 /> - <FmtNum :n=1.0 />% | <FmtNum :n=35 />% |
|
||||
| 2 | <FmtNum :n=1.7 />GB | <FmtNum :n=1.1 /> - <FmtNum :n=1.3 />% | <FmtNum :n=9 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.6 />% | <FmtNum :n=20 />% |
|
||||
| 10 | <FmtNum :n=2.2 />GB | <FmtNum :n=2 /> - <FmtNum :n=2.5 />% | <FmtNum :n=26 />% |
|
||||
|
||||
## Minestom
|
||||
|
||||
Commit: [0ca1dda2fe](https://github.com/Minestom/Minestom/commit/0ca1dda2fe11390a1b89a228bbe7bf78fefc73e1)
|
||||
|
||||
Derleme argümanları:
|
||||
|
||||
Çalıştırma argümanları:
|
||||
|
||||
**Dil:** Kıyaslamalar Kotlin 2.0.0 ile çalıştırıldı. (Minestom'un kendisi Java ile yazılmıştır)
|
||||
|
||||
**Dosya Boyutu:** <FmtNum :n=2.8 />MB (Kütüphane)
|
||||
|
||||
**Başlatma süresi:** <FmtNum :n=310 />ms
|
||||
|
||||
**Kapanma süresi:** <FmtNum :n=0 />ms
|
||||
|
||||
<sub>[Örnek kod şuradan kullanıldı](https://minestom.net/docs/setup/your-first-server)</sub>
|
||||
|
||||
| Oyuncular | RAM | CPU Boşta | CPU Maksimum |
|
||||
| ------- | ------------------- | -------------------------------------- | ---------------- |
|
||||
| 0 | <FmtNum :n=228 />MB | <FmtNum :n=0.1 /> - <FmtNum :n=0.3 />% | <FmtNum :n=1 />% |
|
||||
| 1 | <FmtNum :n=365 />MB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=5 />% |
|
||||
| 2 | <FmtNum :n=371 />MB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=4 />% |
|
||||
| 5 | <FmtNum :n=390 />MB | <FmtNum :n=1.0 />% | <FmtNum :n=6 />% |
|
||||
| 10 | <FmtNum :n=421 />MB | <FmtNum :n=3 />% | <FmtNum :n=9 />% |
|
||||
|
||||
Kıyaslama tarihi: <FmtDateTime :d="new Date('2024-10-15T16:34Z')" />
|
||||
@ -1,187 +0,0 @@
|
||||
# 性能基准测试
|
||||
|
||||
这里将 Pumpkin 与常见的 Minecraft 服务器软件进行比较。
|
||||
|
||||
> [!CAUTION]警告
|
||||
> **这个比较并不完全公平** Pumpkin 目前的功能远少于其他服务器,这可能意味着它使用的资源更少。
|
||||
> 同时也要考虑到其他服务器已经有多年的优化经验。
|
||||
> 原版分支不需要重写整个原版逻辑,可以专注于优化。
|
||||
|
||||

|
||||
|
||||
## 规格
|
||||
|
||||
### 技术规格
|
||||
|
||||
#### 软件
|
||||
|
||||
- 发行版:Manjaro Linux
|
||||
- 架构:x86_64 (64位)
|
||||
- 内核版本:6.11.3-arch1-1
|
||||
|
||||
#### 硬件
|
||||
|
||||
- 主板:MAG B650 TOMAHAWK WIFI
|
||||
- CPU:AMD Ryzen 7600X 6核
|
||||
- 内存:Corsair 2x16GB DDR5 6000Mhz
|
||||
- 存储:Samsung 990 PRO 1TB PCIe 4.0 M.2 SSD
|
||||
- 散热:be quiet Dark Rock Elite
|
||||
|
||||
#### Rust
|
||||
|
||||
- 工具链:stable-x86_64-unknown-linux-gnu (1.81.0)
|
||||
- Rust 编译器:rustc 1.81.0 (eeb90cda1 2024-09-04)
|
||||
|
||||
#### Java
|
||||
|
||||
- JDK 版本:OpenJDK 23 64位 2024-09-17
|
||||
- JRE 版本:OpenJDK Runtime Environment (build 23+37)
|
||||
- 供应商:Oracle
|
||||
|
||||
#### 游戏设置
|
||||
|
||||
- 测试版本:1.21.1
|
||||
- 基础视距:10
|
||||
- 模拟距离:10
|
||||
- 在线模式:false
|
||||
- RCON:false
|
||||
|
||||
<sub><sup>为了使用非正版账号进行更容易的测试,禁用了在线模式。</sup></sub>
|
||||
|
||||
> [!NOTE]注意
|
||||
> 所有测试都进行了多次以获得更准确的结果。
|
||||
> 所有玩家在生成时都没有移动。只加载了初始的 8 个区块。
|
||||
> 所有服务器都使用了各自的地形生成。没有预加载世界。
|
||||
|
||||
> [!IMPORTANT]重要
|
||||
> 单个玩家时 `CPU 最大值` 通常更高,因为初始区块正在被加载。
|
||||
|
||||
## Pumpkin
|
||||
|
||||
构建版本:[8febc50](https://github.com/Snowiiii/Pumpkin/commit/8febc5035d5611558c13505b7724e6ca284e0ada)
|
||||
|
||||
编译参数:`--release`
|
||||
|
||||
运行参数:
|
||||
|
||||
**文件大小:** <FmtNum :n=12.3 />MB
|
||||
|
||||
**启动时间:** <FmtNum :n=8 />ms
|
||||
|
||||
**关闭时间:** <FmtNum :n=0 />ms
|
||||
|
||||
| 玩家 | 内存 | CPU 空闲 | CPU 最大值 |
|
||||
| ------- | --------------------- | ---------------- | ------------------ |
|
||||
| 0 | <FmtNum :n=392.2 />KB | <FmtNum :n=0 />% | <FmtNum :n=0 />% |
|
||||
| 1 | <FmtNum :n=24.9 />MB | <FmtNum :n=0 />% | <FmtNum :n=4 />% |
|
||||
| 2 | <FmtNum :n=25.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=0.6 />% |
|
||||
| 5 | <FmtNum :n=26 />MB | <FmtNum :n=0 />% | <FmtNum :n=1 />% |
|
||||
| 10 | <FmtNum :n=27.1 />MB | <FmtNum :n=0 />% | <FmtNum :n=1.5 />% |
|
||||
|
||||
<sub><sup>Pumpkin 会缓存已加载的区块,除了玩家数据外不会使用额外的内存,且 CPU 使用率最小。</sup></sub>
|
||||
|
||||
### 编译时间
|
||||
|
||||
从零开始编译:
|
||||
|
||||
**调试模式:** <FmtNum :n=10.35 />秒
|
||||
**发布模式:** <FmtNum :n=38.40 />秒
|
||||
|
||||
重新编译 (pumpkin crate):
|
||||
|
||||
**调试模式:** <FmtNum :n=1.82 />秒
|
||||
**发布模式:** <FmtNum :n=28.68 />秒
|
||||
|
||||
## 原版 (Vanilla)
|
||||
|
||||
发布版本:[1.21.1](https://piston-data.mojang.com/v1/objects/59353fb40c36d304f2035d51e7d6e6baa98dc05c/server.jar)
|
||||
|
||||
编译参数:
|
||||
|
||||
运行参数:`nogui`
|
||||
|
||||
**文件大小:** <FmtNum :n=51.6 />MB
|
||||
|
||||
**启动时间:** <FmtNum :n=7 />秒
|
||||
|
||||
**关闭时间:** <FmtNum :n=4 />秒
|
||||
|
||||
| 玩家数 | 内存 | CPU 空闲 | CPU 最大值 |
|
||||
| ------ | -------------------- | --------------------------------------- | ------------------ |
|
||||
| 0 | <FmtNum n="860" />MB | <FmtNum n="0.1" /> - <FmtNum n="0.3" />% | <FmtNum n="51" />% |
|
||||
| 1 | <FmtNum n="1.5" />GB | <FmtNum n="0.9" /> - <FmtNum n="1" />% | <FmtNum n="41" />% |
|
||||
| 2 | <FmtNum n="1.6" />GB | <FmtNum n="1" /> - <FmtNum n="1.1" />% | <FmtNum n="10" />% |
|
||||
| 5 | <FmtNum n="1.8" />GB | <FmtNum n="2" />% | <FmtNum n="20" />% |
|
||||
| 10 | <FmtNum n="2.2" />GB | <FmtNum n="4" />% | <FmtNum n="24" />% |
|
||||
|
||||
## Paper
|
||||
|
||||
构建版本:[122](https://api.papermc.io/v2/projects/paper/versions/1.21.1/builds/122/downloads/paper-1.21.1-122.jar)
|
||||
|
||||
编译参数:
|
||||
|
||||
运行参数:`nogui`
|
||||
|
||||
**文件大小:** <FmtNum :n=49.4 />MB
|
||||
|
||||
**启动时间:** <FmtNum :n=7 />秒
|
||||
|
||||
**关闭时间:** <FmtNum :n=3 />秒
|
||||
|
||||
| 玩家数 | 内存 | CPU 空闲 | CPU 最大值 |
|
||||
| ------ | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.1 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=36 />% |
|
||||
| 1 | <FmtNum :n=1.7 />GB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=47 />% |
|
||||
| 2 | <FmtNum :n=1.8 />GB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=10 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.5 />% | <FmtNum :n=15 />% |
|
||||
| 10 | <FmtNum :n=2 />GB | <FmtNum :n=3 />% | <FmtNum :n=20 />% |
|
||||
|
||||
## Purpur
|
||||
|
||||
构建版本:[2324](https://api.purpurmc.org/v2/purpur/1.21.1/2324/download)
|
||||
|
||||
编译参数:
|
||||
|
||||
运行参数:`nogui`
|
||||
|
||||
**文件大小:** <FmtNum :n=53.1 />MB
|
||||
|
||||
**启动时间:** <FmtNum :n=8 />秒
|
||||
|
||||
**关闭时间:** <FmtNum :n=4 />秒
|
||||
|
||||
| 玩家数 | 内存 | CPU 空闲 | CPU 最大值 |
|
||||
| ------ | ------------------- | -------------------------------------- | ----------------- |
|
||||
| 0 | <FmtNum :n=1.4 />GB | <FmtNum :n=0.2 /> - <FmtNum :n=0.3 />% | <FmtNum :n=25 />% |
|
||||
| 1 | <FmtNum :n=1.6 />GB | <FmtNum :n=0.7 /> - <FmtNum :n=1.0 />% | <FmtNum :n=35 />% |
|
||||
| 2 | <FmtNum :n=1.7 />GB | <FmtNum :n=1.1 /> - <FmtNum :n=1.3 />% | <FmtNum :n=9 />% |
|
||||
| 5 | <FmtNum :n=1.9 />GB | <FmtNum :n=1.6 />% | <FmtNum :n=20 />% |
|
||||
| 10 | <FmtNum :n=2.2 />GB | <FmtNum :n=2 /> - <FmtNum :n=2.5 />% | <FmtNum :n=26 />% |
|
||||
|
||||
## Minestom
|
||||
|
||||
提交版本:[0ca1dda2fe](https://github.com/Minestom/Minestom/commit/0ca1dda2fe11390a1b89a228bbe7bf78fefc73e1)
|
||||
|
||||
编译参数:
|
||||
|
||||
运行参数:
|
||||
|
||||
**语言:** 使用 Kotlin 2.0.0 运行基准测试(Minestom 本身使用 Java 开发)
|
||||
|
||||
**文件大小:** <FmtNum :n=2.8 />MB(库)
|
||||
|
||||
**启动时间:** <FmtNum :n=310 />ms
|
||||
|
||||
**关闭时间:** <FmtNum :n=0 />ms
|
||||
|
||||
<sub>[示例代码来源](https://minestom.net/docs/setup/your-first-server)</sub>
|
||||
|
||||
| 玩家数 | 内存 | CPU 空闲 | CPU 最大值 |
|
||||
| ------ | ------------------- | -------------------------------------- | ---------------- |
|
||||
| 0 | <FmtNum :n=228 />MB | <FmtNum :n=0.1 /> - <FmtNum :n=0.3 />% | <FmtNum :n=1 />% |
|
||||
| 1 | <FmtNum :n=365 />MB | <FmtNum :n=0.9 /> - <FmtNum :n=1.0 />% | <FmtNum :n=5 />% |
|
||||
| 2 | <FmtNum :n=371 />MB | <FmtNum :n=1 /> - <FmtNum :n=1.1 />% | <FmtNum :n=4 />% |
|
||||
| 5 | <FmtNum :n=390 />MB | <FmtNum :n=1.0 />% | <FmtNum :n=6 />% |
|
||||
| 10 | <FmtNum :n=421 />MB | <FmtNum :n=3 />% | <FmtNum :n=9 />% |
|
||||
|
||||
基准测试时间 <FmtDateTime :d="new Date('2024-10-15T16:34Z')" />
|
||||
30
package-lock.json
generated
30
package-lock.json
generated
@ -8,7 +8,7 @@
|
||||
"or": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"@types/bun": "^1.4.2",
|
||||
"vitepress": "^2.0.0-alpha.19",
|
||||
"vue": "^3.6.0-rc.2"
|
||||
}
|
||||
@ -486,13 +486,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/bun": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.14.tgz",
|
||||
"integrity": "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==",
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.4.2.tgz",
|
||||
"integrity": "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bun-types": "1.3.14"
|
||||
"bun-types": "1.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
@ -541,13 +541,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
|
||||
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
|
||||
"version": "26.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.1.tgz",
|
||||
"integrity": "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~8.3.0"
|
||||
"undici-types": "~8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
@ -943,9 +943,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/bun-types": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.14.tgz",
|
||||
"integrity": "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ==",
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.4.2.tgz",
|
||||
"integrity": "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1822,9 +1822,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||
"version": "8.9.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
|
||||
"integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
"preview": "vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"@types/bun": "^1.4.2",
|
||||
"vitepress": "^2.0.0-alpha.19",
|
||||
"vue": "^3.6.0-rc.2"
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user