papermc-skill 1.0.1 → 1.2.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.
Files changed (53) hide show
  1. package/.cursor/rules/paper-plugin-dev.mdc +23 -0
  2. package/.cursorrules +6 -0
  3. package/.paseo/rules.md +8 -0
  4. package/.pi/rules.md +8 -0
  5. package/.windsurfrules +7 -0
  6. package/CHANGELOG.md +50 -0
  7. package/CLAUDE.md +27 -0
  8. package/CODEX.md +12 -0
  9. package/DEEPSEEK.md +18 -0
  10. package/README.md +73 -33
  11. package/assets/banner-animated.svg +360 -0
  12. package/assets/banner.svg +360 -0
  13. package/bin/cli.js +565 -35
  14. package/docs/01-modern-paper-architecture.md +48 -51
  15. package/docs/02-adventure-and-minimessage.md +48 -52
  16. package/docs/03-persistent-data-container.md +39 -39
  17. package/docs/04-commands-and-brigadier.md +32 -35
  18. package/docs/05-concurrency-and-folia.md +44 -44
  19. package/docs/06-events-and-listeners.md +30 -31
  20. package/docs/07-best-practices-and-perf.md +39 -40
  21. package/docs/08-gui-and-menus.md +34 -37
  22. package/docs/09-custom-items-and-combat.md +25 -33
  23. package/docs/10-database-and-storage.md +27 -27
  24. package/docs/11-security-and-exploit-prevention.md +233 -0
  25. package/docs/12-data-components-and-display-entities.md +264 -0
  26. package/docs/13-multi-agent-system-and-tooling.md +90 -0
  27. package/package.json +17 -4
  28. package/skills/paper-plugin-dev/SKILL.md +425 -114
  29. package/templates/paper-modern-template/.gradle/9.7.1/checksums/checksums.lock +0 -0
  30. package/templates/paper-modern-template/.gradle/9.7.1/checksums/md5-checksums.bin +0 -0
  31. package/templates/paper-modern-template/.gradle/9.7.1/checksums/sha1-checksums.bin +0 -0
  32. package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.bin +0 -0
  33. package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.lock +0 -0
  34. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.bin +0 -0
  35. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.lock +0 -0
  36. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/resourceHashesCache.bin +0 -0
  37. package/templates/paper-modern-template/.gradle/9.7.1/kotlin-dsl-plugin-entries/kotlin-dsl-plugin-entries.lock +0 -0
  38. package/templates/paper-modern-template/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  39. package/templates/paper-modern-template/.gradle/buildOutputCleanup/cache.properties +1 -1
  40. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/ExamplePaperPlugin.class +0 -0
  41. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/PaperPluginBootstrap.class +0 -0
  42. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/command/HelloCommand.class +0 -0
  43. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/listener/PlayerEventListener.class +0 -0
  44. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/util/SchedulerAdapter.class +0 -0
  45. package/templates/paper-modern-template/build/tmp/compileJava/previous-compilation-data.bin +0 -0
  46. package/templates/paper-modern-template/build.gradle.kts +7 -7
  47. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/ExamplePaperPlugin.java +6 -6
  48. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/PaperPluginBootstrap.java +2 -2
  49. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/command/HelloCommand.java +2 -4
  50. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/data/PluginDataKeys.java +2 -2
  51. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/listener/PlayerEventListener.java +6 -7
  52. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/util/SchedulerAdapter.java +6 -6
  53. package/templates/paper-modern-template/src/main/resources/config.yml +5 -5
@@ -1,170 +1,481 @@
1
1
  ---
2
2
  name: paper-plugin-dev
3
- description: Expert guidelines, modern standards, and architectural blueprints for developing Minecraft Java server plugins using the PaperMC API (1.20-1.21+), Folia, Adventure API, MiniMessage, and Paperweight.
3
+ description: Expert guidelines, modern architectural blueprints, exploit-prevention patterns, and best practices for developing production-grade Minecraft Java plugins using PaperMC API (1.20-1.21+ / 26.x), Folia (Threaded Regions), Adventure API, MiniMessage, Data Components, Display Entities, and Tricky Trials combat kinematics.
4
4
  ---
5
5
 
6
- # Paper Minecraft Plugin Development Skill
6
+ # Paper & Folia Minecraft Plugin Development Skill
7
7
 
8
- Skill ini memberikan panduan otoritatif, standar arsitektur modern, dan resep coding siap pakai untuk membuat atau memodifikasi plugin Minecraft Java Server berbasis **PaperMC (1.20.6 / 1.21+)** dan **Folia (Threaded Regions)**.
8
+ This skill provides authoritative architectural standards, high-level exploit-prevention blueprints, and modern design patterns for building and auditing Minecraft Java server plugins based on **PaperMC (1.20.6, 1.21+ through 26.x)** and **Folia (Regionized Multi-Threading)**.
9
+
10
+ Compatible with all premier AI coding environments: **Antigravity (agy)**, **Claude Code**, **OpenAI Codex**, **Cursor**, **DeepSeek Harness**, **Paseo**, and **pi.dev**.
9
11
 
10
12
  ---
11
13
 
12
- ## 1. Prasyarat Lingkungan & Dependensi
14
+ ## 1. Environment Prerequisites & Compatibility Matrix
13
15
 
14
- | Komponen | Versi yang Dibutuhkan | Catatan |
15
- | :--- | :--- | :--- |
16
- | **Java SDK** | **Java 21 (LTS)** atau lebih baru | Minecraft 1.20.5+ dan Paper modern **wajib** Java 21+. |
17
- | **Gradle** | **Gradle 8.5+** (Disarankan 8.10+ / 9.x) | Gunakan Gradle Wrapper bawaan (`./gradlew`). |
18
- | **Paper API** | `io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT` | Repositori: `https://repo.papermc.io/repository/maven-public/` |
19
- | **Mojang Mappings** | `io.papermc.paperweight.userdev` | Hanya jika membutuhkan akses langsung ke internal `net.minecraft.*`. |
16
+ | Component | Minimum Standard | Current Recommendation | Critical Notes |
17
+ | :--- | :--- | :--- | :--- |
18
+ | **Java SDK** | **Java 21 (LTS)** | **Java 21 LTS** / Java 25 Ready | Minecraft 1.20.5+ and modern Paper require Java 21+. Target Paper 26.x requires Java 25+. |
19
+ | **Build Tool** | Gradle 8.10+ | **Gradle 9.x+ (Kotlin DSL)** | Use `jvmToolchain(21)` and `pluginManagement { mavenCentral() }`. |
20
+ | **Paper API** | `1.21.4-R0.1-SNAPSHOT` | `1.21.11-R0.1-SNAPSHOT` / `26.3.build.+` | Repository: `https://repo.papermc.io/repository/maven-public/` |
21
+ | **API Discovery** | Manual | **PaperMC Fill v3 API** | Query real-time versions via `https://fill.papermc.io/v3/projects/paper`. |
22
+ | **Text Handling** | Adventure 4.17+ | **Kyori Adventure + MiniMessage** | Never use `org.bukkit.ChatColor` or the legacy section sign `§`. |
23
+ | **Item Data** | Data Components (1.20.5+) | `io.papermc.paper.datacomponent` | Legacy NBT tags are completely deprecated. Use `DataComponentTypes`. |
24
+ | **Visuals/FX** | ArmorStands (Legacy) | **Display Entities (Text/Item/Block)** | Zero-tick lag, JOML `Matrix4f` smooth client-side interpolation. |
25
+
26
+ ---
27
+
28
+ ## 2. Standard AI Coding Agent Workflow
29
+
30
+ When requested to scaffold, modify, or review a Paper/Folia plugin codebase:
31
+
32
+ 1. **Version Discovery & Target Verification**:
33
+ - Run `npx papermc-skill check-update` or query the Fill v3 API to determine current supported Paper builds and minimum Java requirements.
34
+ 2. **Configure Modern Plugin Manifest (`paper-plugin.yml`)**:
35
+ - Must declare `folia-supported: true`.
36
+ - Specify `bootstrapper:` for early lifecycle handling and Brigadier command registration.
37
+ 3. **Select Safe Concurrency Architecture**:
38
+ - Entity or Player state: `player.getScheduler()`.
39
+ - Block, World, or Chunk state: `RegionScheduler`.
40
+ - Global server state or announcements: `GlobalRegionScheduler`.
41
+ - Database, File I/O, Web Requests, or compute-heavy math: `AsyncScheduler` or `CompletableFuture`.
42
+ 4. **Item & Data Component Modernization**:
43
+ - Never use raw NBT manipulation. Mutate items through `DataComponentTypes` (e.g. `FOOD`, `TOOL`, `CUSTOM_MODEL_DATA`, `MAX_STACK_SIZE`).
44
+ 5. **Zero-Lag Visual Displays**:
45
+ - Replace outdated armor-stand floating holograms with `TextDisplay` and `ItemDisplay` using JOML matrix transformations.
46
+ 6. **Enforce Exploit & Dupe Prevention Protocols (Mandatory)**:
47
+ - Validate numeric user input (reject `NaN`, `Infinity`, overflow, and negative amounts).
48
+ - Protect inventory menus against click-spam race conditions via debounce.
49
+ - Store `UUID` rather than `Player` instances across all caches and static collections.
50
+ - Secure custom items using `PersistentDataContainer` (PDC) signatures.
51
+ 7. **Compile & Security Audit**:
52
+ - Run `npx papermc-skill audit` to guarantee 0 vulnerabilities before releasing code.
20
53
 
21
54
  ---
22
55
 
23
- ## 2. Alur Kerja Standar (Workflow) untuk AI / Developer
24
-
25
- Saat diminta membuat fitur atau plugin Paper baru, ikuti urutan berikut:
26
-
27
- 0. **Pemeriksaan Versi Otomatis (Dynamic Version Discovery)**:
28
- - Sebelum memulai scaffolding atau menulis kode baru, periksa rilis terbaru PaperMC & Minecraft via API Fill v3 (`https://fill.papermc.io/v3/projects/paper`) atau jalankan `npx papermc-skill check-update`.
29
- - Pastikan proyek menargetkan versi Paper API dan minimum Java SDK yang paling mutakhir.
30
- 1. **Tentukan Manifest (`src/main/resources/paper-plugin.yml`)**:
31
- - Berikan `folia-supported: true`.
32
- - Cantumkan `bootstrapper:` jika menggunakan Paper Brigadier Command.
33
- 2. **Setup PDC Keys (`PluginDataKeys.java`)**:
34
- - Buat konstanta `static final NamespacedKey` untuk semua data persisten (jangan instansiasi di dalam event loop).
35
- 3. **Pilih Penanganan Teks (Kyori Adventure)**:
36
- - Gunakan `MiniMessage.miniMessage().deserialize(...)`.
37
- - Jangan pernah menggunakan `§` atau `org.bukkit.ChatColor`.
38
- 4. **Pilih Pola Concurrency**:
39
- - Akses entity/block? Gunakan `RegionScheduler` atau `EntityScheduler`.
40
- - File/Database/Network? Gunakan `AsyncScheduler` atau `CompletableFuture`.
41
- 5. **Daftarkan Command & Event**:
42
- - Command -> Daftarkan di `BootstrapContext` via `LifecycleEvents.COMMANDS`.
43
- - Event -> Daftarkan di `JavaPlugin#onEnable()` via `registerEvents(...)`.
56
+ ## 3. Production Security & Exploit-Prevention Protocols
57
+
58
+ ### A. Item Duplication & Inventory Transaction Safety
59
+ Duplication glitches almost always stem from race conditions across inventory events or async state mutations:
60
+ - **Absolute Rule**: Never modify a player's `Inventory` or `ItemStack` from an asynchronous thread! Always dispatch inventory mutations onto the player's entity scheduler (`player.getScheduler()`).
61
+ - **Debounce Click Spam**: Enforce a cooldown (e.g., 150-200ms) between clicks on custom GUIs to stop auto-clicker exploits.
62
+ - **Cursor Item Handling**: When a custom menu closes unexpectedly (`InventoryCloseEvent` or disconnection), ensure items on the player's cursor are safely returned to inventory rather than lost or duplicated.
63
+
64
+ ### B. Economy & Numeric Input Sanitization (Anti-NaN & Anti-Overflow)
65
+ Many economy plugins suffer catastrophic exploits when players supply inputs like `/pay Player NaN`, `/pay Player -1000000`, or values exceeding standard 64-bit bounds:
66
+ - Always use `BigDecimal` or cents-based integer values for monetary storage.
67
+ - Always validate input:
68
+ ```java
69
+ if (!Double.isFinite(amount) || amount <= 0.0) {
70
+ throw new IllegalArgumentException("Invalid transaction amount!");
71
+ }
72
+ ```
73
+
74
+ ### C. PDC Anti-Tamper & Cryptographic Hashing
75
+ Cheating clients or players using anvils/crafting tables often attempt to forge custom item lore to impersonate rare server gear.
76
+ - **Rule**: Never identify a custom item by its `DisplayName` or `Lore`!
77
+ - Store immutable identifiers inside `PersistentDataContainer` (PDC).
78
+ - For high-value items, append an internal server HMAC hash or unique identifier to verify data integrity.
79
+
80
+ ### D. Preventing Denial of Service via Synchronous Chunk Loading
81
+ Invoking `world.getBlockAt(x, y, z)` on ungenerated/unloaded far chunks (e.g., coordinate 30,000,000) freezes the entire main server thread for hundreds of milliseconds.
82
+ - Always check `world.isChunkLoaded(chunkX, chunkZ)` prior to synchronous access, or load asynchronously:
83
+ ```java
84
+ world.getChunkAtAsync(chunkX, chunkZ).thenAccept(chunk -> { ... });
85
+ ```
86
+
87
+ ### E. SQL Injection Defense & Pool Leak Prevention
88
+ - Never concatenate raw user input into SQL queries (`"SELECT * FROM users WHERE name = '" + input + "'"`).
89
+ - Always use `PreparedStatement` with placeholder queries (`?`).
90
+ - Use connection pooling (**HikariCP**) enclosed in `try-with-resources` blocks so connections return cleanly to the pool.
44
91
 
45
92
  ---
46
93
 
47
- ## 3. Resep Kode Cepat (Ready-to-Use Recipes)
94
+ ## 4. Modern Overpowered (OP) Recipes
48
95
 
49
- ### Resep A: Manifest Modern (`paper-plugin.yml`)
96
+ ### Recipe 1: Folia-Ready Modern Manifest (`src/main/resources/paper-plugin.yml`)
50
97
  ```yaml
51
- name: MyAwesomePlugin
52
- version: '1.0.0'
53
- main: com.example.plugin.MyPlugin
54
- bootstrapper: com.example.plugin.MyPluginBootstrap
98
+ name: EpicPaperPlugin
99
+ version: '1.2.0'
100
+ main: com.example.plugin.EpicPlugin
101
+ bootstrapper: com.example.plugin.EpicPluginBootstrap
55
102
  api-version: '1.21'
56
103
  folia-supported: true
57
104
  authors:
58
105
  - DeveloperName
106
+ description: Production-grade Paper & Folia plugin built with modern architectural standards.
59
107
  ```
60
108
 
61
- ### Resep B: Brigadier Command Modern (Tanpa `plugin.yml`)
109
+ ### Recipe 2: Bootstrap Lifecycle & Modern Brigadier Command
110
+ Register commands through Paper's Lifecycle Events (Minecraft 1.20.6+ / 1.21+) without needing command entries in `plugin.yml`:
111
+
62
112
  ```java
63
- // Daftarkan di PluginBootstrap#bootstrap:
64
- context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
65
- event.registrar().register(
66
- Commands.literal("warp")
67
- .requires(source -> source.getSender().hasPermission("plugin.warp"))
68
- .then(Commands.argument("target", StringArgumentType.word())
69
- .suggests((ctx, builder) -> {
70
- builder.suggest("spawn");
71
- builder.suggest("arena");
72
- return builder.buildFuture();
73
- })
74
- .executes(ctx -> {
75
- String target = StringArgumentType.getString(ctx, "target");
76
- ctx.getSource().getSender().sendMessage(
77
- MiniMessage.miniMessage().deserialize("<green>Teleporting to <gold>" + target + "</gold>...</green>")
78
- );
79
- return Command.SINGLE_SUCCESS;
80
- })
81
- )
82
- .build(),
83
- "Warp command description",
84
- List.of("gotowarp")
85
- );
86
- });
113
+ package com.example.plugin;
114
+
115
+ import com.mojang.brigadier.Command;
116
+ import com.mojang.brigadier.arguments.DoubleArgumentType;
117
+ import com.mojang.brigadier.arguments.StringArgumentType;
118
+ import io.papermc.paper.command.brigadier.Commands;
119
+ import io.papermc.paper.plugin.bootstrap.BootstrapContext;
120
+ import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
121
+ import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
122
+ import net.kyori.adventure.text.minimessage.MiniMessage;
123
+ import org.jspecify.annotations.NullMarked;
124
+
125
+ import java.util.List;
126
+
127
+ @NullMarked
128
+ public class EpicPluginBootstrap implements PluginBootstrap {
129
+
130
+ @Override
131
+ public void bootstrap(BootstrapContext context) {
132
+ context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
133
+ final Commands registrar = event.registrar();
134
+
135
+ registrar.register(
136
+ Commands.literal("safepay")
137
+ .requires(source -> source.getSender().hasPermission("epicplugin.pay"))
138
+ .then(Commands.argument("target", StringArgumentType.word())
139
+ .then(Commands.argument("amount", DoubleArgumentType.doubleArg(0.01, 1_000_000_000.0))
140
+ .executes(ctx -> {
141
+ var sender = ctx.getSource().getSender();
142
+ String target = StringArgumentType.getString(ctx, "target");
143
+ double amount = DoubleArgumentType.getDouble(ctx, "amount");
144
+
145
+ // Strict security sanitization
146
+ if (!Double.isFinite(amount) || amount <= 0) {
147
+ sender.sendMessage(MiniMessage.miniMessage().deserialize("<red>Invalid payment amount!</red>"));
148
+ return Command.SINGLE_SUCCESS;
149
+ }
150
+
151
+ sender.sendMessage(MiniMessage.miniMessage().deserialize(
152
+ "<green>Sent <gold>$" + String.format("%.2f", amount) + "</gold> to <yellow>" + target + "</yellow>!</green>"
153
+ ));
154
+ return Command.SINGLE_SUCCESS;
155
+ })
156
+ )
157
+ )
158
+ .build(),
159
+ "Secure player payment with anti-overflow validation",
160
+ List.of("paysecure")
161
+ );
162
+ });
163
+ }
164
+ }
87
165
  ```
88
166
 
89
- ### Resep C: PersistentDataContainer (PDC) Item & Entity
167
+ ### Recipe 3: Modern Data Component Item Engineering (Zero-NBT)
90
168
  ```java
91
- // Menyimpan data pada ItemStack
92
- NamespacedKey KEY = new NamespacedKey(plugin, "item_level");
93
- meta.getPersistentDataContainer().set(KEY, PersistentDataType.INTEGER, 5);
169
+ package com.example.plugin.item;
170
+
171
+ import io.papermc.paper.datacomponent.DataComponentTypes;
172
+ import io.papermc.paper.datacomponent.item.CustomModelData;
173
+ import io.papermc.paper.datacomponent.item.FoodProperties;
174
+ import io.papermc.paper.datacomponent.item.ItemLore;
175
+ import net.kyori.adventure.text.Component;
176
+ import net.kyori.adventure.text.format.NamedTextColor;
177
+ import net.kyori.adventure.text.format.TextDecoration;
178
+ import org.bukkit.Material;
179
+ import org.bukkit.inventory.ItemStack;
180
+
181
+ import java.util.List;
94
182
 
95
- // Membaca data dengan fallback default:
96
- int level = meta.getPersistentDataContainer().getOrDefault(KEY, PersistentDataType.INTEGER, 1);
183
+ public final class ModernItemBuilder {
184
+
185
+ public static ItemStack createGodtierRelic() {
186
+ ItemStack item = ItemStack.of(Material.NETHERITE_SWORD);
187
+
188
+ // Type-safe direct component modification
189
+ item.setData(DataComponentTypes.ITEM_NAME, Component.text("Aegis of the Void", NamedTextColor.LIGHT_PURPLE, TextDecoration.BOLD));
190
+ item.setData(DataComponentTypes.LORE, ItemLore.lore(List.of(
191
+ Component.text("Ancient relic harvested from trial chambers.", NamedTextColor.GRAY),
192
+ Component.empty(),
193
+ Component.text("✦ Ability: Kinetic Shockwave on hit", NamedTextColor.AQUA)
194
+ )));
195
+
196
+ // Modern 1.20.5+ Custom Model Data (floats, strings, flags)
197
+ item.setData(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelData.customModelData()
198
+ .addFloat(2.5f)
199
+ .addString("aegis_void_model")
200
+ .build()
201
+ );
202
+
203
+ // Overrides
204
+ item.setData(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true);
205
+ item.setData(DataComponentTypes.MAX_STACK_SIZE, 1);
206
+ item.setData(DataComponentTypes.UNBREAKABLE, true);
207
+
208
+ return item;
209
+ }
210
+ }
97
211
  ```
98
212
 
99
- ### Resep D: GUI Menu Aman dari Eksploitasi
213
+ ### Recipe 4: Zero-Lag Display Entity Hologram Engine
100
214
  ```java
101
- // 1. Buat Custom InventoryHolder
102
- public class MenuHolder implements InventoryHolder {
103
- private final Inventory inv;
104
- public MenuHolder(Component title, int size) {
105
- this.inv = Bukkit.createInventory(this, size, title);
215
+ package com.example.plugin.display;
216
+
217
+ import net.kyori.adventure.text.Component;
218
+ import org.bukkit.Color;
219
+ import org.bukkit.Location;
220
+ import org.bukkit.entity.Display;
221
+ import org.bukkit.entity.TextDisplay;
222
+ import org.bukkit.util.Transformation;
223
+ import org.joml.AxisAngle4f;
224
+ import org.joml.Vector3f;
225
+
226
+ public final class DisplayHologramEngine {
227
+
228
+ /** Spawns a floating holographic sign with smooth client-side interpolation */
229
+ public static TextDisplay spawnSmoothHologram(Location loc, Component text) {
230
+ return loc.getWorld().spawn(loc, TextDisplay.class, display -> {
231
+ display.text(text);
232
+ display.setBillboard(Display.Billboard.CENTER);
233
+ display.setDefaultBackground(false);
234
+ display.setBackgroundColor(Color.fromARGB(150, 20, 20, 30));
235
+ display.setShadowed(true);
236
+
237
+ // Client-side smooth interpolation (20 ticks = 1 second)
238
+ display.setInterpolationDuration(20);
239
+ display.setInterpolationDelay(0);
240
+
241
+ // Transformation via JOML
242
+ Transformation transform = new Transformation(
243
+ new Vector3f(0f, 0f, 0f),
244
+ new AxisAngle4f(0f, 0f, 1f, 0f),
245
+ new Vector3f(1.1f, 1.1f, 1.1f),
246
+ new AxisAngle4f(0f, 0f, 1f, 0f)
247
+ );
248
+ display.setTransformation(transform);
249
+ });
106
250
  }
107
- @Override public Inventory getInventory() { return inv; }
108
251
  }
252
+ ```
253
+
254
+ ### Recipe 5: 1.21 Tricky Trials Mace & Combat Physics
255
+ ```java
256
+ package com.example.plugin.combat;
257
+
258
+ import net.kyori.adventure.text.Component;
259
+ import net.kyori.adventure.text.format.NamedTextColor;
260
+ import org.bukkit.Location;
261
+ import org.bukkit.Material;
262
+ import org.bukkit.Particle;
263
+ import org.bukkit.Sound;
264
+ import org.bukkit.entity.LivingEntity;
265
+ import org.bukkit.entity.Player;
266
+ import org.bukkit.event.EventHandler;
267
+ import org.bukkit.event.EventPriority;
268
+ import org.bukkit.event.Listener;
269
+ import org.bukkit.event.entity.EntityDamageByEntityEvent;
270
+ import org.bukkit.util.Vector;
271
+
272
+ public final class MaceKineticCombatListener implements Listener {
273
+
274
+ @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
275
+ public void onMaceSmash(EntityDamageByEntityEvent event) {
276
+ if (!(event.getDamager() instanceof Player attacker)) return;
277
+ if (!(event.getEntity() instanceof LivingEntity victim)) return;
109
278
 
110
- // 2. Di Listener:
111
- @EventHandler
112
- public void onMenuClick(InventoryClickEvent event) {
113
- if (event.getInventory().getHolder() instanceof MenuHolder) {
114
- event.setCancelled(true); // Selalu batalkan pergerakan item
115
- ItemStack item = event.getCurrentItem();
116
- if (item != null && item.hasItemMeta()) {
117
- // Baca aksi dari PDC
279
+ if (attacker.getInventory().getItemInMainHand().getType() != Material.MACE) return;
280
+
281
+ float fallDistance = attacker.getFallDistance();
282
+ if (fallDistance > 1.5f) {
283
+ double extraDmg = Math.min(fallDistance * 3.5, 45.0);
284
+ event.setDamage(event.getDamage() + extraDmg);
285
+
286
+ Location loc = victim.getLocation();
287
+ loc.getWorld().spawnParticle(Particle.TRIAL_SPAWNER_DETECTION, loc.add(0, 0.5, 0), 30, 0.5, 0.2, 0.5, 0.05);
288
+ loc.getWorld().playSound(loc, Sound.ITEM_MACE_SMASH_GROUND, 1.2f, 1.0f);
289
+
290
+ // Shockwave knockback
291
+ loc.getWorld().getNearbyLivingEntities(loc, 4.5, e -> !e.equals(attacker) && !e.equals(victim))
292
+ .forEach(e -> {
293
+ Vector impulse = e.getLocation().toVector().subtract(loc.toVector()).normalize().multiply(1.3).setY(0.4);
294
+ e.setVelocity(impulse);
295
+ });
296
+
297
+ attacker.sendActionBar(Component.text("⚡ SMASH IMPACT: +" + String.format("%.1f", extraDmg) + " DMG", NamedTextColor.GOLD));
118
298
  }
119
299
  }
120
300
  }
121
301
  ```
122
302
 
123
- ### Resep E: Universal Folia / Paper Scheduler Adapter
303
+ ### Recipe 6: Anti-Dupe GUI Controller with Debounce
124
304
  ```java
125
- public static void runForPlayer(Plugin plugin, Player player, Runnable runnable) {
126
- try {
127
- Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
128
- player.getScheduler().run(plugin, task -> runnable.run(), null);
129
- } catch (ClassNotFoundException e) {
130
- Bukkit.getScheduler().runTask(plugin, runnable);
305
+ package com.example.plugin.gui;
306
+
307
+ import net.kyori.adventure.text.Component;
308
+ import org.bukkit.Bukkit;
309
+ import org.bukkit.entity.Player;
310
+ import org.bukkit.event.EventHandler;
311
+ import org.bukkit.event.EventPriority;
312
+ import org.bukkit.event.Listener;
313
+ import org.bukkit.event.inventory.InventoryClickEvent;
314
+ import org.bukkit.event.inventory.InventoryCloseEvent;
315
+ import org.bukkit.event.inventory.InventoryDragEvent;
316
+ import org.bukkit.inventory.Inventory;
317
+ import org.bukkit.inventory.InventoryHolder;
318
+
319
+ import java.util.Map;
320
+ import java.util.UUID;
321
+ import java.util.concurrent.ConcurrentHashMap;
322
+
323
+ public class SafeMenuController implements InventoryHolder, Listener {
324
+
325
+ private final Inventory inventory;
326
+ private final Map<UUID, Long> clickDebounce = new ConcurrentHashMap<>();
327
+
328
+ public SafeMenuController(Component title, int size) {
329
+ this.inventory = Bukkit.createInventory(this, size, title);
330
+ }
331
+
332
+ @Override
333
+ public Inventory getInventory() {
334
+ return inventory;
335
+ }
336
+
337
+ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
338
+ public void onClick(InventoryClickEvent event) {
339
+ if (!(event.getInventory().getHolder() instanceof SafeMenuController)) {
340
+ return;
341
+ }
342
+
343
+ // 1. Always cancel item movement
344
+ event.setCancelled(true);
345
+
346
+ if (!(event.getWhoClicked() instanceof Player player)) {
347
+ return;
348
+ }
349
+
350
+ // 2. Click-spam protection (200ms debounce)
351
+ long now = System.currentTimeMillis();
352
+ long lastClick = clickDebounce.getOrDefault(player.getUniqueId(), 0L);
353
+ if (now - lastClick < 200) {
354
+ return;
355
+ }
356
+ clickDebounce.put(player.getUniqueId(), now);
357
+
358
+ // 3. Process only top menu clicks
359
+ if (event.getClickedInventory() == event.getInventory()) {
360
+ handleSlotAction(player, event.getSlot());
361
+ }
362
+ }
363
+
364
+ @EventHandler
365
+ public void onDrag(InventoryDragEvent event) {
366
+ if (event.getInventory().getHolder() instanceof SafeMenuController) {
367
+ event.setCancelled(true);
368
+ }
369
+ }
370
+
371
+ @EventHandler
372
+ public void onClose(InventoryCloseEvent event) {
373
+ if (event.getInventory().getHolder() instanceof SafeMenuController) {
374
+ clickDebounce.remove(event.getPlayer().getUniqueId());
375
+ }
376
+ }
377
+
378
+ private void handleSlotAction(Player player, int slot) {
379
+ // Safe slot logic
131
380
  }
132
381
  }
133
382
  ```
134
383
 
135
- ### Resep F: Raycast & Partikel Sihir
384
+ ### Recipe 7: Universal Folia & Paper Scheduler Adapter
136
385
  ```java
137
- Location eye = player.getEyeLocation();
138
- Vector dir = eye.getDirection().normalize();
386
+ package com.example.plugin.util;
387
+
388
+ import org.bukkit.Bukkit;
389
+ import org.bukkit.Location;
390
+ import org.bukkit.entity.Entity;
391
+ import org.bukkit.plugin.Plugin;
392
+
393
+ import java.util.concurrent.TimeUnit;
394
+
395
+ public final class UniversalScheduler {
396
+
397
+ private static final boolean IS_FOLIA;
398
+
399
+ static {
400
+ boolean folia;
401
+ try {
402
+ Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
403
+ folia = true;
404
+ } catch (ClassNotFoundException e) {
405
+ folia = false;
406
+ }
407
+ IS_FOLIA = folia;
408
+ }
409
+
410
+ public static boolean isFolia() {
411
+ return IS_FOLIA;
412
+ }
139
413
 
140
- for (double d = 1; d <= 25; d += 0.5) {
141
- Location p = eye.clone().add(dir.clone().multiply(d));
142
- player.getWorld().spawnParticle(Particle.FLAME, p, 1, 0, 0, 0, 0);
414
+ public static void runForEntity(Plugin plugin, Entity entity, Runnable runnable) {
415
+ if (IS_FOLIA) {
416
+ entity.getScheduler().run(plugin, task -> runnable.run(), null);
417
+ } else {
418
+ Bukkit.getScheduler().runTask(plugin, runnable);
419
+ }
420
+ }
421
+
422
+ public static void runAtLocation(Plugin plugin, Location location, Runnable runnable) {
423
+ if (IS_FOLIA) {
424
+ Bukkit.getRegionScheduler().execute(plugin, location, runnable);
425
+ } else {
426
+ Bukkit.getScheduler().runTask(plugin, runnable);
427
+ }
428
+ }
429
+
430
+ public static void runAsync(Plugin plugin, Runnable runnable) {
431
+ if (IS_FOLIA) {
432
+ Bukkit.getAsyncScheduler().runNow(plugin, task -> runnable.run());
433
+ } else {
434
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable);
435
+ }
436
+ }
437
+
438
+ public static void runAsyncDelayed(Plugin plugin, Runnable runnable, long delayMs) {
439
+ if (IS_FOLIA) {
440
+ Bukkit.getAsyncScheduler().runDelayed(plugin, task -> runnable.run(), delayMs, TimeUnit.MILLISECONDS);
441
+ } else {
442
+ long ticks = Math.max(1L, delayMs / 50L);
443
+ Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, ticks);
444
+ }
445
+ }
143
446
  }
144
447
  ```
145
448
 
146
449
  ---
147
450
 
148
- ## 4. Anti-Patterns yang Wajib Dihindari
451
+ ## 5. Anti-Patterns vs Modern Standards
149
452
 
150
- | Jangan Pernah Dilakukan ❌ | Cara yang Benar ✔️ |
151
- | :--- | :--- |
152
- | `ChatColor.RED + "Pesan"` | `MiniMessage.miniMessage().deserialize("<red>Pesan</red>")` |
153
- | `public static Set<Player> players;` | `public static Set<UUID> playerUuids;` (Cegah memory leak) |
154
- | `Bukkit.getScheduler()` di server Folia | Gunakan `RegionScheduler`, `EntityScheduler`, atau `AsyncScheduler` |
155
- | `world.getChunkAt(x, z)` acak di loop | `world.getChunkAtAsync(x, z)` |
156
- | `new NamespacedKey(...)` per hit/tick | Simpan `NamespacedKey` di `public static final` |
157
- | Mencocokkan nama item lore untuk aksi GUI | Gunakan `PersistentDataContainer` (PDC) pada item |
158
- | Query database di tick thread | Jalankan via `CompletableFuture.supplyAsync(...)` |
453
+ | Never Do This ❌ | Hazard / Vulnerability ⚠️ | Safe Modern Pattern ✔️ |
454
+ | :--- | :--- | :--- |
455
+ | `ChatColor.RED + "Message"` | Deprecated & breaks console/client formatting | `MiniMessage.miniMessage().deserialize("<red>Message</red>")` |
456
+ | `public static Set<Player> list;` | Causes massive **Memory Leaks** of worlds & connections | `public static Set<UUID> list;` |
457
+ | Direct NBT manipulation (`net.minecraft.nbt`) | Hard crashes on 1.20.5+ / 1.21+ / 26.x | `ItemStack#setData(DataComponentTypes.XXX)` |
458
+ | `ArmorStand` floating text holograms | Extreme entity ticking CPU overhead | `TextDisplay` with JOML interpolated transformations |
459
+ | `world.getChunkAt(x, z)` in a loop | Freezes main server tick thread (major TPS drops) | `world.getChunkAtAsync(x, z)` |
460
+ | `Bukkit.getScheduler()` on Folia | Throws exception or causes thread-safety desync | Use `RegionScheduler`, `EntityScheduler`, or `UniversalScheduler` |
461
+ | Modifying inventories asynchronously | Causes item duplication and memory corruption | Always dispatch inventory modifications to entity region thread |
462
+ | Concatenating raw SQL queries | High vulnerability to **SQL Injection** | Always use `PreparedStatement` with `?` parameters |
463
+ | Unbounded numbers in economy commands | Susceptible to `NaN`, negative amounts, and overflow | Validate `Double.isFinite()` and enforce minimum/maximum boundaries |
159
464
 
160
465
  ---
161
466
 
162
- ## 5. Referensi Kode Asli di Folder `references/`
467
+ ## 6. Pre-Release Security Audit Checklist
468
+
469
+ Prior to releasing a plugin to production or public servers:
163
470
 
164
- - **Arsitektur Enterprise & Database Async**: Periksa [`references/luckperms`](file:///Users/ardianryan/Documents/minecraft-plugin-skill/references/luckperms).
165
- - **Performa Async Chunks & Schedulers**: Periksa [`references/chunky`](file:///Users/ardianryan/Documents/minecraft-plugin-skill/references/chunky).
166
- - **Paperweight & Incendo Cloud Commands**: Periksa [`references/squaremap`](file:///Users/ardianryan/Documents/minecraft-plugin-skill/references/squaremap).
167
- - **MiniMessage & Paper Manifest**: Periksa [`references/miniplaceholders`](file:///Users/ardianryan/Documents/minecraft-plugin-skill/references/miniplaceholders).
168
- - **Ekonomi & Permissions Hooking**: Periksa [`references/vault-api`](file:///Users/ardianryan/Documents/minecraft-plugin-skill/references/vault-api).
169
- - **Setup Server Pengujian Otomatis**: Periksa [`references/run-paper`](file:///Users/ardianryan/Documents/minecraft-plugin-skill/references/run-paper).
170
- - **Display Packet & Hologram**: Periksa [`references/decentholograms`](file:///Users/ardianryan/Documents/minecraft-plugin-skill/references/decentholograms).
471
+ - [ ] `paper-plugin.yml` is defined with `folia-supported: true`.
472
+ - [ ] Zero references to `org.bukkit.ChatColor` across the codebase.
473
+ - [ ] Direct NBT or legacy ItemMeta serialization replaced by `DataComponentTypes`.
474
+ - [ ] Holograms and floating 3D icons use `Display` entities rather than armor stands.
475
+ - [ ] All command registration uses modern Brigadier (`LifecycleEvents.COMMANDS`).
476
+ - [ ] No direct storage of `Player`, `World`, or `Entity` instances in static collections.
477
+ - [ ] All File, Database, and HTTP operations execute 100% asynchronously.
478
+ - [ ] Custom GUIs implement `setCancelled(true)` and click debounce cooldowns.
479
+ - [ ] Custom items are identified and verified via `PersistentDataContainer` (PDC).
480
+ - [ ] All database queries use connection pooling enclosed in `try-with-resources`.
481
+ - [ ] Plugin passes `npx papermc-skill audit` with zero issues.
@@ -1,2 +1,2 @@
1
- #Tue Sep 15 04:15:45 UTC 2026
1
+ #Wed Nov 11 04:17:31 UTC 2026
2
2
  gradle.version=9.7.1