papermc-skill 1.0.1 → 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.
Files changed (43) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +12 -12
  3. package/assets/banner-animated.svg +360 -0
  4. package/assets/banner.svg +360 -0
  5. package/bin/cli.js +107 -2
  6. package/docs/01-modern-paper-architecture.md +48 -51
  7. package/docs/02-adventure-and-minimessage.md +48 -52
  8. package/docs/03-persistent-data-container.md +39 -39
  9. package/docs/04-commands-and-brigadier.md +32 -35
  10. package/docs/05-concurrency-and-folia.md +44 -44
  11. package/docs/06-events-and-listeners.md +30 -31
  12. package/docs/07-best-practices-and-perf.md +39 -40
  13. package/docs/08-gui-and-menus.md +34 -37
  14. package/docs/09-custom-items-and-combat.md +25 -33
  15. package/docs/10-database-and-storage.md +27 -27
  16. package/docs/11-security-and-exploit-prevention.md +233 -0
  17. package/package.json +2 -1
  18. package/skills/paper-plugin-dev/SKILL.md +391 -114
  19. package/templates/paper-modern-template/.gradle/9.7.1/checksums/checksums.lock +0 -0
  20. package/templates/paper-modern-template/.gradle/9.7.1/checksums/md5-checksums.bin +0 -0
  21. package/templates/paper-modern-template/.gradle/9.7.1/checksums/sha1-checksums.bin +0 -0
  22. package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.bin +0 -0
  23. package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.lock +0 -0
  24. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.bin +0 -0
  25. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.lock +0 -0
  26. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/resourceHashesCache.bin +0 -0
  27. package/templates/paper-modern-template/.gradle/9.7.1/kotlin-dsl-plugin-entries/kotlin-dsl-plugin-entries.lock +0 -0
  28. package/templates/paper-modern-template/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  29. package/templates/paper-modern-template/.gradle/buildOutputCleanup/cache.properties +1 -1
  30. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/ExamplePaperPlugin.class +0 -0
  31. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/PaperPluginBootstrap.class +0 -0
  32. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/command/HelloCommand.class +0 -0
  33. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/listener/PlayerEventListener.class +0 -0
  34. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/util/SchedulerAdapter.class +0 -0
  35. package/templates/paper-modern-template/build/tmp/compileJava/previous-compilation-data.bin +0 -0
  36. package/templates/paper-modern-template/build.gradle.kts +7 -7
  37. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/ExamplePaperPlugin.java +6 -6
  38. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/PaperPluginBootstrap.java +2 -2
  39. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/command/HelloCommand.java +2 -4
  40. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/data/PluginDataKeys.java +2 -2
  41. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/listener/PlayerEventListener.java +6 -7
  42. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/util/SchedulerAdapter.java +6 -6
  43. package/templates/paper-modern-template/src/main/resources/config.yml +5 -5
@@ -1,17 +1,17 @@
1
- # Command System Modern: Brigadier & Cloud di Paper
1
+ # Modern Command Systems: Native Brigadier & Cloud in Paper
2
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.
3
+ The Minecraft Java command system has evolved substantially. Legacy Bukkit patterns like `CommandExecutor`, `TabCompleter`, and command declarations in `plugin.yml` have been superseded by:
4
+ 1. **Paper Native Brigadier API** (introduced in Paper 1.20.6+ via `LifecycleEvents.COMMANDS`).
5
+ 2. **Incendo Cloud Framework** (`cloud-paper`): The enterprise standard for annotation-driven and builder-based multi-platform command handling.
6
6
 
7
7
  ---
8
8
 
9
9
  ## 1. Native Paper Brigadier Commands (Paper 1.20.6+)
10
10
 
11
- Mojang Brigadier adalah command dispatcher engine resmi Minecraft. Paper mengekspos API Brigadier secara langsung tanpa perlu hack NMS atau dependensi tambahan.
11
+ Mojang Brigadier is Minecraft's official command dispatcher and parser engine. Paper exposes native Brigadier bindings directly without requiring NMS hacks or external shading.
12
12
 
13
- ### Registrasi Melalui Lifecycle Events
14
- Command didaftarkan saat fase Bootstrap atau saat event `LifecycleEvents.COMMANDS` dipicu:
13
+ ### Registering Commands via Lifecycle Events
14
+ Commands are registered during the Bootstrap phase or when the `LifecycleEvents.COMMANDS` event triggers:
15
15
 
16
16
  ```java
17
17
  package com.example.plugin;
@@ -24,7 +24,6 @@ import io.papermc.paper.plugin.bootstrap.BootstrapContext;
24
24
  import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
25
25
  import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
26
26
  import net.kyori.adventure.text.minimessage.MiniMessage;
27
- import org.bukkit.entity.Player;
28
27
 
29
28
  public final class CommandBootstrapper implements PluginBootstrap {
30
29
 
@@ -33,11 +32,11 @@ public final class CommandBootstrapper implements PluginBootstrap {
33
32
  context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
34
33
  final Commands commands = event.registrar();
35
34
 
36
- // Membangun pohon syntax command /teleportzone <nama> [radius]
35
+ // Construct syntax tree: /teleportzone <name> [radius]
37
36
  commands.register(
38
37
  Commands.literal("teleportzone")
39
38
  .requires(source -> source.getSender().hasPermission("plugin.command.teleportzone"))
40
- .then(Commands.argument("nama", StringArgumentType.word())
39
+ .then(Commands.argument("name", StringArgumentType.word())
41
40
  .suggests((ctx, builder) -> {
42
41
  builder.suggest("lobby");
43
42
  builder.suggest("arena");
@@ -47,12 +46,12 @@ public final class CommandBootstrapper implements PluginBootstrap {
47
46
  .then(Commands.argument("radius", IntegerArgumentType.integer(1, 100))
48
47
  .executes(ctx -> {
49
48
  final CommandSourceStack source = ctx.getSource();
50
- final String zone = StringArgumentType.getString(ctx, "nama");
49
+ final String zone = StringArgumentType.getString(ctx, "name");
51
50
  final int radius = IntegerArgumentType.getInteger(ctx, "radius");
52
51
 
53
52
  source.getSender().sendMessage(
54
53
  MiniMessage.miniMessage().deserialize(
55
- "<green>Mempersiapkan teleportasi ke zona <gold>" + zone + "</gold> dengan radius <yellow>" + radius + "</yellow>!</green>"
54
+ "<green>Preparing teleportation to zone <gold>" + zone + "</gold> with radius <yellow>" + radius + "</yellow>!</green>"
56
55
  )
57
56
  );
58
57
  return com.mojang.brigadier.Command.SINGLE_SUCCESS;
@@ -60,43 +59,42 @@ public final class CommandBootstrapper implements PluginBootstrap {
60
59
  )
61
60
  .executes(ctx -> {
62
61
  final CommandSourceStack source = ctx.getSource();
63
- final String zone = StringArgumentType.getString(ctx, "nama");
62
+ final String zone = StringArgumentType.getString(ctx, "name");
64
63
  source.getSender().sendMessage(
65
- MiniMessage.miniMessage().deserialize("<green>Teleportasi default ke zona <gold>" + zone + "</gold>!</green>")
64
+ MiniMessage.miniMessage().deserialize("<green>Default teleportation to zone <gold>" + zone + "</gold>!</green>")
66
65
  );
67
66
  return com.mojang.brigadier.Command.SINGLE_SUCCESS;
68
67
  })
69
68
  )
70
69
  .build(),
71
- "Perintah teleportasi zona modern",
72
- java.util.List.of("tpzone") // alias
70
+ "Modern zone teleportation command",
71
+ java.util.List.of("tpzone") // aliases
73
72
  );
74
73
  });
75
74
  }
76
75
  }
77
76
  ```
78
77
 
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).
78
+ ### Advantages of Native Paper Brigadier:
79
+ - **Client-Side Syntax Highlighting**: Minecraft clients highlight syntax errors in real-time before sending the command packet.
80
+ - **Automated Tab-Completion**: Eliminates complex manual `TabCompleter` string matching.
81
+ - **Strict Argument Typing**: Automatically validates argument bounds and rejects invalid types.
83
82
 
84
83
  ---
85
84
 
86
85
  ## 2. Incendo Cloud Command Framework (`cloud-paper`)
87
86
 
88
- Untuk plugin yang membutuhkan dependensi injeksi, parsing objek otomatis (seperti `Player`, `OfflinePlayer`, `World`, `Duration`), dan command yang sangat banyak, **Cloud** adalah pilihan terpopuler.
87
+ For large plugin codebases requiring dependency injection, automatic Bukkit argument parsing (`Player`, `OfflinePlayer`, `World`, `Duration`), and hundreds of sub-commands, **Incendo Cloud** is the leading solution.
89
88
 
90
- ### Dependensi Gradle:
89
+ ### Gradle Dependencies:
91
90
  ```kotlin
92
91
  dependencies {
93
92
  implementation("org.incendo:cloud-paper:2.0.0-beta.10")
94
- // atau cloud-annotations jika menyukai gaya @Command
95
93
  implementation("org.incendo:cloud-annotations:2.0.0-beta.10")
96
94
  }
97
95
  ```
98
96
 
99
- ### Inisialisasi Cloud PaperCommandManager:
97
+ ### Initializing Cloud PaperCommandManager:
100
98
  ```java
101
99
  import org.incendo.cloud.paper.PaperCommandManager;
102
100
  import org.incendo.cloud.execution.ExecutionCoordinator;
@@ -111,8 +109,7 @@ public class PluginCommands {
111
109
  ExecutionCoordinator.simpleCoordinator()
112
110
  );
113
111
 
114
- // Daftarkan brigadier mapper jika didukung
115
- if (manager.hasCapability(org.incendo.cloud.paper.PaperCommandManager.Capability.BRIGADIER)) {
112
+ if (manager.hasCapability(PaperCommandManager.Capability.BRIGADIER)) {
116
113
  manager.registerBrigadier();
117
114
  }
118
115
 
@@ -121,7 +118,7 @@ public class PluginCommands {
121
118
  }
122
119
  ```
123
120
 
124
- ### Contoh Perintah dengan Cloud Builder:
121
+ ### Building Commands with Cloud:
125
122
  ```java
126
123
  import org.incendo.cloud.paper.PaperCommandManager;
127
124
  import org.incendo.cloud.bukkit.parser.PlayerParser;
@@ -138,8 +135,8 @@ public void registerGiveRewardCommand(PaperCommandManager<CommandSender> manager
138
135
  CommandSender sender = commandContext.sender();
139
136
  Player target = commandContext.get("target");
140
137
 
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>"));
138
+ target.sendMessage(MiniMessage.miniMessage().deserialize("<gold>You received a special reward!</gold>"));
139
+ sender.sendMessage(MiniMessage.miniMessage().deserialize("<green>Reward dispatched to " + target.getName() + ".</green>"));
143
140
  })
144
141
  );
145
142
  }
@@ -147,11 +144,11 @@ public void registerGiveRewardCommand(PaperCommandManager<CommandSender> manager
147
144
 
148
145
  ---
149
146
 
150
- ## 3. Komparasi: Kapan Memilih Apa?
147
+ ## 3. Comparison: When to Use Which?
151
148
 
152
- | Aspek | Paper Native Brigadier | Incendo Cloud |
149
+ | Aspect | Paper Native Brigadier | Incendo Cloud |
153
150
  | :--- | :--- | :--- |
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 |
151
+ | **Artifact Overhead** | Zero-dependency (built directly into Paper) | Adds ~300KB-800KB (requires shadowing & relocating) |
152
+ | **Complexity Fit** | Best for small-to-medium command suites | Best for complex enterprise command structures |
153
+ | **Parsers** | Requires manual mapping for Bukkit types | Built-in parsers for Players, Locations, Selectors, etc. |
154
+ | **Portability** | Paper 1.20.6+ exclusive | Cross-platform (Velocity, BungeeCord, Fabric, Paper) |
@@ -1,38 +1,38 @@
1
- # Konkurensi, Threading & Kompatibilitas Folia
1
+ # Concurrency, Threading & Folia Multi-Thread Compatibility
2
2
 
3
- Salah satu perubahan paling revolusioner di ekosistem Paper adalah kedatangan **Folia**. Folia memecah satu "Server Main Thread" Minecraft menjadi ratusan thread regional mandiri (**Threaded Regions**).
3
+ One of the most revolutionary milestones in the PaperMC ecosystem is the emergence of **Folia**. Folia breaks Minecraft's monolithic "Server Main Thread" into hundreds of independent regional threads (**Threaded Regions**).
4
4
 
5
- Konsekuensinya, metode konkurensi lama seperti `Bukkit.getScheduler()` atau mengakses state world dari sembarang thread akan menyebabkan **`IllegalStateException` atau crash fatal** di Folia.
5
+ Consequently, legacy Bukkit concurrency assumptions like `Bukkit.getScheduler()` or mutating world state from arbitrary threads result in **`IllegalStateException` crashes or data corruption** on Folia.
6
6
 
7
7
  ---
8
8
 
9
- ## 1. Model Threading: Bukkit vs Paper vs Folia
9
+ ## 1. Threading Architecture: Bukkit vs Paper vs Folia
10
10
 
11
11
  ```
12
12
  [Bukkit / Vanilla]
13
- Satu Main Thread ─── Tick Semua Dunia, Entity, Blok, Redstone, AI, Chunk ───>
14
-
15
- [Folia Regionized Threading]
16
- Region Thread 1 ─── Tick Region Dunia A (Chunk 0..32, 0..32) ───>
17
- Region Thread 2 ─── Tick Region Dunia A (Chunk 64..96, 64..96) ──>
18
- Region Thread 3 ─── Tick Nether (Region B) ──────────────────────>
19
- Global Thread ─── Tick Weather, Time, Player List, Commands ───>
20
- Async Threads ─── Network I/O, File Saving, Database Queries ──>
13
+ Single Main Thread ─── Ticks All Worlds, Entities, Blocks, Redstone, AI, Chunks ───>
14
+
15
+ [Folia Regionized Multi-Threading]
16
+ Region Thread 1 ─── Ticks Overworld Region A (Chunks 0..32, 0..32) ───>
17
+ Region Thread 2 ─── Ticks Overworld Region B (Chunks 64..96, 64..96) ──>
18
+ Region Thread 3 ─── Ticks Nether Region C ────────────────────────────>
19
+ Global Thread ─── Ticks Weather, Time, Player List, Commands ───────>
20
+ Async Threads ─── Network I/O, File Saving, Database Queries ────────>
21
21
  ```
22
22
 
23
- Di Folia:
24
- - Tidak ada lagi konsep "The Main Thread".
25
- - Objek `World`, `Entity`, dan `Block` hanya boleh diakses di **thread region yang memiliki chunk/lokasi tersebut**.
26
- - Mengakses entity pemain dari thread lain akan melempar error `Plugin attempted to access entity from wrong thread`.
23
+ In Folia:
24
+ - There is no single "Main Thread".
25
+ - `World`, `Entity`, and `Block` instances may only be queried or mutated on the **region thread owning that coordinate/chunk**.
26
+ - Accessing an entity from a foreign region thread throws `IllegalStateException: Plugin attempted to access entity from wrong thread`.
27
27
 
28
28
  ---
29
29
 
30
- ## 2. Hirarki Scheduler Modern di Folia & Paper
30
+ ## 2. Modern Scheduler Hierarchy in Paper & Folia
31
31
 
32
- Paper memperkenalkan scheduler baru yang kompatibel dengan Folia dan Paper standar:
32
+ Paper introduces clean scheduler primitives that operate seamlessly across both multi-threaded Folia and single-threaded Paper:
33
33
 
34
34
  ### 1. `RegionScheduler`
35
- Digunakan untuk mengeksekusi task pada lokasi dunia atau koordinat chunk tertentu.
35
+ Used to execute tasks bound to specific world coordinates or chunks:
36
36
 
37
37
  ```java
38
38
  import org.bukkit.Bukkit;
@@ -41,59 +41,59 @@ import org.bukkit.plugin.Plugin;
41
41
 
42
42
  Location loc = player.getLocation();
43
43
 
44
- // Menjalankan task 1-kali pada thread region lokasi tersebut
44
+ // Run a one-time task on the region thread owning this location
45
45
  Bukkit.getRegionScheduler().execute(plugin, loc, () -> {
46
46
  loc.getBlock().setType(org.bukkit.Material.GOLD_BLOCK);
47
47
  });
48
48
 
49
- // Menjalankan task berulang (repeating) pada region
49
+ // Run a repeating task on the region thread
50
50
  Bukkit.getRegionScheduler().runAtFixedRate(plugin, loc, task -> {
51
51
  loc.getWorld().spawnParticle(org.bukkit.Particle.FLAME, loc, 5);
52
- }, 20L, 20L); // delay 20 tick, period 20 tick
52
+ }, 20L, 20L); // 20 ticks initial delay, 20 ticks period
53
53
  ```
54
54
 
55
55
  ### 2. `EntityScheduler`
56
- Setiap `Entity` memiliki scheduler tersendiri. Task akan dieksekusi tepat pada thread region di mana entity tersebut sedang berada, bahkan jika entity tersebut bergerak/teleport antar region.
56
+ Every `Entity` owns its own dedicated scheduler. Tasks execute directly on whichever region thread the entity currently occupies, even across chunk border transitions:
57
57
 
58
58
  ```java
59
59
  player.getScheduler().run(plugin, task -> {
60
60
  player.giveExp(10);
61
- player.sendMessage("Bonus EXP didapatkan!");
61
+ player.sendMessage("Bonus EXP received!");
62
62
  }, () -> {
63
- // Callback jika entity telah mati atau tidak valid sebelum task sempat berjalan
64
- plugin.getLogger().warning("Pemain logout sebelum exp diberikan.");
63
+ // Retired callback if entity is dead or invalidated before execution
64
+ plugin.getLogger().warning("Player disconnected before reward was applied.");
65
65
  });
66
66
  ```
67
67
 
68
68
  ### 3. `GlobalRegionScheduler`
69
- Digunakan untuk hal-hal yang bersifat global server dan tidak terikat pada koordinat block/entity tertentu (misal: pengumuman berkala chat global, update scoreboard global, waktu dunia).
69
+ Used for server-wide tasks unconfined to specific coordinates (e.g., global announcements, world time, or weather):
70
70
 
71
71
  ```java
72
72
  Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, task -> {
73
- Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<gold>[Pengumuman] Kunjungi discord kami!</gold>"));
73
+ Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<gold>[Announcement] Join our Discord server!</gold>"));
74
74
  }, 100L, 1200L);
75
75
  ```
76
76
 
77
77
  ### 4. `AsyncScheduler`
78
- Untuk I/O, database query, request HTTP, komputasi berat non-Bukkit API. Menggunakan waktu nyata (`java.time.TimeUnit`), bukan tick game:
78
+ Used for I/O, database interactions, HTTP calls, and non-Bukkit mathematical computations. Operates using wall-clock time (`java.time.TimeUnit`) rather than game ticks:
79
79
 
80
80
  ```java
81
81
  import java.util.concurrent.TimeUnit;
82
82
 
83
83
  Bukkit.getAsyncScheduler().runDelayed(plugin, task -> {
84
- // Menjalankan query database MySQL / SQLite
84
+ // Run database query off the game thread
85
85
  database.savePlayerData(uuid, data);
86
86
  }, 5, TimeUnit.SECONDS);
87
87
  ```
88
88
 
89
89
  ---
90
90
 
91
- ## 3. Pola Abstraksi Scheduler Universal (Paper + Folia)
91
+ ## 3. Universal Scheduler Abstraction Pattern (Paper + Folia)
92
92
 
93
- Banyak plugin perlu berjalan di server Paper standar (single-thread) dan Folia sekaligus. Cara terbaik adalah membuat lapisan pembungkus (*abstraction wrapper*) seperti yang digunakan oleh **Chunky** dan **squaremap**:
93
+ Most production plugins must run identically on standard single-threaded Paper and multi-threaded Folia. The recommended approach is an abstraction layer (as pioneered by **Chunky** and **squaremap**):
94
94
 
95
95
  ```java
96
- public final class PlatformScheduler {
96
+ public final class UniversalScheduler {
97
97
 
98
98
  private static final boolean IS_FOLIA = checkFolia();
99
99
 
@@ -110,7 +110,7 @@ public final class PlatformScheduler {
110
110
  return IS_FOLIA;
111
111
  }
112
112
 
113
- public static void runLocation(Plugin plugin, Location location, Runnable runnable) {
113
+ public static void runAtLocation(Plugin plugin, Location location, Runnable runnable) {
114
114
  if (IS_FOLIA) {
115
115
  Bukkit.getRegionScheduler().execute(plugin, location, runnable);
116
116
  } else {
@@ -118,7 +118,7 @@ public final class PlatformScheduler {
118
118
  }
119
119
  }
120
120
 
121
- public static void runEntity(Plugin plugin, Entity entity, Runnable runnable) {
121
+ public static void runForEntity(Plugin plugin, Entity entity, Runnable runnable) {
122
122
  if (IS_FOLIA) {
123
123
  entity.getScheduler().run(plugin, task -> runnable.run(), null);
124
124
  } else {
@@ -138,13 +138,13 @@ public final class PlatformScheduler {
138
138
 
139
139
  ---
140
140
 
141
- ## 4. Aturan Wajib untuk Kompatibilitas Folia
141
+ ## 4. Mandatory Folia Compatibility Rules
142
142
 
143
- 1. **Deklarasikan di Manifest**:
144
- Tambahkan `folia-supported: true` di `paper-plugin.yml`. Jika tidak, server Folia akan menampilkan peringatan bahwa plugin Anda berpotensi tidak aman.
145
- 2. **Jangan Pernah Mengakses Chunk/Block dari Async Thread**:
146
- Gunakan `world.getChunkAtAsync(x, z)` yang mengembalikan `CompletableFuture<Chunk>`.
147
- 3. **Gunakan TeleportAsync**:
148
- Gunakan `entity.teleportAsync(location)` alih-alih `entity.teleport(location)`. Di Folia, teleportasi melintasi region thread yang berbeda harus dilakukan secara asinkron.
149
- 4. **Hindari State Statis Global**:
150
- Variabel static yang menyimpan list player atau entity aktif dapat mengalami *race condition* jika dimodifikasi oleh beberapa thread region secara paralel. Gunakan struktur data thread-safe (`ConcurrentHashMap`, `CopyOnWriteArrayList`) bila terpaksa.
143
+ 1. **Declare Compatibility in Manifest**:
144
+ Always include `folia-supported: true` in `paper-plugin.yml`. Without this, Folia flags your plugin as potentially hazardous upon boot.
145
+ 2. **Never Read Chunks/Blocks Synchronously Off-Thread**:
146
+ Always invoke `world.getChunkAtAsync(x, z)` which returns a `CompletableFuture<Chunk>`.
147
+ 3. **Use TeleportAsync**:
148
+ Always call `entity.teleportAsync(location)` instead of `entity.teleport(location)`. Across Folia, cross-region movement must be processed asynchronously.
149
+ 4. **Avoid Global Mutable Static Collections**:
150
+ Static lists of active players or entities experience severe race conditions when mutated by multiple region threads concurrently. Use thread-safe structures (`ConcurrentHashMap`) or delegate state to player PDC.
@@ -1,12 +1,12 @@
1
- # Sistem Event & Listener di Paper
1
+ # Event System & Paper Listeners
2
2
 
3
- Sistem event adalah mekanisme reaktif utama di Paper untuk merespons aksi pemain, lingkungan dunia, entitas, dan siklus server. Paper menyediakan banyak event eksklusif berkinerja tinggi serta integrasi penuh dengan Adventure Component.
3
+ The event bus is Paper's primary reactive mechanism for responding to player interactions, world updates, entity behaviors, and server lifecycle states. Paper provides exclusive high-performance events along with native Kyori Adventure Component integration.
4
4
 
5
5
  ---
6
6
 
7
- ## 1. Anatomi Listener Standar
7
+ ## 1. Anatomy of a Modern Listener
8
8
 
9
- Listener mengimplementasikan antarmuka `org.bukkit.event.Listener` dan menggunakan anotasi `@EventHandler`:
9
+ Listeners implement the `org.bukkit.event.Listener` interface and decorate handler methods with `@EventHandler`:
10
10
 
11
11
  ```java
12
12
  import org.bukkit.event.Listener;
@@ -26,35 +26,35 @@ public final class PlayerJoinListener implements Listener {
26
26
 
27
27
  @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
28
28
  public void onPlayerJoin(PlayerJoinEvent event) {
29
- // Paper menggunakan Adventure Component untuk joinMessage
29
+ // Paper natively uses Adventure Components for join messages
30
30
  event.joinMessage(
31
31
  MiniMessage.miniMessage().deserialize(
32
- "<gray>[<green>+</green>] <yellow>" + event.getPlayer().getName() + "</yellow> bergabung ke server!</gray>"
32
+ "<gray>[<green>+</green>] <yellow>" + event.getPlayer().getName() + "</yellow> joined the server!</gray>"
33
33
  )
34
34
  );
35
35
  }
36
36
  }
37
37
  ```
38
38
 
39
- ### Memahami `EventPriority`:
40
- Urutan eksekusi event dari pertama hingga terakhir:
41
- 1. `LOWEST` (dijalankan paling awal, bagus untuk observasi mentah)
39
+ ### Understanding `EventPriority`:
40
+ Execution sequence from first to last:
41
+ 1. `LOWEST` (Runs first, ideal for raw observation or early preprocessing)
42
42
  2. `LOW`
43
- 3. `NORMAL` (default)
43
+ 3. `NORMAL` (Default priority)
44
44
  4. `HIGH`
45
- 5. `HIGHEST` (dijalankan paling akhir sebelum event final)
46
- 6. `MONITOR` (**HANYA UNTUK MEMBACA/LOGGING**; DILARANG memodifikasi atau membatalkan event di priority ini karena plugin lain tidak akan dapat merespons perubahan Anda).
45
+ 5. `HIGHEST` (Runs last before final decision)
46
+ 6. `MONITOR` (**READ-ONLY / LOGGING ONLY**; strictly forbidden from modifying or cancelling events, as downstream plugins cannot observe changes).
47
47
 
48
- > **Penting**: Selalu tambahkan `ignoreCancelled = true` jika listener Anda tidak perlu memproses aksi yang sudah dibatalkan oleh plugin proteksi area (seperti WorldGuard/GriefPrevention).
48
+ > **Important**: Always specify `ignoreCancelled = true` if your listener does not need to process interactions already prevented by land protection plugins (such as WorldGuard or GriefPrevention).
49
49
 
50
50
  ---
51
51
 
52
- ## 2. Event Modern Eksklusif Paper
52
+ ## 2. Modern Paper-Exclusive Events
53
53
 
54
- Paper mengganti atau melengkapi beberapa event Bukkit lama dengan alternatif yang lebih efisien dan modern:
54
+ Paper introduces modern, high-performance replacements for legacy Bukkit events:
55
55
 
56
- ### `AsyncChatEvent` (Menggantikan `AsyncPlayerChatEvent`)
57
- Paper menggunakan `io.papermc.paper.event.player.AsyncChatEvent` yang bekerja langsung dengan `Component` dan MiniMessage:
56
+ ### `AsyncChatEvent` (Replaces `AsyncPlayerChatEvent`)
57
+ Paper's `io.papermc.paper.event.player.AsyncChatEvent` operates directly with `Component` and MiniMessage:
58
58
 
59
59
  ```java
60
60
  import io.papermc.paper.event.player.AsyncChatEvent;
@@ -63,7 +63,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
63
63
 
64
64
  @EventHandler
65
65
  public void onChat(AsyncChatEvent event) {
66
- // Memodifikasi format pesan dengan Adventure
66
+ // Format chat messages cleanly using Adventure
67
67
  event.renderer((source, sourceDisplayName, message, viewer) ->
68
68
  MiniMessage.miniMessage().deserialize("<gray>[Member] </gray>")
69
69
  .append(sourceDisplayName)
@@ -74,18 +74,18 @@ public void onChat(AsyncChatEvent event) {
74
74
  ```
75
75
 
76
76
  ### `PrePlayerAttackEntityEvent`
77
- Dijalankan sebelum kalkulasi serangan vanilla terjadi. Sangat ideal untuk sistem combat kustom, cooldown attack senjata khusus, atau verifikasi proteksi PvP.
77
+ Fires prior to vanilla attack calculations. Essential for custom combat mechanics, weapon cooldown verification, or anti-griefing checks.
78
78
 
79
79
  ### `ServerTickStartEvent` & `ServerTickEndEvent`
80
- Memonitor performa server secara presisi per-tick tanpa membebani scheduler.
80
+ Provides cycle-accurate server performance metrics per-tick without adding scheduler overhead.
81
81
 
82
82
  ---
83
83
 
84
- ## 3. Membuat Custom Event
84
+ ## 3. Creating Custom Events
85
85
 
86
- Anda dapat membuat event sendiri untuk mengekspos API plugin Anda kepada developer lain (seperti yang dilakukan oleh LuckPerms atau WorldGuard).
86
+ You can create custom events to expose an extensible public API for other plugins (following the design of LuckPerms or WorldGuard):
87
87
 
88
- ### Contoh Custom Event:
88
+ ### Custom Event Example:
89
89
  ```java
90
90
  package com.example.plugin.event;
91
91
 
@@ -103,7 +103,7 @@ public final class PlayerLevelUpEvent extends Event implements Cancellable {
103
103
  private boolean cancelled = false;
104
104
 
105
105
  public PlayerLevelUpEvent(Player player, int newLevel) {
106
- // false jika berjalan di main/region thread (Sync), true jika Async
106
+ // false for synchronous execution on main/region thread, true for asynchronous
107
107
  super(false);
108
108
  this.player = player;
109
109
  this.newLevel = newLevel;
@@ -140,24 +140,23 @@ public final class PlayerLevelUpEvent extends Event implements Cancellable {
140
140
  }
141
141
  ```
142
142
 
143
- ### Memanggil (Dispatch) Event:
143
+ ### Dispatching the Custom Event:
144
144
  ```java
145
145
  PlayerLevelUpEvent event = new PlayerLevelUpEvent(player, 10);
146
146
  Bukkit.getPluginManager().callEvent(event);
147
147
 
148
148
  if (!event.isCancelled()) {
149
- // Lanjutkan aksi jika event tidak dibatalkan oleh plugin lain
150
- player.sendActionBar(MiniMessage.miniMessage().deserialize("<green>Selamat, Anda naik level!</green>"));
149
+ player.sendActionBar(MiniMessage.miniMessage().deserialize("<green>Congratulations, you leveled up!</green>"));
151
150
  }
152
151
  ```
153
152
 
154
153
  ---
155
154
 
156
- ## 4. Unregistering Event & Pembersihan
155
+ ## 4. Listener Unregistration & Memory Hygiene
157
156
 
158
- Untuk mencegah memory leak saat reload atau saat plugin dinonaktifkan:
159
- - Semua event yang didaftarkan melalui `pluginManager.registerEvents(listener, this)` otomatis di-unregister saat `onDisable()`.
160
- - Jika Anda mendaftarkan event dinamis (misal per-minigame match), unregister secara manual ketika game selesai:
157
+ To prevent memory leaks across plugin reload or disable cycles:
158
+ - All listeners registered via `pluginManager.registerEvents(listener, this)` are unregistered automatically upon `onDisable()`.
159
+ - If you register dynamic short-lived listeners (such as for a temporary minigame session), unregister them explicitly upon session completion:
161
160
  ```java
162
161
  HandlerList.unregisterAll(matchListener);
163
162
  ```