papermc-skill 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +90 -110
- package/assets/banner-animated.svg +360 -0
- package/assets/banner.svg +360 -0
- package/bin/cli.js +199 -0
- package/docs/01-modern-paper-architecture.md +48 -51
- package/docs/02-adventure-and-minimessage.md +48 -52
- package/docs/03-persistent-data-container.md +39 -39
- package/docs/04-commands-and-brigadier.md +32 -35
- package/docs/05-concurrency-and-folia.md +44 -44
- package/docs/06-events-and-listeners.md +30 -31
- package/docs/07-best-practices-and-perf.md +39 -40
- package/docs/08-gui-and-menus.md +34 -37
- package/docs/09-custom-items-and-combat.md +25 -33
- package/docs/10-database-and-storage.md +27 -27
- package/docs/11-security-and-exploit-prevention.md +233 -0
- package/package.json +2 -1
- package/skills/paper-plugin-dev/SKILL.md +391 -111
- package/templates/paper-modern-template/.gradle/9.7.1/checksums/checksums.lock +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/checksums/md5-checksums.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/checksums/sha1-checksums.bin +0 -0
- package/templates/paper-modern-template/.gradle/{9.4.1 → 9.7.1}/executionHistory/executionHistory.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.lock +0 -0
- package/templates/paper-modern-template/.gradle/{9.4.1 → 9.7.1}/fileHashes/fileHashes.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.lock +0 -0
- package/templates/paper-modern-template/.gradle/{9.4.1 → 9.7.1}/fileHashes/resourceHashesCache.bin +0 -0
- package/templates/paper-modern-template/.gradle/{9.4.1/checksums/md5-checksums.bin → 9.7.1/kotlin-dsl-plugin-entries/jar-entries.bin} +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/kotlin-dsl-plugin-entries/kotlin-dsl-plugin-entries.lock +0 -0
- package/templates/paper-modern-template/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/templates/paper-modern-template/.gradle/buildOutputCleanup/cache.properties +2 -2
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/ExamplePaperPlugin.class +0 -0
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/PaperPluginBootstrap.class +0 -0
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/command/HelloCommand.class +0 -0
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/listener/PlayerEventListener.class +0 -0
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/util/SchedulerAdapter.class +0 -0
- package/templates/paper-modern-template/build/tmp/compileJava/previous-compilation-data.bin +0 -0
- package/templates/paper-modern-template/build.gradle.kts +10 -10
- package/templates/paper-modern-template/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/templates/paper-modern-template/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/templates/paper-modern-template/gradlew +3 -3
- package/templates/paper-modern-template/gradlew.bat +12 -23
- package/templates/paper-modern-template/settings.gradle.kts +7 -0
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/ExamplePaperPlugin.java +6 -6
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/PaperPluginBootstrap.java +2 -2
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/command/HelloCommand.java +2 -4
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/data/PluginDataKeys.java +2 -2
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/listener/PlayerEventListener.java +6 -7
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/util/SchedulerAdapter.java +6 -6
- package/templates/paper-modern-template/src/main/resources/config.yml +5 -5
- package/templates/paper-modern-template/.gradle/9.4.1/checksums/checksums.lock +0 -0
- package/templates/paper-modern-template/.gradle/9.4.1/checksums/sha1-checksums.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.4.1/executionHistory/executionHistory.lock +0 -0
- package/templates/paper-modern-template/.gradle/9.4.1/fileHashes/fileHashes.lock +0 -0
- package/templates/paper-modern-template/.gradle/file-system.probe +0 -0
- /package/templates/paper-modern-template/.gradle/{9.4.1 → 9.7.1}/fileChanges/last-build.bin +0 -0
- /package/templates/paper-modern-template/.gradle/{9.4.1 → 9.7.1}/gc.properties +0 -0
|
@@ -1,55 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Modern Minecraft Java Plugin Architecture (Paper & Folia)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This guide explores the modern architectural patterns for building Minecraft Java plugins on PaperMC (specifically versions 1.20+ and 1.21+), covering build tooling, manifest configuration, lifecycle bootstrapping, and native Mojang mappings.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 1.
|
|
7
|
+
## 1. Architectural Evolution: Legacy Spigot vs Modern Paper
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Historically, plugins were built on the Bukkit/Spigot API using `plugin.yml` and straightforward Maven/Gradle configurations. However, modern Paper has revolutionized plugin architecture:
|
|
10
10
|
|
|
11
|
-
|
|
|
11
|
+
| Feature | Legacy Spigot | Modern Paper (1.20.6+ / 1.21+) |
|
|
12
12
|
| :--- | :--- | :--- |
|
|
13
|
-
| **Manifest File** | `plugin.yml` | `paper-plugin.yml` (
|
|
14
|
-
| **Text Handling** | `ChatColor` & legacy
|
|
15
|
-
| **Lifecycle
|
|
16
|
-
| **Command
|
|
17
|
-
| **
|
|
18
|
-
| **Multi-
|
|
13
|
+
| **Manifest File** | `plugin.yml` | `paper-plugin.yml` (or hybrid) |
|
|
14
|
+
| **Text Handling** | `ChatColor` & legacy formatting (`§a`) | Kyori Adventure & MiniMessage (`Component`) |
|
|
15
|
+
| **Lifecycle Hooks** | Only `JavaPlugin.onLoad` & `onEnable` | `PluginBootstrap` + `JavaPlugin` + `LifecycleEvents` |
|
|
16
|
+
| **Command Engine** | `getCommand("...").setExecutor(...)` | Paper Native Brigadier (`LifecycleEvents.COMMANDS`) or Cloud |
|
|
17
|
+
| **NMS Mappings** | Obfuscated Spigot mappings (`reobf`) | Runtime Mojang Mappings (1.20.5+) via Paperweight |
|
|
18
|
+
| **Multi-Threading** | Single main thread (`BukkitScheduler`) | Multi-threaded region ticking (Folia & region schedulers) |
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
22
|
## 2. Build Tooling: Gradle & Paperweight Userdev
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
The modern industry standard for Paper projects is **Gradle (Kotlin DSL - `.gradle.kts`)** using PaperMC's **`paperweight-userdev`** plugin.
|
|
25
25
|
|
|
26
|
-
###
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
26
|
+
### Key Benefits of Paperweight Userdev:
|
|
27
|
+
- Direct access to **Mojang Mappings (NMS)** without cumbersome external deobfuscation workflows.
|
|
28
|
+
- Starting from Paper 1.20.5+, Paper servers run native Mojang Mappings in production at runtime.
|
|
29
|
+
- Seamless integration with `xyz.jpenilla.run-paper` to launch automated Paper/Folia test servers with a single command (`./gradlew runServer`).
|
|
30
30
|
|
|
31
|
-
###
|
|
31
|
+
### Example `build.gradle.kts`:
|
|
32
32
|
|
|
33
33
|
```kotlin
|
|
34
34
|
plugins {
|
|
35
35
|
`java-library`
|
|
36
36
|
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
|
|
37
|
-
id("xyz.jpenilla.run-paper") version "3.0
|
|
38
|
-
id("com.gradleup.shadow") version "8.3.6" // Shadow jar
|
|
37
|
+
id("xyz.jpenilla.run-paper") version "3.1.0" // Task: ./gradlew runServer
|
|
38
|
+
id("com.gradleup.shadow") version "8.3.6" // Shadow jar for bundled external libraries
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
group = "com.example"
|
|
42
42
|
version = "1.0.0-SNAPSHOT"
|
|
43
43
|
|
|
44
44
|
java {
|
|
45
|
-
toolchain.languageVersion.set(JavaLanguageVersion.of(21)) // Minecraft 1.20.5+
|
|
45
|
+
toolchain.languageVersion.set(JavaLanguageVersion.of(21)) // Minecraft 1.20.5+ requires Java 21+
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
dependencies {
|
|
49
|
-
paperweight.paperDevBundle("1.21.
|
|
50
|
-
|
|
51
|
-
// Contoh library umum
|
|
52
|
-
// implementation("org.incendo:cloud-paper:2.0.0-beta.10")
|
|
49
|
+
paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT")
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
tasks {
|
|
@@ -59,8 +56,8 @@ tasks {
|
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
shadowJar {
|
|
62
|
-
archiveClassifier.set("") //
|
|
63
|
-
//
|
|
59
|
+
archiveClassifier.set("") // Set shaded jar as default artifact output
|
|
60
|
+
// Relocate external packages to prevent classpath collisions:
|
|
64
61
|
// relocate("org.incendo.cloud", "com.example.plugin.libs.cloud")
|
|
65
62
|
}
|
|
66
63
|
}
|
|
@@ -68,16 +65,16 @@ tasks {
|
|
|
68
65
|
|
|
69
66
|
---
|
|
70
67
|
|
|
71
|
-
## 3.
|
|
68
|
+
## 3. Manifest Configuration: `paper-plugin.yml`
|
|
72
69
|
|
|
73
|
-
Modern Paper
|
|
70
|
+
Modern Paper introduces the `paper-plugin.yml` manifest located at `src/main/resources/paper-plugin.yml`.
|
|
74
71
|
|
|
75
|
-
###
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
72
|
+
### Advantages of `paper-plugin.yml`:
|
|
73
|
+
- Granular dependency management (bootstrapper dependencies vs server runtime dependencies).
|
|
74
|
+
- Declarative bootstrapper class definition (`bootstrapper`).
|
|
75
|
+
- Explicit multi-threaded Folia compatibility (`folia-supported: true`).
|
|
79
76
|
|
|
80
|
-
###
|
|
77
|
+
### Example `paper-plugin.yml` Structure:
|
|
81
78
|
|
|
82
79
|
```yaml
|
|
83
80
|
name: ExamplePlugin
|
|
@@ -92,7 +89,7 @@ authors:
|
|
|
92
89
|
|
|
93
90
|
dependencies:
|
|
94
91
|
bootstrap:
|
|
95
|
-
#
|
|
92
|
+
# Dependencies required during the bootstrap phase
|
|
96
93
|
server:
|
|
97
94
|
Vault:
|
|
98
95
|
load: BEFORE
|
|
@@ -105,21 +102,21 @@ dependencies:
|
|
|
105
102
|
|
|
106
103
|
---
|
|
107
104
|
|
|
108
|
-
## 4. Lifecycle
|
|
105
|
+
## 4. Modern Lifecycle: `PluginBootstrap` vs `JavaPlugin`
|
|
109
106
|
|
|
110
|
-
Paper
|
|
107
|
+
Paper introduces the `io.papermc.paper.plugin.bootstrap.PluginBootstrap` interface.
|
|
111
108
|
|
|
112
|
-
###
|
|
113
|
-
1. **Plugin Loader Creation**:
|
|
109
|
+
### Server Lifecycle Sequence:
|
|
110
|
+
1. **Plugin Loader Creation**: Classpath resolution and dependency wiring.
|
|
114
111
|
2. **Bootstrap Phase (`PluginBootstrap#bootstrap`)**:
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
3. **Plugin Instantiation**:
|
|
118
|
-
4. **`onLoad()`**:
|
|
119
|
-
5. **`onEnable()`**:
|
|
120
|
-
6. **`onDisable()`**: Graceful shutdown,
|
|
112
|
+
- Executes before worlds are loaded.
|
|
113
|
+
- Used to register **Brigadier Commands**, modify registry configurations, and set up global resources.
|
|
114
|
+
3. **Plugin Instantiation**: The `JavaPlugin` instance is created.
|
|
115
|
+
4. **`onLoad()`**: Equivalent to the legacy Bukkit phase.
|
|
116
|
+
5. **`onEnable()`**: Event listener registration, schedulers, database connection pools, and UI/game logic setup.
|
|
117
|
+
6. **`onDisable()`**: Graceful shutdown, database queue flushing, and task cancellation.
|
|
121
118
|
|
|
122
|
-
###
|
|
119
|
+
### Bootstrapper Implementation Example:
|
|
123
120
|
|
|
124
121
|
```java
|
|
125
122
|
package com.example.plugin;
|
|
@@ -134,16 +131,16 @@ public final class ExampleBootstrap implements PluginBootstrap {
|
|
|
134
131
|
|
|
135
132
|
@Override
|
|
136
133
|
public void bootstrap(final BootstrapContext context) {
|
|
137
|
-
//
|
|
134
|
+
// Register Lifecycle Events such as modern Brigadier command handlers
|
|
138
135
|
context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
|
|
139
136
|
final var registrar = event.registrar();
|
|
140
|
-
//
|
|
137
|
+
// Register Brigadier command nodes here
|
|
141
138
|
});
|
|
142
139
|
}
|
|
143
140
|
}
|
|
144
141
|
```
|
|
145
142
|
|
|
146
|
-
###
|
|
143
|
+
### Main Plugin Implementation (`JavaPlugin`):
|
|
147
144
|
|
|
148
145
|
```java
|
|
149
146
|
package com.example.plugin;
|
|
@@ -156,16 +153,16 @@ public final class ExamplePlugin extends JavaPlugin {
|
|
|
156
153
|
|
|
157
154
|
@Override
|
|
158
155
|
public void onEnable() {
|
|
159
|
-
//
|
|
156
|
+
// Using Paper's modern ComponentLogger
|
|
160
157
|
getComponentLogger().info(
|
|
161
|
-
Component.text("ExamplePlugin
|
|
158
|
+
Component.text("ExamplePlugin successfully enabled!", NamedTextColor.GREEN)
|
|
162
159
|
);
|
|
163
160
|
}
|
|
164
161
|
|
|
165
162
|
@Override
|
|
166
163
|
public void onDisable() {
|
|
167
164
|
getComponentLogger().info(
|
|
168
|
-
Component.text("ExamplePlugin
|
|
165
|
+
Component.text("ExamplePlugin safely disabled.", NamedTextColor.RED)
|
|
169
166
|
);
|
|
170
167
|
}
|
|
171
168
|
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
# Adventure API & MiniMessage
|
|
1
|
+
# Adventure API & MiniMessage in Modern Paper
|
|
2
2
|
|
|
3
|
-
Paper
|
|
3
|
+
Paper integrates **Kyori Adventure** natively as its primary text interface. Legacy color formatting using section signs (`§a`, `§l`) or `org.bukkit.ChatColor` has been **deprecated** because it corrupts formatting across modern clients, lacks modularity, and cannot express 24-bit TrueColor RGB gradients cleanly.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 1.
|
|
7
|
+
## 1. Why Kyori Adventure & MiniMessage?
|
|
8
8
|
|
|
9
|
-
- **Type-Safe Components**:
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **MiniMessage Format**:
|
|
9
|
+
- **Type-Safe Components**: Text is represented as an immutable hierarchy of `Component` objects rather than brittle strings.
|
|
10
|
+
- **Full TrueColor Support**: Native 24-bit Hex colors (`#FF5555`), dynamic Gradients, and Rainbow transitions.
|
|
11
|
+
- **Rich Interactivity**: Click events (`run_command`, `suggest_command`, `open_url`), hover tooltips (`show_text`, `show_item`, `show_entity`), and custom typography fonts.
|
|
12
|
+
- **MiniMessage Format**: A human-readable XML-like syntax that prevents formatting injection vulnerabilities.
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
## 2.
|
|
16
|
+
## 2. Basic MiniMessage Usage
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Use `net.kyori.adventure.text.minimessage.MiniMessage`:
|
|
19
19
|
|
|
20
20
|
```java
|
|
21
21
|
import net.kyori.adventure.text.Component;
|
|
@@ -23,25 +23,25 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
|
|
23
23
|
|
|
24
24
|
final MiniMessage mm = MiniMessage.miniMessage();
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
Component c1 = mm.deserialize("<gradient:#ff007f:#7f00ff><bold>
|
|
28
|
-
Component c2 = mm.deserialize("<gray>
|
|
29
|
-
Component c3 = mm.deserialize("<hover:show_text:'<yellow>Status:
|
|
26
|
+
// Colored and gradient text examples
|
|
27
|
+
Component c1 = mm.deserialize("<gradient:#ff007f:#7f00ff><bold>Legendary RPG Server</bold></gradient>");
|
|
28
|
+
Component c2 = mm.deserialize("<gray>Click <green><click:run_command:'/spawn'>HERE</click></green> to return to spawn.</gray>");
|
|
29
|
+
Component c3 = mm.deserialize("<hover:show_text:'<yellow>Status: Online</yellow>'>Hover over me</hover>");
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
###
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
32
|
+
### Popular MiniMessage Tags:
|
|
33
|
+
- Named colors: `<red>`, `<green>`, `<gold>`, `<aqua>`, `<dark_purple>`, `<white>`, etc.
|
|
34
|
+
- Hex colors: `<#rrggbb>` or `<color:#rrggbb>`.
|
|
35
|
+
- Text styling: `<bold>` (`<b>`), `<italic>` (`<i>`), `<underlined>` (`<u>`), `<strikethrough>` (`<s>`), `<obfuscated>` (`<obf>`).
|
|
36
|
+
- Gradients: `<gradient:#55cdfc:#f7a8b8:#ffffff>Trans Rights</gradient>`.
|
|
37
|
+
- Rainbow: `<rainbow>Dynamic rainbow text</rainbow>`.
|
|
38
|
+
- Style resets: `<reset>` or explicit closing tags like `</red>`.
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
-
## 3. Dynamic Tag Resolvers (
|
|
42
|
+
## 3. Dynamic Tag Resolvers (Custom Placeholders)
|
|
43
43
|
|
|
44
|
-
MiniMessage
|
|
44
|
+
MiniMessage provides `TagResolver` to insert dynamic variables safely without risking formatting injection:
|
|
45
45
|
|
|
46
46
|
```java
|
|
47
47
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
|
@@ -50,7 +50,7 @@ import org.bukkit.entity.Player;
|
|
|
50
50
|
|
|
51
51
|
public Component createWelcomeMessage(Player player, int coins) {
|
|
52
52
|
return MiniMessage.miniMessage().deserialize(
|
|
53
|
-
"<gold>
|
|
53
|
+
"<gold>Welcome, <yellow><player_name></yellow>! Balance: <green><coins></green></gold>",
|
|
54
54
|
TagResolver.resolver(
|
|
55
55
|
Placeholder.component("player_name", player.displayName()),
|
|
56
56
|
Placeholder.unparsed("coins", String.valueOf(coins))
|
|
@@ -59,28 +59,28 @@ public Component createWelcomeMessage(Player player, int coins) {
|
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
>
|
|
63
|
-
> - `Placeholder.component(...)`:
|
|
64
|
-
> - `Placeholder.unparsed(...)`:
|
|
62
|
+
> **`component` vs `unparsed`**:
|
|
63
|
+
> - `Placeholder.component(...)`: Inserts an already-parsed child component, preserving rich formatting.
|
|
64
|
+
> - `Placeholder.unparsed(...)`: Inserts raw literal text. Any formatting tags inside are **not** evaluated as MiniMessage tags (crucial for sanitizing chat input).
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
68
|
-
## 4.
|
|
68
|
+
## 4. Sending Messages to Audiences
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
In Paper, every message receiver implements the `net.kyori.adventure.audience.Audience` interface (including `Player`, `ConsoleCommandSender`, and `Server`).
|
|
71
71
|
|
|
72
|
-
### Chat
|
|
72
|
+
### Standard Chat & Console Logging
|
|
73
73
|
```java
|
|
74
|
-
//
|
|
75
|
-
player.sendMessage(MiniMessage.miniMessage().deserialize("<aqua>
|
|
74
|
+
// Send to player
|
|
75
|
+
player.sendMessage(MiniMessage.miniMessage().deserialize("<aqua>Incoming message!</aqua>"));
|
|
76
76
|
|
|
77
|
-
//
|
|
78
|
-
getComponentLogger().info(MiniMessage.miniMessage().deserialize("<green>Plugin
|
|
77
|
+
// Send via ComponentLogger on the plugin instance
|
|
78
|
+
getComponentLogger().info(MiniMessage.miniMessage().deserialize("<green>Plugin ready for connections!</green>"));
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
### Action Bar
|
|
82
82
|
```java
|
|
83
|
-
player.sendActionBar(MiniMessage.miniMessage().deserialize("<red>
|
|
83
|
+
player.sendActionBar(MiniMessage.miniMessage().deserialize("<red>Hazard Zone!</red>"));
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
### Titles & Subtitles
|
|
@@ -89,8 +89,8 @@ import net.kyori.adventure.title.Title;
|
|
|
89
89
|
import java.time.Duration;
|
|
90
90
|
|
|
91
91
|
final Title title = Title.title(
|
|
92
|
-
MiniMessage.miniMessage().deserialize("<gold><bold>
|
|
93
|
-
MiniMessage.miniMessage().deserialize("<gray>
|
|
92
|
+
MiniMessage.miniMessage().deserialize("<gold><bold>VICTORY!</bold></gold>"),
|
|
93
|
+
MiniMessage.miniMessage().deserialize("<gray>You won this match</gray>"),
|
|
94
94
|
Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(1000))
|
|
95
95
|
);
|
|
96
96
|
|
|
@@ -102,16 +102,16 @@ player.showTitle(title);
|
|
|
102
102
|
import net.kyori.adventure.bossbar.BossBar;
|
|
103
103
|
|
|
104
104
|
BossBar bossBar = BossBar.bossBar(
|
|
105
|
-
MiniMessage.miniMessage().deserialize("<dark_red>Ender Dragon
|
|
105
|
+
MiniMessage.miniMessage().deserialize("<dark_red>Ender Dragon Health</dark_red>"),
|
|
106
106
|
0.75f, // 75%
|
|
107
107
|
BossBar.Color.RED,
|
|
108
108
|
BossBar.Overlay.PROGRESS
|
|
109
109
|
);
|
|
110
110
|
|
|
111
|
-
//
|
|
111
|
+
// Display boss bar
|
|
112
112
|
player.showBossBar(bossBar);
|
|
113
113
|
|
|
114
|
-
//
|
|
114
|
+
// Remove when completed
|
|
115
115
|
player.hideBossBar(bossBar);
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -132,9 +132,9 @@ player.playSound(sound);
|
|
|
132
132
|
|
|
133
133
|
---
|
|
134
134
|
|
|
135
|
-
## 5.
|
|
135
|
+
## 5. Item Serialization & Lore with Adventure
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
When constructing `ItemStack` instances, use `Component` methods instead of legacy raw strings:
|
|
138
138
|
|
|
139
139
|
```java
|
|
140
140
|
import org.bukkit.Material;
|
|
@@ -143,18 +143,14 @@ import org.bukkit.inventory.meta.ItemMeta;
|
|
|
143
143
|
import java.util.List;
|
|
144
144
|
|
|
145
145
|
ItemStack sword = new ItemStack(Material.DIAMOND_SWORD);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Custom name menggunakan Component
|
|
150
|
-
meta.displayName(MiniMessage.miniMessage().deserialize("<gradient:#00c6ff:#0072ff><bold>Pedang Es Legendaris</bold></gradient>"));
|
|
146
|
+
sword.editMeta(meta -> {
|
|
147
|
+
// Custom name using Component
|
|
148
|
+
meta.displayName(MiniMessage.miniMessage().deserialize("<gradient:#00c6ff:#0072ff><bold>Frostmourne Blade</bold></gradient>"));
|
|
151
149
|
|
|
152
|
-
// Lore
|
|
150
|
+
// Lore using List<Component>
|
|
153
151
|
meta.lore(List.of(
|
|
154
|
-
MiniMessage.miniMessage().deserialize("<gray>
|
|
155
|
-
MiniMessage.miniMessage().deserialize("<dark_gray>
|
|
152
|
+
MiniMessage.miniMessage().deserialize("<gray>Applies <aqua>Freeze</aqua> on hit.</gray>"),
|
|
153
|
+
MiniMessage.miniMessage().deserialize("<dark_gray>Tier: <gold>★★★★★</gold></dark_gray>")
|
|
156
154
|
));
|
|
157
|
-
|
|
158
|
-
sword.setItemMeta(meta);
|
|
159
|
-
}
|
|
155
|
+
});
|
|
160
156
|
```
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
# PersistentDataContainer (PDC)
|
|
1
|
+
# PersistentDataContainer (PDC) in Modern Paper
|
|
2
2
|
|
|
3
|
-
`PersistentDataContainer` (PDC)
|
|
4
|
-
- **
|
|
5
|
-
- **
|
|
6
|
-
- **Block
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
3
|
+
`PersistentDataContainer` (PDC) is the official, type-safe Bukkit/Paper API for storing custom persistent metadata directly on in-game Minecraft objects, including:
|
|
4
|
+
- **ItemStacks (`ItemMeta`)**
|
|
5
|
+
- **Entities (`Entity`, `LivingEntity`, `Player`)**
|
|
6
|
+
- **Block States / Tile Entities (`TileState`)**
|
|
7
|
+
- **Chunks (`Chunk`)**
|
|
8
|
+
- **Worlds (`World`)**
|
|
9
9
|
|
|
10
|
-
PDC
|
|
10
|
+
PDC eliminates brittle dependencies on legacy third-party NBT libraries (like NBTAPI) or parsing hidden lore strings. Data placed in PDC is serialized natively into Minecraft's vanilla NBT and automatically saved and loaded alongside world saves.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
## 1.
|
|
14
|
+
## 1. Key Concept: NamespacedKey
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Every value stored in PDC is addressed by a unique `NamespacedKey` comprising a `namespace` (typically your lowercase plugin name) and a `key` identifier:
|
|
17
17
|
|
|
18
18
|
```java
|
|
19
19
|
import org.bukkit.NamespacedKey;
|
|
@@ -34,40 +34,37 @@ public final class DataKeys {
|
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
-
## 2.
|
|
37
|
+
## 2. Storing & Reading Primitive Data Types
|
|
38
38
|
|
|
39
|
-
PDC
|
|
39
|
+
PDC supports standard primitive types via the `PersistentDataType` interface:
|
|
40
40
|
- `PersistentDataType.STRING`
|
|
41
41
|
- `PersistentDataType.INTEGER`
|
|
42
42
|
- `PersistentDataType.LONG`
|
|
43
43
|
- `PersistentDataType.DOUBLE`
|
|
44
44
|
- `PersistentDataType.FLOAT`
|
|
45
|
-
- `PersistentDataType.BYTE` (
|
|
45
|
+
- `PersistentDataType.BYTE` (booleans are stored as byte `1` / `0`)
|
|
46
46
|
- `PersistentDataType.BYTE_ARRAY`
|
|
47
|
-
- `PersistentDataType.TAG_CONTAINER` (PDC
|
|
47
|
+
- `PersistentDataType.TAG_CONTAINER` (nested PDC structures)
|
|
48
48
|
|
|
49
|
-
###
|
|
49
|
+
### Example: Storing Metadata on an ItemStack
|
|
50
50
|
```java
|
|
51
51
|
import org.bukkit.Material;
|
|
52
52
|
import org.bukkit.inventory.ItemStack;
|
|
53
|
-
import org.bukkit.inventory.meta.ItemMeta;
|
|
54
53
|
import org.bukkit.persistence.PersistentDataContainer;
|
|
55
54
|
import org.bukkit.persistence.PersistentDataType;
|
|
56
55
|
|
|
57
56
|
public ItemStack createTeleportWand(String wandId) {
|
|
58
57
|
ItemStack wand = new ItemStack(Material.BLAZE_ROD);
|
|
59
|
-
|
|
60
|
-
if (meta != null) {
|
|
58
|
+
wand.editMeta(meta -> {
|
|
61
59
|
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
|
62
60
|
pdc.set(DataKeys.CUSTOM_ID, PersistentDataType.STRING, wandId);
|
|
63
61
|
pdc.set(DataKeys.COOLDOWN_TIMESTAMP, PersistentDataType.LONG, System.currentTimeMillis());
|
|
64
|
-
|
|
65
|
-
}
|
|
62
|
+
});
|
|
66
63
|
return wand;
|
|
67
64
|
}
|
|
68
65
|
```
|
|
69
66
|
|
|
70
|
-
###
|
|
67
|
+
### Example: Reading & Validating ItemStack Metadata
|
|
71
68
|
```java
|
|
72
69
|
public boolean isCustomWand(ItemStack item) {
|
|
73
70
|
if (item == null || !item.hasItemMeta()) return false;
|
|
@@ -84,42 +81,45 @@ public String getWandId(ItemStack item) {
|
|
|
84
81
|
|
|
85
82
|
---
|
|
86
83
|
|
|
87
|
-
## 3.
|
|
84
|
+
## 3. Storing Metadata on Entities and Chunks
|
|
88
85
|
|
|
89
|
-
PDC
|
|
86
|
+
PDC is accessible directly on entities and chunks without wrapping metadata:
|
|
90
87
|
|
|
91
88
|
```java
|
|
92
89
|
import org.bukkit.entity.Entity;
|
|
93
90
|
import org.bukkit.Chunk;
|
|
91
|
+
import org.bukkit.NamespacedKey;
|
|
92
|
+
import org.bukkit.persistence.PersistentDataType;
|
|
94
93
|
|
|
95
|
-
//
|
|
94
|
+
// Tagging custom entities (e.g. Dungeon Boss)
|
|
96
95
|
public void markAsCustomBoss(Entity entity, String bossType) {
|
|
97
96
|
entity.getPersistentDataContainer().set(DataKeys.CUSTOM_ID, PersistentDataType.STRING, bossType);
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
//
|
|
101
|
-
public void markChunkProcessed(Chunk chunk) {
|
|
102
|
-
chunk.getPersistentDataContainer().set(
|
|
99
|
+
// Tagging chunks processed by your world generation systems
|
|
100
|
+
public void markChunkProcessed(Chunk chunk, NamespacedKey key) {
|
|
101
|
+
chunk.getPersistentDataContainer().set(key, PersistentDataType.BYTE, (byte) 1);
|
|
103
102
|
}
|
|
104
103
|
```
|
|
105
104
|
|
|
106
105
|
---
|
|
107
106
|
|
|
108
|
-
## 4.
|
|
107
|
+
## 4. Custom Complex Data Types (`PersistentDataType<T, Z>`)
|
|
109
108
|
|
|
110
|
-
|
|
109
|
+
You can implement custom `PersistentDataType<T, Z>` adapters to store complex Java objects (such as records or POJOs) directly into PDC without manual JSON conversions on every invocation.
|
|
111
110
|
|
|
112
|
-
###
|
|
111
|
+
### Object Record:
|
|
113
112
|
```java
|
|
114
113
|
public record ItemStats(int damageBonus, double critChance, int durability) {}
|
|
115
114
|
```
|
|
116
115
|
|
|
117
|
-
###
|
|
116
|
+
### Custom `PersistentDataType` Adapter:
|
|
118
117
|
```java
|
|
118
|
+
import org.bukkit.NamespacedKey;
|
|
119
119
|
import org.bukkit.persistence.PersistentDataAdapterContext;
|
|
120
120
|
import org.bukkit.persistence.PersistentDataContainer;
|
|
121
121
|
import org.bukkit.persistence.PersistentDataType;
|
|
122
|
-
import org.bukkit.
|
|
122
|
+
import org.bukkit.plugin.Plugin;
|
|
123
123
|
|
|
124
124
|
public final class ItemStatsDataType implements PersistentDataType<PersistentDataContainer, ItemStats> {
|
|
125
125
|
|
|
@@ -127,7 +127,7 @@ public final class ItemStatsDataType implements PersistentDataType<PersistentDat
|
|
|
127
127
|
private final NamespacedKey critKey;
|
|
128
128
|
private final NamespacedKey durabilityKey;
|
|
129
129
|
|
|
130
|
-
public ItemStatsDataType(
|
|
130
|
+
public ItemStatsDataType(Plugin plugin) {
|
|
131
131
|
this.damageKey = new NamespacedKey(plugin, "damage");
|
|
132
132
|
this.critKey = new NamespacedKey(plugin, "crit");
|
|
133
133
|
this.durabilityKey = new NamespacedKey(plugin, "durability");
|
|
@@ -162,18 +162,18 @@ public final class ItemStatsDataType implements PersistentDataType<PersistentDat
|
|
|
162
162
|
}
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
###
|
|
165
|
+
### Usage:
|
|
166
166
|
```java
|
|
167
167
|
ItemStats stats = new ItemStats(25, 0.15, 500);
|
|
168
168
|
pdc.set(DataKeys.ITEM_STATS, new ItemStatsDataType(plugin), stats);
|
|
169
169
|
|
|
170
|
-
//
|
|
170
|
+
// Read back:
|
|
171
171
|
ItemStats loadedStats = pdc.get(DataKeys.ITEM_STATS, new ItemStatsDataType(plugin));
|
|
172
172
|
```
|
|
173
173
|
|
|
174
174
|
---
|
|
175
175
|
|
|
176
|
-
## 5. Best Practices
|
|
177
|
-
1. **
|
|
178
|
-
2. **
|
|
179
|
-
3. **
|
|
176
|
+
## 5. PDC Best Practices
|
|
177
|
+
1. **Singleton Keys**: Never instantiate `new NamespacedKey(...)` repeatedly in hot event loops; define static final constants.
|
|
178
|
+
2. **Consistent Namespaces**: Always use your plugin instance namespace to prevent collisions with third-party plugins.
|
|
179
|
+
3. **Data Cleanup**: Clean up stale metadata using `pdc.remove(key)` to minimize world save file sizes.
|