papermc-skill 1.0.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/LICENSE +21 -0
- package/README.md +169 -0
- package/bin/cli.js +307 -0
- package/docs/01-modern-paper-architecture.md +172 -0
- package/docs/02-adventure-and-minimessage.md +160 -0
- package/docs/03-persistent-data-container.md +179 -0
- package/docs/04-commands-and-brigadier.md +157 -0
- package/docs/05-concurrency-and-folia.md +150 -0
- package/docs/06-events-and-listeners.md +163 -0
- package/docs/07-best-practices-and-perf.md +118 -0
- package/docs/08-gui-and-menus.md +198 -0
- package/docs/09-custom-items-and-combat.md +171 -0
- package/docs/10-database-and-storage.md +187 -0
- package/package.json +44 -0
- package/skills/paper-plugin-dev/SKILL.md +167 -0
- package/templates/paper-modern-template/.editorconfig +8 -0
- package/templates/paper-modern-template/.gradle/9.4.1/checksums/checksums.lock +0 -0
- package/templates/paper-modern-template/.gradle/9.4.1/checksums/md5-checksums.bin +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.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/fileChanges/last-build.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.4.1/fileHashes/fileHashes.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.4.1/fileHashes/fileHashes.lock +0 -0
- package/templates/paper-modern-template/.gradle/9.4.1/fileHashes/resourceHashesCache.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.4.1/gc.properties +0 -0
- package/templates/paper-modern-template/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/templates/paper-modern-template/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/templates/paper-modern-template/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
- package/templates/paper-modern-template/.gradle/file-system.probe +0 -0
- package/templates/paper-modern-template/.gradle/vcs-1/gc.properties +0 -0
- 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/data/PluginDataKeys.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 +54 -0
- package/templates/paper-modern-template/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/templates/paper-modern-template/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/templates/paper-modern-template/gradlew +248 -0
- package/templates/paper-modern-template/gradlew.bat +93 -0
- package/templates/paper-modern-template/settings.gradle.kts +5 -0
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/ExamplePaperPlugin.java +35 -0
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/PaperPluginBootstrap.java +30 -0
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/command/HelloCommand.java +55 -0
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/data/PluginDataKeys.java +22 -0
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/listener/PlayerEventListener.java +61 -0
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/util/SchedulerAdapter.java +77 -0
- package/templates/paper-modern-template/src/main/resources/config.yml +10 -0
- package/templates/paper-modern-template/src/main/resources/paper-plugin.yml +9 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Arsitektur Modern Plugin Paper Minecraft Java
|
|
2
|
+
|
|
3
|
+
Dokumen ini membahas arsitektur modern pembuatan plugin untuk server Paper Minecraft Java (khususnya versi 1.20+ dan 1.21+), mencakup build tools, konfigurasi manifest, lifecycle bootstrapping, dan sistem pemetaan (Mojang mappings).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Evolusi Arsitektur: Spigot vs Modern Paper
|
|
8
|
+
|
|
9
|
+
Secara historis, plugin Minecraft dibuat di atas Bukkit/Spigot API dengan `plugin.yml` dan Maven/Gradle sederhana. Namun, ekosistem Paper modern telah merevolusi cara kerja plugin:
|
|
10
|
+
|
|
11
|
+
| Fitur | Spigot Legacy | Modern Paper (1.20.6+ / 1.21+) |
|
|
12
|
+
| :--- | :--- | :--- |
|
|
13
|
+
| **Manifest File** | `plugin.yml` | `paper-plugin.yml` (atau hybrid) |
|
|
14
|
+
| **Text Handling** | `ChatColor` & legacy string formatting (`§a`) | Kyori Adventure & MiniMessage (`Component`) |
|
|
15
|
+
| **Lifecycle Hook** | Hanya `JavaPlugin.onLoad` & `onEnable` | `PluginBootstrap` + `JavaPlugin` + `LifecycleEvents` |
|
|
16
|
+
| **Command System** | `getCommand("...").setExecutor(...)` | Paper Brigadier Native (`LifecycleEvents.COMMANDS`) atau Cloud |
|
|
17
|
+
| **Mapping NMS** | Obfuscated Spigot mapping (`reobf`) | Mojang Mappings langsung di runtime (1.20.5+) via Paperweight |
|
|
18
|
+
| **Multi-threading** | Single main thread (`BukkitScheduler`) | Multi-thread regionized ready (Folia & Schedulers terpisah) |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. Build Tooling: Gradle & Paperweight Userdev
|
|
23
|
+
|
|
24
|
+
Standard industri modern untuk proyek Paper adalah **Gradle (Kotlin DSL - `.gradle.kts`)** menggunakan plugin **`paperweight-userdev`** dari PaperMC.
|
|
25
|
+
|
|
26
|
+
### Keunggulan Paperweight Userdev:
|
|
27
|
+
- Menyediakan akses ke **Mojang Mappings (NMS)** langsung tanpa alat deobfuscation eksternal yang rumit.
|
|
28
|
+
- Sejak Paper 1.20.5+, server Paper menjalankan Mojang Mappings secara native di runtime.
|
|
29
|
+
- Terintegrasi dengan plugin `xyz.jpenilla.run-paper` untuk menjalankan test server Paper/Folia langsung dengan satu perintah gradle (`./gradlew runServer`).
|
|
30
|
+
|
|
31
|
+
### Contoh `build.gradle.kts`:
|
|
32
|
+
|
|
33
|
+
```kotlin
|
|
34
|
+
plugins {
|
|
35
|
+
`java-library`
|
|
36
|
+
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
|
|
37
|
+
id("xyz.jpenilla.run-paper") version "3.0.2" // Task ./gradlew runServer
|
|
38
|
+
id("com.gradleup.shadow") version "8.3.6" // Shadow jar bila menggunakan dependensi eksternal
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
group = "com.example"
|
|
42
|
+
version = "1.0.0-SNAPSHOT"
|
|
43
|
+
|
|
44
|
+
java {
|
|
45
|
+
toolchain.languageVersion.set(JavaLanguageVersion.of(21)) // Minecraft 1.20.5+ membutuhkan minimal Java 21
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
dependencies {
|
|
49
|
+
paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT")
|
|
50
|
+
|
|
51
|
+
// Contoh library umum
|
|
52
|
+
// implementation("org.incendo:cloud-paper:2.0.0-beta.10")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
tasks {
|
|
56
|
+
compileJava {
|
|
57
|
+
options.release.set(21)
|
|
58
|
+
options.encoding = Charsets.UTF_8.name()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
shadowJar {
|
|
62
|
+
archiveClassifier.set("") // Menjadikan output shadow jar sebagai artifact utama
|
|
63
|
+
// relocate package eksternal agar tidak bentrok dengan plugin lain
|
|
64
|
+
// relocate("org.incendo.cloud", "com.example.plugin.libs.cloud")
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 3. Konfigurasi `paper-plugin.yml`
|
|
72
|
+
|
|
73
|
+
Modern Paper menggunakan manifest `paper-plugin.yml` yang terletak di `src/main/resources/paper-plugin.yml`.
|
|
74
|
+
|
|
75
|
+
### Keuntungan `paper-plugin.yml`:
|
|
76
|
+
- Dukungan dependency loading yang jauh lebih fleksibel (bootstrapper dependencies, server dependencies).
|
|
77
|
+
- Mendukung deklarasi bootstrapper class (`bootstrapper`).
|
|
78
|
+
- Kompatibilitas multi-threaded Folia (`folia-supported: true`).
|
|
79
|
+
|
|
80
|
+
### Contoh Struktur `paper-plugin.yml`:
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
name: ExamplePlugin
|
|
84
|
+
version: '${version}'
|
|
85
|
+
main: com.example.plugin.ExamplePlugin
|
|
86
|
+
bootstrapper: com.example.plugin.ExampleBootstrap
|
|
87
|
+
description: Modern Paper Minecraft Plugin
|
|
88
|
+
api-version: '1.21'
|
|
89
|
+
folia-supported: true
|
|
90
|
+
authors:
|
|
91
|
+
- DeveloperName
|
|
92
|
+
|
|
93
|
+
dependencies:
|
|
94
|
+
bootstrap:
|
|
95
|
+
# Dependensi yang dibutuhkan pada fase bootstrap
|
|
96
|
+
server:
|
|
97
|
+
Vault:
|
|
98
|
+
load: BEFORE
|
|
99
|
+
required: false
|
|
100
|
+
join-classpath: true
|
|
101
|
+
LuckPerms:
|
|
102
|
+
load: BEFORE
|
|
103
|
+
required: false
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 4. Lifecycle Baru: `PluginBootstrap` vs `JavaPlugin`
|
|
109
|
+
|
|
110
|
+
Paper memperkenalkan antarmuka `io.papermc.paper.plugin.bootstrap.PluginBootstrap`.
|
|
111
|
+
|
|
112
|
+
### Urutan Lifecycle Server Paper:
|
|
113
|
+
1. **Plugin Loader Creation**: Resolusi classpath dan dependensi.
|
|
114
|
+
2. **Bootstrap Phase (`PluginBootstrap#bootstrap`)**:
|
|
115
|
+
- Berjalan sangat awal sebelum world di-load.
|
|
116
|
+
- Digunakan untuk meregistrasi **Brigadier Commands**, modifikasi konfigurasi registry, dan setup resource global.
|
|
117
|
+
3. **Plugin Instantiation**: Instance `JavaPlugin` dibuat.
|
|
118
|
+
4. **`onLoad()`**: Mirip fase Spigot tradisional.
|
|
119
|
+
5. **`onEnable()`**: Registrasi listener event, scheduler, inisialisasi database, dan setup UI/Game logic.
|
|
120
|
+
6. **`onDisable()`**: Graceful shutdown, flush queue database, dan pembatalan task.
|
|
121
|
+
|
|
122
|
+
### Contoh Implementasi Bootstrapper:
|
|
123
|
+
|
|
124
|
+
```java
|
|
125
|
+
package com.example.plugin;
|
|
126
|
+
|
|
127
|
+
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
|
|
128
|
+
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
|
|
129
|
+
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
|
130
|
+
import org.jspecify.annotations.NullMarked;
|
|
131
|
+
|
|
132
|
+
@NullMarked
|
|
133
|
+
public final class ExampleBootstrap implements PluginBootstrap {
|
|
134
|
+
|
|
135
|
+
@Override
|
|
136
|
+
public void bootstrap(final BootstrapContext context) {
|
|
137
|
+
// Registrasi Lifecycle Events seperti command handler modern
|
|
138
|
+
context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
|
|
139
|
+
final var registrar = event.registrar();
|
|
140
|
+
// Registrasi syntax Brigadier di sini
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Contoh Implementasi Main Plugin (`JavaPlugin`):
|
|
147
|
+
|
|
148
|
+
```java
|
|
149
|
+
package com.example.plugin;
|
|
150
|
+
|
|
151
|
+
import net.kyori.adventure.text.Component;
|
|
152
|
+
import net.kyori.adventure.text.format.NamedTextColor;
|
|
153
|
+
import org.bukkit.plugin.java.JavaPlugin;
|
|
154
|
+
|
|
155
|
+
public final class ExamplePlugin extends JavaPlugin {
|
|
156
|
+
|
|
157
|
+
@Override
|
|
158
|
+
public void onEnable() {
|
|
159
|
+
// Menggunakan ComponentLogger modern Paper
|
|
160
|
+
getComponentLogger().info(
|
|
161
|
+
Component.text("ExamplePlugin berhasil diaktifkan!", NamedTextColor.GREEN)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@Override
|
|
166
|
+
public void onDisable() {
|
|
167
|
+
getComponentLogger().info(
|
|
168
|
+
Component.text("ExamplePlugin dimatikan secara aman.", NamedTextColor.RED)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Adventure API & MiniMessage di Paper Minecraft
|
|
2
|
+
|
|
3
|
+
Paper mengintegrasikan **Kyori Adventure** secara native sebagai antarmuka pemrosesan teks. Pendekatan lama yang menggunakan tanda warna section (`§a`, `§l`) atau `org.bukkit.ChatColor` telah **ditinggalkan (deprecated)** karena rentan merusak format teks, tidak mendukung RGB hex secara fleksibel, dan tidak modular.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Mengapa Adventure & MiniMessage?
|
|
8
|
+
|
|
9
|
+
- **Type-Safe Components**: Teks bukan sekadar string biasa melainkan hierarki objek `Component` yang immutable.
|
|
10
|
+
- **Dukungan Warna Penuh**: Mendukung warna 24-bit TrueColor / Hex (`#FF5555`), Gradients, dan Rainbows.
|
|
11
|
+
- **Interaktivitas Kaya**: Event klik (`run_command`, `suggest_command`, `open_url`), hover tooltip (`show_text`, `show_item`, `show_entity`), dan font kustom.
|
|
12
|
+
- **MiniMessage Format**: String format yang sangat mudah dibaca manusia berbasis tag XML-like, aman dari injeksi formatting.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 2. Format Dasar MiniMessage
|
|
17
|
+
|
|
18
|
+
Gunakan `net.kyori.adventure.text.minimessage.MiniMessage`:
|
|
19
|
+
|
|
20
|
+
```java
|
|
21
|
+
import net.kyori.adventure.text.Component;
|
|
22
|
+
import net.kyori.adventure.text.minimessage.MiniMessage;
|
|
23
|
+
|
|
24
|
+
final MiniMessage mm = MiniMessage.miniMessage();
|
|
25
|
+
|
|
26
|
+
// Contoh teks berwarna dan bergradasi
|
|
27
|
+
Component c1 = mm.deserialize("<gradient:#ff007f:#7f00ff><bold>Server RPG Terhebat</bold></gradient>");
|
|
28
|
+
Component c2 = mm.deserialize("<gray>Klik <green><click:run_command:'/spawn'>DI SINI</click></green> untuk kembali ke spawn.</gray>");
|
|
29
|
+
Component c3 = mm.deserialize("<hover:show_text:'<yellow>Status: Aktif</yellow>'>Arahkan kursor ke sini</hover>");
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Tag MiniMessage Populer:
|
|
33
|
+
- Warna bawaan: `<red>`, `<green>`, `<gold>`, `<aqua>`, `<dark_purple>`, `<white>`, dll.
|
|
34
|
+
- Warna Hex: `<#rrggbb>` atau `<color:#rrggbb>`.
|
|
35
|
+
- Format style: `<bold>` (`<b>`), `<italic>` (`<i>`), `<underlined>` (`<u>`), `<strikethrough>` (`<s>`), `<obfuscated>` (`<obf>`).
|
|
36
|
+
- Gradasi warna: `<gradient:#55cdfc:#f7a8b8:#ffffff>Trans Rights</gradient>`.
|
|
37
|
+
- Pelangi: `<rainbow>Teks pelangi dinamis</rainbow>`.
|
|
38
|
+
- Reset gaya: `<reset>` atau penutupan tag `</red>`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. Dynamic Tag Resolvers (Placeholder Kustom)
|
|
43
|
+
|
|
44
|
+
MiniMessage menyediakan `TagResolver` untuk memasukkan variabel secara dinamis tanpa merusak parsing atau membuka celah string injection:
|
|
45
|
+
|
|
46
|
+
```java
|
|
47
|
+
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
|
48
|
+
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
|
49
|
+
import org.bukkit.entity.Player;
|
|
50
|
+
|
|
51
|
+
public Component createWelcomeMessage(Player player, int coins) {
|
|
52
|
+
return MiniMessage.miniMessage().deserialize(
|
|
53
|
+
"<gold>Selamat datang, <yellow><player_name></yellow>! Koin Anda: <green><coins></green></gold>",
|
|
54
|
+
TagResolver.resolver(
|
|
55
|
+
Placeholder.component("player_name", player.displayName()),
|
|
56
|
+
Placeholder.unparsed("coins", String.valueOf(coins))
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> **Perbedaan `component` vs `unparsed`**:
|
|
63
|
+
> - `Placeholder.component(...)`: Menyisipkan child component yang sudah di-parse, mempertahankan format.
|
|
64
|
+
> - `Placeholder.unparsed(...)`: Menyisipkan teks murni. Tag apapun di dalamnya **tidak** akan di-evaluasi sebagai tag MiniMessage (aman untuk input dari user/chat chatbox).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 4. Mengirim Pesan ke Audience
|
|
69
|
+
|
|
70
|
+
Di Paper, setiap entitas yang bisa menerima pesan mengimplementasikan antarmuka `net.kyori.adventure.audience.Audience` (termasuk `Player`, `ConsoleCommandSender`, dan `Server`).
|
|
71
|
+
|
|
72
|
+
### Chat Biasa & Console Logging
|
|
73
|
+
```java
|
|
74
|
+
// Mengirim ke pemain
|
|
75
|
+
player.sendMessage(MiniMessage.miniMessage().deserialize("<aqua>Pesan masuk!</aqua>"));
|
|
76
|
+
|
|
77
|
+
// Mengirim via ComponentLogger pada plugin
|
|
78
|
+
getComponentLogger().info(MiniMessage.miniMessage().deserialize("<green>Plugin siap melayani pemain!</green>"));
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Action Bar
|
|
82
|
+
```java
|
|
83
|
+
player.sendActionBar(MiniMessage.miniMessage().deserialize("<red>Zona Bahaya!</red>"));
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Titles & Subtitles
|
|
87
|
+
```java
|
|
88
|
+
import net.kyori.adventure.title.Title;
|
|
89
|
+
import java.time.Duration;
|
|
90
|
+
|
|
91
|
+
final Title title = Title.title(
|
|
92
|
+
MiniMessage.miniMessage().deserialize("<gold><bold>KEMENANGAN!</bold></gold>"),
|
|
93
|
+
MiniMessage.miniMessage().deserialize("<gray>Anda memenangkan ronde ini</gray>"),
|
|
94
|
+
Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(1000))
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
player.showTitle(title);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### BossBar
|
|
101
|
+
```java
|
|
102
|
+
import net.kyori.adventure.bossbar.BossBar;
|
|
103
|
+
|
|
104
|
+
BossBar bossBar = BossBar.bossBar(
|
|
105
|
+
MiniMessage.miniMessage().deserialize("<dark_red>Ender Dragon HP</dark_red>"),
|
|
106
|
+
0.75f, // 75%
|
|
107
|
+
BossBar.Color.RED,
|
|
108
|
+
BossBar.Overlay.PROGRESS
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Tampilkan bossbar
|
|
112
|
+
player.showBossBar(bossBar);
|
|
113
|
+
|
|
114
|
+
// Hapus bila sudah selesai
|
|
115
|
+
player.hideBossBar(bossBar);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Sound Effects
|
|
119
|
+
```java
|
|
120
|
+
import net.kyori.adventure.sound.Sound;
|
|
121
|
+
import org.bukkit.SoundCategory;
|
|
122
|
+
|
|
123
|
+
Sound sound = Sound.sound(
|
|
124
|
+
org.bukkit.Sound.ENTITY_PLAYER_LEVELUP.key(),
|
|
125
|
+
Sound.Source.PLAYER,
|
|
126
|
+
1.0f, // volume
|
|
127
|
+
1.2f // pitch
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
player.playSound(sound);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 5. Serialisasi Item & Lore dengan Adventure
|
|
136
|
+
|
|
137
|
+
Saat membuat `ItemStack`, gunakan metode komponen `Component` alih-alih String lama:
|
|
138
|
+
|
|
139
|
+
```java
|
|
140
|
+
import org.bukkit.Material;
|
|
141
|
+
import org.bukkit.inventory.ItemStack;
|
|
142
|
+
import org.bukkit.inventory.meta.ItemMeta;
|
|
143
|
+
import java.util.List;
|
|
144
|
+
|
|
145
|
+
ItemStack sword = new ItemStack(Material.DIAMOND_SWORD);
|
|
146
|
+
ItemMeta meta = sword.getItemMeta();
|
|
147
|
+
|
|
148
|
+
if (meta != null) {
|
|
149
|
+
// Custom name menggunakan Component
|
|
150
|
+
meta.displayName(MiniMessage.miniMessage().deserialize("<gradient:#00c6ff:#0072ff><bold>Pedang Es Legendaris</bold></gradient>"));
|
|
151
|
+
|
|
152
|
+
// Lore menggunakan List<Component>
|
|
153
|
+
meta.lore(List.of(
|
|
154
|
+
MiniMessage.miniMessage().deserialize("<gray>Memberikan efek <aqua>Freeze</aqua> saat menyerang.</gray>"),
|
|
155
|
+
MiniMessage.miniMessage().deserialize("<dark_gray>Level: <gold>★★★★★</gold></dark_gray>")
|
|
156
|
+
));
|
|
157
|
+
|
|
158
|
+
sword.setItemMeta(meta);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# PersistentDataContainer (PDC) di Paper Minecraft
|
|
2
|
+
|
|
3
|
+
`PersistentDataContainer` (PDC) adalah API resmi Bukkit/Paper untuk menyimpan data kustom secara permanen pada objek dalam game seperti:
|
|
4
|
+
- **Item (`ItemMeta`)**
|
|
5
|
+
- **Entity (`Entity`, `LivingEntity`, `Player`)**
|
|
6
|
+
- **Block State / Tile Entity (`TileState`)**
|
|
7
|
+
- **Chunk (`Chunk`)**
|
|
8
|
+
- **World (`World`)**
|
|
9
|
+
|
|
10
|
+
PDC menggantikan ketergantungan pada library NBT pihak ketiga (seperti NBTAPI) atau pembacaan lore tersembunyi yang rapuh. Data yang disimpan di PDC disimpan langsung dalam NBT vanilla Minecraft dan otomatis disimpan/di-load bersama world data.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Konsep Kunci: NamespacedKey
|
|
15
|
+
|
|
16
|
+
Setiap nilai yang disimpan di PDC diidentifikasi oleh `NamespacedKey` unik yang terdiri dari `namespace` (biasanya nama plugin berhuruf kecil) dan `key`:
|
|
17
|
+
|
|
18
|
+
```java
|
|
19
|
+
import org.bukkit.NamespacedKey;
|
|
20
|
+
import org.bukkit.plugin.Plugin;
|
|
21
|
+
|
|
22
|
+
public final class DataKeys {
|
|
23
|
+
public static NamespacedKey CUSTOM_ID;
|
|
24
|
+
public static NamespacedKey COOLDOWN_TIMESTAMP;
|
|
25
|
+
public static NamespacedKey ITEM_STATS;
|
|
26
|
+
|
|
27
|
+
public static void init(Plugin plugin) {
|
|
28
|
+
CUSTOM_ID = new NamespacedKey(plugin, "custom_id");
|
|
29
|
+
COOLDOWN_TIMESTAMP = new NamespacedKey(plugin, "cooldown_timestamp");
|
|
30
|
+
ITEM_STATS = new NamespacedKey(plugin, "item_stats");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. Menyimpan & Membaca Tipe Data Primitif
|
|
38
|
+
|
|
39
|
+
PDC mendukung berbagai tipe bawaan melalui enum `PersistentDataType`:
|
|
40
|
+
- `PersistentDataType.STRING`
|
|
41
|
+
- `PersistentDataType.INTEGER`
|
|
42
|
+
- `PersistentDataType.LONG`
|
|
43
|
+
- `PersistentDataType.DOUBLE`
|
|
44
|
+
- `PersistentDataType.FLOAT`
|
|
45
|
+
- `PersistentDataType.BYTE` (boolean sering disimpan sebagai byte `1` / `0`)
|
|
46
|
+
- `PersistentDataType.BYTE_ARRAY`
|
|
47
|
+
- `PersistentDataType.TAG_CONTAINER` (PDC bersarang / nested)
|
|
48
|
+
|
|
49
|
+
### Contoh Menyimpan Data pada ItemStack:
|
|
50
|
+
```java
|
|
51
|
+
import org.bukkit.Material;
|
|
52
|
+
import org.bukkit.inventory.ItemStack;
|
|
53
|
+
import org.bukkit.inventory.meta.ItemMeta;
|
|
54
|
+
import org.bukkit.persistence.PersistentDataContainer;
|
|
55
|
+
import org.bukkit.persistence.PersistentDataType;
|
|
56
|
+
|
|
57
|
+
public ItemStack createTeleportWand(String wandId) {
|
|
58
|
+
ItemStack wand = new ItemStack(Material.BLAZE_ROD);
|
|
59
|
+
ItemMeta meta = wand.getItemMeta();
|
|
60
|
+
if (meta != null) {
|
|
61
|
+
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
|
62
|
+
pdc.set(DataKeys.CUSTOM_ID, PersistentDataType.STRING, wandId);
|
|
63
|
+
pdc.set(DataKeys.COOLDOWN_TIMESTAMP, PersistentDataType.LONG, System.currentTimeMillis());
|
|
64
|
+
wand.setItemMeta(meta);
|
|
65
|
+
}
|
|
66
|
+
return wand;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Contoh Membaca & Memvalidasi Data pada ItemStack:
|
|
71
|
+
```java
|
|
72
|
+
public boolean isCustomWand(ItemStack item) {
|
|
73
|
+
if (item == null || !item.hasItemMeta()) return false;
|
|
74
|
+
|
|
75
|
+
PersistentDataContainer pdc = item.getItemMeta().getPersistentDataContainer();
|
|
76
|
+
return pdc.has(DataKeys.CUSTOM_ID, PersistentDataType.STRING);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public String getWandId(ItemStack item) {
|
|
80
|
+
if (!isCustomWand(item)) return null;
|
|
81
|
+
return item.getItemMeta().getPersistentDataContainer().get(DataKeys.CUSTOM_ID, PersistentDataType.STRING);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 3. Menyimpan Data pada Entitas dan Chunk
|
|
88
|
+
|
|
89
|
+
PDC juga dapat diakses langsung pada entitas dan chunk tanpa meta terpisah:
|
|
90
|
+
|
|
91
|
+
```java
|
|
92
|
+
import org.bukkit.entity.Entity;
|
|
93
|
+
import org.bukkit.Chunk;
|
|
94
|
+
|
|
95
|
+
// Menandai entitas khusus (misal Custom Boss)
|
|
96
|
+
public void markAsCustomBoss(Entity entity, String bossType) {
|
|
97
|
+
entity.getPersistentDataContainer().set(DataKeys.CUSTOM_ID, PersistentDataType.STRING, bossType);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Menandai chunk yang telah diproses oleh plugin
|
|
101
|
+
public void markChunkProcessed(Chunk chunk) {
|
|
102
|
+
chunk.getPersistentDataContainer().set(new NamespacedKey("myplugin", "processed"), PersistentDataType.BYTE, (byte) 1);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 4. Tipe Data Kustom (Complex Object Serialization)
|
|
109
|
+
|
|
110
|
+
Kita dapat mengimplementasikan antarmuka `PersistentDataType<T, Z>` kustom untuk menyimpan objek kompleks (seperti record/POJO) ke dalam PDC tanpa perlu serialisasi JSON manual di setiap panggilan.
|
|
111
|
+
|
|
112
|
+
### Contoh Record Objek:
|
|
113
|
+
```java
|
|
114
|
+
public record ItemStats(int damageBonus, double critChance, int durability) {}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Implementasi `PersistentDataType` Kustom:
|
|
118
|
+
```java
|
|
119
|
+
import org.bukkit.persistence.PersistentDataAdapterContext;
|
|
120
|
+
import org.bukkit.persistence.PersistentDataContainer;
|
|
121
|
+
import org.bukkit.persistence.PersistentDataType;
|
|
122
|
+
import org.bukkit.NamespacedKey;
|
|
123
|
+
|
|
124
|
+
public final class ItemStatsDataType implements PersistentDataType<PersistentDataContainer, ItemStats> {
|
|
125
|
+
|
|
126
|
+
private final NamespacedKey damageKey;
|
|
127
|
+
private final NamespacedKey critKey;
|
|
128
|
+
private final NamespacedKey durabilityKey;
|
|
129
|
+
|
|
130
|
+
public ItemStatsDataType(org.bukkit.plugin.Plugin plugin) {
|
|
131
|
+
this.damageKey = new NamespacedKey(plugin, "damage");
|
|
132
|
+
this.critKey = new NamespacedKey(plugin, "crit");
|
|
133
|
+
this.durabilityKey = new NamespacedKey(plugin, "durability");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Override
|
|
137
|
+
public Class<PersistentDataContainer> getPrimitiveType() {
|
|
138
|
+
return PersistentDataContainer.class;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Override
|
|
142
|
+
public Class<ItemStats> getComplexType() {
|
|
143
|
+
return ItemStats.class;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@Override
|
|
147
|
+
public PersistentDataContainer toPrimitive(ItemStats complex, PersistentDataAdapterContext context) {
|
|
148
|
+
PersistentDataContainer container = context.newPersistentDataContainer();
|
|
149
|
+
container.set(damageKey, PersistentDataType.INTEGER, complex.damageBonus());
|
|
150
|
+
container.set(critKey, PersistentDataType.DOUBLE, complex.critChance());
|
|
151
|
+
container.set(durabilityKey, PersistentDataType.INTEGER, complex.durability());
|
|
152
|
+
return container;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@Override
|
|
156
|
+
public ItemStats fromPrimitive(PersistentDataContainer primitive, PersistentDataAdapterContext context) {
|
|
157
|
+
int damage = primitive.getOrDefault(damageKey, PersistentDataType.INTEGER, 0);
|
|
158
|
+
double crit = primitive.getOrDefault(critKey, PersistentDataType.DOUBLE, 0.0);
|
|
159
|
+
int durability = primitive.getOrDefault(durabilityKey, PersistentDataType.INTEGER, 100);
|
|
160
|
+
return new ItemStats(damage, crit, durability);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Penggunaan Langsung:
|
|
166
|
+
```java
|
|
167
|
+
ItemStats stats = new ItemStats(25, 0.15, 500);
|
|
168
|
+
pdc.set(DataKeys.ITEM_STATS, new ItemStatsDataType(plugin), stats);
|
|
169
|
+
|
|
170
|
+
// Membaca kembali:
|
|
171
|
+
ItemStats loadedStats = pdc.get(DataKeys.ITEM_STATS, new ItemStatsDataType(plugin));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 5. Best Practices PDC
|
|
177
|
+
1. **Gunakan Singleton Keys**: Jangan instansiasi `new NamespacedKey(...)` berulang kali di hot-loop (seperti `EntityDamageByEntityEvent`); simpan sebagai static final constants.
|
|
178
|
+
2. **Namespace Konsisten**: Selalu gunakan plugin instance untuk key namespace agar tidak terjadi tabrakan dengan plugin lain.
|
|
179
|
+
3. **Pembersihan Data**: Hapus data yang tidak lagi valid menggunakan `pdc.remove(key)` untuk menghemat ukuran file world/NBT.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Command System Modern: Brigadier & Cloud di Paper
|
|
2
|
+
|
|
3
|
+
Sistem command di Minecraft Java berevolusi signifikan. Metode lama Bukkit seperti `CommandExecutor`, `TabCompleter`, dan deklarasi perintah di `plugin.yml` kini digantikan oleh:
|
|
4
|
+
1. **Paper Native Brigadier API** (diperkenalkan di Paper 1.20.6+ melalui `LifecycleEvents.COMMANDS`).
|
|
5
|
+
2. **Incendo Cloud Framework** (`cloud-paper`): Standar industri untuk framework command berbasis anotasi / builder yang sangat fleksibel dan multi-platform.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Native Paper Brigadier Commands (Paper 1.20.6+)
|
|
10
|
+
|
|
11
|
+
Mojang Brigadier adalah command dispatcher engine resmi Minecraft. Paper mengekspos API Brigadier secara langsung tanpa perlu hack NMS atau dependensi tambahan.
|
|
12
|
+
|
|
13
|
+
### Registrasi Melalui Lifecycle Events
|
|
14
|
+
Command didaftarkan saat fase Bootstrap atau saat event `LifecycleEvents.COMMANDS` dipicu:
|
|
15
|
+
|
|
16
|
+
```java
|
|
17
|
+
package com.example.plugin;
|
|
18
|
+
|
|
19
|
+
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
|
20
|
+
import com.mojang.brigadier.arguments.StringArgumentType;
|
|
21
|
+
import io.papermc.paper.command.brigadier.Commands;
|
|
22
|
+
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
|
23
|
+
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
|
|
24
|
+
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
|
|
25
|
+
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
|
26
|
+
import net.kyori.adventure.text.minimessage.MiniMessage;
|
|
27
|
+
import org.bukkit.entity.Player;
|
|
28
|
+
|
|
29
|
+
public final class CommandBootstrapper implements PluginBootstrap {
|
|
30
|
+
|
|
31
|
+
@Override
|
|
32
|
+
public void bootstrap(BootstrapContext context) {
|
|
33
|
+
context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
|
|
34
|
+
final Commands commands = event.registrar();
|
|
35
|
+
|
|
36
|
+
// Membangun pohon syntax command /teleportzone <nama> [radius]
|
|
37
|
+
commands.register(
|
|
38
|
+
Commands.literal("teleportzone")
|
|
39
|
+
.requires(source -> source.getSender().hasPermission("plugin.command.teleportzone"))
|
|
40
|
+
.then(Commands.argument("nama", StringArgumentType.word())
|
|
41
|
+
.suggests((ctx, builder) -> {
|
|
42
|
+
builder.suggest("lobby");
|
|
43
|
+
builder.suggest("arena");
|
|
44
|
+
builder.suggest("market");
|
|
45
|
+
return builder.buildFuture();
|
|
46
|
+
})
|
|
47
|
+
.then(Commands.argument("radius", IntegerArgumentType.integer(1, 100))
|
|
48
|
+
.executes(ctx -> {
|
|
49
|
+
final CommandSourceStack source = ctx.getSource();
|
|
50
|
+
final String zone = StringArgumentType.getString(ctx, "nama");
|
|
51
|
+
final int radius = IntegerArgumentType.getInteger(ctx, "radius");
|
|
52
|
+
|
|
53
|
+
source.getSender().sendMessage(
|
|
54
|
+
MiniMessage.miniMessage().deserialize(
|
|
55
|
+
"<green>Mempersiapkan teleportasi ke zona <gold>" + zone + "</gold> dengan radius <yellow>" + radius + "</yellow>!</green>"
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
return com.mojang.brigadier.Command.SINGLE_SUCCESS;
|
|
59
|
+
})
|
|
60
|
+
)
|
|
61
|
+
.executes(ctx -> {
|
|
62
|
+
final CommandSourceStack source = ctx.getSource();
|
|
63
|
+
final String zone = StringArgumentType.getString(ctx, "nama");
|
|
64
|
+
source.getSender().sendMessage(
|
|
65
|
+
MiniMessage.miniMessage().deserialize("<green>Teleportasi default ke zona <gold>" + zone + "</gold>!</green>")
|
|
66
|
+
);
|
|
67
|
+
return com.mojang.brigadier.Command.SINGLE_SUCCESS;
|
|
68
|
+
})
|
|
69
|
+
)
|
|
70
|
+
.build(),
|
|
71
|
+
"Perintah teleportasi zona modern",
|
|
72
|
+
java.util.List.of("tpzone") // alias
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Keuntungan Paper Brigadier:
|
|
80
|
+
- **Client-Side Syntax Highlighting**: Minecraft client dapat menandai teks merah saat syntax salah sebelum perintah ditekan.
|
|
81
|
+
- **Auto Tab-Completion**: Tidak perlu menulis logika `TabCompleter` manual yang rumit.
|
|
82
|
+
- **Validasi Tipe Otomatis**: Argument validasi otomatis ditangani (angka out-of-range otomatis ditolak).
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 2. Incendo Cloud Command Framework (`cloud-paper`)
|
|
87
|
+
|
|
88
|
+
Untuk plugin yang membutuhkan dependensi injeksi, parsing objek otomatis (seperti `Player`, `OfflinePlayer`, `World`, `Duration`), dan command yang sangat banyak, **Cloud** adalah pilihan terpopuler.
|
|
89
|
+
|
|
90
|
+
### Dependensi Gradle:
|
|
91
|
+
```kotlin
|
|
92
|
+
dependencies {
|
|
93
|
+
implementation("org.incendo:cloud-paper:2.0.0-beta.10")
|
|
94
|
+
// atau cloud-annotations jika menyukai gaya @Command
|
|
95
|
+
implementation("org.incendo:cloud-annotations:2.0.0-beta.10")
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Inisialisasi Cloud PaperCommandManager:
|
|
100
|
+
```java
|
|
101
|
+
import org.incendo.cloud.paper.PaperCommandManager;
|
|
102
|
+
import org.incendo.cloud.execution.ExecutionCoordinator;
|
|
103
|
+
import org.bukkit.command.CommandSender;
|
|
104
|
+
import org.bukkit.plugin.java.JavaPlugin;
|
|
105
|
+
|
|
106
|
+
public class PluginCommands {
|
|
107
|
+
|
|
108
|
+
public static PaperCommandManager<CommandSender> createManager(JavaPlugin plugin) {
|
|
109
|
+
PaperCommandManager<CommandSender> manager = PaperCommandManager.createNative(
|
|
110
|
+
plugin,
|
|
111
|
+
ExecutionCoordinator.simpleCoordinator()
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Daftarkan brigadier mapper jika didukung
|
|
115
|
+
if (manager.hasCapability(org.incendo.cloud.paper.PaperCommandManager.Capability.BRIGADIER)) {
|
|
116
|
+
manager.registerBrigadier();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return manager;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Contoh Perintah dengan Cloud Builder:
|
|
125
|
+
```java
|
|
126
|
+
import org.incendo.cloud.paper.PaperCommandManager;
|
|
127
|
+
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
|
128
|
+
import org.bukkit.command.CommandSender;
|
|
129
|
+
import org.bukkit.entity.Player;
|
|
130
|
+
import net.kyori.adventure.text.minimessage.MiniMessage;
|
|
131
|
+
|
|
132
|
+
public void registerGiveRewardCommand(PaperCommandManager<CommandSender> manager) {
|
|
133
|
+
manager.command(
|
|
134
|
+
manager.commandBuilder("reward")
|
|
135
|
+
.permission("plugin.reward")
|
|
136
|
+
.required("target", PlayerParser.playerParser())
|
|
137
|
+
.handler(commandContext -> {
|
|
138
|
+
CommandSender sender = commandContext.sender();
|
|
139
|
+
Player target = commandContext.get("target");
|
|
140
|
+
|
|
141
|
+
target.sendMessage(MiniMessage.miniMessage().deserialize("<gold>Kamu menerima hadiah spesial!</gold>"));
|
|
142
|
+
sender.sendMessage(MiniMessage.miniMessage().deserialize("<green>Hadiah terkirim ke " + target.getName() + ".</green>"));
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 3. Komparasi: Kapan Memilih Apa?
|
|
151
|
+
|
|
152
|
+
| Aspek | Paper Native Brigadier | Incendo Cloud |
|
|
153
|
+
| :--- | :--- | :--- |
|
|
154
|
+
| **Ukuran Jar** | Zero-dependency (bawaan Paper) | Menambahkan lib ~300KB-800KB (butuh shadow/relocate) |
|
|
155
|
+
| **Kemudahan** | Cocok untuk command sederhana-menengah | Sangat baik untuk ratusan sub-command |
|
|
156
|
+
| **Argument Parser** | Perlu mapping manual untuk tipe Bukkit | Built-in parser untuk Player, Location, Selector, dll |
|
|
157
|
+
| **Portabilitas** | Hanya berjalan di Paper 1.20.6+ | Mendukung Velocity, Bungee, Fabric, Paper |
|