minecraft-paper-plugin
npx skills add https://github.com/sun-mc-dev/claude-minecraft-skill --skill minecraft-paper-plugin
Agent 安装分布
Skill 文档
Minecraft Paper Plugin Development Skill
Trigger Phrase
This skill activates when the user says “Gigi Aki” followed by their plugin requirements.
Overview
Create production-ready Minecraft Paper plugins following modern best practices:
- Modern Paper API: No deprecated methods, use latest Paper-specific features
- PacketEvents: Packet manipulation for custom mechanics and anti-cheat
- Async & Multi-threaded: Non-blocking operations, proper scheduler usage
- High Performance: Optimized code, caching, minimal allocations
- Flexible Database: Support SQLite (default), MySQL, PostgreSQL, MongoDB, Redis
- Type-safe Configuration: YAML (default) or JSON with validation
- Modern Dependencies: Lombok, HikariCP, Reflections, Gson, Adventure API
- Complete Developer API: Full API system for other plugins to integrate
- Professional Documentation: Auto-generated README.md, API docs, contribution guides
- GitHub Ready: CI/CD workflows, issue templates, changelog, code of conduct
- Unit Testing: JUnit 5 test structure with examples
- Code Quality: EditorConfig, consistent formatting, best practices
Development Workflow
1. Analyze Requirements
When user says “Gigi Aki” + requirements:
- Identify plugin type (combat, economy, game mechanic, anti-cheat, etc.)
- Determine complexity and required features
- Choose appropriate architecture patterns
- Select database type (default SQLite unless specified)
- Choose build system (Maven default, Gradle if multi-module)
- Determine Java version (Java 21 for modern, Java 8 for legacy 1.8)
1.5. Interactive Requirements Gathering
IMPORTANT: Before implementing, engage in a detailed requirements gathering session like an experienced developer. Ask clarifying questions to understand the full scope and details. Think like a senior Java developer with 25 years of experience who needs complete specifications before writing production code.
Core Functionality Questions
For ANY plugin type, ask:
- Target server version: “What Minecraft version are you targeting?” (1.21.x, 1.20.x, 1.8.8, etc.)
- Scale and performance: “How many players will this handle concurrently?” (affects architecture decisions)
- Persistence requirements: “What data needs to be saved?” (player stats, configurations, logs, etc.)
- Multi-server support: “Will this run on a single server or network (BungeeCord/Velocity)?”
Plugin-Specific Deep Dive Questions
For Combat Plugins:
- “What combat mechanics do you want to modify?” (knockback, damage, cooldowns, combos, hit detection)
- “Should it work with vanilla combat or completely override it?”
- “Any specific formulas for damage/knockback calculations?”
- “Do you need hitbox manipulation or just damage/velocity modification?”
- “Should there be weapon-specific mechanics?” (swords vs axes vs bows)
- “Any particle effects or sound effects for hits?”
- “Do you need combat logging prevention? How long is the combat tag?”
- “Should there be different modes/arenas with different rules?”
For Economy Plugins:
- “What currency system?” (single currency, multiple currencies, item-based)
- “Starting balance for new players?”
- “Transaction types needed?” (player-to-player, player-to-shop, admin commands)
- “Should there be a transaction fee/tax system?”
- “Do you need bank accounts, or just wallet balances?”
- “Interest on balances? Offline earning?”
- “Integration with existing plugins?” (Vault API, other economy plugins)
- “Admin tools needed?” (set balance, add/remove money, transaction history, economy reset)
- “GUI shops or command-based?”
For Anti-Cheat Plugins:
- “Which specific cheats to detect?” (killaura, reach, speed, fly, scaffold, etc.)
- “Detection approach?” (statistical analysis, pattern matching, threshold-based)
- “What actions on violation?” (kick, ban, notify admins, rollback, reduce damage)
- “Violation decay system?” (violations expire after time)
- “Bypass permissions for staff?”
- “Should it log to database for analysis?”
- “Integration with existing ban plugins?”
- “False positive handling – how strict vs lenient?”
- “Client brand/mod detection?”
For Parkour Plugins:
- “Checkpoint system?” (automatic, manual, pressure plates)
- “How to define parkour courses?” (worldedit selection, config, in-game commands)
- “Leaderboard requirements?” (per-course, global, time-based, completion-based)
- “Rewards system?” (money, items, permissions on completion)
- “Practice mode vs competitive mode?”
- “Spectator system for other players?”
- “Reset mechanism?” (command, automatic on fall, checkpoint rollback)
- “Time tracking precision?” (milliseconds, seconds)
For Game Mechanic Plugins:
- “What’s the core game loop?”
- “Win/loss conditions?”
- “Team system or free-for-all?”
- “Lobby system needed?”
- “Game phases?” (waiting, starting, active, ending)
- “Arena/map management?”
- “Spectator mode?”
- “Statistics tracking?”
For Shop/GUI Plugins:
- “Item categories and organization?”
- “Dynamic pricing or fixed?”
- “Stock limits? Cooldowns?”
- “Permission-based access to items?”
- “Sell-back system?”
- “GUI layout preferences?” (chest size, navigation style)
- “Search functionality?”
- “Transaction confirmation dialogs?”
Architecture and Technical Questions
Always ask:
- Commands structure: “What commands are needed? Include aliases, permissions, and usage examples.”
- Permissions hierarchy: “How should permissions be organized?” (admin, moderator, user tiers)
- Configuration flexibility: “What should be configurable vs hard-coded?”
- API exposure: “Will other plugins need to hook into this? Need a developer API?”
- Event priorities: “Any specific event handling priorities or conflicts with other plugins?”
- Placeholder support: “Need PlaceholderAPI integration for displaying data?”
- Locale/i18n: “Multiple languages needed? What’s the default?”
Performance and Scalability Questions
For production plugins, ask:
- Expected TPS impact: “What’s your performance budget? Target TPS with plugin active?”
- Data volume: “How much data per player? How many players?”
- Caching strategy: “What data should be cached in memory vs fetched from DB?”
- Async requirements: “Which operations MUST be async?” (database writes, external API calls)
- Batch processing: “Any bulk operations that need optimization?”
Example Complete Requirements Session
User: “Gigi Aki – create a custom knockback plugin”
Assistant: “I’ll create a professional knockback plugin for you. Let me gather some details to ensure it meets your exact needs:
Core Functionality:
- What Minecraft version are you targeting?
- Should this modify vanilla knockback or completely override it?
- What parameters do you want to configure? (horizontal multiplier, vertical multiplier, per-world settings, per-weapon settings)
- Do you need different knockback profiles (PvP mode, practice mode, etc.)?
Packet Implementation: 5. Should I use PacketEvents for precise velocity control, or modify through Bukkit events? 6. Do you want client-side prediction compensation?
Features: 7. Should knockback vary by weapon type? (swords, axes, custom items) 8. Do you need sprint/velocity multipliers? 9. Any special effects? (particles, sounds on hit) 10. Need a test command for admins to try different values live?
Data & Permissions: 11. Should player preferences be saved? (players can choose their knockback style) 12. Permission nodes for bypassing custom knockback? 13. Any integration with combat plugins or anti-cheat?
Performance: 14. How many players will be in combat simultaneously? 15. Should calculations be cached or computed per-hit?
Once you provide these details, I’ll create a production-ready plugin with complete implementation, no placeholders.”
Requirements Documentation
After gathering requirements, create a brief summary before implementation:
=== PLUGIN REQUIREMENTS SUMMARY ===
Type: [Combat/Economy/etc.]
Target Version: [1.21.3/1.8.8/etc.]
Java Version: [21/8]
Build Tool: [Maven/Gradle]
Database: [SQLite/MySQL/etc.]
Core Features:
- Feature 1: [detailed description]
- Feature 2: [detailed description]
Commands:
- /command1 <args> - Description [permission.node]
Configuration:
- setting1: default value
- setting2: default value
Performance Requirements:
- Max players: X
- Data per player: Y
- Async operations: [list]
Special Considerations:
- [any unique requirements]
===================================
This ensures both you and the user are aligned before writing a single line of code.
2. Load Relevant References
Based on plugin requirements, read appropriate reference files:
- Always read:
references/professional-template.mdfor project structure, README, API, GitHub setupreferences/paper-api-patterns.mdfor modern Paper API usage
- For packet manipulation:
references/packetevents-patterns.md - For database features:
references/database-patterns.md - For configuration:
references/config-patterns.md - For Maven projects:
references/maven-template.md - For Gradle projects:
references/gradle-template.md
3. Create Complete Implementation
Generate full plugin structure with:
Project Files
Maven Project (Default):
plugin-name/
âââ .github/
â âââ workflows/
â â âââ build.yml # CI/CD build pipeline
â â âââ release.yml # Release automation
â âââ ISSUE_TEMPLATE/
â â âââ bug_report.md # Bug report template
â â âââ feature_request.md # Feature request template
â âââ dependabot.yml # Dependency updates
âââ src/
â âââ main/
â â âââ java/
â â â âââ [package]/
â â â âââ PluginName.java # Main plugin class
â â â âââ api/ # Public API package
â â â â âââ PluginNameAPI.java
â â â â âââ events/ # Custom events
â â â â âââ managers/ # Manager interfaces
â â â â âââ data/ # Data classes
â â â âââ commands/ # Command classes
â â â âââ listeners/ # Event listeners
â â â âââ managers/ # Manager implementations
â â â âââ database/ # Database layer
â â â âââ config/ # Configuration
â â â âââ util/ # Utilities
â â âââ resources/
â â âââ plugin.yml
â â âââ config.yml
â â âââ messages.yml # i18n messages
â âââ test/
â âââ java/
â âââ [package]/
â âââ PluginNameTest.java # Unit tests
âââ .editorconfig # Code style consistency
âââ .gitignore # Git ignore rules
âââ CHANGELOG.md # Version history
âââ CODE_OF_CONDUCT.md # Community guidelines
âââ CONTRIBUTING.md # Contribution guide
âââ LICENSE # MIT License
âââ README.md # Project documentation
âââ pom.xml # Maven configuration
Gradle Project (for Kotlin or multi-module):
plugin-name/
âââ build.gradle.kts
âââ settings.gradle.kts
âââ gradle/
â âââ libs.versions.toml
âââ src/ (same structure as Maven)
Essential Files to Generate
CRITICAL: Every plugin MUST include these files for a professional project:
- README.md – Complete project documentation (see professional-template.md)
- API Package – Full developer API with:
api/PluginNameAPI.java(interface)PluginNameAPIImpl.java(implementation, package-private)api/events/(custom events)api/managers/(manager interfaces)
- GitHub Workflows:
.github/workflows/build.yml(CI/CD).github/workflows/release.yml(auto-releases)
- Issue Templates:
.github/ISSUE_TEMPLATE/bug_report.md.github/ISSUE_TEMPLATE/feature_request.md
- Contributing Files:
CONTRIBUTING.mdCODE_OF_CONDUCT.mdCHANGELOG.md
- Code Quality:
.editorconfig.gitignore
- Unit Tests:
src/test/java/.../PluginNameTest.java
- LICENSE – MIT License
Reference: See references/professional-template.md for complete templates of all these files.
Core Components
Main Plugin Class (with API integration):
@Getter
public class PluginName extends JavaPlugin {
@Getter
private static PluginName instance;
@Getter
private static PluginNameAPI api;
private ConfigManager<PluginConfig> configManager;
private DatabaseManager database;
@Override
public void onLoad() {
instance = this;
// Initialize PacketEvents
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().load();
}
@Override
public void onEnable() {
long startTime = System.currentTimeMillis();
try {
// Initialize PacketEvents
PacketEvents.getAPI().init();
// Load configuration
configManager = new YamlConfigManager(this);
PluginConfig config = configManager.get();
// Validate configuration
if (!ConfigValidator.validate(config, this)) {
getLogger().severe("Invalid configuration!");
getServer().getPluginManager().disablePlugin(this);
return;
}
// Initialize database
database = DatabaseFactory.create(this, getConfig());
database.initialize().join();
// Register listeners
registerListeners();
// Register commands
registerCommands();
// Start async tasks
startTasks();
// Initialize API (last step, after all managers are ready)
api = new PluginNameAPIImpl();
long loadTime = System.currentTimeMillis() - startTime;
getLogger().info("Plugin enabled successfully in " + loadTime + "ms!");
} catch (Exception e) {
getLogger().log(Level.SEVERE, "Failed to enable plugin!", e);
getServer().getPluginManager().disablePlugin(this);
}
}
@Override
public void onDisable() {
getLogger().info("Disabling plugin...");
// Shutdown PacketEvents
PacketEvents.getAPI().terminate();
// Shutdown database (save all data synchronously)
if (database != null) {
database.shutdown().join();
}
getLogger().info("Plugin disabled successfully!");
}
private void registerListeners() {
// Bukkit listeners
getServer().getPluginManager().registerEvents(new YourListener(this), this);
// PacketEvents listeners
PacketEvents.getAPI().getEventManager().registerListener(new YourPacketListener(this));
}
private void registerCommands() {
// Register commands
}
private void startTasks() {
// Start async repeating tasks
}
}
4. Implementation Guidelines
Always Use Modern APIs
// CORRECT - Modern Paper scheduler
plugin.getServer().getAsyncScheduler().runNow(plugin, task -> {
// Async work
});
// WRONG - Deprecated BukkitScheduler
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {});
Packet-Based Features
For custom mechanics, use PacketEvents:
public class CustomKnockbackListener extends PacketListenerAbstract {
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.ENTITY_VELOCITY) {
WrapperPlayServerEntityVelocity packet =
new WrapperPlayServerEntityVelocity(event);
// Modify velocity
Vector3d velocity = packet.getVelocity();
packet.setVelocity(new Vector3d(
velocity.x * 1.5,
velocity.y * 1.2,
velocity.z * 1.5
));
}
}
}
Developer API Implementation
CRITICAL: Every plugin must include a complete, well-documented API for other plugins to integrate with.
API Interface (api/PluginNameAPI.java):
package com.example.plugin.api;
/**
* Main API interface for PluginName
* <p>
* Access via: PluginName.getApi()
* <p>
* This API is guaranteed to be stable across minor versions.
* Breaking changes will only occur in major version updates.
*
* @since 1.0.0
*/
public interface PluginNameAPI {
/**
* Get the API version
* @return API version string in format "major.minor.patch"
*/
String getApiVersion();
/**
* Check if the plugin is fully loaded and ready to use
* @return true if ready, false during startup/shutdown
*/
boolean isReady();
// Add your API methods here with full JavaDoc
// Example:
/**
* Get player data for the specified player
* @param uuid Player UUID
* @return CompletableFuture containing player data, or null if not found
*/
// CompletableFuture<PlayerData> getPlayerData(UUID uuid);
}
API Implementation (PluginNameAPIImpl.java, package-private):
package com.example.plugin;
import com.example.plugin.api.PluginNameAPI;
/**
* Internal implementation of the API
* <p>
* Do not use this class directly - use PluginNameAPI interface instead
*/
class PluginNameAPIImpl implements PluginNameAPI {
@Override
public String getApiVersion() {
return "1.0.0";
}
@Override
public boolean isReady() {
return PluginName.getInstance() != null &&
PluginName.getInstance().isEnabled();
}
// Implement your API methods here
}
Custom Events (api/events/CustomEvent.java):
package com.example.plugin.api.events;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* Called when [describe when this event is called]
* <p>
* This event is cancellable. Cancelling it will prevent [action].
*
* @since 1.0.0
*/
@Getter
public class CustomEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private final Player player;
// Add your event data fields
@Setter
private boolean cancelled;
public CustomEvent(Player player) {
super(true); // async if needed
this.player = player;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
Calling Custom Events:
// Fire your custom events
CustomEvent event = new CustomEvent(player);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return; // Another plugin cancelled it
}
// Continue with action
Database Operations
Always async with CompletableFuture:
database.save("player:" + uuid, playerData)
.thenAccept(success -> {
if (success) {
player.sendMessage("Data saved!");
}
});
Configuration Management
Type-safe with Lombok:
@Data
@Builder
public class PluginConfig {
private boolean enabled;
private String prefix;
private DatabaseConfig database;
private CombatConfig combat;
}
5. Quality Standards
Every plugin must include:
-
Complete API System:
- Public API interface (
api/PluginNameAPI.java) - Package-private implementation
- Custom events in
api/events/ - Full JavaDoc on all public methods
- Example usage in README.md
- Public API interface (
-
Professional Documentation:
- README.md with features, installation, commands, API usage
- CONTRIBUTING.md for contributors
- CHANGELOG.md following Keep a Changelog format
- CODE_OF_CONDUCT.md
- Issue templates for bugs and features
-
GitHub Actions CI/CD:
.github/workflows/build.ymlfor automated builds.github/workflows/release.ymlfor automated releases- Dependabot for dependency updates
-
Unit Tests:
- JUnit 5 test structure
- Test core functionality
- Mock Bukkit/Paper API appropriately
-
Code Quality:
- Use zero deprecated APIs
- Handle all operations asynchronously where appropriate
- Include proper error handling and logging
- Use concurrent collections for thread safety
- Implement proper cleanup in onDisable()
- Include configuration validation
- Use Lombok to reduce boilerplate
- Follow .editorconfig formatting
-
Follow modern Java patterns (records, switch expressions where appropriate)
-
Include proper plugin.yml with api-version
-
Implement complete, working features (no placeholders)
6. Plugin.yml Template
name: PluginName
version: ${project.version}
main: com.example.pluginname.PluginName
api-version: '1.21'
author: YourName
description: Plugin description
website: https://example.com
# Load order
load: POSTWORLD
# or load: STARTUP for early loading
# Dependencies (optional)
depend: []
softdepend: []
loadbefore: []
# Commands (if any)
commands:
commandname:
description: Command description
usage: /<command> [args]
permission: pluginname.command
aliases: [alias1, alias2]
# Permissions (optional)
permissions:
pluginname.admin:
description: Admin permission
default: op
pluginname.use:
description: Basic usage permission
default: true
Example Plugin Types
Combat Plugin
- Custom knockback with PacketEvents
- Hit detection and validation
- Combat cooldowns
- Damage modifiers
- Anti-cheat integration
Economy Plugin
- Database-backed player balances
- Transaction system with async operations
- Shop GUI with Adventure API
- Multi-currency support
- Transaction history
Game Mechanic Plugin
- Custom entity behavior
- Parkour systems with checkpoints
- Mini-game framework
- Event-driven mechanics
- Async world manipulation
Anti-Cheat Plugin
- Packet-based movement checks
- Combat analysis (reach, killaura, etc.)
- Speed and fly detection
- Violation tracking and punishment
- Analytics and logging
Performance Optimization
-
Cache frequently accessed data
private final Map<UUID, PlayerData> cache = new ConcurrentHashMap<>(); -
Use async for I/O operations
plugin.getServer().getAsyncScheduler().runNow(plugin, task -> { // Database or file operations }); -
Minimize allocations in hot paths
// Reuse objects, use primitive collections where appropriate -
Use packet manipulation instead of events when possible
// PacketEvents is more performant than Bukkit events for certain tasks -
Batch database operations
// Save multiple records in one transaction
Common Patterns
Manager Pattern
public class PlayerManager {
private final Map<UUID, PlayerData> players = new ConcurrentHashMap<>();
public CompletableFuture<PlayerData> loadPlayer(UUID uuid) {
return database.load("player:" + uuid)
.thenApply(opt -> {
PlayerData data = opt.orElse(new PlayerData(uuid));
players.put(uuid, data);
return data;
});
}
public CompletableFuture<Void> savePlayer(UUID uuid) {
PlayerData data = players.get(uuid);
if (data != null) {
return database.save("player:" + uuid, data).thenAccept(success -> {});
}
return CompletableFuture.completedFuture(null);
}
}
Command Pattern with Reflections
public abstract class BaseCommand implements CommandExecutor {
protected final JavaPlugin plugin;
public BaseCommand(JavaPlugin plugin) {
this.plugin = plugin;
}
}
// Auto-register all commands
Reflections reflections = new Reflections("com.example.pluginname.commands");
Set<Class<? extends BaseCommand>> commands =
reflections.getSubTypesOf(BaseCommand.class);
for (Class<? extends BaseCommand> cmdClass : commands) {
BaseCommand cmd = cmdClass.getConstructor(JavaPlugin.class).newInstance(plugin);
// Register command
}
Final Notes
- Complete implementations only – No TODO comments or placeholder code
- Test critical paths – Ensure database operations, packet handling work correctly
- Modern Java features – Use records, pattern matching, text blocks where appropriate
- Proper logging – Use plugin logger for important events
- Clean code – Follow Java naming conventions, proper indentation
- Documentation – Add Javadocs for public APIs and complex methods
Advanced Scenarios and Edge Cases
As an experienced developer, always consider and implement solutions for:
Concurrency and Thread Safety
Race Conditions:
- Use
ConcurrentHashMapfor shared mutable state - Consider
AtomicInteger,AtomicReferencefor counters/flags - Use
synchronizedblocks orReentrantLockonly when necessary - Prefer immutable objects (records, final fields)
Example:
// GOOD - Thread-safe counter
private final AtomicInteger activeGames = new AtomicInteger(0);
// GOOD - Immutable data transfer
public record PlayerStats(UUID uuid, int kills, int deaths, double kdr) {}
// CAREFUL - Synchronized block only for compound operations
private final Map<UUID, Integer> scores = new ConcurrentHashMap<>();
public void addScore(UUID player, int amount) {
scores.merge(player, amount, Integer::sum); // Thread-safe
}
Memory Leaks Prevention
Common causes:
- Listeners not unregistered – Store listener instances, unregister in onDisable()
- Scheduled tasks not cancelled – Cancel all tasks in onDisable()
- Player data not cleaned up – Remove on PlayerQuitEvent
- Static collections – Clear in onDisable()
- Event handler references – Use WeakHashMap if caching players/entities
Example:
private final Set<BukkitTask> tasks = ConcurrentHashMap.newKeySet();
public void startTask(Runnable task) {
BukkitTask scheduled = plugin.getServer().getScheduler()...;
tasks.add(scheduled);
}
@Override
public void onDisable() {
// Cancel all tasks
tasks.forEach(BukkitTask::cancel);
tasks.clear();
// Clear player cache
playerCache.clear();
// Unregister listeners if dynamically registered
}
Database Transaction Safety
Handle failures gracefully:
public CompletableFuture<Boolean> transferMoney(UUID from, UUID to, double amount) {
return CompletableFuture.supplyAsync(() -> {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // Start transaction
// Deduct from sender
boolean deducted = deductBalance(conn, from, amount);
if (!deducted) {
conn.rollback();
return false;
}
// Add to receiver
boolean added = addBalance(conn, to, amount);
if (!added) {
conn.rollback();
return false;
}
conn.commit(); // Commit transaction
return true;
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex) {
plugin.getLogger().severe("Failed to rollback: " + ex.getMessage());
}
}
plugin.getLogger().severe("Transaction failed: " + e.getMessage());
return false;
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException e) {
// Log but don't throw
}
}
}
});
}
Packet Event Edge Cases
Handle cancelled events:
@Override
public void onPacketReceive(PacketReceiveEvent event) {
// Check if already cancelled by another listener
if (event.isCancelled()) {
return;
}
// Your logic here
// Cancel with reason
if (shouldCancel) {
event.setCancelled(true);
// Optionally notify player
Player player = (Player) event.getPlayer();
if (player != null) {
player.sendMessage("Action blocked: " + reason);
}
}
}
Handle rapid packet spam:
private final Map<UUID, RateLimiter> packetLimiters = new ConcurrentHashMap<>();
@Override
public void onPacketReceive(PacketReceiveEvent event) {
Player player = (Player) event.getPlayer();
UUID uuid = player.getUniqueId();
RateLimiter limiter = packetLimiters.computeIfAbsent(uuid,
k -> new RateLimiter(50, 1000)); // 50 packets per second
if (!limiter.tryAcquire()) {
event.setCancelled(true);
plugin.getLogger().warning(player.getName() + " is sending packets too quickly!");
return;
}
// Process packet normally
}
Configuration Migration
Handle config version changes:
public class ConfigMigration {
private static final int CURRENT_VERSION = 3;
public static void migrate(FileConfiguration config, JavaPlugin plugin) {
int version = config.getInt("config-version", 1);
if (version < CURRENT_VERSION) {
plugin.getLogger().info("Migrating config from v" + version + " to v" + CURRENT_VERSION);
if (version < 2) {
migrateV1ToV2(config);
}
if (version < 3) {
migrateV2ToV3(config);
}
config.set("config-version", CURRENT_VERSION);
try {
config.save(new File(plugin.getDataFolder(), "config.yml"));
} catch (IOException e) {
plugin.getLogger().severe("Failed to save migrated config: " + e.getMessage());
}
}
}
private static void migrateV1ToV2(FileConfiguration config) {
// Rename old settings
if (config.contains("old-setting")) {
config.set("new-setting", config.get("old-setting"));
config.set("old-setting", null);
}
// Add new default values
config.addDefault("new-feature.enabled", true);
}
}
Graceful Plugin Reload
Handle /reload command:
@Override
public void onDisable() {
// Save all pending data SYNCHRONOUSLY
saveAllData().join(); // Wait for completion
// Cancel tasks
cancelAllTasks();
// Close connections
closeConnections();
getLogger().info("Plugin disabled safely");
}
@Override
public void onEnable() {
// Check if this is a reload
if (isReloading()) {
getLogger().warning("Plugin reloaded. Some features may not work correctly. Restart recommended.");
}
// Re-initialize everything
}
private boolean isReloading() {
// Check if players are online (would be 0 on first server start)
return !Bukkit.getOnlinePlayers().isEmpty();
}
API Version Compatibility
Handle different Paper versions:
public class VersionAdapter {
private static final String VERSION;
static {
String packageName = Bukkit.getServer().getClass().getPackage().getName();
VERSION = packageName.substring(packageName.lastIndexOf('.') + 1);
}
public static boolean isModernVersion() {
// 1.20.5+ has different packet structure
return VERSION.compareTo("v1_20_R4") >= 0;
}
public static void sendActionBar(Player player, String message) {
if (isModernVersion()) {
player.sendActionBar(Component.text(message));
} else {
// Legacy method
player.spigot().sendMessage(ChatMessageType.ACTION_BAR,
TextComponent.fromLegacyText(message));
}
}
}
Error Recovery Patterns
Implement circuit breaker for external services:
public class CircuitBreaker {
private final int threshold;
private final long timeout;
private AtomicInteger failures = new AtomicInteger(0);
private volatile long lastFailureTime = 0;
private volatile boolean open = false;
public boolean isOpen() {
if (open && System.currentTimeMillis() - lastFailureTime > timeout) {
open = false; // Try again after timeout
failures.set(0);
}
return open;
}
public void recordSuccess() {
failures.set(0);
open = false;
}
public void recordFailure() {
lastFailureTime = System.currentTimeMillis();
if (failures.incrementAndGet() >= threshold) {
open = true;
}
}
}
// Usage in database operations
public CompletableFuture<Boolean> save(String key, Object value) {
if (circuitBreaker.isOpen()) {
plugin.getLogger().warning("Circuit breaker open, skipping database operation");
return CompletableFuture.completedFuture(false);
}
return CompletableFuture.supplyAsync(() -> {
try {
// Database operation
circuitBreaker.recordSuccess();
return true;
} catch (Exception e) {
circuitBreaker.recordFailure();
plugin.getLogger().severe("Database operation failed: " + e.getMessage());
return false;
}
});
}
Performance Monitoring
Add built-in performance metrics:
public class PerformanceMonitor {
private final Map<String, LongAdder> operationCounts = new ConcurrentHashMap<>();
private final Map<String, LongAdder> operationTimes = new ConcurrentHashMap<>();
public void recordOperation(String operation, long durationNanos) {
operationCounts.computeIfAbsent(operation, k -> new LongAdder()).increment();
operationTimes.computeIfAbsent(operation, k -> new LongAdder())
.add(durationNanos);
}
public void printStats() {
plugin.getLogger().info("=== Performance Stats ===");
operationCounts.forEach((op, count) -> {
long totalTime = operationTimes.get(op).sum();
long avgTime = totalTime / count.sum();
plugin.getLogger().info(String.format("%s: %d calls, avg %d μs",
op, count.sum(), avgTime / 1000));
});
}
}
// Usage
long start = System.nanoTime();
try {
// Your operation
} finally {
performanceMonitor.recordOperation("database.save", System.nanoTime() - start);
}
These advanced patterns should be implemented where appropriate based on the plugin’s complexity and requirements.
Documentation and Project Delivery
CRITICAL: Complete Project Checklist
When delivering a plugin, ALWAYS generate ALL of these files:
1. Source Code Files
- â All Java source files in proper package structure
- â Complete API package with interfaces and events
- â
Unit tests in
src/test/ - â
plugin.ymlwith all metadata - â
config.ymlwith all configuration options - â
messages.yml(if using messages)
2. Build Configuration
- â
pom.xmlorbuild.gradle.ktswith all dependencies - â
Maven wrapper files (
mvnw,mvnw.cmd) or Gradle wrapper
3. Documentation Files
- â
README.md – Complete project documentation with:
- Project description and features
- Installation instructions
- Configuration guide
- Commands and permissions tables
- API usage examples
- Links to wiki/Discord/issues
- Badges (build status, license, version)
- â CHANGELOG.md – Version history following Keep a Changelog
- â CONTRIBUTING.md – How to contribute
- â CODE_OF_CONDUCT.md – Community guidelines
- â LICENSE – MIT License (or as specified)
4. GitHub Configuration
- â
.github/workflows/build.yml– CI/CD build pipeline - â
.github/workflows/release.yml– Automated releases - â
.github/ISSUE_TEMPLATE/bug_report.md– Bug report template - â
.github/ISSUE_TEMPLATE/feature_request.md– Feature request template - â
.github/dependabot.yml– Dependency updates
5. Code Quality Files
- â
.editorconfig– Code formatting rules - â
.gitignore– Git ignore rules
README.md Generation Guidelines
ALWAYS customize the README.md with:
-
Replace all placeholders:
PluginNameâ actual plugin nameusernameâ GitHub username[Feature descriptions]â actual features- Example commands â actual commands from plugin
- Example permissions â actual permissions
- Discord/support links â real links or remove
-
Add accurate feature list based on what the plugin actually does
-
Include complete command/permission tables with all commands implemented
-
Write API usage examples using the actual API methods created
-
Add relevant badges:
[](https://github.com/username/plugin-name/actions) [](https://opensource.org/licenses/MIT) [](https://papermc.io/) -
Include configuration example from the actual
config.yml
CHANGELOG.md Initialization
Start with:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Initial plugin features
## [1.0.0] - YYYY-MM-DD
### Added
- [List all initial features]
- Complete developer API
- Database support (SQLite/MySQL/PostgreSQL)
- Configuration system
- [Specific feature 1]
- [Specific feature 2]
[Unreleased]: https://github.com/username/plugin-name/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/username/plugin-name/releases/tag/v1.0.0
Unit Test Example
Always include at least one test:
package com.example.plugin;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.*;
class PluginNameTest {
@BeforeEach
void setUp() {
// Setup mocks if needed
}
@Test
void testApiVersion() {
// Example test - adapt to your plugin
assertEquals("1.0.0", "1.0.0");
}
@Test
void testConfigValidation() {
// Test configuration validation
assertTrue(true);
}
}
File Organization
Organize generated files in the output directory:
/mnt/user-data/outputs/PluginName/
âââ src/
â âââ main/
â â âââ java/...
â â âââ resources/...
â âââ test/...
âââ .github/...
âââ README.md
âââ CHANGELOG.md
âââ CONTRIBUTING.md
âââ CODE_OF_CONDUCT.md
âââ LICENSE
âââ pom.xml
âââ .editorconfig
âââ .gitignore
âââ mvnw / mvnw.cmd
Presentation to User
After generating all files, present them with:
I've created a complete, production-ready [PluginName] plugin with:
â
Full source code with API system
â
Complete documentation (README, CHANGELOG, CONTRIBUTING)
â
GitHub Actions CI/CD
â
Unit test structure
â
Issue templates and code of conduct
The plugin includes:
- [Feature 1]
- [Feature 2]
- Complete developer API for integrations
- [Database type] database support
- Async operations throughout
To use:
1. Build: `mvn clean package`
2. Find JAR in `target/` directory
3. Place in your server's `plugins/` folder
For development:
- API usage examples in README.md
- Extend the API by adding methods to PluginNameAPI interface
- Add custom events in `api/events/` package
All files are ready for GitHub - just create a repository and push!
Reference Templates
For complete file templates, ALWAYS refer to references/professional-template.md which contains:
- Complete README.md template
- GitHub Actions workflows
- Issue templates
- All documentation files
- API structure examples
This ensures consistent, professional output for every plugin!