papermc-skill 1.1.0 → 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 +31 -0
- package/CLAUDE.md +27 -0
- package/CODEX.md +12 -0
- package/DEEPSEEK.md +18 -0
- package/README.md +66 -26
- package/bin/cli.js +461 -36
- 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 +16 -4
- package/skills/paper-plugin-dev/SKILL.md +144 -110
- 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/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/tmp/compileJava/previous-compilation-data.bin +0 -0
package/bin/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ const c = {
|
|
|
18
18
|
green: '\x1b[32m',
|
|
19
19
|
yellow: '\x1b[33m',
|
|
20
20
|
red: '\x1b[31m',
|
|
21
|
+
magenta: '\x1b[35m',
|
|
21
22
|
gray: '\x1b[90m'
|
|
22
23
|
};
|
|
23
24
|
|
|
@@ -28,7 +29,7 @@ ${c.cyan}${c.bold} ____ __ __ ____
|
|
|
28
29
|
| |_) / _\` | '_ \\ / _ \\ '__|____| |\\/| | | / ___|| | __(_) | |
|
|
29
30
|
| __/ (_| | |_) | __/ | |_____| | | | |___ \\___ \\| |/ / | | |
|
|
30
31
|
|_| \\__,_| .__/ \\___|_| |_| |_|\\____| |____/|_|\\_\\_|_|_|
|
|
31
|
-
|_| ${c.reset}${c.dim}Modern Paper & Folia Plugin Toolchain${c.reset}
|
|
32
|
+
|_| ${c.reset}${c.dim}Modern Paper & Folia Plugin Toolchain (1.21+ / 26.x)${c.reset}
|
|
32
33
|
`);
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -118,7 +119,7 @@ function copyDirRecursive(src, dest, replacements = {}) {
|
|
|
118
119
|
|
|
119
120
|
async function handleCreate(targetDirArg) {
|
|
120
121
|
printBanner();
|
|
121
|
-
console.log(`${c.bold}Scaffolding a new Modern Paper Plugin...${c.reset}\n`);
|
|
122
|
+
console.log(`${c.bold}Scaffolding a new Modern Paper & Folia Plugin...${c.reset}\n`);
|
|
122
123
|
|
|
123
124
|
const rawPluginName = await prompt('Plugin Name', targetDirArg || 'MyPaperPlugin');
|
|
124
125
|
const pluginName = sanitizePluginName(rawPluginName);
|
|
@@ -178,32 +179,427 @@ async function handleCreate(targetDirArg) {
|
|
|
178
179
|
console.log(` ${c.cyan}./gradlew runFoliaServer${c.reset} # Launches Folia multi-threaded test server${c.reset}\n`);
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
|
|
182
|
+
// Universal Multi-Agent Installer
|
|
183
|
+
async function handleInstallSkill(options = []) {
|
|
182
184
|
printBanner();
|
|
183
|
-
console.log(`${c.bold}
|
|
185
|
+
console.log(`${c.bold}Universal AI Coding Agent Skill Installer${c.reset}\n`);
|
|
184
186
|
|
|
185
|
-
const isGlobal = options.includes('--global') || options.includes('-g');
|
|
186
187
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
187
|
-
|
|
188
|
-
let targetSkillDir;
|
|
189
|
-
if (isGlobal) {
|
|
190
|
-
targetSkillDir = path.join(homeDir, '.gemini', 'config', 'skills', 'paper-plugin-dev');
|
|
191
|
-
} else {
|
|
192
|
-
// Current workspace customization root
|
|
193
|
-
targetSkillDir = path.resolve(process.cwd(), '.agents', 'skills', 'paper-plugin-dev');
|
|
194
|
-
}
|
|
195
|
-
|
|
188
|
+
const cwd = process.cwd();
|
|
196
189
|
const srcSkillDir = path.join(ROOT_DIR, 'skills', 'paper-plugin-dev');
|
|
190
|
+
|
|
197
191
|
if (!fs.existsSync(srcSkillDir)) {
|
|
198
192
|
console.error(`${c.red}Error:${c.reset} Source skill directory not found at ${srcSkillDir}`);
|
|
199
193
|
process.exit(1);
|
|
200
194
|
}
|
|
201
195
|
|
|
202
|
-
|
|
196
|
+
// Parse target flags
|
|
197
|
+
let targetAgent = null;
|
|
198
|
+
const isGlobal = options.includes('--global') || options.includes('-g');
|
|
199
|
+
const isLocal = options.includes('--local') || options.includes('-l');
|
|
200
|
+
const isAll = options.includes('--all') || options.includes('-a');
|
|
201
|
+
|
|
202
|
+
for (let i = 0; i < options.length; i++) {
|
|
203
|
+
if (options[i].startsWith('--agent=')) {
|
|
204
|
+
targetAgent = options[i].split('=')[1].toLowerCase();
|
|
205
|
+
} else if (options[i] === '--agent' && options[i + 1]) {
|
|
206
|
+
targetAgent = options[i + 1].toLowerCase();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// If no target specified and not --all, offer interactive prompt
|
|
211
|
+
if (!targetAgent && !isAll) {
|
|
212
|
+
console.log(`${c.bold}Supported AI Coding Agent Platforms:${c.reset}`);
|
|
213
|
+
console.log(` ${c.cyan}1.${c.reset} All Agents (Antigravity, Claude, Codex, Cursor, DeepSeek, Paseo, pi.dev, Windsurf)`);
|
|
214
|
+
console.log(` ${c.cyan}2.${c.reset} Antigravity / agy (~/.gemini/config/skills)`);
|
|
215
|
+
console.log(` ${c.cyan}3.${c.reset} Claude Code (~/.claude/skills & CLAUDE.md)`);
|
|
216
|
+
console.log(` ${c.cyan}4.${c.reset} OpenAI Codex (~/.codex/skills & CODEX.md)`);
|
|
217
|
+
console.log(` ${c.cyan}5.${c.reset} Cursor (.cursor/rules & .cursorrules)`);
|
|
218
|
+
console.log(` ${c.cyan}6.${c.reset} DeepSeek Harness (~/.deepseek & DEEPSEEK.md)`);
|
|
219
|
+
console.log(` ${c.cyan}7.${c.reset} Paseo (~/.paseo & .paseo/rules.md)`);
|
|
220
|
+
console.log(` ${c.cyan}8.${c.reset} pi.dev (~/.pi & .pi/rules.md)`);
|
|
221
|
+
console.log(` ${c.cyan}9.${c.reset} Windsurf (.windsurfrules)\n`);
|
|
222
|
+
|
|
223
|
+
const selection = await prompt('Select target agent platform [1-9]', '1');
|
|
224
|
+
const mapping = {
|
|
225
|
+
'1': 'all',
|
|
226
|
+
'2': 'agy',
|
|
227
|
+
'3': 'claude',
|
|
228
|
+
'4': 'codex',
|
|
229
|
+
'5': 'cursor',
|
|
230
|
+
'6': 'deepseek',
|
|
231
|
+
'7': 'paseo',
|
|
232
|
+
'8': 'pi',
|
|
233
|
+
'9': 'windsurf'
|
|
234
|
+
};
|
|
235
|
+
targetAgent = mapping[selection] || 'all';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const agentsToInstall = (targetAgent === 'all' || isAll)
|
|
239
|
+
? ['agy', 'claude', 'codex', 'cursor', 'deepseek', 'paseo', 'pi', 'windsurf']
|
|
240
|
+
: [targetAgent];
|
|
241
|
+
|
|
242
|
+
console.log(`\n${c.yellow}Installing skill for:${c.reset} ${c.bold}${agentsToInstall.join(', ')}${c.reset}\n`);
|
|
243
|
+
|
|
244
|
+
for (const agent of agentsToInstall) {
|
|
245
|
+
try {
|
|
246
|
+
switch (agent) {
|
|
247
|
+
case 'agy':
|
|
248
|
+
case 'antigravity': {
|
|
249
|
+
const dest = (isLocal && !isGlobal)
|
|
250
|
+
? path.join(cwd, '.agents', 'skills', 'paper-plugin-dev')
|
|
251
|
+
: path.join(homeDir, '.gemini', 'config', 'skills', 'paper-plugin-dev');
|
|
252
|
+
copyDirRecursive(srcSkillDir, dest);
|
|
253
|
+
console.log(` ${c.green}✔${c.reset} ${c.bold}Antigravity (agy):${c.reset} Installed to ${c.dim}${dest}${c.reset}`);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
case 'claude':
|
|
258
|
+
case 'claudecode': {
|
|
259
|
+
const globalDir = path.join(homeDir, '.claude', 'skills', 'paper-plugin-dev');
|
|
260
|
+
copyDirRecursive(srcSkillDir, globalDir);
|
|
261
|
+
const claudeMdSrc = path.join(ROOT_DIR, 'CLAUDE.md');
|
|
262
|
+
if (fs.existsSync(claudeMdSrc)) {
|
|
263
|
+
fs.copyFileSync(claudeMdSrc, path.join(cwd, 'CLAUDE.md'));
|
|
264
|
+
}
|
|
265
|
+
console.log(` ${c.green}✔${c.reset} ${c.bold}Claude Code:${c.reset} Skill placed in ~/.claude/skills and workspace ${c.dim}CLAUDE.md${c.reset}`);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
case 'codex':
|
|
270
|
+
case 'openai': {
|
|
271
|
+
const globalDir = path.join(homeDir, '.codex', 'skills', 'paper-plugin-dev');
|
|
272
|
+
copyDirRecursive(srcSkillDir, globalDir);
|
|
273
|
+
const codexMdSrc = path.join(ROOT_DIR, 'CODEX.md');
|
|
274
|
+
if (fs.existsSync(codexMdSrc)) {
|
|
275
|
+
fs.copyFileSync(codexMdSrc, path.join(cwd, 'CODEX.md'));
|
|
276
|
+
}
|
|
277
|
+
console.log(` ${c.green}✔${c.reset} ${c.bold}OpenAI Codex:${c.reset} Placed in ~/.codex/skills and workspace ${c.dim}CODEX.md${c.reset}`);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
case 'cursor': {
|
|
282
|
+
const cursorRulesDir = path.join(cwd, '.cursor', 'rules');
|
|
283
|
+
fs.mkdirSync(cursorRulesDir, { recursive: true });
|
|
284
|
+
const mdcSrc = path.join(ROOT_DIR, '.cursor', 'rules', 'paper-plugin-dev.mdc');
|
|
285
|
+
if (fs.existsSync(mdcSrc)) {
|
|
286
|
+
fs.copyFileSync(mdcSrc, path.join(cursorRulesDir, 'paper-plugin-dev.mdc'));
|
|
287
|
+
}
|
|
288
|
+
const cursorRulesSrc = path.join(ROOT_DIR, '.cursorrules');
|
|
289
|
+
if (fs.existsSync(cursorRulesSrc)) {
|
|
290
|
+
fs.copyFileSync(cursorRulesSrc, path.join(cwd, '.cursorrules'));
|
|
291
|
+
}
|
|
292
|
+
console.log(` ${c.green}✔${c.reset} ${c.bold}Cursor:${c.reset} Created ${c.dim}.cursor/rules/paper-plugin-dev.mdc${c.reset} and ${c.dim}.cursorrules${c.reset}`);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
case 'deepseek': {
|
|
297
|
+
const globalDir = path.join(homeDir, '.deepseek', 'skills', 'paper-plugin-dev');
|
|
298
|
+
copyDirRecursive(srcSkillDir, globalDir);
|
|
299
|
+
const deepseekSrc = path.join(ROOT_DIR, 'DEEPSEEK.md');
|
|
300
|
+
if (fs.existsSync(deepseekSrc)) {
|
|
301
|
+
fs.copyFileSync(deepseekSrc, path.join(cwd, 'DEEPSEEK.md'));
|
|
302
|
+
}
|
|
303
|
+
console.log(` ${c.green}✔${c.reset} ${c.bold}DeepSeek Harness:${c.reset} Placed in ~/.deepseek/skills and workspace ${c.dim}DEEPSEEK.md${c.reset}`);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
case 'paseo': {
|
|
308
|
+
const globalDir = path.join(homeDir, '.paseo', 'skills', 'paper-plugin-dev');
|
|
309
|
+
copyDirRecursive(srcSkillDir, globalDir);
|
|
310
|
+
const paseoRulesDir = path.join(cwd, '.paseo');
|
|
311
|
+
fs.mkdirSync(paseoRulesDir, { recursive: true });
|
|
312
|
+
const paseoSrc = path.join(ROOT_DIR, '.paseo', 'rules.md');
|
|
313
|
+
if (fs.existsSync(paseoSrc)) {
|
|
314
|
+
fs.copyFileSync(paseoSrc, path.join(paseoRulesDir, 'rules.md'));
|
|
315
|
+
}
|
|
316
|
+
console.log(` ${c.green}✔${c.reset} ${c.bold}Paseo:${c.reset} Placed in ~/.paseo/skills and workspace ${c.dim}.paseo/rules.md${c.reset}`);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
case 'pi':
|
|
321
|
+
case 'pidev': {
|
|
322
|
+
const globalDir = path.join(homeDir, '.pi', 'skills', 'paper-plugin-dev');
|
|
323
|
+
copyDirRecursive(srcSkillDir, globalDir);
|
|
324
|
+
const piRulesDir = path.join(cwd, '.pi');
|
|
325
|
+
fs.mkdirSync(piRulesDir, { recursive: true });
|
|
326
|
+
const piSrc = path.join(ROOT_DIR, '.pi', 'rules.md');
|
|
327
|
+
if (fs.existsSync(piSrc)) {
|
|
328
|
+
fs.copyFileSync(piSrc, path.join(piRulesDir, 'rules.md'));
|
|
329
|
+
}
|
|
330
|
+
console.log(` ${c.green}✔${c.reset} ${c.bold}pi.dev:${c.reset} Placed in ~/.pi/skills and workspace ${c.dim}.pi/rules.md${c.reset}`);
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
case 'windsurf': {
|
|
335
|
+
const windsurfSrc = path.join(ROOT_DIR, '.windsurfrules');
|
|
336
|
+
if (fs.existsSync(windsurfSrc)) {
|
|
337
|
+
fs.copyFileSync(windsurfSrc, path.join(cwd, '.windsurfrules'));
|
|
338
|
+
}
|
|
339
|
+
console.log(` ${c.green}✔${c.reset} ${c.bold}Windsurf:${c.reset} Placed ${c.dim}.windsurfrules${c.reset} in workspace root`);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
default:
|
|
344
|
+
console.log(` ${c.yellow}⚠ Unknown agent '${agent}'. Skipped.${c.reset}`);
|
|
345
|
+
}
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.error(` ${c.red}✘ Failed installing for ${agent}:${c.reset}`, err.message);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
203
350
|
|
|
204
|
-
console.log(
|
|
205
|
-
console.log(`
|
|
206
|
-
|
|
351
|
+
console.log(`\n${c.green}${c.bold}✔ Skill configuration completed successfully!${c.reset}`);
|
|
352
|
+
console.log(`Your AI coding agents now enforce modern Paper & Folia exploit-proof standards.\n`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Scaffolder for Overpowered (OP) Recipes
|
|
356
|
+
async function handleGenerate(recipeArg, nameArg) {
|
|
357
|
+
printBanner();
|
|
358
|
+
const recipe = (recipeArg || '').toLowerCase();
|
|
359
|
+
|
|
360
|
+
const supported = ['component-item', 'display-entity', 'mace-combat', 'menu-gui'];
|
|
361
|
+
|
|
362
|
+
if (!recipe || !supported.some(s => s.includes(recipe) || recipe.includes(s.split('-')[0]))) {
|
|
363
|
+
console.log(`${c.bold}Available Overpowered (OP) Code Generators:${c.reset}\n`);
|
|
364
|
+
console.log(` ${c.cyan}1. component-item${c.reset} ${c.gray}Modern 1.20.5+ / 1.21+ / 26.x DataComponent item builder${c.reset}`);
|
|
365
|
+
console.log(` ${c.cyan}2. display-entity${c.reset} ${c.gray}Zero-lag interpolated TextDisplay & ItemDisplay hologram engine${c.reset}`);
|
|
366
|
+
console.log(` ${c.cyan}3. mace-combat${c.reset} ${c.gray}1.21 Tricky Trials Mace smash & wind burst kinetic listener${c.reset}`);
|
|
367
|
+
console.log(` ${c.cyan}4. menu-gui${c.reset} ${c.gray}Anti-dupe debounce-protected inventory menu controller${c.reset}\n`);
|
|
368
|
+
console.log(`Usage: ${c.cyan}npx papermc-skill generate <recipe> [ClassName]${c.reset}\n`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const cwd = process.cwd();
|
|
373
|
+
|
|
374
|
+
if (recipe.includes('item') || recipe.includes('component')) {
|
|
375
|
+
const className = nameArg || 'ModernItemBuilder';
|
|
376
|
+
const filePath = path.join(cwd, `${className}.java`);
|
|
377
|
+
const code = `package com.example.item;
|
|
378
|
+
|
|
379
|
+
import io.papermc.paper.datacomponent.DataComponentTypes;
|
|
380
|
+
import io.papermc.paper.datacomponent.item.CustomModelData;
|
|
381
|
+
import io.papermc.paper.datacomponent.item.FoodProperties;
|
|
382
|
+
import io.papermc.paper.datacomponent.item.ItemLore;
|
|
383
|
+
import net.kyori.adventure.text.Component;
|
|
384
|
+
import net.kyori.adventure.text.format.NamedTextColor;
|
|
385
|
+
import net.kyori.adventure.text.format.TextDecoration;
|
|
386
|
+
import org.bukkit.Material;
|
|
387
|
+
import org.bukkit.inventory.ItemStack;
|
|
388
|
+
|
|
389
|
+
import java.util.List;
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Modern Data Component Item Builder (MC 1.20.5 - 26.x compatible).
|
|
393
|
+
* Zero legacy NBT. Type-safe and crash-proof.
|
|
394
|
+
*/
|
|
395
|
+
public final class ${className} {
|
|
396
|
+
|
|
397
|
+
public static ItemStack createMythicRelic() {
|
|
398
|
+
ItemStack item = ItemStack.of(Material.NETHERITE_SWORD);
|
|
399
|
+
|
|
400
|
+
// Direct Adventure Item Name & Lore
|
|
401
|
+
item.setData(DataComponentTypes.ITEM_NAME, Component.text("Aegis of the Void", NamedTextColor.LIGHT_PURPLE, TextDecoration.BOLD));
|
|
402
|
+
item.setData(DataComponentTypes.LORE, ItemLore.lore(List.of(
|
|
403
|
+
Component.text("Harvested from trial chambers.", NamedTextColor.GRAY),
|
|
404
|
+
Component.empty(),
|
|
405
|
+
Component.text("✦ Ability: Kinetic Shockwave on hit", NamedTextColor.AQUA)
|
|
406
|
+
)));
|
|
407
|
+
|
|
408
|
+
// Modern Custom Model Data (floats, strings, flags)
|
|
409
|
+
item.setData(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelData.customModelData()
|
|
410
|
+
.addFloat(2.5f)
|
|
411
|
+
.addString("aegis_void_model")
|
|
412
|
+
.build()
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
// Component Overrides
|
|
416
|
+
item.setData(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true);
|
|
417
|
+
item.setData(DataComponentTypes.MAX_STACK_SIZE, 1);
|
|
418
|
+
item.setData(DataComponentTypes.UNBREAKABLE, true);
|
|
419
|
+
|
|
420
|
+
return item;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
`;
|
|
424
|
+
fs.writeFileSync(filePath, code, 'utf-8');
|
|
425
|
+
console.log(`${c.green}✔ Generated Modern Data-Component Item Builder:${c.reset} ${c.bold}${filePath}${c.reset}\n`);
|
|
426
|
+
} else if (recipe.includes('display') || recipe.includes('hologram')) {
|
|
427
|
+
const className = nameArg || 'DisplayHologramEngine';
|
|
428
|
+
const filePath = path.join(cwd, `${className}.java`);
|
|
429
|
+
const code = `package com.example.display;
|
|
430
|
+
|
|
431
|
+
import net.kyori.adventure.text.Component;
|
|
432
|
+
import org.bukkit.Color;
|
|
433
|
+
import org.bukkit.Location;
|
|
434
|
+
import org.bukkit.entity.Display;
|
|
435
|
+
import org.bukkit.entity.TextDisplay;
|
|
436
|
+
import org.bukkit.util.Transformation;
|
|
437
|
+
import org.joml.AxisAngle4f;
|
|
438
|
+
import org.joml.Vector3f;
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* High-performance Display Entity Hologram Controller.
|
|
442
|
+
* Zero-tick entity lag. Client-side smooth JOML Matrix4f interpolation.
|
|
443
|
+
*/
|
|
444
|
+
public final class ${className} {
|
|
445
|
+
|
|
446
|
+
public static TextDisplay spawnSmoothHologram(Location loc, Component text) {
|
|
447
|
+
return loc.getWorld().spawn(loc, TextDisplay.class, display -> {
|
|
448
|
+
display.text(text);
|
|
449
|
+
display.setBillboard(Display.Billboard.CENTER);
|
|
450
|
+
display.setDefaultBackground(false);
|
|
451
|
+
display.setBackgroundColor(Color.fromARGB(150, 20, 20, 30));
|
|
452
|
+
display.setShadowed(true);
|
|
453
|
+
|
|
454
|
+
// Client-side smooth interpolation (20 ticks = 1s)
|
|
455
|
+
display.setInterpolationDuration(20);
|
|
456
|
+
display.setInterpolationDelay(0);
|
|
457
|
+
|
|
458
|
+
// Scale & Position Transformation
|
|
459
|
+
Transformation transform = new Transformation(
|
|
460
|
+
new Vector3f(0f, 0f, 0f),
|
|
461
|
+
new AxisAngle4f(0f, 0f, 1f, 0f),
|
|
462
|
+
new Vector3f(1.1f, 1.1f, 1.1f),
|
|
463
|
+
new AxisAngle4f(0f, 0f, 1f, 0f)
|
|
464
|
+
);
|
|
465
|
+
display.setTransformation(transform);
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
`;
|
|
470
|
+
fs.writeFileSync(filePath, code, 'utf-8');
|
|
471
|
+
console.log(`${c.green}✔ Generated Display Entity Hologram Controller:${c.reset} ${c.bold}${filePath}${c.reset}\n`);
|
|
472
|
+
} else if (recipe.includes('mace') || recipe.includes('combat')) {
|
|
473
|
+
const className = nameArg || 'MaceKineticCombatListener';
|
|
474
|
+
const filePath = path.join(cwd, `${className}.java`);
|
|
475
|
+
const code = `package com.example.combat;
|
|
476
|
+
|
|
477
|
+
import net.kyori.adventure.text.Component;
|
|
478
|
+
import net.kyori.adventure.text.format.NamedTextColor;
|
|
479
|
+
import org.bukkit.Location;
|
|
480
|
+
import org.bukkit.Material;
|
|
481
|
+
import org.bukkit.Particle;
|
|
482
|
+
import org.bukkit.Sound;
|
|
483
|
+
import org.bukkit.entity.LivingEntity;
|
|
484
|
+
import org.bukkit.entity.Player;
|
|
485
|
+
import org.bukkit.event.EventHandler;
|
|
486
|
+
import org.bukkit.event.EventPriority;
|
|
487
|
+
import org.bukkit.event.Listener;
|
|
488
|
+
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
|
489
|
+
import org.bukkit.util.Vector;
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* 1.21 Tricky Trials Mace Smash & Kinetic Shockwave Combat Listener.
|
|
493
|
+
*/
|
|
494
|
+
public final class ${className} implements Listener {
|
|
495
|
+
|
|
496
|
+
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
|
497
|
+
public void onMaceSmash(EntityDamageByEntityEvent event) {
|
|
498
|
+
if (!(event.getDamager() instanceof Player attacker)) return;
|
|
499
|
+
if (!(event.getEntity() instanceof LivingEntity victim)) return;
|
|
500
|
+
|
|
501
|
+
if (attacker.getInventory().getItemInMainHand().getType() != Material.MACE) return;
|
|
502
|
+
|
|
503
|
+
float fallDistance = attacker.getFallDistance();
|
|
504
|
+
if (fallDistance > 1.5f) {
|
|
505
|
+
double extraDmg = Math.min(fallDistance * 3.5, 45.0);
|
|
506
|
+
event.setDamage(event.getDamage() + extraDmg);
|
|
507
|
+
|
|
508
|
+
Location loc = victim.getLocation();
|
|
509
|
+
loc.getWorld().spawnParticle(Particle.TRIAL_SPAWNER_DETECTION, loc.add(0, 0.5, 0), 30, 0.5, 0.2, 0.5, 0.05);
|
|
510
|
+
loc.getWorld().playSound(loc, Sound.ITEM_MACE_SMASH_GROUND, 1.2f, 1.0f);
|
|
511
|
+
|
|
512
|
+
// Kinetic shockwave knockback to nearby hostiles
|
|
513
|
+
loc.getWorld().getNearbyLivingEntities(loc, 4.5, e -> !e.equals(attacker) && !e.equals(victim))
|
|
514
|
+
.forEach(e -> {
|
|
515
|
+
Vector impulse = e.getLocation().toVector().subtract(loc.toVector()).normalize().multiply(1.3).setY(0.4);
|
|
516
|
+
e.setVelocity(impulse);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
attacker.sendActionBar(Component.text("⚡ SMASH IMPACT: +" + String.format("%.1f", extraDmg) + " DMG", NamedTextColor.GOLD));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
`;
|
|
524
|
+
fs.writeFileSync(filePath, code, 'utf-8');
|
|
525
|
+
console.log(`${c.green}✔ Generated Mace Kinetic Combat Listener:${c.reset} ${c.bold}${filePath}${c.reset}\n`);
|
|
526
|
+
} else if (recipe.includes('gui') || recipe.includes('menu')) {
|
|
527
|
+
const className = nameArg || 'SafeMenuController';
|
|
528
|
+
const filePath = path.join(cwd, `${className}.java`);
|
|
529
|
+
const code = `package com.example.gui;
|
|
530
|
+
|
|
531
|
+
import net.kyori.adventure.text.Component;
|
|
532
|
+
import org.bukkit.Bukkit;
|
|
533
|
+
import org.bukkit.entity.Player;
|
|
534
|
+
import org.bukkit.event.EventHandler;
|
|
535
|
+
import org.bukkit.event.EventPriority;
|
|
536
|
+
import org.bukkit.event.Listener;
|
|
537
|
+
import org.bukkit.event.inventory.InventoryClickEvent;
|
|
538
|
+
import org.bukkit.event.inventory.InventoryCloseEvent;
|
|
539
|
+
import org.bukkit.event.inventory.InventoryDragEvent;
|
|
540
|
+
import org.bukkit.inventory.Inventory;
|
|
541
|
+
import org.bukkit.inventory.InventoryHolder;
|
|
542
|
+
|
|
543
|
+
import java.util.Map;
|
|
544
|
+
import java.util.UUID;
|
|
545
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Exploit-proof Inventory Menu Controller with click debounce.
|
|
549
|
+
*/
|
|
550
|
+
public class ${className} implements InventoryHolder, Listener {
|
|
551
|
+
|
|
552
|
+
private final Inventory inventory;
|
|
553
|
+
private final Map<UUID, Long> clickDebounce = new ConcurrentHashMap<>();
|
|
554
|
+
|
|
555
|
+
public ${className}(Component title, int size) {
|
|
556
|
+
this.inventory = Bukkit.createInventory(this, size, title);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
@Override
|
|
560
|
+
public Inventory getInventory() {
|
|
561
|
+
return inventory;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
565
|
+
public void onClick(InventoryClickEvent event) {
|
|
566
|
+
if (!(event.getInventory().getHolder() instanceof ${className})) return;
|
|
567
|
+
|
|
568
|
+
event.setCancelled(true);
|
|
569
|
+
if (!(event.getWhoClicked() instanceof Player player)) return;
|
|
570
|
+
|
|
571
|
+
long now = System.currentTimeMillis();
|
|
572
|
+
long lastClick = clickDebounce.getOrDefault(player.getUniqueId(), 0L);
|
|
573
|
+
if (now - lastClick < 200) return;
|
|
574
|
+
clickDebounce.put(player.getUniqueId(), now);
|
|
575
|
+
|
|
576
|
+
if (event.getClickedInventory() == event.getInventory()) {
|
|
577
|
+
handleSlotAction(player, event.getSlot());
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@EventHandler
|
|
582
|
+
public void onDrag(InventoryDragEvent event) {
|
|
583
|
+
if (event.getInventory().getHolder() instanceof ${className}) {
|
|
584
|
+
event.setCancelled(true);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
@EventHandler
|
|
589
|
+
public void onClose(InventoryCloseEvent event) {
|
|
590
|
+
if (event.getInventory().getHolder() instanceof ${className}) {
|
|
591
|
+
clickDebounce.remove(event.getPlayer().getUniqueId());
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
protected void handleSlotAction(Player player, int slot) {
|
|
596
|
+
// Slot action implementation
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
`;
|
|
600
|
+
fs.writeFileSync(filePath, code, 'utf-8');
|
|
601
|
+
console.log(`${c.green}✔ Generated Anti-Dupe Safe Menu Controller:${c.reset} ${c.bold}${filePath}${c.reset}\n`);
|
|
602
|
+
}
|
|
207
603
|
}
|
|
208
604
|
|
|
209
605
|
function handleDocs(topic) {
|
|
@@ -244,7 +640,7 @@ async function handleCheckUpdate(options = []) {
|
|
|
244
640
|
try {
|
|
245
641
|
const res = await fetch('https://fill.papermc.io/v3/projects/paper', {
|
|
246
642
|
headers: {
|
|
247
|
-
'User-Agent': 'papermc-skill/1.
|
|
643
|
+
'User-Agent': 'papermc-skill/1.2.0 (https://github.com/ardianryan/papermc-skill)'
|
|
248
644
|
}
|
|
249
645
|
});
|
|
250
646
|
|
|
@@ -263,15 +659,13 @@ async function handleCheckUpdate(options = []) {
|
|
|
263
659
|
console.log(`${c.green}${c.bold}✔ Successfully queried PaperMC Fill v3 API${c.reset}\n`);
|
|
264
660
|
console.log(`${c.bold}Available Major Version Groups:${c.reset} ${versionGroups.slice(0, 5).join(', ')}`);
|
|
265
661
|
|
|
266
|
-
// Candidate group (latest supported group)
|
|
267
662
|
const candidateGroup = versionGroups[0];
|
|
268
663
|
const latestVersions = data.versions[candidateGroup] || [];
|
|
269
664
|
const latestVersionId = latestVersions[0] || candidateGroup;
|
|
270
665
|
|
|
271
|
-
// Fetch details for candidate version
|
|
272
666
|
const verRes = await fetch(`https://fill.papermc.io/v3/projects/paper/versions/${latestVersionId}`, {
|
|
273
667
|
headers: {
|
|
274
|
-
'User-Agent': 'papermc-skill/1.
|
|
668
|
+
'User-Agent': 'papermc-skill/1.2.0 (https://github.com/ardianryan/papermc-skill)'
|
|
275
669
|
}
|
|
276
670
|
});
|
|
277
671
|
|
|
@@ -290,7 +684,6 @@ async function handleCheckUpdate(options = []) {
|
|
|
290
684
|
console.log(` Support: ${status === 'SUPPORTED' ? c.green : c.gray}${status}${c.reset}`);
|
|
291
685
|
console.log(` Minimum Java: ${c.bold}Java ${minJava}+${c.reset}\n`);
|
|
292
686
|
|
|
293
|
-
// Check template version
|
|
294
687
|
const templateBuildGradle = path.join(ROOT_DIR, 'templates', 'paper-modern-template', 'build.gradle.kts');
|
|
295
688
|
if (fs.existsSync(templateBuildGradle)) {
|
|
296
689
|
const content = fs.readFileSync(templateBuildGradle, 'utf-8');
|
|
@@ -303,7 +696,6 @@ async function handleCheckUpdate(options = []) {
|
|
|
303
696
|
let updated = content.replaceAll(currentConfigured, latestVersionId);
|
|
304
697
|
fs.writeFileSync(templateBuildGradle, updated, 'utf-8');
|
|
305
698
|
|
|
306
|
-
// Update paper-plugin.yml
|
|
307
699
|
const pluginYamlPath = path.join(ROOT_DIR, 'templates', 'paper-modern-template', 'src', 'main', 'resources', 'paper-plugin.yml');
|
|
308
700
|
if (fs.existsSync(pluginYamlPath)) {
|
|
309
701
|
let yml = fs.readFileSync(pluginYamlPath, 'utf-8');
|
|
@@ -375,6 +767,18 @@ async function handleAudit(targetDirInput) {
|
|
|
375
767
|
level: 'CRITICAL',
|
|
376
768
|
msg: 'Static collection holding Player instances detected! This causes massive Memory Leaks. Store UUID instead.'
|
|
377
769
|
},
|
|
770
|
+
{
|
|
771
|
+
id: 'LEGACY_NBT_USAGE',
|
|
772
|
+
regex: /net\.minecraft\.nbt\.|NBTTagCompound|NBTTagList/,
|
|
773
|
+
level: 'CRITICAL',
|
|
774
|
+
msg: 'Direct NBT manipulation detected! In MC 1.20.5+ through 26.x, use DataComponentTypes directly on ItemStack.'
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
id: 'BUKKIT_RUNNABLE',
|
|
778
|
+
regex: /new\s+BukkitRunnable\s*\(\s*\)/,
|
|
779
|
+
level: 'HIGH',
|
|
780
|
+
msg: 'BukkitRunnable is incompatible with Folia region threading. Migrate to UniversalScheduler or Entity/Region schedulers.'
|
|
781
|
+
},
|
|
378
782
|
{
|
|
379
783
|
id: 'SYNC_CHUNK_LOAD',
|
|
380
784
|
regex: /world\s*\.\s*getChunkAt\s*\(/,
|
|
@@ -387,6 +791,12 @@ async function handleAudit(targetDirInput) {
|
|
|
387
791
|
level: 'CRITICAL',
|
|
388
792
|
msg: 'Potential SQL Injection via string concatenation detected in query. Use PreparedStatement with ? placeholders.'
|
|
389
793
|
},
|
|
794
|
+
{
|
|
795
|
+
id: 'ARMOR_STAND_HOLOGRAM',
|
|
796
|
+
regex: /spawn\s*\([^,]+,\s*ArmorStand\.class/i,
|
|
797
|
+
level: 'INFO',
|
|
798
|
+
msg: 'ArmorStand spawned. For floating text or item pedestals, prefer modern TextDisplay/ItemDisplay for zero-lag smooth interpolation.'
|
|
799
|
+
},
|
|
390
800
|
{
|
|
391
801
|
id: 'RAW_DOUBLE_NAN_CHECK',
|
|
392
802
|
regex: /DoubleArgumentType\.doubleArg\s*\(/,
|
|
@@ -418,7 +828,7 @@ async function handleAudit(targetDirInput) {
|
|
|
418
828
|
console.log(`${c.green}${c.bold}✔ 0 Vulnerabilities or Anti-Patterns Found!${c.reset}`);
|
|
419
829
|
console.log(`${c.green}Codebase adheres to modern Paper & Folia exploit-proof security standards.${c.reset}\n`);
|
|
420
830
|
} else {
|
|
421
|
-
console.log(`${c.yellow}${c.bold}Found ${issueCount} potential issue(s).${c.reset} Review docs/11-security-and-exploit-prevention.md for guidelines.\n`);
|
|
831
|
+
console.log(`${c.yellow}${c.bold}Found ${issueCount} potential issue(s).${c.reset} Review docs/11-security-and-exploit-prevention.md and docs/12-data-components-and-display-entities.md for guidelines.\n`);
|
|
422
832
|
}
|
|
423
833
|
}
|
|
424
834
|
|
|
@@ -428,26 +838,37 @@ function printHelp() {
|
|
|
428
838
|
npx papermc-skill <command> [options]
|
|
429
839
|
|
|
430
840
|
${c.bold}Commands:${c.reset}
|
|
431
|
-
${c.cyan}create [name]${c.reset} / ${c.cyan}init
|
|
432
|
-
${c.cyan}check-update [--apply]${c.reset} Query PaperMC Fill API for
|
|
841
|
+
${c.cyan}create [name]${c.reset} / ${c.cyan}init${c.reset} Scaffold a modern Paper & Folia plugin project
|
|
842
|
+
${c.cyan}check-update [--apply]${c.reset} Query PaperMC Fill API for latest Minecraft & Paper releases
|
|
433
843
|
${c.cyan}audit [path]${c.reset} Audit Java plugin codebase for security exploits & anti-patterns
|
|
434
|
-
${c.cyan}install-skill [
|
|
844
|
+
${c.cyan}install-skill [options]${c.reset} Install skill to AI agents (agy, claude, codex, cursor, deepseek, paseo, pi, windsurf)
|
|
845
|
+
${c.cyan}generate <recipe> [name]${c.reset} Generate OP recipes (component-item, display-entity, mace-combat, menu-gui)
|
|
435
846
|
${c.cyan}docs [number|keyword]${c.reset} Browse or print documentation guides
|
|
436
847
|
${c.cyan}help${c.reset} / ${c.cyan}--help${c.reset} Display this help screen
|
|
437
848
|
${c.cyan}--version${c.reset} Show version information
|
|
438
849
|
|
|
850
|
+
${c.bold}Install Skill Options:${c.reset}
|
|
851
|
+
${c.cyan}--all${c.reset} Install to all supported agent configurations
|
|
852
|
+
${c.cyan}--agent <name>${c.reset} Target specific agent: agy, claude, codex, cursor, deepseek, paseo, pi, windsurf
|
|
853
|
+
${c.cyan}--global${c.reset} / ${c.cyan}-g${c.reset} Install globally to home directory
|
|
854
|
+
${c.cyan}--local${c.reset} / ${c.cyan}-l${c.reset} Install rules to current workspace directory
|
|
855
|
+
|
|
439
856
|
${c.bold}Examples:${c.reset}
|
|
440
|
-
${c.dim}#
|
|
441
|
-
npx papermc-skill
|
|
857
|
+
${c.dim}# Install skill to all AI coding agents${c.reset}
|
|
858
|
+
npx papermc-skill install-skill --all
|
|
442
859
|
|
|
443
|
-
${c.dim}#
|
|
444
|
-
npx papermc-skill
|
|
860
|
+
${c.dim}# Install skill specifically for Claude Code and Cursor${c.reset}
|
|
861
|
+
npx papermc-skill install-skill --agent claude
|
|
862
|
+
npx papermc-skill install-skill --agent cursor
|
|
445
863
|
|
|
446
|
-
${c.dim}#
|
|
447
|
-
npx papermc-skill
|
|
864
|
+
${c.dim}# Generate modern DataComponent item builder${c.reset}
|
|
865
|
+
npx papermc-skill generate component-item EpicSwordBuilder
|
|
448
866
|
|
|
449
|
-
${c.dim}#
|
|
450
|
-
npx papermc-skill
|
|
867
|
+
${c.dim}# Generate zero-lag Display Entity hologram engine${c.reset}
|
|
868
|
+
npx papermc-skill generate display-entity HologramController
|
|
869
|
+
|
|
870
|
+
${c.dim}# Audit current project for exploit vectors${c.reset}
|
|
871
|
+
npx papermc-skill audit
|
|
451
872
|
`);
|
|
452
873
|
}
|
|
453
874
|
|
|
@@ -471,6 +892,10 @@ async function main() {
|
|
|
471
892
|
case 'skill':
|
|
472
893
|
await handleInstallSkill(args.slice(1));
|
|
473
894
|
break;
|
|
895
|
+
case 'generate':
|
|
896
|
+
case 'gen':
|
|
897
|
+
await handleGenerate(args[1], args[2]);
|
|
898
|
+
break;
|
|
474
899
|
case 'docs':
|
|
475
900
|
case 'doc':
|
|
476
901
|
case 'guide':
|