### Install Required Tools in WSL Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Installs necessary packages for PaperMC development within a WSL environment. Ensure you have the correct tool names from the requirements section. ```bash sudo apt-get update && sudo apt-get install $TOOL_NAMES -y ``` -------------------------------- ### Build Paper with Docker Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Example of running a Docker container to build Paper, mounting the current directory and executing a Java compiler command to check the version. ```console # docker run -it -v "$(pwd)":/data --rm eclipse-temurin:25.0.2_10-jdk bash Pulling image... root@abcdefg1234:/# javac -version javac 25.0.2 ``` -------------------------------- ### Task Scheduling Examples in Java Source: https://context7.com/papermc/paper/llms.txt Demonstrates various scheduling patterns including synchronous execution, delayed tasks, repeating timers, asynchronous operations, and BukkitRunnable usage. ```java package com.example.myplugin; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; import java.time.Duration; public class TaskExamples { private final JavaPlugin plugin; private BukkitTask autoSaveTask; public TaskExamples(JavaPlugin plugin) { this.plugin = plugin; } // Run task on next tick (synchronous) public void runNextTick() { Bukkit.getScheduler().runTask(plugin, () -> { // This runs on the main server thread for (Player player : Bukkit.getOnlinePlayers()) { player.sendMessage(Component.text("Server announcement!")); } }); } // Run task after delay (20 ticks = 1 second) public void runDelayed() { Bukkit.getScheduler().runTaskLater(plugin, () -> { Bukkit.broadcast(Component.text("This message appears after 5 seconds")); }, 100L); // 100 ticks = 5 seconds } // Run repeating task public void startAutoSave() { autoSaveTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { // Runs every 5 minutes (6000 ticks) Bukkit.savePlayers(); plugin.getLogger().info("Auto-saved player data"); }, 6000L, 6000L); // Initial delay, then repeat period } public void stopAutoSave() { if (autoSaveTask != null) { autoSaveTask.cancel(); } } // Asynchronous task (for IO operations, web requests, etc.) public void loadDataAsync(Player player) { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { // This runs on a separate thread - DO NOT access Bukkit API here String data = fetchDataFromDatabase(player.getUniqueId().toString()); // Switch back to main thread to use Bukkit API Bukkit.getScheduler().runTask(plugin, () -> { player.sendMessage(Component.text("Your data: " + data)); }); }); } // Using BukkitRunnable for more control public void startCountdown(Player player, int seconds) { new BukkitRunnable() { int remaining = seconds; @Override public void run() { if (remaining <= 0) { player.showTitle(Title.title( Component.text("GO!"), Component.empty(), Title.Times.times(Duration.ZERO, Duration.ofSeconds(1), Duration.ofMillis(500)) )); cancel(); return; } player.showTitle(Title.title( Component.text(String.valueOf(remaining)), Component.text("Get ready..."), Title.Times.times(Duration.ZERO, Duration.ofSeconds(1), Duration.ZERO) )); remaining--; } }.runTaskTimer(plugin, 0L, 20L); } private String fetchDataFromDatabase(String uuid) { // Simulate database fetch try { Thread.sleep(100); } catch (InterruptedException ignored) {} return "sample_data"; } } ``` -------------------------------- ### Example Patch Header and Notes Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Illustrates the format for patch headers and notes when submitting feature patches to Paper. These notes explain the intent and technical details of the changes. ```patch From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sun, 15 Oct 2017 00:29:07 +0100 Subject: [PATCH] revert serverside behavior of keepalives This patch intends to bump up the time that a client has to reply to the server back to 30 seconds as per pre 1.12.2, which allowed clients more than enough time to reply potentially allowing them to be less temperamental due to lag spikes on the network thread, e.g. that caused by plugins that are interacting with netty. We also add a system property to allow people to tweak how long the server will wait for a reply. There is a compromise here between lower and higher values, lower values will mean that dead connections can be closed sooner, whereas higher values will make this less sensitive to issues such as spikes from networking or during connections flood of chunk packets on slower clients, at the cost of dead connections being kept open for longer. diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java index a92bf8967..d0ab87d0f 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java ``` -------------------------------- ### Interactive Program Notice (GNU GPL) Source: https://github.com/papermc/paper/blob/main/licenses/GPL.md Display this notice when your program starts in interactive mode to inform users about its free software status and warranty conditions. Replace placeholder commands with actual ones. ```plaintext Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. ``` -------------------------------- ### Publish Paper APIs to Maven Local Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Command to build and install the Paper APIs and Server to your local Maven repository. This is useful for testing API changes in external plugins. ```bash ./gradlew publishToMavenLocal ``` -------------------------------- ### Get All Loaded Worlds Source: https://context7.com/papermc/paper/llms.txt Returns a list of all currently loaded world instances on the server. ```java public List getWorlds() { return Bukkit.getWorlds(); } ``` -------------------------------- ### Get World Spawn Location Source: https://context7.com/papermc/paper/llms.txt Retrieves the designated spawn point for a given world. ```java public Location getSpawnLocation(World world) { return world.getSpawnLocation(); } ``` -------------------------------- ### Get World by Name Source: https://context7.com/papermc/paper/llms.txt Retrieves a loaded world instance using its name. Ensure the world is loaded before calling this. ```java public World getWorld(String name) { return Bukkit.getWorld(name); } ``` -------------------------------- ### Get Player Balance from Custom Config Source: https://context7.com/papermc/paper/llms.txt Retrieves a player's balance from 'playerdata.yml', defaulting to 0.0 if not found. Requires player UUID. ```java public double getPlayerBalance(String uuid) { return playerData.getDouble("players." + uuid + ".balance", 0.0); } ``` -------------------------------- ### Add New Global Configuration Setting Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Example of adding a new miscellaneous setting to the GlobalConfiguration class. The setting name defaults to snake-case of the field name, but can be overridden with the @Setting annotation. ```java public class GlobalConfiguration { // other sections public class Misc extends ConfigurationPart { // other settings public boolean lagCompensateBlockBreaking = true; public boolean useDimensionTypeForCustomSpawners = false; public int maxNumOfPlayers = 20; // This is the new setting } } ``` -------------------------------- ### Get Player by Name or UUID Source: https://context7.com/papermc/paper/llms.txt Retrieve a Player object using their username or unique identifier. Ensure the player is currently online. ```java public Player getPlayer(String name) { return Bukkit.getPlayer(name); } ``` ```java public Player getPlayer(UUID uuid) { return Bukkit.getPlayer(uuid); } ``` -------------------------------- ### Marking Code Modifications in PaperMC Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Use specific comment formats to denote modifications made to Vanilla files. Single-line changes use '// Paper - ', while multi-line changes use '// Paper start - ' and '// Paper end - '. ```java entity.getWorld().dontBeStupid(); // Paper - Move away from beStupid() entity.getFriends().forEach(Entity::explode); entity.updateFriends(); // Paper start - Use plugin-set spawn // entity.getWorld().explode(entity.getWorld().getSpawn()); Location spawnLocation = ((CraftWorld) entity.getWorld()).getSpawnLocation(); entity.getWorld().explode(new BlockPosition(spawnLocation.getX(), spawnLocation.getY(), spawnLocation.getZ())); // Paper end - Use plugin-set spawn ``` -------------------------------- ### Get Entities in World Source: https://context7.com/papermc/paper/llms.txt Retrieves all entities currently present in a specified world. ```java public Collection getEntities(World world) { return world.getEntities(); } ``` -------------------------------- ### Initialize and Load Plugin Configurations Source: https://context7.com/papermc/paper/llms.txt Initializes the configuration manager by saving default config.yml and loading custom playerdata.yml. Ensures configurations are ready for use. ```java public ConfigManager(JavaPlugin plugin) { this.plugin = plugin; loadConfigs(); } private void loadConfigs() { // Load default config.yml plugin.saveDefaultConfig(); config = plugin.getConfig(); // Load custom config file playerDataFile = new File(plugin.getDataFolder(), "playerdata.yml"); if (!playerDataFile.exists()) { plugin.saveResource("playerdata.yml", false); } playerData = YamlConfiguration.loadConfiguration(playerDataFile); } ``` -------------------------------- ### Get Block at Location Source: https://context7.com/papermc/paper/llms.txt Retrieves the Block object at a specific coordinate within a given world. ```java public Block getBlock(World world, int x, int y, int z) { return world.getBlockAt(x, y, z); } ``` -------------------------------- ### Standard GNU GPL Notice Source: https://github.com/papermc/paper/blob/main/licenses/GPL.md Include this notice at the beginning of each source file to state the exclusion of warranty and provide licensing information. Ensure copyright and license details are present. ```plaintext Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ``` -------------------------------- ### Get Nearby Entities Source: https://context7.com/papermc/paper/llms.txt Finds all entities within a cubic radius around a specific location. ```java public Collection getNearbyEntities(Location location, double radius) { return location.getWorld().getNearbyEntities(location, radius, radius, radius); } ``` -------------------------------- ### Implement a Custom GUI Manager Source: https://context7.com/papermc/paper/llms.txt A complete implementation of a GUI manager that handles menu creation, item placement, and click event interception. ```java package com.example.myplugin; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public class GUIManager implements Listener { private final JavaPlugin plugin; private final Map openMenus = new HashMap<>(); public GUIManager(JavaPlugin plugin) { this.plugin = plugin; Bukkit.getPluginManager().registerEvents(this, plugin); } // Create and open a custom menu public void openMainMenu(Player player) { Inventory inventory = Bukkit.createInventory( null, 27, // Size (must be multiple of 9) Component.text("Main Menu", NamedTextColor.DARK_PURPLE) ); // Fill border with glass panes ItemStack border = createItem(Material.GRAY_STAINED_GLASS_PANE, Component.empty()); for (int i = 0; i < 27; i++) { if (i < 9 || i >= 18 || i % 9 == 0 || i % 9 == 8) { inventory.setItem(i, border); } } // Add menu items inventory.setItem(11, createItem(Material.DIAMOND_SWORD, Component.text("PvP Arena", NamedTextColor.RED))); inventory.setItem(13, createItem(Material.ENDER_PEARL, Component.text("Teleport Menu", NamedTextColor.LIGHT_PURPLE))); inventory.setItem(15, createItem(Material.GOLD_INGOT, Component.text("Shop", NamedTextColor.GOLD))); player.openInventory(inventory); openMenus.put(player.getUniqueId(), "main_menu"); } public void openShopMenu(Player player) { Inventory shop = Bukkit.createInventory(null, 54, Component.text("Shop", NamedTextColor.GOLD)); // Add items for sale shop.setItem(10, createShopItem(Material.DIAMOND_SWORD, "Diamond Sword", 100)); shop.setItem(11, createShopItem(Material.DIAMOND_PICKAXE, "Diamond Pickaxe", 80)); shop.setItem(12, createShopItem(Material.GOLDEN_APPLE, "Golden Apple", 50)); shop.setItem(13, createShopItem(Material.ENDER_PEARL, "Ender Pearl", 25)); // Close button shop.setItem(49, createItem(Material.BARRIER, Component.text("Close", NamedTextColor.RED))); player.openInventory(shop); openMenus.put(player.getUniqueId(), "shop"); } @EventHandler public void onInventoryClick(InventoryClickEvent event) { if (!(event.getWhoClicked() instanceof Player player)) return; String menuType = openMenus.get(player.getUniqueId()); if (menuType == null) return; event.setCancelled(true); // Prevent taking items ItemStack clicked = event.getCurrentItem(); if (clicked == null || clicked.getType() == Material.AIR) return; switch (menuType) { case "main_menu" -> handleMainMenuClick(player, clicked); case "shop" -> handleShopClick(player, clicked, event.getSlot()); } } private void handleMainMenuClick(Player player, ItemStack clicked) { switch (clicked.getType()) { case DIAMOND_SWORD -> { player.closeInventory(); player.sendMessage(Component.text("Teleporting to PvP Arena...", NamedTextColor.RED)); } case ENDER_PEARL -> { player.closeInventory(); player.sendMessage(Component.text("Opening teleport menu...", NamedTextColor.LIGHT_PURPLE)); } case GOLD_INGOT -> openShopMenu(player); } } private void handleShopClick(Player player, ItemStack clicked, int slot) { if (clicked.getType() == Material.BARRIER) { player.closeInventory(); return; } // Process purchase logic here player.sendMessage(Component.text("You purchased: " + clicked.getType().name(), NamedTextColor.GREEN)); } @EventHandler public void onInventoryClose(InventoryCloseEvent event) { openMenus.remove(event.getPlayer().getUniqueId()); } private ItemStack createItem(Material material, Component name) { ItemStack item = ItemStack.of(material); ItemMeta meta = item.getItemMeta(); meta.displayName(name); item.setItemMeta(meta); return item; ``` -------------------------------- ### Get All Online Players Source: https://context7.com/papermc/paper/llms.txt Obtain a collection of all players currently connected to the server. This method returns a Collection of Player objects. ```java public Collection getOnlinePlayers() { return Bukkit.getOnlinePlayers(); } ``` -------------------------------- ### Check Windows Version Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Verifies your current Windows version. This is useful for ensuring compatibility with WSL 2. ```bash winver ``` -------------------------------- ### Create a Basic Paper Plugin Source: https://context7.com/papermc/paper/llms.txt This Java code demonstrates the basic structure of a Paper plugin, extending JavaPlugin and implementing essential lifecycle methods like onEnable and onDisable. ```java package com.example.myplugin; import org.bukkit.plugin.java.JavaPlugin; public class MyPlugin extends JavaPlugin { @Override public void onEnable() { // Called when the plugin is enabled getLogger().info("MyPlugin has been enabled!"); // Save default config.yml from resources saveDefaultConfig(); // Register event listeners getServer().getPluginManager().registerEvents(new MyListener(), this); } @Override public void onDisable() { // Called when the plugin is disabled getLogger().info("MyPlugin has been disabled!"); } } ``` -------------------------------- ### Apply Potion Effect Source: https://context7.com/papermc/paper/llms.txt Adds a potion effect to a player, such as SPEED. Duration is in seconds and amplifier starts at 0. Particles and icon visibility can be controlled. ```java public void applySpeed(Player player, int durationSeconds, int amplifier) { player.addPotionEffect(new PotionEffect( PotionEffectType.SPEED, durationSeconds * 20, // Convert to ticks amplifier, false, // ambient true, // particles true // icon )); } ``` -------------------------------- ### Create or Load World Source: https://context7.com/papermc/paper/llms.txt Creates a new world with specified settings or loads an existing one if it already exists. Configures environment and structure generation. ```java public World createWorld(String name) { WorldCreator creator = new WorldCreator(name); creator.environment(World.Environment.NORMAL); creator.generateStructures(true); return creator.createWorld(); } ``` -------------------------------- ### Apply Patches with Gradle Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Command to apply patches to the Minecraft source files using Gradle. Ensure you are in the root directory of the cloned repository. ```bash ./gradlew applyPatches ``` -------------------------------- ### Monitor Server Performance with Paper API Source: https://context7.com/papermc/paper/llms.txt Use these methods to retrieve server-wide metrics like TPS, tick times, and player counts. Requires access to the Bukkit static API. ```java package com.example.myplugin; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.entity.Player; public class PerformanceMonitor { // Get server TPS (1m, 5m, 15m averages) public double[] getTPS() { return Bukkit.getTPS(); } public String getTpsStatus() { double[] tps = getTPS(); double currentTps = tps[0]; // 1-minute average NamedTextColor color; if (currentTps >= 19.0) { color = NamedTextColor.GREEN; } else if (currentTps >= 15.0) { color = NamedTextColor.YELLOW; } else { color = NamedTextColor.RED; } return String.format("TPS: %.2f (1m), %.2f (5m), %.2f (15m)", tps[0], tps[1], tps[2]); } // Get average tick time in milliseconds public double getAverageTickTime() { return Bukkit.getAverageTickTime(); } // Get tick time samples public long[] getTickTimes() { return Bukkit.getTickTimes(); } // Get current tick number public int getCurrentTick() { return Bukkit.getCurrentTick(); } // Check if running on main thread public boolean isMainThread() { return Bukkit.isPrimaryThread(); } // Check if server is stopping public boolean isStopping() { return Bukkit.isStopping(); } // Get online player count public int getOnlinePlayerCount() { return Bukkit.getOnlinePlayers().size(); } // Get maximum player count public int getMaxPlayers() { return Bukkit.getMaxPlayers(); } // Send performance report to player public void sendPerformanceReport(Player player) { double[] tps = getTPS(); double avgTick = getAverageTickTime(); player.sendMessage(Component.text("=== Server Performance ===", NamedTextColor.GOLD)); player.sendMessage(Component.text(String.format("TPS: %.2f / %.2f / %.2f", tps[0], tps[1], tps[2]), getTpsColor(tps[0]))); player.sendMessage(Component.text(String.format("Avg Tick: %.2fms", avgTick), avgTick < 50 ? NamedTextColor.GREEN : NamedTextColor.RED)); player.sendMessage(Component.text(String.format("Players: %d/%d", getOnlinePlayerCount(), getMaxPlayers()), NamedTextColor.AQUA)); // World-specific stats for (World world : Bukkit.getWorlds()) { player.sendMessage(Component.text(String.format( "World '%s': %d entities, %d chunks", world.getName(), world.getEntityCount(), world.getChunkCount() ), NamedTextColor.GRAY)); } } private NamedTextColor getTpsColor(double tps) { if (tps >= 19.0) return NamedTextColor.GREEN; if (tps >= 15.0) return NamedTextColor.YELLOW; return NamedTextColor.RED; } } ``` -------------------------------- ### Read String Configuration Value Source: https://context7.com/papermc/paper/llms.txt Retrieves a string value from the main configuration file with a default fallback. Use for settings like server names. ```java public String getServerName() { return config.getString("server.name", "Default Server"); } ``` -------------------------------- ### Read String List Configuration Value Source: https://context7.com/papermc/paper/llms.txt Retrieves a list of strings from the configuration. Ideal for lists of commands or items. ```java public List getBlockedCommands() { return config.getStringList("settings.blocked-commands"); } ``` -------------------------------- ### Import Handling in Vanilla Classes Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md When adding new imports to Vanilla classes, prefer using fully qualified names (FQN) to avoid potential patch conflicts. Imports can be added if a type is used frequently, but FQNs are preferred for less frequent usage. ```java import net.minecraft.server.MinecraftServer; // don't add import here, use FQN like below public class SomeVanillaClass { public final org.bukkit.Location newLocation; // Paper - add location } ``` -------------------------------- ### Interactive Git Rebase Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Initiates an interactive rebase session in the `paper-server/src/minecraft/java` directory, allowing you to edit, squash, or reorder commits. The `base` reference points to the initial commit of the Minecraft source files. ```bash git rebase -i base ``` -------------------------------- ### Create Fixup Commit Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Create a fixup commit to apply changes to a specific patch. Use the patch's hash, filename, or subject to identify it. Use --squash to also modify the commit message. ```bash git commit -a --fixup ``` ```bash git commit -a --fixup file ``` ```bash git commit -a --fixup "Subject of Patch name" ``` -------------------------------- ### Implement BasicCommand for a Heal Command Source: https://context7.com/papermc/paper/llms.txt This class implements the BasicCommand interface to create a 'heal' command. It handles healing the sender or a specified player and provides tab completion for player names. Ensure the command is registered in your plugin's onEnable method. ```java package com.example.myplugin; import io.papermc.paper.command.brigadier.BasicCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; public class HealCommand implements BasicCommand { @Override public void execute(CommandSourceStack stack, String[] args) { if (args.length == 0) { // Heal the command sender if (stack.getSender() instanceof Player player) { player.setHealth(player.getMaxHealth()); player.sendMessage(Component.text("You have been healed!", NamedTextColor.GREEN)); } else { stack.getSender().sendMessage(Component.text("Console must specify a player!", NamedTextColor.RED)); } } else { // Heal specified player Player target = Bukkit.getPlayer(args[0]); if (target != null) { target.setHealth(target.getMaxHealth()); target.sendMessage(Component.text("You have been healed!", NamedTextColor.GREEN)); stack.getSender().sendMessage(Component.text("Healed " + target.getName(), NamedTextColor.GREEN)); } else { stack.getSender().sendMessage(Component.text("Player not found!", NamedTextColor.RED)); } } } @Override public Collection suggest(CommandSourceStack stack, String[] args) { if (args.length == 1) { return Bukkit.getOnlinePlayers().stream() .map(Player::getName) .filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase())) .collect(Collectors.toList()); } return List.of(); } @Override public String permission() { return "myplugin.heal"; } } // Register in onEnable(): // this.registerCommand("heal", "Heal yourself or another player", List.of("h"), new HealCommand()); ``` -------------------------------- ### Create and Manipulate ItemStacks in Java Source: https://context7.com/papermc/paper/llms.txt Provides a comprehensive utility class for building custom items, managing metadata, handling enchantments, and using persistent data containers. ```java package com.example.myplugin; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; import org.bukkit.plugin.java.JavaPlugin; import java.util.List; public class ItemBuilder { private final JavaPlugin plugin; public ItemBuilder(JavaPlugin plugin) { this.plugin = plugin; } // Create basic item public ItemStack createItem(Material material, int amount) { return ItemStack.of(material, amount); } // Create custom named item with lore public ItemStack createCustomItem(Material material, Component name, List lore) { ItemStack item = ItemStack.of(material); ItemMeta meta = item.getItemMeta(); meta.displayName(name.decoration(TextDecoration.ITALIC, false)); meta.lore(lore.stream() .map(line -> line.decoration(TextDecoration.ITALIC, false)) .toList()); item.setItemMeta(meta); return item; } // Create enchanted diamond sword public ItemStack createLegendarySword() { ItemStack sword = ItemStack.of(Material.DIAMOND_SWORD); ItemMeta meta = sword.getItemMeta(); meta.displayName(Component.text("Legendary Blade", NamedTextColor.GOLD) .decoration(TextDecoration.ITALIC, false) .decoration(TextDecoration.BOLD, true)); meta.lore(List.of( Component.text("A sword of immense power", NamedTextColor.GRAY) .decoration(TextDecoration.ITALIC, false), Component.empty(), Component.text("LEGENDARY", NamedTextColor.GOLD) .decoration(TextDecoration.ITALIC, false) )); sword.setItemMeta(meta); // Add enchantments sword.addEnchantment(Enchantment.SHARPNESS, 5); sword.addEnchantment(Enchantment.FIRE_ASPECT, 2); sword.addEnchantment(Enchantment.UNBREAKING, 3); return sword; } // Store custom data in item public ItemStack createTrackedItem(Material material, String trackingId) { ItemStack item = ItemStack.of(material); NamespacedKey key = new NamespacedKey(plugin, "tracking_id"); item.editPersistentDataContainer(container -> { container.set(key, PersistentDataType.STRING, trackingId); }); return item; } // Read custom data from item public String getTrackingId(ItemStack item) { NamespacedKey key = new NamespacedKey(plugin, "tracking_id"); return item.getPersistentDataContainer().get(key, PersistentDataType.STRING); } // Check if item has specific enchantment public boolean hasEnchantment(ItemStack item, Enchantment enchantment) { return item.containsEnchantment(enchantment); } // Create player head public ItemStack createPlayerHead(String playerName) { ItemStack head = ItemStack.of(Material.PLAYER_HEAD); org.bukkit.inventory.meta.SkullMeta meta = (org.bukkit.inventory.meta.SkullMeta) head.getItemMeta(); meta.setOwningPlayer(org.bukkit.Bukkit.getOfflinePlayer(playerName)); meta.displayName(Component.text(playerName + "'s Head", NamedTextColor.YELLOW)); head.setItemMeta(meta); return head; } } ``` -------------------------------- ### Configure Paper API Dependency in Gradle Source: https://github.com/papermc/paper/blob/main/README.md Add the Paper repository and API dependency to your build.gradle.kts file. Ensure you are using JDK 25 as specified in the toolchain configuration. ```kotlin repositories { maven { url = uri("https://repo.papermc.io/repository/maven-public/") } } dependencies { compileOnly("io.papermc.paper:paper-api:26.1.2.build.+") } java { toolchain.languageVersion.set(JavaLanguageVersion.of(25)) } ``` -------------------------------- ### Rebase with Autosquash Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md After creating a fixup or squash commit, use this command to automatically move the commit to the correct position for rebasing. ```bash git rebase -i --autosquash base ``` -------------------------------- ### Reload Plugin Configurations Source: https://context7.com/papermc/paper/llms.txt Reloads both the main configuration and player data from their respective files. Use when configuration changes need to be applied dynamically. ```java public void reloadConfigs() { plugin.reloadConfig(); config = plugin.getConfig(); playerData = YamlConfiguration.loadConfiguration(playerDataFile); } ``` -------------------------------- ### Paper Plugin Configuration (plugin.yml) Source: https://context7.com/papermc/paper/llms.txt This YAML file defines the metadata, dependencies, and permissions for a Paper plugin. It must be placed in the src/main/resources directory. ```yaml name: MyPlugin version: 1.0.0 main: com.example.myplugin.MyPlugin description: An example Paper plugin author: YourName api-version: "1.21" load: POSTWORLD dependencies: server: - name: Vault required: false permissions: myplugin.admin: description: Allows access to admin commands default: op myplugin.use: description: Allows basic plugin usage default: true ``` -------------------------------- ### Create Shop Item with Custom Lore Source: https://context7.com/papermc/paper/llms.txt Creates an ItemStack with a custom display name and lore, including price and purchase instructions. Useful for in-game shops. ```java private ItemStack createShopItem(Material material, String name, int price) { ItemStack item = ItemStack.of(material); ItemMeta meta = item.getItemMeta(); meta.displayName(Component.text(name, NamedTextColor.WHITE)); meta.lore(List.of( Component.empty(), Component.text("Price: $" + price, NamedTextColor.GOLD), Component.text("Click to purchase", NamedTextColor.GRAY) )); item.setItemMeta(meta); return item; } ``` -------------------------------- ### Check Method Arguments with Preconditions Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Use Preconditions#checkArgument to validate method arguments and throw IllegalArgumentException if conditions are not met. Avoid Preconditions#checkNotNull as it throws NullPointerException. ```java public void sendMessage(Player player, Component message) { Preconditions.checkArgument(player != null, "player cannot be null"); Preconditions.checkArgument(player.isOnline(), "player %s must be online", player.getName()); Preconditions.checkArgument(message != null, "message cannot be null"); // rest of code } ``` -------------------------------- ### Read Integer Configuration Value Source: https://context7.com/papermc/paper/llms.txt Retrieves an integer value from the configuration with a default. Suitable for numerical settings like maximum homes. ```java public int getMaxHomes() { return config.getInt("settings.max-homes", 3); } ``` -------------------------------- ### Configure Paper API Dependency in Maven Source: https://github.com/papermc/paper/blob/main/README.md Add the Paper repository and dependency to your pom.xml file. The dependency scope is set to provided as the server environment will supply the API. ```xml papermc https://repo.papermc.io/repository/maven-public/ ``` ```xml io.papermc.paper paper-api [26.1.2.build,) provided ``` -------------------------------- ### Rebase PR to Upstream Main Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Fetch latest changes from upstream, rebase your feature branch onto it, and apply updated patches. ```bash git fetch upstream ``` ```bash git switch patch-branch && git rebase upstream/main ``` ```bash ./gradlew applyPatches ``` -------------------------------- ### Handle Player Join and Quit Events Source: https://context7.com/papermc/paper/llms.txt This Java code implements a listener for player join and quit events, customizing the join/quit messages using the Adventure API and sending a welcome message to joining players. ```java package com.example.myplugin; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; public class MyListener implements Listener { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); // Set custom join message using Adventure API event.joinMessage(Component.text() .append(Component.text("[+] ", NamedTextColor.GREEN)) .append(player.displayName()) .append(Component.text(" joined the server!", NamedTextColor.GRAY)) .build()); // Send welcome message to the player player.sendMessage(Component.text("Welcome to the server!", NamedTextColor.GOLD)); } @EventHandler public void onPlayerQuit(PlayerQuitEvent event) { event.quitMessage(Component.text() .append(Component.text("[-] ", NamedTextColor.RED)) .append(event.getPlayer().displayName()) .append(Component.text(" left the server", NamedTextColor.GRAY)) .build()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onPlayerInteract(PlayerInteractEvent event) { if (event.getItem() != null && event.getItem().getType().name().endsWith("_SWORD")) { event.getPlayer().sendMessage(Component.text("You swung a sword!")); } } @EventHandler public void onEntityDamage(EntityDamageByEntityEvent event) { if (event.getDamager() instanceof Player attacker) { if (event.getEntity() instanceof Player victim) { // Cancel PvP damage event.setCancelled(true); attacker.sendMessage(Component.text("PvP is disabled!", NamedTextColor.RED)); } } } } ``` -------------------------------- ### Set Player Balance in Custom Config Source: https://context7.com/papermc/paper/llms.txt Sets a player's balance in the custom 'playerdata.yml' and saves the changes. Requires player UUID. ```java public void setPlayerBalance(String uuid, double balance) { playerData.set("players." + uuid + ".balance", balance); savePlayerData(); } ``` -------------------------------- ### Rebuild Patches with Gradle Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Command to rebuild the patch files after making modifications and running `fixupSourcePatches`. This is typically run from the root directory. ```bash ./gradlew rebuildPatches ``` -------------------------------- ### Registering Brigadier Commands Source: https://context7.com/papermc/paper/llms.txt Uses the LifecycleEvents.COMMANDS event to register a command tree with subcommands and argument resolvers. ```java package com.example.myplugin; import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; public class BrigadierCommands { public static void register(JavaPlugin plugin) { LifecycleEventManager manager = plugin.getLifecycleManager(); manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { Commands commands = event.registrar(); // /economy give // /economy take // /economy balance [player] commands.register( Commands.literal("economy") .then(Commands.literal("give") .then(Commands.argument("player", ArgumentTypes.player()) .then(Commands.argument("amount", IntegerArgumentType.integer(1)) .executes(ctx -> { PlayerSelectorArgumentResolver resolver = ctx.getArgument("player", PlayerSelectorArgumentResolver.class); Player target = resolver.resolve(ctx.getSource()).getFirst(); int amount = IntegerArgumentType.getInteger(ctx, "amount"); // Give money logic here ctx.getSource().getSender().sendMessage( Component.text("Gave $" + amount + " to " + target.getName(), NamedTextColor.GREEN) ); return Command.SINGLE_SUCCESS; })))) .then(Commands.literal("take") .then(Commands.argument("player", ArgumentTypes.player()) .then(Commands.argument("amount", IntegerArgumentType.integer(1)) .executes(ctx -> { PlayerSelectorArgumentResolver resolver = ctx.getArgument("player", PlayerSelectorArgumentResolver.class); Player target = resolver.resolve(ctx.getSource()).getFirst(); int amount = IntegerArgumentType.getInteger(ctx, "amount"); ctx.getSource().getSender().sendMessage( Component.text("Took $" + amount + " from " + target.getName(), NamedTextColor.RED) ); return Command.SINGLE_SUCCESS; })))) .then(Commands.literal("balance") .executes(ctx -> { if (ctx.getSource().getSender() instanceof Player player) { player.sendMessage(Component.text("Your balance: $1000", NamedTextColor.GOLD)); } return Command.SINGLE_SUCCESS; }) .then(Commands.argument("player", ArgumentTypes.player()) .executes(ctx -> { PlayerSelectorArgumentResolver resolver = ctx.getArgument("player", PlayerSelectorArgumentResolver.class); Player target = resolver.resolve(ctx.getSource()).getFirst(); ctx.getSource().getSender().sendMessage( Component.text(target.getName() + "'s balance: $1000", NamedTextColor.GOLD) ); return Command.SINGLE_SUCCESS; }))) .build(), "Economy management commands", List.of("eco", "money") ); }); } } ``` -------------------------------- ### Force Push Changes Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md After rebasing and rebuilding patches, force push your changes to your fork. Ensure no commits are lost. ```bash git push --force ``` -------------------------------- ### Save Player Data to File Source: https://context7.com/papermc/paper/llms.txt Saves the current state of the player data configuration to its file. Includes error handling for IOExceptions. ```java private void savePlayerData() { try { playerData.save(playerDataFile); } catch (IOException e) { plugin.getLogger().severe("Could not save player data: " + e.getMessage()); } } ``` -------------------------------- ### Spawn Entity with Customization Source: https://context7.com/papermc/paper/llms.txt Spawns a Zombie entity at a location and applies custom properties like name, visibility, size, and health using a lambda. ```java public Zombie spawnCustomZombie(Location location) { return location.getWorld().spawn(location, Zombie.class, zombie -> { zombie.customName(net.kyori.adventure.text.Component.text("Boss Zombie")); zombie.setCustomNameVisible(true); zombie.setBaby(false); zombie.setHealth(40.0); }); } ``` -------------------------------- ### Play Sound for Player Source: https://context7.com/papermc/paper/llms.txt Plays a specified sound effect at the player's current location with a default volume and pitch. Requires a Player object and a Sound enum. ```java public void playSound(Player player, Sound sound) { player.playSound(player.getLocation(), sound, 1.0f, 1.0f); } ``` -------------------------------- ### Set World Time Source: https://context7.com/papermc/paper/llms.txt Sets the current in-game time for a world. Time is measured in ticks. ```java public void setTime(World world, long time) { world.setTime(time); } ``` -------------------------------- ### Access Global Configuration Value Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md How to access a newly added global configuration value in your code. For global values, the fully qualified class name is often used. ```java int maxPlayers = GlobalConfiguration.get().misc.maxNumOfPlayers; ``` -------------------------------- ### Access World Configuration Value Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md How to access a newly added world configuration value when you have an instance of net.minecraft.world.level.Level. ```java int maxPlayers = level.paperConfig().misc.maxNumOfPlayers; ``` -------------------------------- ### Set World to Day Source: https://context7.com/papermc/paper/llms.txt Sets the world's time to the morning (1000 ticks). ```java public void setDay(World world) { world.setTime(1000); // Morning } ``` -------------------------------- ### Spawn Entity Source: https://context7.com/papermc/paper/llms.txt Spawns a specified entity type at a given location in the world. ```java public Zombie spawnZombie(Location location) { return (Zombie) location.getWorld().spawnEntity(location, EntityType.ZOMBIE); } ``` -------------------------------- ### Check Object State with Preconditions Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Use Preconditions#checkState to validate the internal state of an object within a method, throwing IllegalStateException if conditions are not met. ```java private Player player; @Override public void sendMessage(Component message) { Preconditions.checkArgument(message != null, "message cannot be null"); Preconditions.checkState(this.player != null, "player cannot be null"); Preconditions.checkState(this.player.isOnline(), "player %s must be online", this.player.getName()); // rest of code } ``` -------------------------------- ### Continue Git Rebase Source: https://github.com/papermc/paper/blob/main/CONTRIBUTING.md Resumes the Git rebase process after conflicts have been resolved or changes have been amended. This command should be run from the root directory after completing manual edits during a rebase. ```git git rebase --continue ```