v1_20_R3
). This may be as soon as 1.20.5, as we expect almost every plugin using internals to break due to major changes in vanilla anyways.private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
public static String cbClass(String className) {
return CRAFTBUKKIT_PACKAGE + "." + className);
}
Class.forName(cbClass("entity.CraftBee"))
String craftBukkitPackage = Bukkit.getServer().getClass().getPackage().getName();
// This is the *bad* part, including any other parsing of the version
String version = craftBukkitPackage.substring(craftBukkitPackage.lastIndexOf('.') + 1);
if (version.equals("v1_20_R3")) {
// ...
} else {
// Unsupported
}
// Paper method that was added in 2020
// Example value: 1.20.4
String minecraftVersion = Bukkit.getServer().getMinecraftVersion();
// Bukkit method that was added in 2011
// Example value: 1.20.4-R0.1-SNAPSHOT
String bukkitVersion = Bukkit.getServer().getBukkitVersion();
if (minecraftVersion.equals("1.20.4")) {
// ...
} else {
// Assume latest still works, or error as unsupported
// Alternatively for extra compatibility, check if
// the latest package version is valid by catching
// ClassNotFoundException with: Class.forName("org.bukkit.craftbukkit.v1_20_R3.CraftServer")
}
Bukkit.getUnsafe
.Alternatively, they might log an error saying you are using an unknown or unsupported server version.[11:46:19] [Server thread/ERROR]: Error occurred while enabling PLUGINNAME v1 (Is it up to date?)
java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at ...
@Override
public void onEnable() {
final LifecycleEventManager<Plugin> lifecycles = this.getLifecycleManager();
lifecycles.registerEventHandler(LifecycleEvents.DUMMY_STATIC.newHandler(event -> {
final DummyResourceRegistrar registrar = event.registrar();
System.out.println("dummy_static hook FIRST");
}).priority(-1));
lifecycles.registerEventHandler(LifecycleEvents.DUMMY_STATIC.newHandler(event -> {
final DummyResourceRegistrar registrar = event.registrar();
System.out.println("dummy_static hook FOURTH (monitor)");
}).monitor());
lifecycles.registerEventHandler(LifecycleEvents.DUMMY_STATIC.newHandler(event -> {
final DummyResourceRegistrar registrar = event.registrar();
System.out.println("dummy_static hook THIRD");
}).priority(100));
}
CommandBuilder.of(plugin, "admin")
.then(
LiteralArgumentBuilder.<CommandSourceStack>literal("execute")
.redirect(Bukkit.getCommandDispatcher().getRoot().getChild("execute"))
)
.then(
LiteralArgumentBuilder.<CommandSourceStack>literal("signed_message").then(
RequiredArgumentBuilder.argument("msg", VanillaArguments.signedMessage()).executes((context) -> {
MessageArgumentResponse argumentResponse = context.
getArgument("msg", MessageArgumentResponse.class); // Gets the raw argument.
// This is a better way of getting signed messages,
// includes the concept of "disguised" messages.
argumentResponse.resolveSignedMessage("msg", context)
.thenAccept((signedMsg) -> {
Component comp = Component.text("STATIC");
context.getSource()
.getBukkitSender()
.sendMessage(signedMsg, ChatType.SAY_COMMAND.bind(comp));
});
return 1;
})
)
))
.description("Cool command showcasing what you can do!")
.aliases("alias_for_admin_that_you_shouldnt_use", "a")
.register();
static final TypedKey<GameEvent> NEW_EVENT = GameEventKeys.create(Key.key("machine_maker", "best_event"));
@Override
public void bootstrap(@NotNull BootstrapContext context) {
final LifecycleEventManager<BootstrapContext> lifecycles = context.getLifecycleManager();
// registers a new handler for the prefreeze event for the game event registry
lifecycles.registerEventHandler(RegistryEvents.GAME_EVENT.preFreeze().newHandler(event -> {
// the RegistryView provided here is writable so you can register new objects
event.registry().register(NEW_EVENT, builder -> {
builder.range(2);
});
}));
// registers a handler for the addition event
lifecycles.registerEventHandler(RegistryEvents.GAME_EVENT.newAdditionHandler(event -> {
// checks if the object being registered is the block open game event
if (event.key().equals(GameEventKeys.BLOCK_OPEN)) {
// multiplies the range by 2
event.builder().range(event.builder().range() * 2);
}
}));
}
update-announcements
channel and provide more small-stepped info in the forum channel below it. You might have to add these channels to your list via "Channels & Roles" at the top of the channel list first.sendResourcePacks
and removeResourcePacks
methods, you can give each pack its own UUID to be individually added and removed later, which means that you can have multiple packs applied at once! The existing setResourcePack
method will override all previous ones to retain expected behavior.Keyed
interface may be removed on some typesKeyed
provides a NamespacedKey getKey()
to get keys for biomes, item and block types, sounds, etc. However, trim patterns and trim materials mark the first two registry based objects that do not require a key in all cases, hence the nonnull getKey
method is not valid for these.Registry#getKey(Object)
. While the getKey
methods will be available until actually broken, using the method on Registry
will make sure your plugin does not suddenly break later. Note that because of the possibility of no key existing, this method is nullable. If you are sure one will exist, you can also use the nonnull Registry#getKeyOrThrow
.private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
public static String cbClass(String clazz) {
return CRAFTBUKKIT_PACKAGE + "." + clazz);
}
Class.forName(cbClass("entity.CraftBee"))
String craftBukkitPackage = Bukkit.getServer().getClass().getPackage().getName();
// This is the *bad* part, including any other parsing of the version
String version = craftBukkitPackage.substring(craftBukkitPackage.lastIndexOf('.') + 1);
if (version.equals("v1_20_R1")) {
// ...
} else {
// Unsupported
}
// Paper method that was added in 2020
// Example value: 1.20.1
String minecraftVersion = Bukkit.getServer().getMinecraftVersion();
// Bukkit method that was added in 2011
// Example value: 1.20.1-R0.1-SNAPSHOT
String bukkitVersion = Bukkit.getServer().getBukkitVersion();
if (minecraftVersion.equals("1.20.1")) {
// ...
} else {
// Assume latest still works, or error as unsupported
// Alternatively for extra compatibility, check if
// the latest package version is valid by catching
// ClassNotFoundException with: Class.forName("org.bukkit.craftbukkit.v1_20_R1.CraftServer")
}
Bukkit.getUnsafe
.SmithingRecipe
has been replaced with SmithingTrimRecipe
and SmithingTransformRecipe
InventoryType.SMITHING
now uses 1.20's new smithing table interfaceSign#isEditable()
has been deprecated in favor of a new method called isWaxed
, same for its setterexperimental
and default
. For future updates, we will no longer provide any early experimental builds on Discord, instead using the experimental
channel in our downloads API. This means that you will need to distinguish between channels in your scripts to avoid getting highly experimental and potentially breaking versions. Please adjust your download scripts accordingly. Experimental builds marked as such will be available to download on our homepage as well.ClickEvent.callback
methods, you can now easily register message click event callbacks without having to keep track of them yourself. This code for example will create a click event to open a book that can be used for up to 2 minutes and has 5 uses:ClickCallback.Options options = ClickCallback.Options.builder()
.lifetime(Duration.ofMinutes(2))
.uses(5)
.build();
ClickEvent clickEvent = ClickEvent.callback(
audience -> audience.openBook(book),
options
);
player.sendMessage(Component.text().content("Click me!").clickEvent(clickEvent));
LivingEntity#setHurtDirection
throws an UnsupportedOperationException if called on a non-playerHopperMinecart#setCooldown
and getCooldown throw UnsupportedOperationExceptionupdate_1_20
to the initial-enabled-packs
option in the server properties file (with entries separated with a comma) and will be applied to newly generated worlds. We do not recommend enabling these feature packs on production servers, as the features that come with them (such as Camels and the new bamboo blocks) will not survive world upgrades and are still riddled with bugs.AsyncChatDecorateEvent
and AsyncChatCommandDecorateEvent
; going forward, we will mostly likely encourage changing a message's content through the decorate events, with changes to viewers and the chat type being done in AsyncChatEvent
. Finalized changes to API regarding chat and signed chat will be held off until 1.20 so it is less likely to break again with Mojang still doing such major changes to the system.java.net.NoRouteToHostException: No route to host
grep -R "plugin-config.bin" .
findstr /sml /c:"plugin-config.bin" *
config
directory: paper-global.yml
, where you can configure options that apply to the whole server, and paper-world-defaults.yml
, where you can set default per-world values; you can change the directory from config
to any directory you like with the new --paper-settings-directory
command line argument. The per-world configuration has been split into each individual world directory (paper-world.yml
), so for example, for the world world_the_end
, you will find the configuration file at world_the_end/paper-world.yml
.&
or §
codes) will no longer work in the paper configs. Instead, you use MiniMessage, which allows modern formatting with RGB colors, gradients, translatable components, and a lot more. You can find more information about MiniMessage here: https://docs.adventure.kyori.net/minimessage/format.html`paper.yml
will automatically be backed-up into config/legacy-backup/paper.yml.old
.redstone-implementation
to alternate-current
. As of now, Alternate Current is faster and more stable than the already implemented Eigencraft option (and a lot faster than Vanilla's redstone), but its behavior slightly deviates from Vanilla in certain edge cases, such as the order of surrounding block updates. Read more about Alternate Current and how it differs from other redstone implementations on its README.sendPlainMessage(String)
and sendRichMessage(String)
methods to the CommandSender
interface to make developers more aware of the distinction between legacy, plain, and MiniMessage text formatting – we strongly discourage the use of the old sendMessage(String)
methods using legacy formatting.experimental
and default
. The first few 1.19 builds were released in the experimental
channel, which has now been changed back to the default
channel.experimental
channel in our downloads API. This means that you will need to distinguish between channels in your scripts to avoid getting highly experimental and potentially breaking versions. Please adjust your download scripts accordingly. Experimental builds marked as such will be available to download on our homepage as well.below-zero-generation-in-existing-chunks
to false
in spigot.yml
. This option is not recommended and may not work correctly in conjunction with --forceUpgrade
or with worlds older than 1.14. Mojang has also introduced world blending to cleanly transition from old to new generation at the border of chunks that have not been generated before.max-block-height
to 64, but you might want to increase it even further. Please be aware that higher numbers might impact performance, especially with engine-mode: 2
. See stonar's anti-xray guide and the updated ore distribution for more information.shadowJar
and reobfJar
Gradle tasks to create a runnable (but not distributable) jar, you now need the createMojmapBundlerJar
or createReobfBundlerJar
tasks. Similarly, Paperclip (distributable) jars are now created with the createMojmapPaperclipJar
or createReobfPaperclipJar
task. You can get a full list of tasks by running gradlew tasks
. An updated, in-depth guide on contributing to Paper can be found here.settings.gradle.kts
.io.papermc.paperweight.userdev
, keeping the version in sync with the paperweight version used in Paper.assemble
task depend on the reobfJar
task.settings.gradle.kts
and build.gradle.kts
are important! Paperweight Userdev integrates with the Gradle Shadow plugin, no special configuration is required.gradle init --dsl kotlin
. If you have any issues getting started with Userdev, please come by the #paper-dev
channel on our Discord.channel
field to the build
response, allowing builds to be marked as experimental.