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.
- package/.cursor/rules/paper-plugin-dev.mdc +23 -0
- package/.cursorrules +6 -0
- package/.paseo/rules.md +8 -0
- package/.pi/rules.md +8 -0
- package/.windsurfrules +7 -0
- package/CHANGELOG.md +50 -0
- package/CLAUDE.md +27 -0
- package/CODEX.md +12 -0
- package/DEEPSEEK.md +18 -0
- package/README.md +73 -33
- package/assets/banner-animated.svg +360 -0
- package/assets/banner.svg +360 -0
- package/bin/cli.js +565 -35
- package/docs/01-modern-paper-architecture.md +48 -51
- package/docs/02-adventure-and-minimessage.md +48 -52
- package/docs/03-persistent-data-container.md +39 -39
- package/docs/04-commands-and-brigadier.md +32 -35
- package/docs/05-concurrency-and-folia.md +44 -44
- package/docs/06-events-and-listeners.md +30 -31
- package/docs/07-best-practices-and-perf.md +39 -40
- package/docs/08-gui-and-menus.md +34 -37
- package/docs/09-custom-items-and-combat.md +25 -33
- package/docs/10-database-and-storage.md +27 -27
- package/docs/11-security-and-exploit-prevention.md +233 -0
- package/docs/12-data-components-and-display-entities.md +264 -0
- package/docs/13-multi-agent-system-and-tooling.md +90 -0
- package/package.json +17 -4
- package/skills/paper-plugin-dev/SKILL.md +425 -114
- package/templates/paper-modern-template/.gradle/9.7.1/checksums/checksums.lock +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/checksums/md5-checksums.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/checksums/sha1-checksums.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.lock +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.lock +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/resourceHashesCache.bin +0 -0
- package/templates/paper-modern-template/.gradle/9.7.1/kotlin-dsl-plugin-entries/kotlin-dsl-plugin-entries.lock +0 -0
- package/templates/paper-modern-template/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/templates/paper-modern-template/.gradle/buildOutputCleanup/cache.properties +1 -1
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/ExamplePaperPlugin.class +0 -0
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/PaperPluginBootstrap.class +0 -0
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/command/HelloCommand.class +0 -0
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/listener/PlayerEventListener.class +0 -0
- package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/util/SchedulerAdapter.class +0 -0
- package/templates/paper-modern-template/build/tmp/compileJava/previous-compilation-data.bin +0 -0
- package/templates/paper-modern-template/build.gradle.kts +7 -7
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/ExamplePaperPlugin.java +6 -6
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/PaperPluginBootstrap.java +2 -2
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/command/HelloCommand.java +2 -4
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/data/PluginDataKeys.java +2 -2
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/listener/PlayerEventListener.java +6 -7
- package/templates/paper-modern-template/src/main/java/com/example/paperplugin/util/SchedulerAdapter.java +6 -6
- package/templates/paper-modern-template/src/main/resources/config.yml +5 -5
|
@@ -1,170 +1,481 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: paper-plugin-dev
|
|
3
|
-
description: Expert guidelines, modern
|
|
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
|
-
|
|
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.
|
|
14
|
+
## 1. Environment Prerequisites & Compatibility Matrix
|
|
13
15
|
|
|
14
|
-
|
|
|
15
|
-
| :--- | :--- | :--- |
|
|
16
|
-
| **Java SDK** | **Java 21 (LTS)**
|
|
17
|
-
| **
|
|
18
|
-
| **Paper API** | `
|
|
19
|
-
| **
|
|
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
|
-
##
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
##
|
|
94
|
+
## 4. Modern Overpowered (OP) Recipes
|
|
48
95
|
|
|
49
|
-
###
|
|
96
|
+
### Recipe 1: Folia-Ready Modern Manifest (`src/main/resources/paper-plugin.yml`)
|
|
50
97
|
```yaml
|
|
51
|
-
name:
|
|
52
|
-
version: '1.
|
|
53
|
-
main: com.example.plugin.
|
|
54
|
-
bootstrapper: com.example.plugin.
|
|
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
|
-
###
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
###
|
|
167
|
+
### Recipe 3: Modern Data Component Item Engineering (Zero-NBT)
|
|
90
168
|
```java
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
###
|
|
213
|
+
### Recipe 4: Zero-Lag Display Entity Hologram Engine
|
|
100
214
|
```java
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
###
|
|
303
|
+
### Recipe 6: Anti-Dupe GUI Controller with Debounce
|
|
124
304
|
```java
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
###
|
|
384
|
+
### Recipe 7: Universal Folia & Paper Scheduler Adapter
|
|
136
385
|
```java
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
##
|
|
451
|
+
## 5. Anti-Patterns vs Modern Standards
|
|
149
452
|
|
|
150
|
-
|
|
|
151
|
-
| :--- | :--- |
|
|
152
|
-
| `ChatColor.RED + "
|
|
153
|
-
| `public static Set<Player>
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
|
|
|
158
|
-
|
|
|
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
|
-
##
|
|
467
|
+
## 6. Pre-Release Security Audit Checklist
|
|
468
|
+
|
|
469
|
+
Prior to releasing a plugin to production or public servers:
|
|
163
470
|
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
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.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.lock
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#
|
|
1
|
+
#Wed Nov 11 04:17:31 UTC 2026
|
|
2
2
|
gradle.version=9.7.1
|
|
Binary file
|
|
Binary file
|