Timing
uses by then. You can see our docs page as well as the GitHub Discussions page for more details and also provide feedback there.pause-when-empty-seconds
to -1
to disable it.entities.spawning.despawn-time
, you can now configure hard despawn times in ticks for when an entity should be forcefully despawned. An example usecase of this is preventing certain projectiles from being kept alive permanently. This patch was ported from Pufferfish with Kevin's go-ahead.legacy-ender-pearl-behavior
config option to prevent ender pearls from being saved to the player and loading chunks, meaning they will behave like they did in 1.21.1 and before. Paper will default to the new vanilla behavior.Server#allowPausing(Plugin, Boolean)
to prevent accidental use of the feature by users. Similarly you can also check whether it is currently enabled by calling Server#isPaused
.DataComponentTypes
, although we will keep adding getter/setter helper methods to ItemStack or ItemMeta where appropriate.ItemStack itemStack = new ItemStack(Material.DIAMOND_HELMET);
// Update parts of the already existing equippable data:
// Use the netherrite helmet model when worn and change the equip sound
Equippable.Builder equippable = itemStack.getData(DataComponentTypes.EQUIPPABLE).toBuilder()
.model(Material.NETHERITE_HELMET.getDefaultData(DataComponentTypes.EQUIPPABLE).model())
.equipSound(SoundEventKeys.ENTITY_GHAST_HURT);
itemStack.setData(DataComponentTypes.EQUIPPABLE, equippable);
// Create new food data
FoodProperties.Builder food = FoodProperties.food()
.canAlwaysEat(true)
.nutrition(2)
.saturation(3.5f);
itemStack.setData(DataComponentTypes.FOOD, food);
@Experimental
and follows similar API safety as the registry API. It may change dramatically between Minecraft versions without backwards compatiblity attempts.PlayerItemGroupCooldownEvent
to listen to cooldowns that may not be directly associated with using an item, since cooldowns are now added via cooldown groups rather than item types. The already existing PlayerItemCooldownEvent
extends the new event.TeleportFlag.Relative
enum members have been deprecated. The new members with more appropriate names are: VELOCITY_X
, VELOCITY_Y
, VELOCITY_Z
, and VELOCITY_ROTATION
EntityDamageEvent
now has the INVULNERABILITY_REDUCTION
causeSoundEventKeys
) now implement Key
, so they can be used in API like the data componenents API directlyRegistryEvents.PAINTING_VARIANT
. More are coming over time as well - see https://docs.papermc.io/paper/dev/registries for more info on how to use themTiming
uses by then. You can see our docs page as well as the GitHub Discussions page for more details and also provide feedback there.disable-teleportation-suffocation-check
config option is now gone, as this check no longer exists in vanilla. We have also changed a number of default configuration values to improve the gameplay experience:merge-radius.exp
: 3
->-1
(disabled). vanilla has its own less aggressive merging logic, but generally xp orbs were not a large performance concern; otherwise we might instead offer new options to change vanilla’s merge logicmerge-radius.item
: 2.5
->0.5
, reflecting the vanilla default, since the increased value was often seen as disruptive. If you expect large number of the same item types to be lying around close to one another, you can increase this againentity-activation-range.raider
: 48
->64
entity-tracking-range
have been increased to make sure you can actually see monsters and players attacking you from farther away (e.g. no longer running into invisible ghasts shooting at you in the nether. Note that this does not affect ticking, only whether they are sent to a player)48
->128
48
->96
48
->96
32
->96
paper.disableOldApiSupport
startup flag. However, the flag will only work if all of your plugins are built against the latest API version.Done (7.392s)! For help, type "help"
message now shows the time from when the Minecraft server initially bootstrapped. Previously, it was in a kind of weird spot where it only tracked world loading, plugins, and a few other parts of server startup. We have also reinstated vanilla's original Done preparing level
message next to the total startup time..paper-remapped
folder in the plugin directory caused a bit of confusion, so here's what it does: It stores the remapped plugin jars as well as a cached server jar, so that all of these don't have to be processed during every single server startup. The folder is automatically cleaned up, so you don't need to (and generally shouldn't) touch it.ItemMeta#hasDamageValue
to check whether the damage item data component has been added to an item. hasDamage
will still return whether there is a non-0 amount of damage. For resetting damage, we recommend using resetDamage
instead of setting it to 0 to improve item comparison.EntityPortalEnterEvent
with PortalType.END_GATEWAY
to check for initial entriesProjectile#getWeapon
is now nullablePaper.DisableCommandConverter
system property/startup flag. For this reason, we also highly discourage upgrading your old world with non-Paper servers.hide-item-meta
config option has not yet been updated, but everything else should work as expected.spawnChunkRadius
gamerulekeep-spawn-loaded
and keep-spawn-loaded-range
config options have been removed, as Mojang has added the spawnChunkRadius
gamerule, serving the same function.paperweight-mappings-namespace
to mojang
). Alternatively, you can add the entry manually in your gradle or maven build scripts.canPlaceOn
and canBreak
breakas always, BACKUP YO SHIT, so long, and thanks for the fish
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.