# Adventure An overview of the Adventure library. import PageCards from "/src/components/PageCards.astro"; Documentation for Adventure and its many libraries. --- # Audiences A guide to Adventure Audiences. An audience, at its core, is a grouping of 0 or more viewers of some content. The concept of an audience is where Adventure makes its most clear break from other Minecraft platforms. As an API, `Audience` is designed to be a universal interface for any player, command sender, console, or otherwise who can receive text, titles, boss bars, and other Minecraft media. This allows extending audiences to cover more than one individual receiver - possible "audiences" could include a team, server, world, or all players that satisfy some predicate (such as having a certain permission). The universal interface also allows reducing boilerplate by gracefully degrading functionality if it is not applicable. For instance, it does not make much sense to send a boss bar to a command sender, and you can't send titles to Minecraft 1.7 clients. You will normally get audience instances from one of the [Platforms](/adventure/platform). The Adventure API includes two audience implementations itself: one that does not support any action (and thus does nothing). `Audience.empty()`, and one that forwards an action to each member in the audience, `Audience.audience()` and related methods, along with the `ForwardingAudience` that implements the forwarding logic for you. Most users using will primarily use this API to show content created by other parts of the API. ## Pointers Audiences can also provide arbitrary information, such as display name or UUID. This is done using the pointer system. Examples: ```java // get the uuid from an audience member, returning an Optional audience.get(Identity.UUID); // get the display name, returning a default audience.getOrDefault(Identity.DISPLAY_NAME, Component.text("no display name!")); ``` --- # Books A guide to Adventure Books. ## Constructing a Book Books are composed of: * A component used for the title of the book * A component used for the author of the book * A collection of components used for the book pages **Example:** ```java // Create and open a book about cats for the target audience public void openMyBook(final Audience target) { final Component bookTitle = Component.text("Encyclopedia of cats"); final Component bookAuthor = Component.text("kashike"); final Collection bookPages = Cats.getCatKnowledge(); final Book myBook = Book.book(bookTitle, bookAuthor, bookPages); target.openBook(myBook); } ``` ## Extra info regarding Books Books in adventure are not necessarily connected to an interactable book item in the client. As of the current release such a connection needs to be implemented outside of adventure. Any component that surpasses the game limit for text per page will be truncated client side, the same applies to the amount of components (pages). Further reading about these limits can be done at the [Minecraft Wiki](https://minecraft.wiki/w/Book_and_Quill#Writing). --- # Boss bars A guide to Adventure BossBars. ## Constructing a BossBar Boss Bars are composed of: * A component used for the title of the boss bar * A number from 0 to 1 used to determine how full the boss bar should be * A color, will be downsampled for clients <1.9 * An overlay that determines the amount of visual segments on the boss bar **Examples:** ```java private @Nullable BossBar activeBar; public void showMyBossBar(final Audience target) { final Component name = Component.text("Awesome BossBar"); // Creates a red boss bar which has no progress and no notches final BossBar emptyBar = BossBar.bossBar(name, 0, BossBar.Color.RED, BossBar.Overlay.PROGRESS); // Creates a green boss bar which has 50% progress and 10 notches final BossBar halfBar = BossBar.bossBar(name, 0.5f, BossBar.Color.GREEN, BossBar.Overlay.NOTCHED_10); // etc.. final BossBar fullBar = BossBar.bossBar(name, 1, BossBar.Color.BLUE, BossBar.Overlay.NOTCHED_20); // Send a bossbar to your audience target.showBossBar(fullBar); // Store it locally to be able to hide it manually later this.activeBar = fullBar; } public void hideActiveBossBar(final Audience target) { target.hideBossBar(this.activeBar); this.activeBar = null; } ``` ## Changing an active BossBar Boss bars are mutable and listen for changes on their object, the in-game view will change automatically without having to manually refresh it! Therefore, if this boss bar is currently active ```java final BossBar bossBar = BossBar.bossBar(Component.text("Cat counter"), 0, BossBar.Color.RED, BossBar.Overlay.PROGRESS); ``` and `BossBar.name()` with a component is called ```java final Component newText = Component.text("Duck counter"); bossBar.name(newText); ``` the boss bar will be updated automatically. The same thing goes for `progress`, `color` and `overlay`. --- # Community libraries Libraries utilizing the Adventure API. Adventure aims to provide the core libraries needed for interacting with chat components. However, with the limited resources and time of the Adventure team and the sheer number of possible use cases, we can't hope to provide direct solutions for every problem. Luckily, many of our community members have produced libraries that complement Adventure, providing additional features and integrations with other software. :::note This list of libraries is provided for reference only. The Adventure team does not endorse any specific library, and cannot provide any information or support beyond the provided links. If you have a library that you'd like included, please open a pull request on the [PaperMC/docs](https://github.com/PaperMC/docs) repository. ::: {/* Elements in all of these tables should be alphabetized */} ## Serializers These are libraries focused around providing additional serialization formats for chat components. They typically have no dependencies on a specific platform, just Adventure and potentially a library with which they integrate. {/* spellchecker:off */} Name | Description | Link ----------------------------|-----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------- EnhancedLegacyText | Alternative input format that is legacy compatible with new features | [Vankka/EnhancedLegacyText](https://github.com/Vankka/EnhancedLegacyText) MCDiscordReserializer | Serializers for going between Minecraft & Discord | [Vankka/MCDiscordReserializer](https://github.com/Vankka/MCDiscordReserializer) Minedown | A markdown-style format for representing components | [Phoenix616/MineDown](https://github.com/Phoenix616/MineDown) {/* spellchecker:on */} ## Platforms These are libraries that provide implementations of Adventure on different platforms that aren't officially integrated into the software or maintained by the Adventure team. {/* spellchecker:off */} Name | Description | Link ----------------------------|-----------------------------------------------|----------------------------------------------------------------------------------------------------- adventure-platform-hytale | Adventure platform implementation for Hytale | [ArikSquad/adventure-platform-hytale](https://github.com/ArikSquad/adventure-platform-hytale) {/* spellchecker:on */} ## Expansions These are libraries that expand upon the core Adventure API, providing platform-agnostic additions that are not included or not suitable for inclusion in the main project. {/* spellchecker:off */} Name | Description | Link --------------------|-----------------------------------------------------------------------|---------------------------------------------------- MiniPlaceholders | A platform-agnostic MiniMessage Component-based Placeholders library | [MiniPlaceholders/MiniPlaceholders](https://github.com/MiniPlaceholders/MiniPlaceholders) {/* spellchecker:on */} ## Additional user interface libraries These are libraries with a focus on something other than chat components, that use Adventure in their API. They provide support for using Adventure in user interface elements not supported by the core Adventure libraries. This includes, but is not limited to, commands, scoreboards, and inventories. These libraries will often depend on one or more specific platforms to support their functionality. {/* spellchecker:off */} Name | Description | Link --------------------|-----------------------------------------------------------------------|---------------------------------------------------- Cloud | A general-purpose Java command dispatcher & framework | [Incendo/cloud](https://github.com/Incendo/cloud) Creative | A resource-pack library for Minecraft: Java Edition | [unnamed/creative](https://github.com/unnamed/creative) Inventory Framework | An inventory framework for managing GUIs | [stefvanschie/IF](https://github.com/stefvanschie/IF) LiteCommands | A annotation based command framework for Velocity, Bukkit, BungeeCord | [Rollczi/LiteCommands](https://github.com/Rollczi/LiteCommands) ProtocolSidebar | An easy to use sidebar library for Paper/Spigot servers | [CatCoderr/ProtocolSidebar](https://github.com/CatCoderr/ProtocolSidebar) ScoreboardLibrary | A scoreboard library for Paper/Spigot servers | [MegavexNetwork/scoreboard-library](https://github.com/MegavexNetwork/scoreboard-library) Triumph GUI | A library made to simplify the creation of inventory GUIs | [TriumphTeam/triumph-gui](https://github.com/TriumphTeam/triumph-gui) {/* spellchecker:on */} --- # FAQ Frequently asked questions. We find that there are some issues users come across relatively frequently while applying the Adventure library in certain contexts. These may not be directly related to Adventure itself, but these answers are published here those that ask them: ## Why is my lore in italics? Components will inherit style from their parent. For example, in the following code snippet, each word will be red, despite red not being explicitly set on the appended component: `text("hi", RED).append(text("also red!"))`. In vanilla Minecraft, some places where components are rendered have parent styles. For example, lore text has a parent style that makes all text italic. This means that you will need to set italic to false if you do not want any component you are storing in lore to be italic. The `Component.decorationIfAbsent()` method can apply this to existing components without overriding any formatting specifically set by users. ## Messages not sending? Hex colors not working? Events not appearing? Fonts messed up? - Test on a vanilla client, without any mods or resource packs. Modded clients (such as Badlion), client mods, and even resource packs can break many elements of the modern JSON chat format and mess with incoming chat packets in ways that cause a myriad of issues. - Try without other plugins/mods. If another plugin/mod is modifying outgoing packets or formatting chat messages, this could cause a loss of formatting in the messages you send. Try without any other plugins to see if any are causing issues. - For RGB colors, test on a client of at least version *1.16*. Mojang added RGB support in this version. The JSON message format has evolved over time and has had many new additions since its introduction many, many years ago. For a full version history, see [the Minecraft wiki](https://minecraft.wiki/w/Text_component_format). ## How can I support both MiniMessage and legacy (§-code) formatting? If you have legacy in configuration files, or other places, it is suggested that you migrate them once using the legacy deserializer to turn them into a component and then MiniMessage to serialize them into proper MiniMessage format. There are no working, recommended, or supported ways of using both MiniMessage and legacy color codes and there never will be. Even simple find-and-replace style techniques do not work and will fail to take into account the quirks of style resetting in legacy formatting. ## How can I use Bukkit's PlaceholderAPI in MiniMessage messages? PlaceholderAPI placeholders are not supported in MiniMessage. However, you can easily create a custom tag resolver that can allow users to use PlaceholderAPI placeholders in MiniMessage strings, like in the following example:
Example Example method to create a MiniMessage placeholder that parses PlaceholderAPI placeholders for a player. The tag added is of the format ``. For example, ``. Credit to `mbaxter`. ```java /** * Creates a tag resolver capable of resolving PlaceholderAPI tags for a given player. * * @param player the player * @return the tag resolver */ public TagResolver papiTag(final Player player) { return TagResolver.resolver("papi", (argumentQueue, context) -> { // Get the string placeholder that they want to use. final String papiPlaceholder = argumentQueue.popOr("papi tag requires an argument").value(); // Then get PAPI to parse the placeholder for the given player. final String parsedPlaceholder = PlaceholderAPI.setPlaceholders(player, '%' + papiPlaceholder + '%'); // We need to turn this ugly legacy string into a nice component. final Component componentPlaceholder = LegacyComponentSerializer.legacySection().deserialize(parsedPlaceholder); // Finally, return the tag instance to insert the placeholder! return Tag.selfClosingInserting(componentPlaceholder); }); } ```
## Why am I getting a `NoSuchFieldError`, `NoSuchMethodError`, `ClassNotFoundException` or similar when updating/using `adventure-platform-*`, `adventure-text-minimessage`, `adventure-api` or other related libraries/tools? In the case of `adventure-platform-fabric`, you need to make sure you are properly `include()`-`ing` the mod. For legacy platform implementations, you need to make sure you are properly shading and relocating your specific dependencies. Specific issues may include: - Not shading the correct version of `adventure-api`. You can check your dependency tree to see what or why your build tool is not including the correct version of the API that matches the one used by the platform version you are using. - Not relocating your dependencies. If you are running on a platform that includes an older version of the API, or another mod/plugin is also not properly relocating their dependencies, you will use their out-of-date version of the API, causing errors. - Building/running against a native implementation of `adventure-api`. If you are running on a platform that includes an older version of the API, this could cause issues if the library depends on newer features that are not available in the outdated version of the API, your library will not be able to find these methods, causing errors. - Relocating `adventure-api` and trying to use native/library methods. If you relocate the API, you will not be able to use any methods that use the API in native implementations or libraries as method signatures will differ. Either shade and relocate this software, or do not use native methods. Alternatively, if you are shading and relocating a library but want to use the API, make sure you are only relocating the packages that you are shading. Please consult the documentation for your build tool for more information on how to shade, relocate and manage your dependencies. We do not provide one-on-one support for these sorts of issues, as there are far too many project-specific variables that make isolating issues difficult. --- # Getting started A guide to getting started with Adventure. import { Tabs, TabItem } from "@astrojs/starlight/components"; import Dependency from "/src/components/Dependency.astro"; import { LATEST_ADVENTURE_API_RELEASE } from "/src/utils/versions"; To use Adventure in your project, you will need to add the following dependency (and repository if using Gradle): Declaring the dependency: Need development/snapshot builds? [Using Snapshot Builds](#using-snapshot-builds) Some platforms already use Adventure natively. In this case, you will not need to add Adventure as a dependency. To view the list of platforms that include Adventure, see [Native Support](/adventure/platform/native). To use Adventure with other platforms, you may wish to look at the platform-specific adapters. A list of platforms with supported adapters can be found at [Platforms](/adventure/platform). ## Using snapshot builds To use snapshot builds, you will need to add the following repository: ```kotlin title="build.gradle.kts" repositories { maven(url = "https://central.sonatype.com/repository/maven-snapshots/") { name = "central-snapshots" } } ``` ```groovy title="build.gradle" repositories { maven { name = 'central-snapshots' url = 'https://central.sonatype.com/repository/maven-snapshots/' } } ``` ```xml title="pom.xml" central-snapshots https://central.sonatype.com/repository/maven-snapshots/ ``` --- # Localization Utilizing localization in Adventure. Adventure provides a way to utilize Minecraft's built-in localization system for client-side translations as well as an additional Adventure-specific system for translating text. ## Using Minecraft's localization To send text to a player that will be translated in the language they have selected in their client settings, use a translatable component. For example, `Component.translatable("block.minecraft.diamond_block")` will render as "Block of Diamond" (or translated to another language) when viewed by the client. Some translation keys have arguments which are inserted into the translated content. For example, `Component.translatable("block.minecraft.player_head.named", Component.text("Mark"))` will render as "Mark's Head". Translatable components can have styling, hover/click events and children components just like any other component type. ### Resource pack language files You can provide translation files in a resource pack in order to change existing translations or add new ones. For a guide on how to do that, see the [Minecraft Wiki](https://minecraft.wiki/w/Resource_pack#Language). ### Using Adventure's localization Adventure also provides a way to handle localization in Adventure itself. This can be useful in environments where you do not have access to resource packs, or wish to do translations yourself, without relying on Minecraft's translation system. Any component that is sent to a client is ran through the `GlobalTranslator` using the locale of the client. This means that if you wish to have automatic translation of components using your own translation data, you can add a `Translator` to the `GlobalTranslator`. You can either provide your own implementation of `Translator` or use one of the implementations that Adventure provides. Once you have a `Translator` instance, you can register it to the `GlobalTranslator` using `GlobalTranslator.translator().addSource(myTranslator)`. This will then make it available for automatic translation across the platform. :::caution Some implementations may not use the `GlobalTranslator` in every area, or at all. For example, Paper does not use it for items, and Minestom does not use it unless specifically enabled. Please consult the documentation for your platform for any limitations. ::: ## Using a custom `Translator` A `Translator` is a simple interface that provides two ways of translating content. The first `translate` method provides the translation key and locale as an argument and expects a nullable `MessageFormat` in return. This system is comparable to Minecraft's built-in localization system, using the standard Java [message format](jd:java:java.text.MessageFormat) for arguments. If the first `translate` method returns `null`, the second method which provides the translatable component and locale as an argument can be used. This method allows for much richer customization of the translation process as you can return an entire component. This means you can, for example, customize the color and styling of the translated component, rather than relying solely on strings for the message format system. :::caution If you are overriding the component `translate` method, you should be careful not to unintentionally lose the children of the translatable component. See the Javadocs for the translate method for a code example of how to avoid this common error. ::: Below is an example of how one might implement a custom `Translator`. ```java title="MyTranslator.java" public class MyTranslator implements Translator { @Override public Key name() { // Every translator has a name which is used to identify this specific translator instance. return Key.key("mynamespace:mykey"); } @Override public @Nullable MessageFormat translate(final String key, final Locale locale) { // You could retrieve a string from a properties file, a config file, or some other system. // As an example, we will hard-code a check for a specific key here. if (key.equals("mytranslation.key") && locale == Locale.US) { return new MessageFormat("Hello {0}!", locale); } else { // If you only want to use component translation, you can override this method and always return `null`. return null; } } @Override public @Nullable Component translate(final TranslatableComponent component, final Locale locale) { // As above, we will hardcode a check here, but you should be reading this from elsewhere. if (component.key().equals("mytranslation.colorful") && locale == Locale.US) { return Component.text("Hello, ", NamedTextColor.GREEN) .append(component.arguments().stream().map(it -> it.asComponent().color(NamedTextColor.RED)).toList()) .append(component.children()) // Always make sure to copy the children over! .applyFallbackStyle(component.style()); } else { return null; } } } ``` ### Using a `TranslationStore` A `TranslationStore` is a store of translations. It provides a simpler way creating a `Translator` without having to implement the logic for determining and storing translations yourself. You can create a translation store and then add or remove translations at will, even after registering it to the global translator. Adventure provides two translation stores, one for message format translating and one for component translating. An example of how to use a translation store is below. ```java // As above, every translator needs an identifying name! // Could also use TranslationStore#component(Key) to work with components instead. final TranslationStore.StringBased myStore = TranslationStore.messageFormat(Key.key("mynamespace:mykey")); // You can add translations one-by-one, or in bulk. Consult the Javadocs for a full list of methods. myStore.register("mytranslation.key", Locale.US, new MessageFormat("Hello {0}!", Locale.US)); // You can then register this to the global translator so the translations are available there! GlobalTranslator.translator().addSource(myStore); ``` There are additional methods on the message format translation store to bulk register from [resource bundles](jd:java:java.util.ResourceBundle). ### Using MiniMessage for translations Adventure also provides a translator that can use MiniMessage strings, with automatic support for placeholders and arguments. For more information, see [MiniMessage Translator](/adventure/minimessage/translator). --- # Migration Moving to the latest Adventure version. These guides provide advice and replacements useful when migrating to the latest version of Adventure. This includes migrating from older versions of Adventure, as well as migrating from other libraries. * [Migrating from Adventure 4.x to 5.x](/adventure/migration/adventure-4.x) * [A modern codebase](/adventure/migration/adventure-4.x#a-modern-codebase) * [Updated dependencies](/adventure/migration/adventure-4.x#updated-dependencies) * [Breaking changes](/adventure/migration/adventure-4.x#breaking-changes) * [Removal of deprecated methods and classes](/adventure/migration/adventure-4.x#removal-of-deprecated-methods-and-classes) * [Migrating from the BungeeCord Chat API](/adventure/migration/bungeecord-chat-api) * [Audiences](/adventure/migration/bungeecord-chat-api#audiences) * [Decoration and styling](/adventure/migration/bungeecord-chat-api#decoration-and-styling) * [Chat colors](/adventure/migration/bungeecord-chat-api#chat-colors) * [Differences in `ComponentBuilder`](/adventure/migration/bungeecord-chat-api#differences-in-componentbuilder) * [Immutability](/adventure/migration/bungeecord-chat-api#immutability) * [Serializers](/adventure/migration/bungeecord-chat-api#serializers) * [Backwards compatibility](/adventure/migration/bungeecord-chat-api#backwards-compatibility) * [Migrating from text 3.x](/adventure/migration/text-3.x) * [A word of caution](/adventure/migration/text-3.x#a-word-of-caution) * [Breaking changes from text 3.x](/adventure/migration/text-3.x#breaking-changes-from-text-3x) * [Serializer](/adventure/migration/text-3.x#serializer) --- # Migrating from Adventure 4.x to 5.x Move from Adventure 4.x to 5.x. With the release of Adventure 5.0, some breaking changes have been introduced from the Adventure 4.x series. This page documents the changes made and how you as a developer can migrate your code. ## A modern codebase One of the main goals for Adventure 5.0 was to migrate to a more modern codebase. The minimum version of Java required to use Adventure is now Java 21. By updating to Java 21, Adventure has taken advantage of sealed classes and interfaces. Almost every interface/class that was annotated with `@ApiStatus.NonExtendable` has now been made sealed. This means that you can no longer extend these classes, although you shouldn't have been doing that in the first place! One relatively common incorrect usage was to create custom `Component` implementations. This is now no longer possible, and you should instead be using the `VirtualComponent` API. Another side effect of wanting a modern codebase is that the `adventure-extra-kotlin` module has been removed. This module will be re-introduced in a separate repo under a new module in the future. This will allow for more flexibility working around the more frequent Kotlin updates. The `adventure-text-serializer-gson-legacy-impl` module has also been removed. This module has been replaced with the implementation-agnostic `adventure-text-serializer-json-legacy-impl` module. Finally, Adventure now contains proper `module-info.java` files for those of you using the Java 9+ module system. ## Updated dependencies Many non-breaking updates to dependencies have been made. There are a few notable breaking/major changes that are documented below. Adventure has migrated to using JSpecify for nullness annotations. These are applied at a package/class level, so unless otherwise specified, everything should be treated as non-null. As most of the internal implementation of Adventure is now using records, we no longer need to use the Examination library for `toString` generation. The Examination library has been entirely removed from Adventure and is no longer a transitive dependency. The `adventure-text-logger-slf4j` module has been updated to use SLF4J 2.0. The GSON library has been updated to 2.13.2. Although this version is higher than most Minecraft versions that are supported by Adventure, it is not our intention to drop support for these older versions. We will endeavor to not use newer GSON features that would break support for older versions of GSON used in legacy Minecraft versions. ## Breaking changes ### Click event changes The `ClickEvent` class is now a typed interface. The type argument for this click event is the payload type. This does not change how you construct click events but does make serialization and deserialization easier. This change also extends to `ClickEvent$Action`, which is now no longer an enum and instead is a typed interface. Additionally, the `nbt` field for custom click event payloads is now nullable. This is to allow for the possibility of custom click events without NBT and to be more in line with Vanilla Minecraft behavior. ### Component renderer changes With the addition of the new object component, the `AbstractComponentRenderer` interface has been updated to include a new method to render object components. This is a breaking change, as implementations of this method will be required. Going forward, we will be adding new methods to this class whenever Mojang adds new component types. This breaking change is documented in the `AbstractComponentRenderer` javadocs. ### Sealing of `TextFormat` interface Although it was never recommended to extend `TextFormat`, it was possible to do so in the past. This ability has been removed from the API, and the interface is now sealed. If you were extending this interface, you should instead consider extending the `StyleBuilderApplicable` interface for a more powerful and widely-used alternative. ### Chat type changes Due to changes in the chat system, specifically around the creation of dynamic chat types, it is possible for chat types to not be keyed. Due to this internal change, `ChatType#key` is now nullable. Additionally, the class no longer implemented `Keyed`. ## Removal of deprecated methods and classes A number of methods and classes have been deprecated in across the Adventure 4.x series. This section documents the removals that have been made and how you can migrate your code, if applicable. * **`BuildableComponent` has been removed.**\ You can now obtain a `ComponentBuilder` directly from a `Component` using `Component#toBuilder`. A breaking side effect of this change is that `NBTComponent` now only accepts one type argument, rather than two. * **Legacy chat signing/identifying methods have been removed.**\ Although legacy versions still use these features, they did not have enough usage to warrant their continued existence in Adventure. Generally speaking, you should migrate to using signed messages if you intend to send identified/chat messages. * **The `MessageType` enum has been removed entirely.**\ Chat messages are now identified when sending a signed message. All other messages are system messages. * **All `Audience#sendMessage` methods that accept an `Identity` or `Identified` have been removed.**\ Prefer sending signed messages instead. * **Boss bar percent has been removed.**\ This includes the max/min percent constants and methods to change/get the percent. You should instead be using the progress constants/methods. * **`of` style static methods have been removed.**\ These methods have been deprecated for some time, and each has named replacements. * **Custom click payload data has been removed.**\ As custom click payloads contain NBT, you should instead be using the `nbt` method. This includes the custom click event constructor methods that accept strings instead of NBT. * **`ClickEvent#create(Action, String)` has been removed.**\ As click events can now hold data other than strings, this method has been removed. If you were using this method, you should migrate to the `create` method that accepts a payload or use the direct construct methods (e.g. `ClickEvent#openUrl(String)`). * **`ClickEvent#value` has been removed.**\ As noted above, click events can now hold data other than strings. Therefore, this method has been removed in favor of the `payload` method. * **`AbstractComponent` has been removed.**\ As this class was primarily an implementation detail, it has been removed with no replacement. * **Non-builder component joining has been removed.**\ This includes the `Component#join` family of methods that do not accept a `JoinConfiguration`. You should instead be using `Component#textOfChildren` or `join` methods that accept a `JoinConfiguration`. * **`Component#detectCycle(Component)` has been removed.**\ As components are immutable, this method is not required and has therefore been removed with no replacement. * **Non-builder component text replacement has been removed.**\ This includes the `Component#replace[First]Text` family of methods that do not accept a `TextReplacementConfig`. You should instead be using the `replaceText` methods that accept a `TextReplacementConfig`. * **`TranslationRegistry` has been removed.**\ Registries have been replaced with the more powerful `TranslationStore`. See static methods on `TranslationStore` for a compatible replacement. * **`JSONComponentConstants` has been removed.**\ This has been replaced with `ComponentTreeConstants` from the `adventure-text-serializer-commons` module. * **`PlainComponentSerializer` has been removed.**\ This has been replaced with the equivalent `PlainTextComponentSerializer` class. * **`ClickEvent$Action#payloadType` has been removed.**\ As click event actions are now typed, this field is no longer required. * **`UTF8ResourceBundleControl` has been removed.**\ From Java 9 onwards, resource bundles are loaded using UTF-8 by default. Therefore, this class is no longer required. Instead of using this class, you can load properties files without any resource bundle control. * **Removal of methods from `GSONComponentSerializer`.**\ Since the addition of the options system to customize JSON serializers, the `downsampleColors` and `emitLegacyHoverEvent` options in the `GSONComponentSerializer` has been removed. You should instead use `EMIT_RGB` and `EMIT_HOVER_EVENT_TYPE` fields in `JSONOptions` respectively. Additionally, the `legacyHoverEventSerializer` that accepts a `LegacyHoverEventSerializer` from the GSON module has been removed in favor of the generic alternative in the JSON module. * **Typos have been removed.**\ Some incorrectly spelt/named methods, such as `Argument#numeric` and `ComponentSerializer#deseializeOrNull`, have been removed. These methods have been deprecated, and correctly spelt/named methods have been available for a while. --- # Migrating from the BungeeCord Chat API Move from the BungeeCord Chat API to the Adventure API. Adventure's text API and the BungeeCord Chat API are designed along very different methodologies. This page goes over some notable differences. ## Audiences It is strongly recommended you read about [Audiences](/adventure/audiences) first. Unlike BungeeCord, which limits functionality to specific user types, Adventure allows only the specific operations that apply to an audience to be taken. ## Decoration and styling The BungeeCord Chat API stores all decorations in the `BaseComponent`. Adventure separates out styles into their own `Style` class. BungeeCord allows you to merge the styles from one component into another. Adventure provides equivalent methods that merge styles together, or allows you to replace the styles on one component with another. ## Chat colors Adventure's chat color and styling hierarchy differs from that of BungeeCord's `ChatColor` API. This is probably where the most stark contrast between the Adventure API and BungeeCord/Bukkit will manifest. ### Replacement for `ChatColor` Adventure's equivalents for `ChatColor` are split over three types: * Formatting types (such as `BOLD` or `ITALIC`) are in `TextDecoration`, and can be set on a component or a style with the `decoration` method. Decorations also use a tristate to specify if they are enabled, disabled, or not set (in which case the component inherits the setting from its parent component). * Named colors (also called the legacy Mojang color codes) now exist in the `NamedTextColor` class. * RGB colors are constructed using the `TextColor.color()` methods (this is equivalent to the `ChatColor.of()` method in the BungeeCord `ChatColor` 1.16 API. ### Legacy strings can't be constructed The BungeeCord `ChatColor` API's heritage is in the Bukkit API. The Bukkit `ChatColor` API in turn dates from the early days of Minecraft (Beta 1.0), when the normal and accepted way of sending formatted messages to the client was to concatenate magical strings that told the client what to format. A formatted chat message would be sent to the client like this: ```java player.sendMessage(ChatColor.GREEN + "Hi everyone, " + ChatColor.BOLD + "this message is in green and bold" + ChatColor.RESET + ChatColor.GREEN + "!"); ``` This style of sending messages has persisted to this day, even as Mojang introduced rich chat components into Minecraft 1.7.2. Bukkit preserved this backwards-compatible behavior, and BungeeCord introduced the change as a result of being compatible with the Bukkit `ChatColor` class. In Adventure, you can't concatenate magical formatting codes. The equivalent of `ChatColor` in Adventure, `TextColor`, instead returns descriptive text describing the color when its `toString()` is called. The recommended replacement is to convert all legacy messages to components. ### `ChatColor.stripColor()` `ChatColor.stripColor()` does not exist in Adventure. An equivalent would be to use `PlainTextComponentSerializer.plainText().serialize(LegacyComponentSerializer.legacySection().deserialize(input))`. ### `ChatColor.translateAlternateColorCodes()` `ChatColor.translateAlternateColorCodes()` does not exist in Adventure. Instead you should use `LegacyComponentSerializer.legacy(altChar).deserialize(input)` when deserializing a legacy string. ## Differences in `ComponentBuilder` The BungeeCord `ComponentBuilder` treats each component independently and allows you to manually carry over styles from a prior component. In Adventure, there are multiple component builders. The closest equivalent for a BungeeCord `ComponentBuilder` is to append components to a top-level empty component using `Component.text()` as a base. To replicate the behavior of `ComponentBuilder`, consider doing the following: * Use the `Style` class to store common styles and the `mergeStyle` and `style` methods to merge and replace styles on a component. * Use the Adventure `TextComponent` builder to create one component at a time and then append to a top-level text component builder that is empty. As an example, this BungeeCord component: ```java new ComponentBuilder("hello") .color(ChatColor.GOLD) .append(" world", FormatRetention.NONE) .build() ``` becomes this Adventure equivalent: ```java Component.text() .append(Component.text("hello", NamedTextColor.GOLD)) .append(Component.text(" world")) .build() ``` Likewise, ```java new ComponentBuilder("hello") .color(ChatColor.GOLD) .bold(true) .append(" world") .build() ``` becomes ```java Style style = Style.style(NamedTextColor.GOLD, TextDecoration.BOLD); Component.text() .append(Component.text("hello", style)) .append(Component.text(" world", style)) .build() ``` ## Immutability In the BungeeCord Chat API, all components are mutable. Adventure text components, however, are immutable - any attempt to change a component results in a new component being created that is a copy of the original component with the change you requested. ## Serializers The BungeeCord Chat API includes three serializers. All three have equivalents in Adventure: * The `TextComponent.fromLegacyText()` deserialization method is equivalent to the `deserialize` method of the [Legacy](/adventure/serializer/legacy) text serializer. Likewise, the `BaseComponent.toLegacyText()` serialization method is equivalent to the `serialize` method on the legacy text serializer. * The `TextComponent.toPlainText()` serialization method is equivalent to the `serialize` method of the [Plain](/adventure/serializer/plain) text serializer. A component can be created from a plain-text string using `Component.text(string)` * The Adventure equivalent of `ComponentSerializer` is the [Gson](/adventure/serializer/gson) text serializer. ## Backwards compatibility The `BungeeCordComponentSerializer` allows you to convert between Adventure [Components](/adventure/text) and the native BungeeCord chat component API and back. This can be used when native platform support is unavailable. The serializer is available in the `adventure-platform-text-serializer-bungeecord` artifact. --- # Migrating from text 3.x Moving from text 3.x to Adventure. Adventure is an evolution of the text 3.x API. If you've worked with the text API before, the switch to Adventure should be relatively quick. For the most part, you'll just need to depend on the Adventure API and the relevant [Platform](/adventure/platform) you support and replace references to classes in `net.kyori.text` to `net.kyori.adventure.text`, though see below for major breaking changes. ## A word of caution However, before you continue, it is strongly recommended you read about [Audiences](/adventure/audiences). Unlike text, Adventure defines a standard interface for sending content (including chat messages) to viewers. In addition, Adventure defines interfaces for other game play mechanics that can be arbitrarily sent to players. ## Breaking changes from text 3.x ### Factory methods renamed In text 3.x, components could be constructed using the `Component.of()` methods. In Adventure, we've changed to using `Component.(/*...*/)` style methods to allow for easier static imports. Similarly, `Style.of(/*...*/)` is changed to `Style.style(/*...*/)`. ### `.builder()` Builders are now created by calling the aforementioned factory methods with no parameters. For example, `TextComponent.builder()` becomes `Component.text()`. Note that the equivalent of `TextComponent.builder("hello")` is `Component.text().content("hello")`. ### `.append()` with a String argument Component builders in 3.x had a shorthand for appending a new text component: `builder.append("wow")`. In Adventure you have to write it in full, `builder.append(Component.text("wow"))` in this case. ### `LegacyComponentSerializer` In text 3.x, you would deserialize a component that used a color code prefix that differed from the section symbol normally used by using `LegacyComponentSerializer.legacy().deserialize(string, altChar)`. In Adventure, the API to use is `LegacyComponentSerializer.legacy(altChar).deserialize(string)`. To make a linking serializer you have to use the builder. Change `LegacyComponentSerializer.legacyLinking(style)` to `LegacyComponentSerializer.builder().extractUrl(style).build()`. ### `TextColor` renamed to `NamedTextColor` In order to accommodate the new RGB colors introduced in 1.16, all the named text colors were moved to the `NamedTextColor` class. References to the old `TextColor` class should be updated to refer to `NamedTextColor`. ## Serializer If you have a need to interoperate with clients using the old text 3.x API, you can use the `adventure-text-serializer-legacy-text3` artifact, which includes a `LegacyText3ComponentSerializer` that can convert from Adventure to text 3.x components and back. Note that RGB colors will be downsampled. --- # MiniMessage Documentation regarding MiniMessage. The MiniMessage format is a simple string representation of chat components, designed to be easy for end users to learn, and for developers to extend. ```mm Hello world, isn't MiniMessage fun? ``` If you're looking to write messages with MiniMessage, take a look at the [MiniMessage Format](/adventure/minimessage/format), or if you're looking to develop software that uses MiniMessage, take a look at the [API overview](/adventure/minimessage/api). - [Format](/adventure/minimessage/format) - [API](/adventure/minimessage/api) - [Dynamic replacements](/adventure/minimessage/dynamic-replacements) - [MiniMessage translator](/adventure/minimessage/translator) --- # MiniMessage API A guide on using MiniMessage in your code. import Dependency from "/src/components/Dependency.astro"; import { LATEST_ADVENTURE_API_RELEASE } from "/src/utils/versions"; ## Dependency Declaring the dependency: :::note Some platforms already provide MiniMessage natively. In this case you will not need to add MiniMessage as a dependency. ::: ## Getting started MiniMessage exposes a simple API via the `MiniMessage` class. A standard instance of the serializer is available through the `miniMessage()` method. This uses the default set of tags and is not in strict mode. Additional customization of MiniMessage is possible via the [Builder](#builder). MiniMessage allows you to both serialize components into MiniMessage strings and to parse/deserialize MiniMessage strings into components. Here's a short example to try things out: ```java final Audience player = ...; final MiniMessage mm = MiniMessage.miniMessage(); final Component parsed = mm.deserialize("Hello world, isn't MiniMessage fun?"); player.sendMessage(parsed); ``` For more advanced uses, additional tag resolvers can be registered, which when given a tag name and arguments will produce a `Tag` instance. These are described in more detail below. ### Presets MiniMessage also provides a set of presets that can be used instead of the main MiniMessage instance. These presets are intended to easily allow developers to avoid accidentally allowing users to use abusable component features, such as the run command click event. Below is a list of available presets and their functionality: * **`DEFAULT`**\ The default preset, containing all MiniMessage features. This is the same as `MiniMessage.miniMessage()`. * **`NON_INTERACTABLE`**\ A preset that disables all component features that allow for interaction. This includes click events, hover events, and text insertion. It also includes a custom post-processor that removes any interactable elements from the resulting component. Therefore, this can also be used with custom tag resolvers that may produce interactable components. * **`FORMATTED_TEXT`**\ A preset that only allows text components and associated formatting. This includes coloring, shadow, font, and text decoration (e.g., bold, italics). It also includes a custom post-processor that removes any non-text components or interactable elements from the resulting component. Therefore, this can also be used with custom tag resolvers. ```java // You can get a pre-built MiniMessage instance based on any preset. final MiniMessage miniMessage = MiniMessage.miniMessage(MiniMessage.Preset.NON_INTERACTABLE); // Alternatively, you can get a pre-configured builder... final MiniMessage.Builder builder = MiniMessage.builder(MiniMessage.Preset.NON_INTERACTABLE); // ...then add your own tags or override other settings if needed... builder.editTags(tags -> { tags .resolver(new MyCustomTagResolver()) .resolver(fetchExternalTags()); }); // ...and finally, build your MiniMessage instance! final MiniMessage myCustomMiniMessageInstance = builder.build(); ``` ### Builder To make customizing MiniMessage easier, we provide a Builder. The specific methods on the builder are explained in the javadoc. ```java final MiniMessage mm = MiniMessage.builder() .tags(TagResolver.builder() .resolver(StandardTags.color()) .resolver(StandardTags.decorations()) .resolver(this.someResolvers) .build() ) .build(); ``` :::tip It's a good idea to initialize such a MiniMessage instance once, in a central location, and then use it for all your messages. Exception being if you want to customize MiniMessage based on permissions of a user (for example, admins should be allowed to use color and decoration in the message, normal users not) ::: ### Error handling By default, MiniMessage will never throw an exception caused by user input. Instead, it will treat any invalid tags as normal text. `MiniMessage.Builder#strict(true)` mode will enable strict mode, which throws exceptions on unclosed tags, but still will allow any improperly specified tags through. To capture information on why a parse may have failed, `MiniMessage.Builder#debug(Consumer)` can be provided, which will accept debug logging for an input string. ## Tag resolvers All tag resolution goes through tag resolvers. There is one global tag resolver, which describes the tags available through a `MiniMessage` instance, plus parse-specific resolvers which can provide additional input-specific tags. Tag resolvers are the binding between a name and arguments, and the logic to produce a `Component` contained in a `Tag` instance. They are composable so a `TagResolver` can produce any number of actual `Tag` instances. The tag name passed to resolvers will always be lower-cased, to ensure case-insensitive searches. Tag names are only allowed to contain the characters a-z, 0-9, `_`, and `-`. They can also optionally start with any of the following characters: `!?#`. You can create your own `TagResolver` by using the static factory methods in `TagResolver`. To replace tags dynamically with text MiniMessage has built-in `Placeholder` and `Formatter`. Where possible, these built-in resolvers should be used, as MiniMessage can flatten combinations of these resolvers into a more efficient format. For built-in dynamic replacements take a look [here](/adventure/minimessage/dynamic-replacements). To combine multiple resolvers, take a look at the tag resolver builder, `TagResolver.builder()`. The builder for `MiniMessage` allows providing a custom tag resolver rather than the default (`StandardTags.all()`), allowing MiniMessage also provides convenience methods to do that: ```java final MiniMessage serializer = MiniMessage.builder() .tags(TagResolver.builder() .resolver(StandardTags.color()) .build() ) .build(); Component parsed = serializer.deserialize("Hai"); // Assertion passes assertEquals(Component.text("Hai", NamedTextColor.GREEN), parsed); ``` Because the `` tag is not enabled on this builder, the bold tag is interpreted as literal text. ### Handling arguments Tag resolvers have an `ArgumentQueue` parameter, which provides any tag arguments that are present in the input. Helper methods on `Tag.Argument` can assist with conversions of the tag. Exceptions thrown by the `popOr()` methods will interrupt execution, but are not currently exposed to users outside of debug output. We plan to add an auto-completion function that can reveal some of this information to the user, so please do try to write useful error messages in custom tag resolvers. ## Tags Once a tag resolver has handled arguments, it returns a `Tag` object. These objects implement the logic of producing or modifying a component tree. There are three main kinds of `Tag` -- all custom implementations must implement one of these interfaces. ### Pre-process These tags implement the `PreProcess` interface, and have a value of a raw MiniMessage string that is replaced into the user input before parsing continues. Due to limitations in the current parser implementation, note that pre-process tags will adjust offsets in error messages, and may inhibit tab completion. However, they are currently the only way to integrate markup fragments into a message. ### Inserting These tags are fairly straightforward: they represent a literal `Component`. The vast majority of Tag implementations will want to be `Inserting` tags. `Inserting` tags may also optionally be self-closing -- by default, this is only true for tags created by `Placeholder.unparsed(String)` and `Placeholder.component(Component)`, so that placeholders are self-contained. Most `standard tags <./format>` are `Inserting`. These tags will either directly insert a component, or use the helper `Tag.styling(StyleBuilderApplicable...)` to apply style to components. This helper can be used to efficiently apply a collection of styles with one tag. For example, to create a `Title` tag, that makes the `Title` text into a link that opens a URL with traditional link styling, this could be used: ```java Component aTagExample() { final String input = "Hello, click me! but not me!"; final MiniMessage extendedInstance = MiniMessage.builder() .editTags(b -> b.tag("a", MiniMessageTest::createA)) .build(); return extendedInstance.deserialize(input); } static Tag createA(final ArgumentQueue args, final Context ctx) { final String link = args.popOr("The tag requires exactly one argument, the link to open").value(); return Tag.styling( NamedTextColor.BLUE, TextDecoration.UNDERLINED, ClickEvent.openUrl(link), HoverEvent.showText(Component.text("Open " + link)) ); } ``` This allows producing rich styling relatively quickly. ### Modifying Modifying tags are the most complex, and most specialized of the tag types available. These tags receive the node tree and have an opportunity to analyze it before components are constructed, and then receive every produced child component and can modify those children. This is used for the built-in `` and `` tags, but can be applied for similar complex transformations. Modifying tags are first given an opportunity to visit every node of the tree in a depth-first traversal. If a `Modifying` instance stores any state during this traversal, its resolver should return a new instance every time to prevent state corruption. :::note The `Node` API in 4.10.0 is currently not very well developed -- most aspects are still internal. Additional information can be exposed as needed by tag developers. ::: Once the whole parse tree has been visited, the `postVisit()` method is called. This method can optionally be overridden if any additional calculations must be performed. Next, the `Modifying` instance enters the application phase, where the component tree is presented to the tag for transformation. This allows the tag to *modify* the contents of these components, giving it its name. ### Parser directives Parser directives are a special kind of tag, as they are instructions for the parser, and therefore cannot be implemented by end users. There is currently only one, but more may be added at any time. | Directive | Description | |-----------|----------------------------------------------------------------------------------| | RESET | This indicates to the parser that this tag should close all currently open tags. | This can be used to provide the functionality of a `` tag under a different name. For example: ```java final var clearTag = TagResolver.resolver("clear", ParserDirective.RESET); final var parser = MiniMessage.builder() .editTags(t -> t.resolver(clearTag)) .build(); final Component parsed = parser.deserialize("hello world, how are you?"); ``` This code would add a `` tag, behaving identically to the `` tag available by default — ", how are you?" would not be bold or colored red. --- # Dynamic replacements A guide on tag resolvers. MiniMessage has some included `TagResolver` s which can replace tags dynamically when parsing those. Those resolvers can replace a tag with dynamic input such as a string or a formatted number. ## Placeholders Placeholders replace the tag with a specific text. Those are the most basic replacements: ### Insert a component You can simply insert a component for the tag with the component placeholder. ```java MiniMessage.miniMessage().deserialize("Hello :)", Placeholder.component("name", Component.text("TEST", NamedTextColor.RED))); ``` This will insert the red text component "TEST" for the tag name. ### Insert some unparsed text Sometimes it's better to not parse dynamic text such as user inputs. For those things MiniMessage provides the unparsed placeholder. With this method you can sanitize user input without escaping the tags directly. ```java MiniMessage.miniMessage().deserialize("Hello ", Placeholder.unparsed("name", "TEST :)")); ``` This will insert the text without parsing. The result will be a gray text with `Hello TEST :)`. ### Insert and parse text When you want to insert a text and allow MiniMessage to parse the tags you can use the parsed placeholder. The parsed placeholder will insert the replacement before parsing the string. The tags in the placeholder can affect the parsed result after the placeholder. ```java MiniMessage.miniMessage().deserialize("Hello :)", Placeholder.parsed("name", "TEST")); // returns Component.text("Hello ", NamedTextColor.GRAY).append(Component.text("TEST :)", NamedTextColor.RED)); ``` This will insert and parse the text. ### Insert a style When you want to create your own styling tag you can use the styling placeholder. ```java MiniMessage.miniMessage().deserialize("Hello :) How are you?", Placeholder.styling("my-style", ClickEvent.suggestCommand("/say hello"), NamedTextColor.RED, TextDecoration.BOLD)); // will apply a click event, a red text color and bold decoration to the text ``` This will insert the style with a click event and a red text. Styling placeholders can be used for any style, e.g. colors, text decoration and events. Create your own styling tags: ```java Placeholder.styling("fancy", TextColor.color(150, 200, 150)); // will replace the color between "" and "" Placeholder.styling("myhover", HoverEvent.showText(Component.text("test"))); // will display your custom text as hover Placeholder.styling("mycmd", ClickEvent.runCommand("/mycmd is cool")); // will create a clickable text which will run your specified command. ``` :::tip Styling placeholders can be used to sanitize input from players in click events. Instead of using a parsed placeholder the string can be used directly. ::: ## Formatters Not everything is a text, sometimes its useful to display a number or a date. For that you can use the provided formatters from MiniMessage ### Insert a number You can insert a `Number` by using the number formatter in MiniMessage. To specify the locale and format of the number the formatter accepts optionally tag arguments. You can specify the locale and the number format. It's possible to pass both as arguments to the tag but you have provide the locale first. ```java MiniMessage.miniMessage().deserialize("Hello my number !", Formatter.number("no", 250.25d)); MiniMessage.miniMessage().deserialize("Hello my number !", Formatter.number("no", 250.25d)); MiniMessage.miniMessage().deserialize("Hello my number !", Formatter.number("no", 250.25d)); MiniMessage.miniMessage().deserialize("Hello my number !", Formatter.number("no", 250.25d)); ``` All those examples are valid and will insert the number as the tag. Refer to Locale and DecimalFormat for valid locale tags and usable patterns. :::tip You can change the style such as the color by a more complex pattern: ```java MiniMessage.miniMessage().deserialize("Your current balance is #.00;-#.00'>.", Formatter.number("no", 250.25d)); ``` This will display the balance in red for negative numbers, otherwise the number will be green. ::: ### Insert a date To insert an instance of an `TemporalAccessor` such as a `LocalDateTime` you can use the date formatter. The tag resolver requires a tag argument for the format. Refer to DateTimeFormatter for a usable patterns. ```java MiniMessage.miniMessage().deserialize("Current date is: !", Formatter.date("date", LocalDateTime.now(ZoneId.systemDefault())); ``` This will display the current date with the specified format. E.g. as `2022-05-27 11:30:25`. ### Insert a choice To insert a number and format some text based on the number you can use the choice formatter. This will accept a ChoiceFormat pattern. ```java MiniMessage.miniMessage().deserialize("I met !", Formatter.choice("choice", 5)); ``` This will format your input based on the provided ChoiceFormat. In this case it will be `I met many developers!` ## Complex placeholders You can simply create your own placeholders. Take a look at the [Formatter](https://github.com/PaperMC/adventure/blob/main/5/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/resolver/Formatter.java) and [Placeholder](https://github.com/PaperMC/adventure/blob/main/5/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/resolver/Placeholder.java) class from MiniMessage for examples. ### Examples Create a custom tag which makes its contents clickable: ```java TagResolver.resolver("click-by-version", (args, context) -> { final String version = args.popOr("version expected").value(); return Tag.styling(ClickEvent.openUrl("https://jd.papermc.io/adventure/ " + version + "/")); }); // creates a tag to get javadocs of adventure by the version: ``` You can create your own complex placeholders with multiple arguments and their own logic. --- # Format The MiniMessage format documentation. import { Image } from "astro:assets"; import { Badge } from "@astrojs/starlight/components"; import Shadow1 from "./assets/shadow_1.png"; import Shadow2 from "./assets/shadow_2.png"; import Shadow3 from "./assets/shadow_3.png"; import Color1 from "./assets/color_1.png"; import Color2 from "./assets/color_2.png"; import ColorVerbose1 from "./assets/color_verbose_1.png"; import ColorVerbose2 from "./assets/color_verbose_2.png"; import Decoration1 from "./assets/decoration_1.png"; import Reset1 from "./assets/reset_1.png"; import Click1 from "./assets/click_1.png"; import Hover1 from "./assets/hover_1.png"; import Hover2 from "./assets/hover_2.png"; import Key1 from "./assets/key_1.png"; import Translatable1 from "./assets/translatable_1.png"; import Translatable2 from "./assets/translatable_2.png"; import Insertion1 from "./assets/insertion_1.png"; import Rainbow1 from "./assets/rainbow_1.png"; import Gradient1 from "./assets/gradient_1.png"; import Transition1 from "./assets/transition_1.png"; import Font1 from "./assets/font_1.png"; import Newline1 from "./assets/newline_1.png"; import Selector1 from "./assets/selector_1.png"; The MiniMessage language uses tags. Everything you do will be defined with tags. Tags have a start tag and an end tag (the `` tag is an exception here). Start tags are mandatory (obviously), but end tags aren't outside of `strict` mode. The following are all visually identical: ```mm Hello World! Hello World! Hello World! ``` For tags with no content, tags can be auto-closed by using the format ``. With this format, even in strict mode no separate closing tag should be provided. All tag names are case-insensitive to reduce the possibility for conflict, but we recommend keeping all tag names lowercase (or at the very least, being consistent). Some tags have argument. Those look like this: `stuff`. For example: ```mm test:TEST">TEST TEST ``` As you can see, those sometimes contain components, sometimes just numbers, strings, or other types. Refer to the detailed docs below. Single (`'`) and double (`"`) quotes can be used interchangeably. We recommend staying consistent, though in order to minimize escaping it might make more sense to switch quote types for some arguments. Any meaningful token can be escaped in the locations where they have influence. In plain text, tag open characters (`<`) can be escaped with a leading backslash (`\`). Within quoted strings, the opening quote character can be escaped (`'` or `"`). In either place, the escape character can be escaped in places where it would otherwise be relevant. Unquoted tag arguments cannot have escapes, for simplicity. In locations where escaping is not supported, the literal escape character will be passed through. In locations where escaping *is* supported but a literal escape character is desired, the escape character can itself be escaped to produce a `\`. The default tags try to represent components in a manner compatible with Vanilla, but simplifying some elements. It might be helpful to use [the Minecraft wiki](https://minecraft.wiki/w/Text_component_format) as a reference for the Vanilla component system, especially for things like the actions and values of click and hover events. The [MiniMessage Web Viewer](https://webui.advntr.dev) allows testing MiniMessage text locally, without having to spin up a Minecraft instance. It can be helpful to put examples from these docs into the viewer while learning. ## Strict mode By default, MiniMessage is extremely lenient, and any invalid tags will just be ignored. Any tags left unclosed at the end of an input string will be automatically closed. Applications can optionally enable *strict mode*, which prohibits using ``, and requires all tags to be closed in reverse order of opening. Any application using MiniMessage should make it clear to end users which language variant is being used. ## Standard tags These are the tags included and enabled by default in MiniMessage. Specific parsers of MiniMessage may add custom tags to this list, or restrict the available tags to a subset of this list. Consult application documentation for details. ### Color {/* spellchecker:off */} Color the next parts Tag * `<_colorname_>` Arguments * `_colorname_`, any minecraft color constant: `black`, `dark_blue`, `dark_green`, `dark_aqua`, `dark_red`, `dark_purple`, `gold`, `gray`, `dark_gray`, `blue`, `green`, `aqua`, `red`, `light_purple`, `yellow`, or `white`. `dark_grey` can be used in place of `dark_gray`, and so can `grey` in place of `gray`. Hex colors are supported as well, with the format `#RRGGBB`. {/* spellchecker:on */} Examples ```mm Hello World! This is a test! <#00ff00>R G B! ```
The result of parsing `<yellow>Hello <blue>World</blue>!`, shown in-game in the Minecraft client's chat window The result of parsing `<red>This is a <green>test!`, shown in-game in the Minecraft client's chat window ### Color (verbose) A more verbose way of defining colors Tag * `` {/* spellchecker:off */} Aliases * `colour`, `c` {/* spellchecker:on */} Arguments * `_colorNameOrHex_`, can be any of the values from above (so named colors or hex colors) Examples ```mm Hello World! This is a test! ```
The result of parsing `<color:yellow>Hello <color:blue>World</color:blue>!`, shown in-game in the Minecraft client's chat window The result of parsing `<color:#FF5555>This is a <color:#55FF55>test!`, shown in-game in the Minecraft client's chat window ### Shadow Color Color the shadow of the next parts Tag * `` * `` as an alias to disable the shadow (equivalent to ``) Arguments * `_colorNameOrHex_`, a named color or hex color string with the format `#RRGGBB` or `#RRGGBBAA` * `[alpha_as_float]`, a float value between 0 and 1, representing the alpha value of the shadow. Optional, defaults to 0.25. Has no effect if an alpha value is already provided in the hex color string. Examples ```mm Hello World! This is a test! Thicc ```
The result of parsing `<shadow:yellow>Hello <shadow:aqua:0.5>World</shadow>!` shown in-game in the Minecraft client's chat window The result of parsing `<shadow:#FF5555>This is a <shadow:#55FF55>test!` shown in-game in the Minecraft client's chat window The result of parsing `<shadow:#000000FF><b>Thicc` shown in-game in the Minecraft client's chat window ### Decoration Decorate the next parts Tag * `<_decorationname_[:false]>`, or `` as an alias to invert the decoration. Arguments * `_decorationname_`, Any decoration supported in Minecraft: | Decoration | Aliases | | ----------------- | --------------- | | `bold` | `b` | | `italic` | `em` or `i` | | `underlined` | `u` | | `strikethrough` | `st` | | `obfuscated` | `obf` | Examples ```mm This is important! ```
The result of parsing `<underlined>This is <bold>important</bold>!`, shown in-game in the Minecraft client's chat window ### Reset Close all currently open tags, resetting color/decoration/etc. The reset tag cannot be closed. In strict mode, reset tags are forbidden. Tag * `` Arguments * none Examples ```mm Hello world! ```
The result of parsing `<yellow><bold>Hello <reset>world!`, shown in-game in the Minecraft client's chat window ### Click Allows doing multiple things when clicking on the component. Tag * `` Arguments * `_action_`, the type of click event, one of [this list](jd:adventure:net.kyori.adventure.text.event.ClickEvent$Action#field-summary) * `_value_`, the argument for that particular event, refer to [the minecraft wiki](https://minecraft.wiki/w/Text_component_format) Examples ```mm Click to show the world seed! Click this to copy your score! ```
The result of parsing `<click:run_command:/seed>Click</click> to show the world seed!`, shown in-game in the Minecraft client's chat window :::caution Since the introduction of chat signatures in 1.19.1, the client no longer executes commands that require signed arguments like the `/say` or `/tell` command to prevent the server from sending signed messages on the client's behalf. ::: ### Hover Allows doing multiple things when hovering on the component. Tag * `` Arguments * `_action_`, the type of hover event, one of this [list](jd:adventure:net.kyori.adventure.text.event.HoverEvent$Action#field-summary) * `_value_`, argument(s) specific to each event action: [//]: # (FIXME: Starlight's padding doesn't apply for a spanned cell, so a manual padding is added) | Action | Value | Description | |---------------|------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `show_text` | `_text_` | a MiniMessage string | | `show_item` | `_type_[:_count_[(:_componentKey_:_componentValue_)...]]` | a `Key` for the item's type, optionally followed by count (an integer) and a list of [data component](https://minecraft.wiki/w/Data_component_format) key value pairs | | ^ | `_type_[:_count_[:tag]]` | a `Key` for the item's type, optionally followed by count (an integer) and tag (a [SNBT](https://minecraft.wiki/w/NBT_format#SNBT_format) string) | | `show_entity` | `_type_:_uuid_[:_name_]` | a `Key` ID of the entity type, the entity's UUID, and an optional custom name | :::caution The `_type_[:_count_[:tag]]` value format for the `show_item` action is considered legacy and support for it may eventually be removed. The recommended format is `_type_[:_count_[(:_componentKey_:_componentValue_)...]]`. ::: Examples ```mm test'>TEST ```
The result of parsing `<hover:show_text:'<red>test'>TEST`, shown in-game in the Minecraft client's chat window ```mm Very sharp sword! ```
<hover:show_item:diamond_sword:1:enchantments:'{sharpness:3,knockback:2}'>Very sharp sword!</hover> ### Keybind Allows displaying the configured key for actions Tag * `` Arguments * `_key_`, the keybind identifier of the action Examples ```mm Press to jump! ```
The result of parsing `Press <red><key:key.jump> to jump!`, shown in-game in the Minecraft client's chat window ### Translatable Allows displaying minecraft messages using the player locale Tag * `` Aliases * `tr`, `translate` Arguments * `_key_`, the translation key * `_valueX_`, optional values that are used for placeholders in the key (they will end up in the `with` tag in the JSON) Examples ```mm You should get a ! 1':'Stone'>! ```
The result of parsing `You should get a <lang:block.minecraft.diamond_block>!`, shown in-game in the Minecraft client's chat window in English The result of parsing `<lang:commands.drop.success.single:'<red>1':'<blue>Stone'>!`, shown in-game in the Minecraft client's chat window in English ### Fallback :::note The fallback option is only available since Minecraft 1.19.4. ::: Allows displaying minecraft messages using the player locale, or a fallback if no text is available Tag * `` Aliases * `tr_or`, `translate_or` Arguments * `_key_`, the translation key * `_fallback_`, the fallback text to display * `_valueX_`, optional values that are used for placeholders in the key (they will end up in the `with` tag in the JSON) Examples ```mm You should get a ! ``` ### Insertion Allow insertion of text into chat via shift click Tag * `` Arguments * `_text_`, the text to insert Examples ```mm Shift-click this to insert! ```
The result of parsing `Shift-click <insert:test>this</insert> to insert!`, shown in-game in the Minecraft client's chat window ### Rainbow Rainbow-colored text?! Tag * `` Arguments * phase, optional * `!`, literal value which reverses the rainbow, optional Examples ```mm Woo: ||||||||||||||||||||||||! Woo: ||||||||||||||||||||||||
! Woo: ||||||||||||||||||||||||! Woo: ||||||||||||||||||||||||! ```
The result of parsing all four examples in series, shown in-game in the Minecraft client's chat window ### Gradient Gradient colored text Tag * `` Arguments * a list of 1 to n colors, either hex or named colors and an optional phase parameter (range -1 to 1) allows you to shift the gradient around, creating animations. Examples ```mm Woo: ||||||||||||||||||||||||! Woo: ||||||||||||||||||||||||! Woo: ||||||||||||||||||||||||! Woo: ||||||||||||||||||||||||! ```
The result of parsing the examples for the gradient tag, shown in-game in the Minecraft client's chat window ### Transition Transitions between colors. Similar to a gradient, but everything is the same color and the phase chooses that color Tag * `` Arguments * a list of 1 to n colors, either hex or named colors and an optional phase parameter (range -1 to 1) allows you to shift the transition around, creating animations. Examples ```mm ||||||||| Hello world [phase] ```
The result of parsing `<transition:white:black:red:[phase]>Hello World [phase]</transition>`, shown in-game in the Minecraft client's chat window ### Font Allows to change the font of the text Tag * `` Arguments * the namespaced key of the font, defaulting to `minecraft` Examples ```mm Nothing Uniform Alt Uniform Uses a custom font from a resource pack ```
The result of parsing `Nothing <font:uniform>Uniform <font:alt>Alt  </font> Uniform`, shown in-game in the Minecraft client's chat window ### Newline Insert a newline character. Tag * `` Aliases * `br` Arguments * none Examples ```mm Let me insert a line break here. Hover with aline break'>Text withline break ```
The result of parsing `<hover:show_text:'<red>Hover with a<newline><green>line break'>Text with<newline>line break</hover>`, shown in-game in the Minecraft client's chat window ### Selector *(since v4.11.0)* Insert a selector component Tag * `` Aliases * `sel` Arguments * `_sel_`, the selector pattern to insert * `_separator_` (optional), the separator to insert between values the selector matches Examples ```mm Hello , I'm ! ```
The result of parsing `Hello <selector:@e[limit=5]>, I'm <selector:@s>!`, show in-game in the Minecraft client's chat window ### Score *(since v4.13.0)* Insert a score component. :::note The score component requires *rendering* on the server to be seen by clients. This is a platform-specific operation. ::: Tag * `` Arguments: * `_name_`, the name of the score holder on the server scoreboard, or a selector resolved with receiver context * `_objective_`, the name of the objective to get `name`'s score in Examples ```mm You have won games! ``` ### NBT *(since v4.13.0)* Insert a NBT component. The syntax of this tag is intended to be familiar to users of vanilla Minecraft's `/data` command. :::note The produced NBT component requires *rendering* on the server to be seen by clients. This is a platform-specific operation. ::: Tag * `` Aliases * `data` Arguments: * `block|entity|storage` the type of data source to read from -- a `block` entity, an `entity` selector, or the persistent command `storage` container * `_id_`, the position for a block NBT component, a selector for an entity NBT component, or a key (resource location) for a storage NBT component * `_path_`, the NBT path to resolve from within the data source * `_separator_`, the separator between multiple values, if (primarily for entity NBT) the data source returns more than one * `interpret`, the literal text `interpret` if the result should be parsed as component JSON Examples ```mm Your health is ``` ### Pride *(since v4.18.0)* Colors the text inside the tags with a gradient corresponding to a pride flag. Tag * `` Arguments * `flag` the flag to use, may be one of pride, progress, trans, bi, pan, nb, lesbian, ace, agender, demisexual, genderqueer, genderfluid, intersex, aro, baker, philly, queer, gay, bigender, demigender, femboy or intersex inclusive. * `phase` phase, a number between -1 and 1, optional Examples ```mm Happy pride month! Kyori supports trans rights! ``` ### Sprite *(since v4.25.0)* Inserts a sprite. Tag * `` Arguments * `atlas` the atlas to use, e.g. `minecraft:blocks`. * `sprite` the sprite to use, e.g. `item/emerald`. Examples ```mm Look at my ! This item costs 10 x . ``` ### Head *(since v4.25.0)* Inserts a player head. Tag * `` Arguments * `name|uuid|texture` the name, UUID or path to the texture of the skin to use to draw the head. * `outer_layer` either `true` or `false`, determines if the outer layer (or "hat" layer) should be drawn. Defaults to `true`. Examples ```mm My favorite dev is . Do you prefer Steve or Alex? Thanks for the docs! ``` --- # MiniMessage translator A guide on the MiniMessage translator. :::note For more information about both Minecraft and Adventure's localization systems, see [`localization`](/adventure/localization). ::: MiniMessage provides a `Translator` implementation that allows you to use MiniMessage as translation strings. It also provides automatic support for argument placeholders, letting you use simple translatable components throughout your codebase. ## Creating a MiniMessage translator To start, create an implementation of the `MiniMessageTranslator` and register it to the `GlobalTranslator`. This can be done using `GlobalTranslator.translator().addSource(myMiniMessageTranslator)`. For an example of how to create your own `MiniMessageTranslator`, see the code block below. ```java public class MyMiniMessageTranslator extends MiniMessageTranslator { public MyMiniMessageTranslator() { // By default, the standard MiniMessage instance will be used. // You can specify a custom one in the super constructor. super(MiniMessage.miniMessage()); } @Override public Key name() { // Every translator has a name which is used to identify this specific translator instance. return Key.key("mynamespace:mykey"); } @Override public @Nullable String getMiniMessageString(final String key, final Locale locale) { // Creating a custom MiniMessage translator is as simple as overriding this one method. // All you need to do is return a MiniMessage string for the provided key and locale. // In this example we will hardcode this, but you could pull it from a resource bundle, a properties file, a config file or something else entirely. if (key.equals("mykey") && locale == Locale.US) { return "Hello, ! Today is ."; } else { // Returning null "ignores" this translation. return null; } } } ``` ### MiniMessage translation store In order to make managing a `MiniMessageTranslator` easier, we also provide a `TranslationStore` implementation using MiniMessage strings. For documentation on how to use translation stores, see `localization`. Note that the `MiniMessageTranslationStore` contains the same methods as the message format translation store for populating a translation store using resource bundles. ## Using a MiniMessage translator The MiniMessage translator will automatically turn translatable component arguments into a custom tag. This tag will be `` or `` where `index` is the zero indexed position of the argument. For example, this component `Component.translatable(key, Component.text("Kezz"))` with the MiniMessage string `Hello, !` will produce "Hello, Kezz!". You can also use the `Argument` class to create named tags for ease of use. For example, this component `Component.translatable(key, Argument.component("name", Component.text("Kezz"))` will produce the string "Hello, Kezz!" when used with either `Hello, !` or `Hello, !`. Finally, you can also add entirely custom tags or tag resolvers to the deserialization by using the rest of the methods on `Argument`. For a full list, please see the Javadocs for the `Argument` class. --- # Platforms Documentation regarding various implementations of the Adventure API. Adventure integrates with many of the Minecraft platforms out there. Some platforms support Adventure natively, but other legacy platforms have their own types and need an adapter to handle Adventure types. To enable you to use Adventure with these platforms, Adventure provides a number of platform-specific adapters to allow you to obtain `Audience` instances from native user types. :::note[FAQ] **Why is adventure-platform not sending any messages or not working correctly?** Firstly, please ensure you are on the latest stable version. It can be found on [Maven Central](https://central.sonatype.com/search?q=g%3Anet.kyori+adventure-platform*). Next, make sure that the feature you are using exists on the client version that is receiving the action. For example, hex color codes won't work on clients older than 1.16, so hex colors will be down-sampled. If it's still not working, it is useful to enable debug mode by setting the system property `net.kyori.adventure.debug` to `true` and looking at the output. This will show what facets are being selected which will help point towards why it is not working for you. If you still cannot figure out the issue by yourself, you can always ask in the [`#adventure-platform-help`](https://discord.com/channels/289587909051416579/1342379165663363112) channel in the PaperMC Discord! ::: ### Content: * [Native support](/adventure/platform/native) * [Modded (Fabric and NeoForge shared API)](/adventure/platform/modded) * [Dependency](/adventure/platform/modded#dependency) * [Basic use](/adventure/platform/modded#basic-use) * [Working with native types](/adventure/platform/modded#working-with-native-types) * [Fabric](/adventure/platform/fabric) * [Dependency](/adventure/platform/fabric#dependency) * [Basic use](/adventure/platform/fabric#basic-use) * [Server](/adventure/platform/fabric#server) * [Client](/adventure/platform/fabric#dependency) * [Working with native types](/adventure/platform/fabric#working-with-native-types) * [NeoForge](/adventure/platform/neoforge) * [Dependency](/adventure/platform/neoforge#dependency) * [Basic use](/adventure/platform/neoforge#basic-use) * [Server](/adventure/platform/neoforge#server) * [Commands](/adventure/platform/neoforge#commands) * [Client](/adventure/platform/neoforge#dependency) * [Implementing platforms](/adventure/platform/implementing) * [Services](/adventure/platform/implementing#services) * [Conventional behaviors](/adventure/platform/implementing#conventional-behaviors) ### Legacy platforms :::danger Adventure platform implementations for Bungeecord, Bukkit/Spigot, and Sponge API 7, as well as associated serializers, are no longer maintained. The Adventure team no longer provides support for using these libraries. We recommend that users of these libraries update to modern platforms that [natively support Adventure](/adventure/platform/native) (e.g., Velocity, Paper, Sponge API 8+). For users who develop for modded platforms, we recommend that you use [adventure-platform-mod](/adventure/platform/modded) for near-native support for Fabric and NeoForge. ::: * [Bukkit](/adventure/platform/bukkit) * [Usage](/adventure/platform/bukkit#usage) * [Component serializers](/adventure/platform/bukkit#component-serializers) * [BungeeCord](/adventure/platform/bungeecord) * [Usage](/adventure/platform/bungeecord#usage) * [Component serializers](/adventure/platform/bungeecord#component-serializers) * [SpongeAPI](/adventure/platform/spongeapi) * [Usage](/adventure/platform/spongeapi#usage) * [ViaVersion](/adventure/platform/viaversion) --- # Bukkit The Bukkit Adventure implementation. import Dependency from "/src/components/Dependency.astro"; import { LATEST_ADVENTURE_SUPPORTED_MC, LATEST_ADVENTURE_PLATFORM_RELEASE } from "/src/utils/versions"; The Adventure platform implementation for Bukkit targets Paper, Spigot, and Bukkit for Minecraft 1.7.10 through {LATEST_ADVENTURE_SUPPORTED_MC}. :::danger Adventure platform implementation for Bukkit/Spigot and the Minecraft/Bungeecord component serializers are no longer maintained. The Adventure team no longer provides support for using these libraries. We recommend that users of these libraries update to modern software that [natively supports Adventure](/adventure/platform/native) (e.g., [Paper](/paper)). ::: Declaring the dependency: ## Usage You should first obtain a `BukkitAudiences` object by using `BukkitAudiences.create(plugin)`. This object is thread-safe and can be reused from different threads if needed. From here, Bukkit `CommandSender`s and `Player`s may be converted into `Audience`s using the appropriate methods on `BukkitAudiences`. The audiences object should also be closed when a plugin is disabled in order to clean up resources and increase the likelihood of a successful `/reload`. ```java public class MyPlugin extends JavaPlugin { private BukkitAudiences adventure; public BukkitAudiences adventure() { if (this.adventure == null) { throw new IllegalStateException("Tried to access Adventure when the plugin was disabled!"); } return this.adventure; } @Override public void onEnable() { // Initialize an audiences instance for the plugin this.adventure = BukkitAudiences.create(this); // then do any other initialization } @Override public void onDisable() { if (this.adventure != null) { this.adventure.close(); this.adventure = null; } } } ``` This audience provider should be used over the serializers directly, since it will handle compatibility measures for sending messages across versions. ## Component serializers For areas that aren't covered by the `Audience` interface, the Bukkit platform provides the `MinecraftComponentSerializer` (available on CraftBukkit-based servers), and the `BungeeComponentSerializer` (available on Spigot and Paper servers) to convert directly between Adventure [Components](/adventure/text) and other component types. For uses that don't integrate directly with native types, JSON and legacy format serializers for the running server version are exposed in `BukkitComponentSerializer`. --- # BungeeCord The BungeeCord Adventure implementation. import Dependency from "/src/components/Dependency.astro"; import { LATEST_ADVENTURE_PLATFORM_RELEASE } from "/src/utils/versions"; Adventure targets the latest version of BungeeCord and BungeeCord-compatible forks, such as Waterfall. :::danger Adventure platform implementation for Bungeecord and the Bungeecord component serializer is no longer maintained. The Adventure team no longer provides support for using these libraries. We recommend that users of these libraries update to modern software that [natively supports Adventure](/adventure/platform/native) (e.g., [Velocity](/velocity)). ::: Declaring the dependency: ## Usage You should first obtain a `BungeeAudiences` object by using `BungeeAudiences.create(plugin)`. This object is thread-safe and can be reused from different threads if needed. This object should also be *closed* when the plugin is disabled. Note that not all functionality is available on the proxy. Sending chat messages, action bar messages, titles, and boss bars, and tab list header and footer are supported, but all other requests will fail silently. A simple example of how to appropriately initialize this platform follows: ```java public class MyPlugin extends Plugin { private BungeeAudiences adventure; public BungeeAudiences adventure() { if (this.adventure == null) { throw new IllegalStateException("Cannot retrieve audience provider while plugin is not enabled"); } return this.adventure; } @Override public void onEnable() { this.adventure = BungeeAudiences.create(this); } @Override public void onDisable() { if (this.adventure != null) { this.adventure.close(); this.adventure = null; } } } ``` ## Component serializers For functionality not already supported by `Audience`, the `BungeeComponentSerializer` allows you to convert between Adventure [Components](/adventure/text) and the native BungeeCord chat component API and back. :::cautions For some areas of the proxy (notably, sending server list responses), the component serializer cannot be appropriately injected unless a `BungeeAudiences` instance has been initialized. Using Adventure `Component` instances **will not** work without a created `BungeeAudiences` instance. ::: --- # Fabric The Fabric Adventure implementation. import Dependency from "/src/components/Dependency.astro"; Adventure supports Fabric on *Minecraft: Java Edition* 1.16 and up, for both server-side and client-side use. Each major version of Minecraft will usually require a new release of the platform. The platform supports all features, including localization and custom renderers. When using at least version 5.3.0, this platform provides a *near-native* experience by directly implementing Adventure interfaces on Minecraft classes where possible. :::danger[Attention] Version 6.x of adventure-platform-fabric, utilizing a shared implementation with NeoForge (see [Modded (Fabric and NeoForge shared API)](/adventure/platform/modded)) is not published for Minecraft 1.20-1.21.1. This is to avoid conflicts with existing mods using 5.x. Starting with Minecraft 1.21.2, both platforms have version 6.x published. ::: ## Dependency The Fabric platform is packaged as a mod, designed to be included in mods via jar-in-jar packaging. As with the rest of the Adventure projects, releases are distributed on Maven Central, and snapshots on Sonatype OSS: The Fabric platform requires *fabric-api-base* in order to provide the locale change event, *fabric-command-api-v2* for the callback click event, and can optionally use [Colonel](https://gitlab.com/stellardrift/colonel) (or *fabric-networking-api-v1*) to allow the `Component` and `Key` argument types to be used on clients without the mod installed. There are no other dependencies. :::danger[Attention] Each major Minecraft release will require different platform versions. For older Minecraft versions, consult the table below.
Historic Versions | Minecraft Version | Adventure version | `adventure-platform-fabric` version | |-------------------|-------------------|-------------------------------------| | 1.16.2-1.16.4 | 4.9.3 | 4.0.0 | | 1.17.x | 4.9.3 | 4.1.0 | | 1.18, 1.18.1 | 4.10.0 | 5.1.0 | | 1.18.2 | 4.11.0 | 5.3.1 | | 1.19 | 4.11.0 | 5.4.0 | | 1.19.1-1.19.2 | 4.12.0 | 5.5.2 | | 1.19.3 | 4.13.0 | 5.7.0 | | 1.19.4 | 4.13.0 | 5.8.0 | | 1.20-1.20.1 | 4.14.0 | 5.9.0 | | 1.20.2 | 4.14.0 | 5.10.1 | | 1.20.4 | 4.16.0 | 5.12.0 | | 1.20.5-1.20.6 | 4.17.0 | 5.13.0 | | 1.21-1.21.1 | 4.17.0 | 5.14.1 |
::: ## Basic use The easiest way to get started with this platform is to work with the Minecraft game objects that directly implement Adventure interfaces (requires Loom 0.11 or newer). This covers almost all cases where the default renderer is used. The following Adventure interfaces are directly implemented: `Audience` - `net.minecraft.commands.CommandSourceStack`, `net.minecraft.server.MinecraftServer`, `net.minecraft.server.rcon.RconConsoleSource`, - `net.minecraft.server.level.ServerPlayer`, `net.minecraft.client.player.LocalPlayer` `Sound.Emitter` - `net.minecraft.world.entity.Entity` `Sound.Type` - `net.minecraft.sounds.SoundEvent` `Identified` - `net.minecraft.world.entity.player.Player` `ComponentLike` - `net.minecraft.network.chat.Component` `Key` - `net.minecraft.resources.ResourceLocation` `Keyed` - `net.minecraft.resources.ResourceKey` `HoverEventSource` - `net.minecraft.world.entity.Entity`, - `net.minecraft.world.item.ItemStack` `SignedMessage` - `net.minecraft.network.chat.PlayerChatMessage` `SignedMessage.Signature` - `net.minecraft.network.chat.MessageSignature` Additionally, all `Key`s created will be `ResourceLocation` instances (on Loader 0.14.0+) Using these injections, getting started is as simple as: ```java void greet(final ServerPlayer player) { final Component message = Component.text() .content("Hello ") .append(player.get(Identity.DISPLAY_NAME) .get() .color(NamedTextColor.RED) ); player.sendMessage(message); } ``` For more complex use cases, `FabricServerAudiences` or `FabricClientAudiences` provide additional API. ## Server The logical-server side of the Fabric platform can be accessed any time a server is available, through a `MinecraftServerAudiences` instance. By default, translatable components will be rendered with the global translator, but a custom renderer can be passed when initializing the platform. All `AudienceProvider` interface methods are supported, except for the `permission` method. This will become supported as soon as Fabric gets a suitable permissions API. To get started with Adventure, set up an audience provider like this: ```java public class MyMod implements ModInitializer { private volatile MinecraftServerAudiences adventure; public MinecraftServerAudiences adventure() { if (this.adventure == null) { throw new IllegalStateException("Tried to access Adventure without a running server!"); } return this.adventure; } @Override public void onInitialize() { // Register with the server lifecycle callbacks // This will ensure any platform data is cleared between game instances // This is important on the integrated server, where multiple server instances // can exist for one mod initialization. ServerLifecycleEvents.SERVER_STARTING.register(server -> this.adventure = MinecraftServerAudiences.of(server)); ServerLifecycleEvents.SERVER_STOPPED.register(server -> this.adventure = null); } } ``` From here, audiences can be acquired for players and any other `CommandSource`. Specialized serializer instances are also available, to allow using game information in component serialization. ### Localization As part of the platform's translation support, the `PlayerLocales.CHANGED_EVENT` callback will be called any time a player on the server receives an updated language from their client, and allows accessing the current locale for a player. ### Commands The Fabric platform provides custom argument types to specify `Key` and `Component` parameters in Brigadier commands, and has helpers to easily get an `Audience` from a `CommandSourceStack` (yarn: `ServerCommandSource`) instance. :::caution If these custom argument types are used (pre-1.19), Vanilla clients will not be able to join unless the [Colonel](https://gitlab.com/stellardrift/colonel) mod is installed on the server. Like the platform, it is small and easily included in your mod jar. ::: As an example, here's a simple command that will echo whatever is provided as input: ```java // A potential method to be in the mod initializer class above private static final String ARG_MESSAGE = "message"; void registerCommands(final CommandDispatcher dispatcher, final boolean isDedicated) { dispatcher.register(literal("echo").then(argument(ARG_MESSAGE, component()).executes(ctx -> { final Component message = component(ctx, ARG_MESSAGE); ctx.getSource().sendMessage(Component.text("You said: ").append(message)); }))); } ``` ## Client Special for the Fabric platform, purely client-side operations are supported. The setup is less involved than it is for the server, since the client is a singleton, and there is only one subject that can be acted on: the client's player. This means that for most users the `MinecraftClientAudiences` object can be treated as a singleton. The only exception is users using a custom renderer. This makes using Adventure audiences fairly simple, as this code example shows: ```java void doThing() { // Get the audience final Audience client = MinecraftClientAudiences.of().audience(); // Do something. This will only work when the player is in game. client.sendMessage(Component.text("meow", NamedTextColor.DARK_PURPLE)); } ``` The full functionality of the `Audience` interface is available, including localization! ## Working with native types Sadly, Adventure can't provide API for every place chat components are used in the game. However, for areas not covered by the API in `Audience`, it's possible to convert components between native and Adventure types. See certain native types which implement Adventure interfaces, and the methods on `FabricAudiences` for other available conversions. --- # Implementing platforms Implementing Adventure for your own platform. Most users will be here to look at information about existing platform implementations, but for those who are looking to build their own platform integrations, look no further. While at its core Adventure 'just' provides data structures and serializers, as the game evolves and more functionality is added there are more tunable options and platform hooks necessary to produce the correct output for the applicable game version. This has led to the introduction of a variety of services that platforms can provide implementations of using Java's `ServiceLoader` mechanism. Some other behaviors are expected by convention. As there are not that many platforms that integrate with Adventure, this page is an attempt to cover the common points. Please don't be afraid to ask us questions, and together we can work on fleshing out this page. ## Services ### `ComponentSerializer` services Most of the serializers (Gson, legacy, etc.) have `Provider` SPI's that allow customizing the default behaviors of serializers. These are most applicable for the Gson/other JSON serializers where the data structures have changed over time, but the legacy serializer's options can be worth referencing too. See the Javadoc for each serializer for more information. For any `JSONComponentSerializer` subtype, we have tried to gather relevant tunable options within a single system, keyed by the game's active [data version](https://minecraft.wiki/w/Data_version). To handle hover events in pre-1.16 game versions, there's the additional `LegacyHoverEventSerializer` interface. We offer an implementation that uses `adventure-nbt` as a separate submodule, but platforms may wish to use a native NBT library for this instead. Both of these options should be set on builders in the appropriate `Provider` implementation. ### Data component values To handle storing platform-specific data on `show_item` hover events, we expose opaque data objects in-API. Platforms should provide logic to convert between different implementations by providing an implementation of `DataComponentValueConverterRegistry.Provider`. For the most part this is just converting between platform-specific types and the generic `TagSerializable` and `Removed` types, but platforms should make sure to include converters to `GsonDataComponentValue` (from both platform types *and* the generic `TagSerializable` that requires parsing SNBT for a conversion to occur). ### Click callbacks As callbacks are a commonly desired feature, Adventure provides a 'virtual' click event type for callback functions. This action is not persistent between runs, and needs platforms to register a command to trigger callbacks to execute. This is implemented via the `ClickCallback.Provider` SPI. This command should not be sent as part of the command tree that clients receive to avoid spamming them. Platforms implementing the click callback provider must register a command at the appropriate time, and maintain a registry of active callbacks that is added to any time a callback command is requested. The platform is responsible for ensuring any execution conditions apply and implementing the effects of any `Option`s that may be set on the callback. ### Component logging `ComponentLogger`, as part of the `adventure-text-logger-slf4j` module, provides a logging interface that extends SLF4J and wraps any existing SLF4J logger (compatible with v1 and v2). Platforms are responsible for providing the adapter that looks up the appropriate logger by name and serializes components to text. This should involve performing any translations if necessary. The default behavior of the logger is to serialize to plain text, but platforms may want to look at the `/serializer/ansi` serializer instead for colored output. ### Boss bars Boss bars are logistically somewhat complicated. As one of the few holders of mutable state in the library, they have to re-sync any state changes to their viewers. In order to track viewers and link up to any internal state, the `BossBarImplementation.Provider` SPI allows platforms to provide their own implementation hooks per-bar. ## Conventional behaviors Some behaviors are expected by platforms beyond what is explicitly required by implementing certain interfaces. These are: * When implementing `Audience`, any unsupported operations should fail silently. * When sending components to a player, they should be passed through `GlobalTranslator` before sending to perform any translations (note: `GlobalTranslator` is only for custom translations, and should not contain vanilla resource pack translations - they have a different interpolation syntax than `GlobalTranslator` uses) * There is no specific required list of Adventure modules to distribute with your platform, but we recommend `adventure-api`, `adventure-text-minimessage`, `adventure-text-logger-slf4j`, plus whatever serializers are required for the integration. We specifically do not recommend distributing the `adventure-text-serializer-legacy` module unless it is necessary for backwards compatibility within your platform. --- # Modded (Fabric and NeoForge shared API) Fabric and NeoForge shared Adventure implementation. import { Tabs, TabItem } from "@astrojs/starlight/components"; Starting with *Minecraft: Java Edition* 1.21.2, Adventure is implemented using mostly shared code between NeoForge and Fabric. Each major version of Minecraft will usually require a new release of the platform. The platform supports all features, including localization and custom renderers. ## Dependency When building multi-loader mods, we often want to move as much code as possible into a loader-agnostic part of our projects. Adventure facilities this through the `net.kyori:adventure-platform-mod-shared` artifact. A second variant, `net.kyori:adventure-platform-mod-shared-fabric-repack`, is also published. This variant should be used when your common code is managed by `fabric-loom`. :::caution[Note] These artifacts are for the common module on multi-platform mods. For specific platforms, take a look at the [Fabric](/adventure/platform/fabric) or [NeoForge](/adventure/platform/neoforge) pages. If you are building a Fabric-only or NeoForge-only mod, or do not care to use the platform API in shared code, then you don't need to use this artifact explicitly. This is because both platforms depend on it transitively. ::: As with the rest of the Adventure projects, releases are distributed on Maven Central, and snapshots on Sonatype OSS: ```kotlin title="build.gradle.kts" replace repositories { // for development builds maven(url = "https://central.sonatype.com/repository/maven-snapshots/") { name = "central-snapshots" mavenContent { snapshotsOnly() } } // for releases mavenCentral() } dependencies { // Loom project modCompileOnly("net.kyori:adventure-platform-mod-shared-fabric-repack:\{LATEST_ADVENTURE_PLATFORM_MOD_RELEASE}") // for Minecraft \{LATEST_ADVENTURE_SUPPORTED_MC_RANGE} // NeoGradle/ModDevGradle/VanillaGradle project compileOnly("net.kyori:adventure-platform-mod-shared:\{LATEST_ADVENTURE_PLATFORM_MOD_RELEASE}") // for Minecraft \{LATEST_ADVENTURE_SUPPORTED_MC_RANGE} } ``` ```groovy title="build.gradle" replace repositories { // for development builds maven { name = 'central-snapshots' url = 'https://central.sonatype.com/repository/maven-snapshots/' mavenContent { snapshotsOnly() } } // for releases mavenCentral() } dependencies { // Loom project modCompileOnly('net.kyori:adventure-platform-mod-shared-fabric-repack:\{LATEST_ADVENTURE_PLATFORM_MOD_RELEASE}') // for Minecraft \{LATEST_ADVENTURE_SUPPORTED_MC_RANGE} // NeoGradle/ModDevGradle/VanillaGradle project compileOnly('net.kyori:adventure-platform-mod-shared:\{LATEST_ADVENTURE_PLATFORM_MOD_RELEASE}') // for Minecraft \{LATEST_ADVENTURE_SUPPORTED_MC_RANGE} } ``` :::note[Attention] Each major Minecraft release will require different platform versions. For older Minecraft versions, consult the table below.
Historic Versions | Minecraft Version | Adventure version | `adventure-platform-(mod-shared/fabric/neoforge)` version | |-------------------|-------------------|-----------------------------------------------------------| | 1.21.9-1.21.10 | 4.25.0 | 6.7.0 | | 1.21.6-1.21.8 | 4.24.0 | 6.6.0 | | 1.21.5 | 4.21.0 | 6.4.0 | | 1.21.2-1.21.4 | 4.20.0 | 6.3.0 | | 1.21-1.21.1 | 4.17.0 | 6.0.0 |
::: ## Basic use The easiest way to get started with this platform is to work with the Minecraft game objects that directly implement Adventure interfaces. This covers almost all cases where the default renderer is used. On Fabric, interface injection is used so that you can directly call interface methods on Minecraft objects (with loom 0.11+). On NeoForge, you must manually cast or use the helpers provided in `MinecraftAudiences`. The following Adventure interfaces are directly implemented: `Audience` - `net.minecraft.commands.CommandSourceStack`, `net.minecraft.server.MinecraftServer`, `net.minecraft.server.rcon.RconConsoleSource`, - `net.minecraft.server.level.ServerPlayer`, `net.minecraft.client.player.LocalPlayer` `AdventureCommandSourceStack` - `net.minecraft.commands.CommandSourceStack` `Sound.Emitter` - `net.minecraft.world.entity.Entity` `Sound.Type` - `net.minecraft.sounds.SoundEvent` `Identified` - `net.minecraft.world.entity.player.Player` `ComponentLike` - `net.minecraft.network.chat.Component` `Key` - `net.minecraft.resources.ResourceLocation` `Keyed` - `net.minecraft.resources.ResourceKey` `HoverEventSource` - `net.minecraft.world.entity.Entity`, - `net.minecraft.world.item.ItemStack` `SignedMessage.Signature` - `net.minecraft.network.chat.MessageSignature` Using these injections, getting started is as simple as: ```java void greet(final ServerPlayer player) { Component message = Component.text() .content("Hello ") .append(player.get(Identity.DISPLAY_NAME) .get() .color(NamedTextColor.RED) ); player.sendMessage(message); } ``` For more complex use cases, `MinecraftServerAudiences` or `MinecraftClientAudiences` provide additional API. ### Commands The platform provides custom argument types to specify `Key` and `Component` parameters in Brigadier commands. :::caution See the platform-specific documentation for details on registration and syncing of these argument types. ::: As an example, here's a simple command that will echo whatever is provided as input: ```java // A potential method to be in the mod initializer class above private static final String ARG_MESSAGE = "message"; void registerCommands(final CommandDispatcher dispatcher, final boolean isDedicated) { dispatcher.register(literal("echo").then(argument(ARG_MESSAGE, component()).executes(ctx -> { final Component message = component(ctx, ARG_MESSAGE); ((Audience) ctx.getSource()).sendMessage(Component.text("You said: ").append(message)); })); } ``` ## Working with native types Sadly, Adventure can't provide API for every place chat components are used in the game. However, for areas not covered by the API in `Audience`, it's possible to convert components between native and Adventure types. See certain native types which implement Adventure interfaces, and the methods on `MinecraftAudiences` for other available conversions. --- # Native support All native supported software. Native platforms integrate Adventure directly with their platform's provided API, and bundle Adventure automatically. This allows them to more tightly integrate Adventure with the rest of the game, and avoids users having to handle distributing Adventure and some platform adapter themselves. The following software provide native support for Adventure. | Platform | Minimum Version | Additional Notes | |-----------|--------------------------------------|------------------------------------------------------------------------------------------------------------------| | Sponge | Sponge 8 (1.16.5) | | | Velocity | 1.1.0 build 158 | For more information, see the [Velocity Docs](/velocity/dev/pitfalls#audience-operations-are-not-fully-supported) | | Paper | 1.16.5 build 473 | | | Minestom | Build 7494725 | For more information, see the [Minestom Wiki](https://minestom.net/docs/feature/adventure) | | Fabric | `adventure-platform-fabric` 5.3.0 | This is not strictly native, but injected interfaces provide a near-native experience | --- # NeoForge The NeoForge Adventure implementation. import Dependency from "/src/components/Dependency.astro"; Adventure supports NeoForge on *Minecraft: Java Edition* 1.21 and up, for both server-side and client-side use. Each major version of Minecraft will usually require a new release of the platform. The platform supports all features, including localization and custom renderers. ## Dependency The NeoForge platform is packaged as a mod, designed to be included in mods via jar-in-jar packaging. As with the rest of the Adventure projects, releases are distributed on Maven Central, and snapshots on Sonatype OSS: :::danger[Attention] Each major Minecraft release will require different platform versions. For older Minecraft versions, consult the table at [Modded (Fabric and NeoForge shared API)](/adventure/platform/modded). ::: ## Basic use See [Modded (Fabric and NeoForge shared API)](/adventure/platform/modded) for usage details common between NeoForge and Fabric. ## Server The logical-server side of the modded platform can be accessed any time a server is available, through a `MinecraftServerAudiences` instance. By default, translatable components will be rendered with the global translator, but a custom renderer can be passed when initializing the platform. All `AudienceProvider` interface methods are supported. To get started with Adventure, set up an audience provider like this: ```java @Mod("my_mod") public class MyMod { private volatile MinecraftServerAudiences adventure; public MinecraftServerAudiences adventure() { if (this.adventure == null) { throw new IllegalStateException("Tried to access Adventure without a running server!"); } return this.adventure; } public MyMod() { // Register with the server lifecycle callbacks // This will ensure any platform data is cleared between game instances // This is important on the integrated server, where multiple server instances // can exist for one mod initialization. NeoForge.EVENT_BUS.addListener((ServerStartingEvent e) -> this.adventure = MinecraftServerAudiences.of(e.getServer()) ); NeoForge.EVENT_BUS.addListener((ServerStoppedEvent e) -> this.adventure = null ); } } ``` From here, audiences can be acquired for players and any other `CommandSource`. Specialized serializer instances are also available, to allow using game information in component serialization. ## Commands The NeoForge platform includes a method to register the `KeyArgumentType` and `ComponentArgumentType`: - `AdventureArgumentTypes.register();` This should be called from the constructor of your `@Mod`-annotated class. Registering the argument types on the server will require all clients that join to have the argument types registered as well. ## Client Special for the modded platform, purely client-side operations are supported. The setup is less involved than it is for the server, since the client is a singleton, and there is only one subject that can be acted on: the client's player. This means that for most users the `MinecraftClientAudiences` object can be treated as a singleton. The only exception is users using a custom renderer. This makes using Adventure audiences fairly simple, as this code example shows: ```java void doThing() { // Get the audience final Audience client = MinecraftClientAudiences.of().audience(); // Do something. This will only work when the player is ingame. client.sendMessage(Component.text("meow", NamedTextColor.DARK_PURPLE)); } ``` The full functionality of the `Audience` interface is available, including localization! --- # SpongeAPI The SpongeAPI Adventure implementation. import Dependency from "/src/components/Dependency.astro"; import { LATEST_ADVENTURE_PLATFORM_RELEASE } from "/src/utils/versions"; Adventure provides a platform for SpongeAPI 7 for *Minecraft: Java Edition* 1.12. :::danger Adventure platform implementation for SpongeAPI 7 is no longer maintained. The Adventure team no longer provides support for using these libraries. We recommend that users of these libraries update to modern software that [natively supports Adventure](/adventure/platform/native) (e.g., SpongeAPI 8+). ::: Declaring the dependency: ## Usage The SpongeAPI platform can either be created through Guice dependency injection, or created directly. We recommend using injection, since less boilerplate is required. An example plugin is fairly straightforward: ```java @Plugin(/* [...] */) public class MyPlugin { private final SpongeAudiences adventure; @Inject MyPlugin(final SpongeAudiences adventure) { this.adventure = adventure; } public SpongeAudiences adventure() { return this.adventure; } } ``` This sets up a `SpongeAudiences` instance that can provide audiences for players, or any `MessageReceiver`. --- # ViaVersion Using Adventure with ViaVersion. :::danger Adventure platform implementation for ViaVersion is no longer maintained. The Adventure team no longer provides support for using these libraries. We recommend that users of these libraries update to modern platforms that [natively support Adventure](/adventure/platform/native) (e.g., Velocity, Paper, Sponge API 8+). ::: On supported platforms (Sponge 7 and Bukkit), Adventure is able to enhance its functionality by using the [ViaVersion](https://hangar.papermc.io/ViaVersion/ViaVersion) API to send packets directly to the client. This allows, for instance, for a plugin on a Minecraft 1.7 server to send RGB chat messages and titles to clients on newer versions of Minecraft. If you include the Sponge or Bukkit platforms, no further action is required: ViaVersion will be detected and support for it will be enabled. --- # Resource packs A guide to using resource packs with Adventure. On top of the resource packs controlled by each player on their client, the game allows servers to send resource pack URLs to clients that the players can choose to accept. This allows servers to provide customized styling. Initially this just allowed sending a single resource pack, but starting with *Minecraft 1.20.3* the server can send multiple resource packs to be stacked, and if needed removed individually. ## Sending resource packs A resource pack is identified by: * its UUID * a URI to the resource pack ZIP file * the SHA-1 hash of the resource pack ZIP file as a hex string This is referred to as `ResourcePackInfo`. For every batch of resource packs being sent, a `ResourcePackRequest`, there is: * one or more resource packs * a callback to perform actions based on the responses from the client * a toggle for whether to replace any existing server-provided resource packs, or stack the most recent packs on top * whether these resource packs are required * a prompt to display to the user if they have not yet chosen whether to allow server resource packs ## Examples Send a single resource pack to a client that is required, with a UUID computed based on its name. ```java private static final ResourcePackInfo PACK_INFO = ResourcePackInfo.resourcePackInfo() .uri(URI.create("https://example.com/resourcepack.zip")) .hash("2849ace6aa689a8c610907a41c03537310949294") .build(); public void sendResourcePack(final Audience target) { final ResourcePackRequest request = ResourcePackRequest.resourcePackRequest() .packs(PACK_INFO) .prompt(Component.text("Please download the resource pack!")) .required(true) .build(); // Send the resource pack request to the target audience target.sendResourcePacks(request); } public void sendOptionalResourcePack(final Audience target) { final ResourcePackRequest request = ResourcePackRequest.resourcePackRequest() .packs(PACK_INFO) .prompt(Component.text("Please download the resource pack!")) .required(false) .build(); // Send the resource pack request to the target audience target.sendResourcePacks(request); } ``` ## Callbacks The callback function allows servers to respond to pack download feedback sent by the client. Newer versions of the game provide more information about different phases, but any version will provide basic status info about download and application. Keep in mind that the responses are entirely driven by the client, so modded clients may send incorrect information (for example, saying a required resource pack has been applied when it has not), none at all, or even nonsensical information (status updates after a terminal update has been received). Any action taken based on a callback should therefore be defensively designed to cope with client creativity. The audience provided in the callback aims to be the exact same audience the resource pack was sent to in the case of wrapping audiences, re-wrapping any underlying returned value where necessary. ## Removing resource packs Resource packs can be removed, either some quantity at a time with `Audience.removeResourcePacks()`, or all at once with `Audience.clearResourcePacks()`. The removal methods have multiple overloads, allowing removal by a bare UUID, or by reusing the data structures used for applying resource packs. --- # Text serializers Everything to know about text/component serializers. The lowest-level way to convert between Adventure's data and other formats are serializers. Some serializers convert to standard formats, while others convert to Adventure's own formats. - [JSON](/adventure/serializer/json) - [Gson](/adventure/serializer/gson) - [Legacy](/adventure/serializer/legacy) - [Plain](/adventure/serializer/plain) - [MiniMessage](/adventure/minimessage) Components can be converted using any of these serializers: ```java // Creates a text component final TextComponent textComponent = Component.text() .content("Hello ") .color(NamedTextColor.GOLD) .append(Component.text("world", NamedTextColor.AQUA, TextDecoration.BOLD)) .append(Component.text("!", NamedTextColor.RED)) .build(); // Converts textComponent to the JSON form used for serialization by Minecraft. final String json = JSONComponentSerializer.json().serialize(textComponent); // Converts textComponent to a legacy string - "&6Hello &b&lworld&c!" final String legacy = LegacyComponentSerializer.legacyAmpersand().serialize(textComponent); // Converts textComponent to a plain string - "Hello world!" final String plain = PlainTextComponentSerializer.plainText().serialize(textComponent); ``` The same is of course also possible in reverse for deserialization. ```java // Converts JSON in the form used for serialization by Minecraft to a Component final Component component = JSONComponentSerializer.json().deserialize(json); // Converts a legacy string (using formatting codes) to a TextComponent final Component component = LegacyComponentSerializer.legacyAmpersand().deserialize("&6Hello &b&lworld&c!"); // Converts a plain string to a TextComponent final Component component = PlainTextComponentSerializer.plainText().deserialize("Hello world!"); ``` ## Text encoders Text encoders are similar to serializers, but they only provide one-way operations, allowing for serialization but not deserialization. - [ANSI](/adventure/serializer/ansi) --- # ANSI Serializing components to ANSI. import Dependency from "/src/components/Dependency.astro"; import { LATEST_ANSI_RELEASE, LATEST_ADVENTURE_API_RELEASE } from "/src/utils/versions"; The ANSI text serializer is an encoder that converts components to text containing [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code), which allows for styled text in a terminal. This can then be used to, for example, output server logs containing components while preserving their color and style. Note that since it's an encoder, it can only serialize components, and can't deserialize text back into components. Declaring the dependency: ## Usage Different kind of ANSI escape codes exist, allowing for different levels of color precision, however, not all terminals support newer kinds of ANSI escape sequences. By default, `ANSIComponentSerializer` will attempt to guess the supported kinds of escape sequences based on the system's environment variables. This can be overridden individually for a single serializer instance using the `colorLevel` method of the builder. It can also be overridden globally using system properties, using the property `net.kyori.ansi.colorLevel`, which can be set when launching the JVM using the command-line option `-Dnet.kyori.ansi.colorLevel=value`. 4 different values can be set: * `none`: Prevent any ANSI escape sequences from being emitted at all. * `indexed16`: The original set of 16 colors. * `indexed256`: Slightly newer set of 256 colors. * `truecolor`: Full 24-bit spectrum of colors. ANSI escape sequences can also be disabled using the `terminal.ansi` system property, by setting it to `false`. ## ANSI library :::note This section talks about the component-agnostic library. If you are only interested in the Adventure component-specific implementation, you do not need to read this section. ::: The `AnsiComponentSerializer` is built upon a separate ANSI library, which deals with the lower-level ANSI escape sequence logic, and also allows for creating an ANSI converter for any kind of component, not just those by Adventure. Declaring the dependency: ### Implementation usage To begin with, you need to create a class that implements `StyleOps`, where `S` is the "style" type for your component type. This adapter class allows for the ANSI logic to access properties about the style. To actually begin conversion, create an instance of a `ANSIComponentRenderer`, by using one of the static methods, the simplest of which is `ANSIComponentRenderer.toString()`, passing it an instance of your `StyleOps` adapter described above. Then, you will need to traverse the structure of your component's tree, using the `pushStyle()`, `text()` and `popStyle()` methods of the renderer instance. Finally, call `complete()` after traversing the tree has finished. The renderer's job is now concluded. In the case of the `ToString` renderer, you can access the result using the `asString()` method. As described in the [ANSI Usage](#usage) section, the library will, by default, try to guess the supported colors of the current environment. This may be overridden by passing a custom `ColorLevel` when creating the renderer, or by using the system properties as previously described. --- # Gson Serializing components to JSON with Gson. import Dependency from "/src/components/Dependency.astro"; import { LATEST_ADVENTURE_API_RELEASE } from "/src/utils/versions"; The Gson text serializer converts chat components to their JSON representation and back using the Gson library. If you are interested in sending a chat component for display in a Minecraft client, or want to support advanced chat component features, you should use the Gson text serializer. An average user of this text serializer will typically want to only deserialize a component from an external source - serialization is done automatically by the [Platforms](/adventure/platform) when the component is sent to the user. Declaring the dependency: ## Usage In Minecraft 1.16, Mojang made several major changes to the JSON chat format, adding RGB chat colors and changing how hover events are serialized. Components generated for older versions of Minecraft will still be able to be displayed in a 1.16 client, however components serialized for a 1.16 client will not be able to be displayed in a Minecraft 1.15.2 client or lower. To get a serializer that works with 1.16 clients and above, use `GsonComponentSerializer.gson()`. To get a serializer that works with all versions of Minecraft that support text components, use `GsonComponentSerializer.colorDownsamplingGson()`. This serializer downsamples RGB colors to the closest Mojang legacy color and serializes hover events in a way that is backwards compatible with older clients. ### Which serializer should I use? If all you're doing is loading and saving components to a configuration file or a database, you probably want to use the default 1.16 serializer. If you're looking to send a component to a client, first consider whether you can one of the provided platforms. If you can't use a platform, generally you should prefer the default serializer for deserializing components (as it is backwards-compatible), and make a decision on whether to use the default or the color downsampling serializer based on the version the client is on. ### Advanced usage The Gson serializer exposes both the backing `Gson` instance and a populator that allows registering Adventure serializers on any `GsonBuilder` instance. --- # JSON Serializing components to their JSON representation. The JSON serializer provides a common interface for serializer implementations that translate between a Component and JSON strings. This allows a library to support any underlying JSON library that an application may want to use. ## Use The JSON serializer works similar to all others, providing the basic serialize and deserialize operations: ```java // Component to text final String jsonText = JSONComponentSerializer.json().serialize(Component.text("Hello world", NamedTextColor.LIGHT_PURPLE)); // JSON string to component final Component comp = JSONComponentSerializer.json().deserialize(jsonText); ``` Additionally, there is a `JSONComponentSerializer.builder()` available for advanced use that requires configuring legacy compatibility options. ## Known implementations | Name | Description | | ------------------------------------------------------------ | ------------------------------------------------------- | | [adventure-text-serializer-gson](/adventure/serializer/gson) | A mature serializer working with Google's Gson library. | --- # Legacy (Deprecated) Serializing components to legacy format. import Dependency from "/src/components/Dependency.astro"; import { LATEST_ADVENTURE_API_RELEASE } from "/src/utils/versions"; The legacy text serializer converts text to and from the traditional chat format used in Minecraft prior to Minecraft 1.7, and continues to be used to this day for its familiarity to server owners. The legacy text serializer does not support most advanced features, including hover and click events, components besides text components, and insertions. RGB colors are supported (see more in the [RGB support](#rgb-support) section) and URLs can be transformed into clickable components if explicitly requested (note, however, that click events containing a URL will *not* be serialized). If advanced features are desired, consider using [MiniMessage](/adventure/minimessage). Declaring the dependency: ## Usage The legacy text serializer is accessed using the `LegacyComponentSerializer`. The default pre-provided serializers include one that uses the section symbol (§) (for display in old clients) and another that uses an ampersand (&) typically used in configuration and commands to specify color codes. The default configuration for the legacy text serializer will deserialize all three of the RGB formats supported by Adventure but will only serialize legacy Mojang colors (downsampling to the nearest color as needed) and does not transform URLs in text to links. You can configure an instance to automatically add click events to URLs in components and allow the serializer to serialize RGB colors in either the Adventure RGB format or the BungeeCord RGB format using the builder. ## RGB support The legacy serializer supports deserializing three different formats: * Legacy Mojang color and formatting codes (such as `§a` or `§l`). * An Adventure-specific RGB format that is intended to be easy to edit (such as `§#a25981`). * A BungeeCord RGB color code format that is backwards compatible with older deserialization routines but is difficult to manipulate and makes it the user's responsibility to assign a fallback for non-RGB clients (such as `§x§a§2§5§9§8§1`). The legacy serializer downsamples RGB colors by default, but you can create a serializer that serializes RGB colors in either the Adventure or BungeeCord RGB formats using the builder. --- # Plain Retrieving plain text content from components. import Dependency from "/src/components/Dependency.astro"; import { LATEST_ADVENTURE_API_RELEASE } from "/src/utils/versions"; The plain text serializer converts chat components to their plain-text representation and back. It is thus the simplest text serializer in Adventure. This serializer is useful for supporting legacy clients, logging, clearing formatting from a component that originates from external source, and provides a small, self-contained example of a text serializer. The plain text serializer, by its nature, does not support any advanced features, including color, hover and click events, URL linking, or insertions. If advanced features are desired, consider using [MiniMessage](/adventure/minimessage). Declaring the dependency: ## Usage This produces a default instance that silently ignores keybind and translatable components. You can also construct your own `PlainTextComponentSerializer` that maps the components to some plain-text representation. The deserialization of plain text is equivalent to `Component.text(string)`. No preprocessing is done on the input. The deserialization is implemented in order to provide API consistency. --- # Sound A guide to playing sound with Adventure. Adventure contains an API to play any built-in or resource pack-provided sound. Note that not all platforms implement playing sound. ## Constructing a Sound Sounds are composed of: * A Key (also known as `Identifier` or `ResourceLocation`) that decides which sound to play. Any custom sounds from resource packs can be used. If a client does not know about sounds, it will ignore the sound (though a warning will be printed to the client log). * A Sound source, used to tell the client what type of sound its hearing. The clients sound settings are also attributed to a source. * A number, determining the radius where the sound can be heard * A number from 0 to 2 determining the pitch the sound will be played at **Examples:** ```java // Create a built-in sound using standard volume and pitch Sound musicDisc = Sound.sound(Key.key("music_disc.13"), Sound.Source.MUSIC, 1f, 1f); // Create a sound from our resource pack with a higher pitch Sound myCustomSound = Sound.sound(Key.key("adventure", "rawr"), Sound.Source.AMBIENT, 1f, 1.1f); ``` ## Playing a Sound :::caution The client can play multiple sounds at once, but as of version 1.16 is limited to 8 sounds playing at once. In 1.15.2-1.16.5, due to [`MC-138832`](https://mojira.dev/MC-138832), the volume and pitch of sounds played with an emitter are ignored. As documented in [`MC-146721`](https://mojira.dev/MC-146721), any stereo sounds will not play at a specific position or following an entity, therefore, the location or emitter parameters will be ignored. ::: Once you've created a sound, they can be played to an audience using multiple methods: ```java // Play a sound at the location of the audience audience.playSound(sound); // Play a sound at a specific location audience.playSound(sound, 100, 0, 150); // Play a sound that follows the audience member audience.playSound(sound, Sound.Emitter.self()); // Play a sound that follows another emitter (usually an entity) audience.playSound(sound, someEntity); ``` ## Stopping Sounds A sound stop will stop the chosen sounds -- ranging from every sound the client is playing, to specific named sounds. ```java public void stopMySound(final Audience target) { // Stop a sound for the target target.stopSound(SoundStop.named(Key.key("music_disc.13"))); // Stop all weather sounds for the target target.stopSound(SoundStop.source(Sound.Source.WEATHER)); // Stop all sounds for the target target.stopSound(SoundStop.all()); } ``` Sound stops can be constructed using the methods in the example block above. Alternatively, they can be constructed directly from a sound. ```java // Get a sound stop that will stop a specific sound mySound.asStop(); // Sounds can also be stopped directly using the stopSound method audience.stopSound(mySound); ``` ## Creating a custom sound Use the `sounds.json` file to define sounds in a resource pack. Further reading about this limits can be done at the [Minecraft Wiki](https://minecraft.wiki/w/Sounds.json) --- # Player list/Tab list Setting the player list with Adventure. Adventure only supports changing the header (above the players) and footer (below the players) of the tab list. ![Image showing a tab list from a multiplayer server with the header and footer encased, shown through the vanilla Minecraft client](./assets/tablist.png) **Usage** With any `Audience` use `Audience.sendPlayerListHeader(Component)`, `Audience.sendPlayerListFooter(Component)` and/or `Audience.sendPlayerListHeaderAndFooter(Component, Component)`. Whether sending a header or footer by itself will display another existing header or footer will vary depending on which platform you are working on. Servers will most likely support keeping headers or footers when sending them separately, while proxies are more likely to only let you send everything at once. **Examples** ```java public void onPlayerJoin(final Audience player) { final Component header = Component.text("My Cool Server", NamedTextColor.BLUE); final Component footer = Component.text("It is: today!"); player.sendPlayerListHeaderAndFooter(header, footer); } ``` Depending on your platform this next example might display an existing header as well ```java public void onDayChange(final Audience server) { final Component footer = Component.text("It is: tomorrow!"); server.sendPlayerListFooter(footer); } ``` --- # Text (Chat Components) Everything you need to know about Components. Components represent Minecraft chat components. ## Creating components ```java // Creates a line of text saying "You're a Bunny! Press to jump!", with some coloring and styling. final TextComponent textComponent = Component.text("You're a ") .color(TextColor.color(0x443344)) .append(Component.text("Bunny", NamedTextColor.LIGHT_PURPLE)) .append(Component.text("! Press ")) .append( Component.keybind("key.jump") .color(NamedTextColor.LIGHT_PURPLE) .decoration(TextDecoration.BOLD, true) ) .append(Component.text(" to jump!")); // now you can send `textComponent` to something, such as a client ``` You can also use a builder, which is mutable, and creates one final component with the children. ```java // Creates a line of text saying "You're a Bunny! Press to jump!", with some coloring and styling. final TextComponent textComponent2 = Component.text() .content("You're a ") .color(TextColor.color(0x443344)) .append(Component.text().content("Bunny").color(NamedTextColor.LIGHT_PURPLE)) .append(Component.text("! Press ")) .append( Component.keybind().keybind("key.jump") .color(NamedTextColor.LIGHT_PURPLE) .decoration(TextDecoration.BOLD, true) .build() ) .append(Component.text(" to jump!")) .build(); // now you can send `textComponent2` to something, such as a client ``` ## Styling components Styles are a superset of TextColor and TextDecoration and can be applied to text components. TextColor represents any color in the RGB spectrum. You can also use NamedTextColor to choose from the default color palette. The following TextDecorations are available: * *Italic* * **Bold** * Strikethrough * Underlined * Obfuscated ## Events There are currently two types of events available for text components. Hover events allow you to show another component, item or entity when a user hovers their mouse over the text. When a user clicks on the text component, a click event is fired which can perform one of the following actions: * Open a URL * Open a file * Run a command * Suggest a command * Change a book's page * Copy a string to clipboard * Open a dialog * Send a custom payload back to the server ## Serializing and deserializing components Serialization to JSON, legacy, and plain representations is also supported. Components can be serialized with [Text Serializers](/adventure/serializer). ## Using components within your application The way you use components within your application will of course vary depending on what you're aiming to achieve. However, the most common task is likely to be sending a component to some sort of Minecraft client. The method for doing this will depend on the platform your program is running on, however it is likely to involve serializing the component to Minecraft's JSON format, and then sending the JSON through another method provided by the platform. The text library is platform-agnostic and therefore doesn't provide any way to send components to clients. Some platforms implement [Adventure natively](/adventure/platform/native), so `Components` can be directly used with their API. For other platforms (Spigot/Bukkit, BungeeCord, and SpongeAPI 7), we provide compatibility bridges as [Platforms](/adventure/platform) which can be distributed with your own plugins. --- # Titles Displaying titles to players. ## Constructing a Title Titles are composed of: * A component used for the main title * A component used for the subtitle * Optionally, a `Title.Times` object can be used to determine the fade-in, stay on screen and fade-out durations **Examples:** ```java public void showMyTitle(final Audience target) { final Component mainTitle = Component.text("This is the main title", NamedTextColor.WHITE); final Component subtitle = Component.text("This is the subtitle", NamedTextColor.GRAY); // Creates a simple title with the default values for fade-in, stay on screen and fade-out durations final Title title = Title.title(mainTitle, subtitle); // Send the title to your audience target.showTitle(title); } public void showMyTitleWithDurations(final Audience target) { final Title.Times times = Title.Times.times(Duration.ofMillis(500), Duration.ofMillis(3000), Duration.ofMillis(1000)); // Using the times object this title will use 500ms to fade in, stay on screen for 3000ms and then fade out for 1000ms final Title title = Title.title(Component.text("Hello!"), Component.empty(), times); // Send the title, you can also use Audience#clearTitle() to remove the title at any time target.showTitle(title); } ``` --- # Version history Pages dedicated to displaying the latest changelogs! import { CardGrid, LinkCard } from "@astrojs/starlight/components"; These changelogs for individual projects mirror those posted on the GitHub Releases page of each project's repository. --- # Folia Documentation import { CardGrid, LinkCard } from "@astrojs/starlight/components"; Folia is a fork of Paper which adds regionized multithreading to the dedicated server. --- # Administration import PageCards from "/src/components/PageCards.astro"; Welcome to the Folia administration guide! This guide includes information and tutorials regarding the administration of a Folia server. #### Reference --- # Reference import PageCards from "/src/components/PageCards.astro"; A reference of how Folia works. --- # Frequently asked questions Questions frequently asked by our community, answered by us! ## What server types can benefit from Folia? Server types that naturally spread players out, like skyblock or SMP, will benefit the most from Folia. The server should have a sizeable player count, too. ## What hardware will Folia run best on? Ideally, at least 16 _cores_ (not threads). ## How to best configure Folia? First, it is recommended that the world is pre-generated so that the number of chunk system worker threads required is reduced greatly. The following is a _very rough_ estimation based off of the testing done before Folia was released on the test server we ran that had ~330 players peak. So, it is not exact and will require further tuning - just take it as a starting point. The total number of cores on the machine available should be taken into account. Then, allocate threads for: - netty IO: ~4 per 200-300 players - chunk system io threads: ~3 per 200-300 players - chunk system workers if pre-generated, ~2 per 200-300 players - There is no best guess for chunk system workers if not pre-generated, as on the test server we ran we gave 16 threads but chunk generation was still slow at ~300 players. - GC Settings: ???? But, GC settings _do_ allocate concurrent threads, and you need to know exactly how many. This is typically through the `-XX:ConcGCThreads=n` flag. Do not confuse this flag with `-XX:ParallelGCThreads=n`, as parallel GC threads only run when the application is paused by GC and as such should not be taken into account. After all of that allocation, the remaining cores on the system until 80% allocation (total threads allocated < 80% of cpus available) can be allocated to tickthreads (under global config, `threaded-regions.threads`). The reason you should not allocate more than 80% of the cores is due to the fact that plugins or even the server may make use of additional threads that you cannot configure or even predict. Additionally, the above is all a rough guess based on player count, but it is very likely that the thread allocation will not be ideal, and you will need to tune it based on usage of the threads that you end up seeing. ## What commands does Folia disable? Folia currently disables a handful of commands. These are them: - Bossbar commands - Clone commands - Data commands - Datapack - Debug - Function - Item commands - Loot - Reload - Return - Ride - Rotate - Schedule - Scoreboard - Spectate - SpreadPlayers - Tag - Team - TeamMsg - Tick - Trigger - Perf - SaveAll - Restart --- # Overview An overview of how Folia works. Described in this document is the abstract overview of changes done by Folia. Folia splits the chunks within all loaded worlds into independently ticking regions so that the regions are ticked independently and in parallel. Described first will be intra region operations, and then inter region operations. ## Rules for independent regions In order to ensure that regions are independent, the rules for maintaining regions must ensure that a ticking region has no directly adjacent neighbor regions which are ticking. The following rules guarantee the invariant is upheld: 1. Any ticking region may not grow while it is ticking. 2. Any ticking region must initially own a small buffer of chunks outside its perimeter. 3. Regions may not _begin_ to tick if they have a neighboring adjacent region. 4. Adjacent regions must eventually merge to form a single region. Additionally, to ensure that a region is not composed of independent regions (which would hinder parallelism), regions composed of more than one independent area must be eventually split into independent regions when possible. Finally, to ensure that ticking regions may store and maintain data about the current region (i.e. tick count, entities within the region, chunks within the region, block/fluid tick lists, and more), regions have their own data object that may only be accessed while ticking the region and by the thread ticking the region. Also, there are callbacks to merging or splitting regions so that the data object may be updated appropriately. The implementation of these rules is described in [Region Logic](/folia/reference/region-logic). The end result of applying these rules is that a ticking region can ensure that only the current thread has write access to any data contained within the region, and that at any given time the number of independent regions is close to maximum. ## Intra-region operations Intra-region operations refer to any operations that only deal with data for a single region by the owning region, or to merge/split logic. ### Ticking for independent regions Independent regions tick independently and in parallel. To tick independently means that regions maintain their own deadlines for scheduling the next tick. For example, consider two regions A and B such that A's next tick start is at t=15ms and B's next tick start is at t=0ms. Consider the following sequence of events: 1. At t = 0ms, B begins to tick. 2. At t = 15ms, A begins to tick. 3. At t = 20ms, B is finished its tick. It is then scheduled to tick again at t = 50ms. 4. At t = 50ms, B begins its 2nd tick. 5. At t = 70ms, B finishes its 2nd tick and is scheduled to tick again at t = 100ms. 6. At t = 95ms, A finishes its _first_ tick. It is scheduled to tick again at t = 95ms. It is important to note that at no time was B's schedule affected by the fact that A fell behind its 20TPS target. To implement the described behavior, each region maintains a repeating task on a scheduled executor (see `SchedulerThreadPool`) that schedules tasks according to an earliest-start-time-first scheduling algorithm. The algorithm is similar to EDF, but schedules according to start time. However, given that the deadline for each tick is 50ms + the start time, it behaves identically to the EDF algorithm. The EDF-like algorithm is selected so that as long as the thread pool is not maximally utilized, that all regions that take <= 50ms to tick will maintain 20TPS. However, the scheduling algorithm is neither NUMA aware nor CPU core aware - it will not make attempts (when n regions > m threads) to pin regions to certain cores. Since regions tick independently, they maintain their own tick counters. The implications of this are described in the next section. ### Tick counters In standard Vanilla, there are several important tick counters: Current Tick, Game Time Tick, and Daylight Time Tick. The Current Tick counter is used for determining the tick number since the server has booted. The Game Time Tick is maintained per world and is used to schedule block ticks for redstone, fluids, and other physics events. The Daylight Time Tick is simply the number of ticks since noon, maintained per world. In Folia, the Current Tick is maintained per region. The Game Time Tick is split into two counters: Redstone Time and Global Game Time. Redstone Time is maintained per region. Global Game Time and Daylight Time are maintained by the "global region." At the start of each region tick, the global game time tick and daylight time tick are copied from the global region and any time the current region retrieves those values, it will retrieve from the copy received at the start of tick. This is to ensure that for any two calls to retrieve the tick number throughout the tick, that those two calls report the same tick number. The global game time is maintained for a couple of reasons: 1. There needs to be a counter representing how many ticks a world has existed for, since the game does track total number of days the world has gone on for. 2. Significant amounts of new entity AI code uses game time (for a reason I cannot divine) to store absolute deadlines of tasks. It is not impossible to write code to adjust the deadlines of all of these tasks, but the amount of work is significant. #### Global region The global region is a single scheduled task that is always scheduled to run at 20TPS that is responsible for maintaining data that is not tied to any specific region: game rules, global game time, daylight time, console command handling, world border, weather, and others. Unlike the other regions, the global region does not need to perform any special logic for merging or splitting because it is never split or merged - there is only one global region at any time. The global region does not own any region specific data. #### Merging and splitting region tick times Since redstone and current ticks are maintained per region, there needs to be appropriate logic to adjust the tick deadlines used by the block/fluid tick scheduler and anything else that schedules by redstone/current absolute tick time so that the relative deadline is unaffected. When merging a region x (from) into a region y (into or to), we can either adjust both the deadlines of x and y or just one of x and y. It is simply easier to adjust one, and arbitrarily the region x is chosen. Then, the deadlines of x must be adjusted so that considering the current ticks of y that the relative deadlines remain unchanged. Consider a deadline d1 = from tick + relative deadline in region x. We then want the adjusted deadline d2 to be d2 = to tick + relative deadline in region y, so that the relative tick deadline is maintained. We can achieve this by applying an offset o to d1 so that d1 + o = d2, and the offset used is o = tick to - tick from. This offset must be calculated for redstone tick and current tick separately, since the logic to increase redstone tick can be turned off by the `Level#tickTime` field. Finally, the split case is easy - when a split occurs, the independent regions from the split inherit the redstone/current tick from the parent region. Thus, the relative deadlines are maintained as there is no tick number change. In all cases, redstone or any other events scheduled by current tick remain unaffected when regions split or merge as the relative deadline is maintained by applying an offset in the merge case and by copying the tick number in the split case. ## Inter-region operations Inter-region operations refer to operations that work with other regions that are not the current ticking region that are in a completely unknown state. These regions may be transient, may be ticking, or may not even exist. ### Utilities to assist operations In order to assist in inter region operations, several utilities are provided. In NMS, these utilities are the `EntityScheduler`, the `RegionizedTaskQueue`, the global region task queue, and the region-local data provider `RegionizedData`. The Folia API has similar analogues, but does not have a region-local data provider as the NMS data provider holds critical locks and is invoked in critical areas of code when performing any callback logic and is thus highly susceptible to fatal plugin errors involving lengthy I/O or world state modification. #### `EntityScheduler` The `EntityScheduler` allows tasks to be scheduled to be executed on the region that owns the entity. This is particularly useful when dealing with entity teleportation, as once an entity begins an asynchronous teleport the entity cannot tick until the teleport has completed, and the timing is undefined. #### `RegionizedTaskQueue` The `RegionizedTaskQueue` allows tasks to be scheduled to be executed on the next tick of a region that owns a specific location, or creating such region if it does not exist. This is useful for tasks that may need to edit or retrieve world/block/chunk data outside the current region. #### Global region task queue The global region task queue is simply used to perform edits on data that the global region owns, such as game rules, day time, weather, or to execute commands using the console command sender. #### `RegionizedData` The `RegionizedData` class allows regions to define region-local data, which allow regions to store data without having to consider concurrent data access from other regions. For example, current per region entity/chunk/block/fluid tick lists are maintained so that regions do not need to consider concurrent access to these data sets. The utilities allow various cross-region issues to be resolved in a simple fashion, such as editing block/entity/world state from any region by using tasks queues, or by avoiding concurrency issues by using RegionizedData. More advanced operations such as teleportation, player respawning, and portalling, all make use of these utilities to ensure the operation is thread-safe. ### Entity intra- and inter-dimension teleports Entities need special logic in order to teleport safely between other regions or other dimensions. In all cases however, the call to teleport/place an entity must be invoked on the region owning the entity. The `EntityScheduler` can be used to easily schedule code to execute in such a context. #### Simple teleportation In a simple teleportation, the entity already exists in a world at a location and the target location and dimension are known. This operation is split into two parts: transform and async place. In this case, the transform operation removes the entity from the current world, then adjusts the position. The async place operation schedules a task to the target location using the `RegionizedTaskQueue` to add the entity to the target dimension at the target position. The various implementation details such as non-player entities being copied in the transform operation are left out, as those are not relevant for the high level overview. Things such as player login and player respawn are generally considered simple teleportation. The player login case only differs since the player does not exist in any world at the start, and that the async transform must additionally find a place to spawn the player. The player respawn is similar to the player login as the respawn differs by having the player in the world at the time of respawn. #### Portal teleport Portal teleport differs from simple teleportation as portalling does _not_ know the exact location of the teleport. Thus, the transform step does not update the entity position, but rather a new operation is inserted between transform and async place: an async search/create, which is responsible for finding and/or creating the exit portal. Additionally, the current Vanilla code can refuse a teleport if the entity is non-player and the nether exit portal does not already exist. But since the portal location is only determined by the async place, it is too late to abort - so, the portal logic has been re-done so that there is no difference between players and entities. Now both entities and players create exit portals, whether it be for the nether or end. #### Shutdown during teleport Since the teleport happens over multiple steps, the server shutdown process must deal with uncompleted teleportations manually. ## Server shutdown process The shutdown process occurs by spawning a separate shutdown thread, which then runs the shutdown logic: 1. Shutdown the tick region scheduler, stopping any further ticks 2. Halt metrics processing 3. Disable plugins 4. Stop accepting new connections 5. Send disconnect (but do not remove) packets to all players 6. Halt the chunk systems for all worlds 7. Execute shutdown logic for all worlds by finish all pending teleports for all regions, then saving all chunks in the world, and finally saving the level data for the world (level.dat and other .dat files). 8. Save all players 9. Shutting down the resource manager 10. Releasing the level lock 11. Halting remaining executors (Util executor, region I/O threads, etc.) The important differences to Vanilla is that the player kick and world saving logic is replaced by steps 5-8. For step 5, the players cannot be kicked before teleportations are finished, as kicking would save the player dat file. So, save is moved after. For step 6, the chunk system halt is done before saving so that all chunk generation is halted. This will reduce the load on the server as it shuts down, which may be critical in memory-constrained scenarios. For step 7, teleportations are completed differently depending on the type: simple or portal. Simple teleportations are completed by ensuring the addition of the teleporting entity to the destination chunk specified by teleportation. This allows the entity to be saved at the target position, as if the teleportation did complete before shutdown. Portal teleportations are completed by forcing the addition of the teleporting entity to the source chunk, from where the entity should have been teleported _from_. Since the target location is not known, the entity can only be placed back at the origin (no teleportation). While this behavior is not ideal, the shutdown logic _must_ account for any broken world state - which means that finding or creating the target exit portal may not be an option. The teleportation completion must be performed before the world save so that the teleport completed entities save. For step 8, only save players after the teleportations are completed. The remaining steps are Vanilla. --- # Region logic An overview to how Folia's regionizer works. ## Fundamental regionizing logic ## Region A region is simply a set of owned chunk positions and implementation defined unique data object tied to that region. It is important to note that for any non-dead region x, that for each chunk position y it owns that there is no other non-dead region z such that the region z owns the chunk position y. ## Regionizer Each world has its own regionizer. The regionizer is a term used to describe the logic that the class `ThreadedRegionizer` executes to create, maintain, and destroy regions. Maintenance of regions is done by merging nearby regions together, marking which regions are eligible to be ticked, and finally by splitting any regions into smaller independent regions. Effectively, it is the logic performed to ensure that groups of nearby chunks are considered a single independent region. ## Guarantees the regionizer provides The regionizer provides a set of important invariants that allows regions to tick in parallel without race conditions: ### First invariant The first invariant is simply that any chunk holder that exists has one, and only one, corresponding region. ### Second invariant The second invariant is that for every _existing_ chunk holder x that is contained in a region that every each chunk position within the "merge radius" of x is owned by the region. Effectively, this invariant guarantees that the region is not close to another region, which allows the region to assume while ticking it can create data for chunk holders "close" to it. ### Third invariant The third invariant is that a ticking region _cannot_ expand the chunk positions it owns as it ticks. The third invariant is important as it prevents ticking regions from "fighting" over non-owned nearby chunks, to ensure that they truly tick in parallel, no matter what chunk loads they may issue while ticking. To comply with the first invariant, the regionizer will create "transient" regions _around_ ticking regions. Specifically, around in this context means close enough that would require a merge, but not far enough to be considered independent. The transient regions created in these cases will be merged into the ticking region when the ticking region finishes ticking. Both of the second invariant and third invariant combined allow the regionizer to guarantee that a ticking region may create and then access chunk holders around it (i.e. sync loading) without the possibility that it steps on another region's toes. ### Fourth invariant The fourth invariant is that a region is only in one of four states: "transient", "ready", "ticking", or "dead." The "ready" state allows a state to transition to the "ticking" state, while the "transient" state is used as a state for a region that may not tick. The "dead" state is used to mark regions which should not be use. The states transitions are explained later, as it ties in with the regionizer's merge and split logic. ## Regionizer implementation The regionizer implementation is a description of how the class `ThreadedRegionizer` adheres to the four invariants described previously. ### Splitting the world into sections The regionizer does not operate on chunk coordinates, but rather on "region section coordinates." Region section coordinates simply represent a grouping of NxN chunks on a grid, where N is some power of two. The actual number is left ambiguous, as region section coordinates are only an internal detail of how chunks are grouped. For example, with N=16 the region section (0,0) encompasses all chunks x in [0,15] and z in [0,15]. This concept is similar to how the chunk coordinate (0,0) encompasses all blocks x in [0, 15] and z in [0, 15]. Another example with N=16, the chunk (17, -5) is contained within region section (1, -1). Region section coordinates are used only as a performance tradeoff in the regionizer, as by approximating chunks to their region coordinate allows it to treat NxN chunks as a single unit for regionizing. This means that regions do not own chunks positions, but rather own region section positions. The grouping of NxN chunks allows the regionizing logic to be performed only on the creation/destruction of region sections. For example with N=16 this means up to NxN-1=255 possible less operations in areas such as addChunk/region recalculation assuming region sections are always full. ### Implementation variables The implementation variables control how aggressively the regionizer will maintain regions and merge regions. #### Recalculation count The recalculation count is the minimum number of region sections that a region must own to allow it to re-calculate. Note that a recalculation operation simply calculates the set of independent regions that exist within a region to check if a split can be performed. This is a simple performance knob that allows split logic to be turned off for small regions, as it is unlikely that small regions can be split in the first place. #### Max dead section percent The max dead section percent is the minimum percent of dead sections in a region that must exist before a region can run re-calculation logic. #### Empty section creation radius The empty section creation radius variable is used to determine how many empty region sections are to exist around _any_ region section with at least one chunk. Internally, the regionizer enforces the third invariant by preventing ticking regions from owning new region sections. The creation of empty sections around any non-empty section will then enforce the second invariant. #### Region section merge radius The merge radius variable is used to ensure that for any existing region section x that for any other region section y within the merge radius are either owned by region that owns x or are pending a merge into the region that owns x or that the region that owns x is pending a merge into the region that owns y. #### Region section chunk shift The region section chunk shift is simply log2(grid size N). Thus, N = 1 << region section chunk shift. The conversion from chunk position to region section is additionally defined as region coordinate = chunk coordinate >> region section chunk shift. ### Operation The regionizer is operated by invoking `ThreadedRegionizer#addChunk(x, z)` or `ThreadedRegionizer#removeChunk(x, z)` when a chunk holder is created or destroyed. Additionally, `ThreadedRegion#tryMarkTicking` can be used by a caller that attempts to move a region from the "ready" state to the "ticking" state. It is vital to note that this function will return false if the region is not in the "ready" state, as it is possible that even a region considered to be "ready" in the past (i.e. scheduled to tick) may be unexpectedly marked as "transient." Thus, the caller needs to handle such cases. The caller that successfully marks a region as ticking must mark it as non-ticking by using `ThreadedRegion#markNotTicking`. The function ThreadedRegion#markNotTicking returns true if the region was migrated from "ticking" state to "ready" state, and false in all other cases. Effectively, it returns whether the current region may be later ticked again. ### Region section state A region section state is one of "dead" or "alive." A region section may additionally be considered "non-empty" if it contains at least one chunk position, and "empty" otherwise. A region section is considered "dead" if and only if the region section is also "empty" and that there exist no other "empty" sections within the empty section creation radius. The existence of the dead section state is purely for performance, as it allows the recalculation logic of a region to be delayed until the region contains enough dead sections. However, dead sections are still considered to belong to the region that owns them just as alive sections. ### Addition of chunks (`addChunk`) The addition of chunks to the regionizer boils down to two cases: #### Target region section already exists and is not empty In this case, it simply adds the chunk to the section and returns. #### Target region section does not exist or is empty In this case, the region section will be created if it does not exist. Additionally, the region sections in the "create empty radius" will be created as well. Then, any region in the create empty radius + merge radius are collected into a set X. This set represents the regions that need to be merged later to adhere to the second invariant. If the set X contains no elements, then a region is created in the ready state to own all of the created sections. If the set X contains just 1 region, then no regions need to be merged and no region state is modified, and the sections are added to this 1 region. Merge logic needs to occur when there are more than 1 region in the set X. From the set X, a region x is selected that is not ticking. If no such x exists, then a region x is created. Every region section created is added to the set x, as it is the section that is known to not be ticking - this is done to adhere to the third invariant. Every region y in the set X that is not x is merged into x if y is not in the ticking state, otherwise x runs the merge later logic into y. ### Merge later logic A merge later operation may only take place from a non-ticking, non-dead region x into a ticking region y. The merge later logic relies on maintaining a set of regions to merge into later per region, and another set of regions that are expected to merge into this region. Effectively, a merge into later operation from x into y will add y into x's merge into later set, and add x into y's expecting merge from set. When the ticking region finishes ticking, the ticking region will perform the merge logic for all expecting merges. ### Merge logic A merge operation may only take place between a dead region x and another region y which may be either "transient" or "ready." The region x is effectively absorbed into the region y, as the sections in x are moved to the region y. The merge into later is also forwarded to the region y, such so that the regions x was to merge into later, y will now merge into later. Additionally, if there is implementation specific data on region x, the region callback to merge the data into the region y is invoked. The state of the region y may be updated after a merge operation completes. For example, if the region x was "transient", then the region y should be downgraded to transient as well. Specifically, the region y should be marked as transient if region x contained merge later targets that were not y. The downgrading to transient is required to adhere to the second invariant. ### Removal of chunks (`removeChunk`) Removal of chunks from region sections simple updates the region sections state to "dead" or "alive", as well as the region sections in the empty creation radius. It will not update any region state, and nor will it purge region sections. ### Region tick start (`tryMarkTicking`) The tick start simply migrates the state to ticking, so that invariants #2 and #3 can be met. ### Region tick end (`markNotTicking`) At the end of a tick, the region's new state is not immediately known. First, the region must process its pending merges. After it processes its pending merges, it must then check if the region is now pending merge into any other region. If it is, then it transitions to the transient state. Otherwise, it will process the removal of dead sections and attempt to split into smaller regions. Note that it is guaranteed that if a region can be possibly split, it must remove dead sections, otherwise, this would contradict the rules used to build the region in the first place. --- # Welcome Documentation for all projects under the PaperMC umbrella, including Paper, Velocity and Folia. import { CardGrid } from "@astrojs/starlight/components"; import { ContributorList } from "astro-contributors"; import LinkCard from "/src/components/LinkCard.astro"; High performance Minecraft server that aims to fix gameplay and mechanics inconsistencies. The modern, next-generation Minecraft server proxy. A fork of Paper which adds regionized multithreading to the dedicated server. A Java library for server-controllable user interface elements in Minecraft: Java Edition. A discontinued BungeeCord proxy fork that aimed to improve performance and stability. Various other documentation and tools.
## [Contribute](https://github.com/PaperMC/docs) We’d like to extend a warm thank you to all the contributors who have helped bring this documentation site to life. Whether you've written code, submitted feedback, fixed a typo, or shared your expertise - every contribution is deeply appreciated. This is very much a team effort, and we’re grateful to have you with us on the journey! --- # Miscellaneous import PageCards from "/src/components/PageCards.astro"; Documentation that does not cleanly fit in any other category. --- # Art assets The official PaperMC and Velocity logomarks and the terms under which you may use them. This page provides the official PaperMC and Velocity logomarks and the terms under which you may use them. Images on this page are available through our CDN, and using the provided URLs is encouraged (but not necessary) when referencing these assets in your projects, as long as you adhere to the usage guidelines outlined below. :::caution The logomarks are subject to their own separate licensing terms and do not inherit any from the projects they represent. ::: ## PaperMC You may: - Use the PaperMC logomark to represent the project in blogposts and other places in order to bring attention to the project. - Use the PaperMC logomark to represent Paper-Server in downloads, server selectors, and similar places. - Crop out extra transparent canvas space behind the PaperMC logomark, so it fits better next to other content. You may not: - Alter any of the colors used in the PaperMC logomark. - Change the dimensions of the PaperMC logomark. - Create modified versions of the PaperMC logomark or derivative works of it. - Add your own project images or branding to the PaperMC logomark. - Claim the logomark as your own work or use it as a representation of your own projects. - Sell the PaperMC logomark on its own or as part of other products without explicit permission. - Alter the transparency of any elements within the PaperMC logomark. | Image | URL | |----------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| |
![](https://assets.papermc.io/brand/papermc_combination_mark_dark.min.svg)
| `https://assets.papermc.io/brand/papermc_combination_mark_dark.min.svg` | |
![](https://assets.papermc.io/brand/papermc_combination_mark_light.min.svg)
| `https://assets.papermc.io/brand/papermc_combination_mark_light.min.svg` | | ![](https://assets.papermc.io/brand/papermc_logo.min.svg) | `https://assets.papermc.io/brand/papermc_logo.min.svg` | | ![](https://assets.papermc.io/brand/papermc_logo.256.png) | `https://assets.papermc.io/brand/papermc_logo.256.png` | | ![](https://assets.papermc.io/brand/papermc_logo.512.png) | `https://assets.papermc.io/brand/papermc_logo.512.png` | ## Velocity Please do not edit, recolor, rearrange, or distort the Velocity logo. Resizing the logo and cropping out any blank space is acceptable. The logo should not be used in a matter that suggests the Velocity project officially endorses some product or service. For instance, you may advertise a plugin as being compatible with Velocity, but you may not make the Velocity logo prominent in that advertising. | Image | URL | |-----------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| | ![](https://assets.papermc.io/brand/velocity_combination_mark_blue.min.svg) | `https://assets.papermc.io/brand/velocity_combination_mark_blue.min.svg` | |
![](https://assets.papermc.io/brand/velocity_combination_mark_white.min.svg)
| `https://assets.papermc.io/brand/velocity_combination_mark_white.min.svg` | | ![](https://assets.papermc.io/brand/velocity_logo_blue.min.svg) | `https://assets.papermc.io/brand/velocity_logo_blue.min.svg` | |
![](https://assets.papermc.io/brand/velocity_logo_white.min.svg)
| `https://assets.papermc.io/brand/velocity_logo_white.min.svg` | --- # Contact us There are many ways to contact us, see here for more information. ## Discord The PaperMC project handles most communication via Discord. Use the following invite: https://discord.gg/papermc ## Forums Reach out for support, or contact us on our forums. - [Forums](https://forums.papermc.io) ## Twitter/X We often tweet out version release notes, update notices, and other information via our Twitter page. - [@PaperPowered](https://x.com/PaperPowered) You should not DM or @ this account for support. It is not checked as regularly as the above locations. --- # Downloads Service Paper provides a downloads service that you can use to access builds. PaperMC provides a downloads service to facilitate automated downloads access. Full documentation can be found on the [Downloads Service Docs](https://fill.papermc.io/swagger-ui/index.html#/). :::danger We emphatically **do not recommend** using unstable builds or auto-updaters within production environments. ::: All requests must now include a valid User-Agent header that: - Clearly identifies your software or company - Is not generic (e.g. curl, wget, or similar defaults) - Includes a contact URL or email address (e.g. a homepage, bot info page, or support email) **Some examples:** ``` mc-image-helper/1.39.11 (https://github.com/itzg/docker-minecraft-server) nodecraft/packifier/1.0.0 (staff@nodecraft.com) ``` ## REST API examples :::note We require `jq` to be installed for the examples below. You can install it with `sudo apt-get install jq` on Debian/Ubuntu. ::: ### Getting the latest version ```shell #!/usr/bin/env sh PROJECT="paper" USER_AGENT="cool-project/1.0.0 (contact@me.com)" LATEST_VERSION=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT} | \ jq -r '.versions | to_entries[0] | .value[0]') echo "Latest version is $LATEST_VERSION" ``` This will get the latest available Minecraft version for the given project. ### Getting the latest stable build number ```shell replace #!/usr/bin/env sh PROJECT="paper" MINECRAFT_VERSION="\{LATEST_PAPER_RELEASE}" USER_AGENT="cool-project/1.0.0 (contact@me.com)" LATEST_BUILD=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT}/versions/${MINECRAFT_VERSION}/builds | \ jq -r 'map(select(.channel == "STABLE")) | .[0] | .id') if [ "$LATEST_BUILD" != "null" ]; then echo "Latest stable build is $LATEST_BUILD" else echo "No stable build for version $MINECRAFT_VERSION found :(" fi ``` This will get the latest stable build for the given project and Minecraft version, if it's available. ### Downloading the latest stable build ```shell replace #!/usr/bin/env sh PROJECT="paper" MINECRAFT_VERSION="\{LATEST_PAPER_RELEASE}" USER_AGENT="cool-project/1.0.0 (contact@me.com)" # First check if the requested version has a stable build BUILDS_RESPONSE=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT}/versions/${MINECRAFT_VERSION}/builds) # Check if the API returned an error if echo "$BUILDS_RESPONSE" | jq -e '.ok == false' > /dev/null 2>&1; then ERROR_MSG=$(echo "$BUILDS_RESPONSE" | jq -r '.message // "Unknown error"') echo "Error: $ERROR_MSG" exit 1 fi # Try to get a stable build URL for the requested version PAPERMC_URL=$(echo "$BUILDS_RESPONSE" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // "null"') FOUND_VERSION="$MINECRAFT_VERSION" # If no stable build for requested version, find the latest version with a stable build if [ "$PAPERMC_URL" == "null" ]; then echo "No stable build for version $MINECRAFT_VERSION, searching for latest version with stable build..." # Get all versions for the project (using the same endpoint structure as the "Getting the latest version" example) # The versions are organized by version group, so we need to extract all versions from all groups # Then sort them properly as semantic versions (newest first) VERSIONS=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT} | \ jq -r '.versions | to_entries[] | .value[]' | \ sort -V -r) # Iterate through versions to find one with a stable build for VERSION in $VERSIONS; do VERSION_BUILDS=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT}/versions/${VERSION}/builds) # Check if this version has a stable build STABLE_URL=$(echo "$VERSION_BUILDS" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // "null"') if [ "$STABLE_URL" != "null" ]; then PAPERMC_URL="$STABLE_URL" FOUND_VERSION="$VERSION" echo "Found stable build for version $VERSION" break fi done fi if [ "$PAPERMC_URL" != "null" ]; then # Download the latest Paper version curl -o server.jar $PAPERMC_URL echo "Download completed (version: $FOUND_VERSION)" else echo "No stable builds available for any version :(" exit 1 fi ``` This is the most common use case for the API. It will download the latest stable build for the given project and Minecraft version. You should always serve & use the stable builds. Experimental builds are prone to error and do not receive support. ## GraphQL API examples Fill also supports a GraphQL API, which can be accessed at `https://fill.papermc.io/graphql`. Fill's GraphQL API uses standard pagination, which you can learn more about [here](https://graphql.org/learn/pagination/). A built-in GraphQL playground is available at https://fill.papermc.io/graphiql?path=/graphql. Common API tools such as Postman will introspect the API and provide a UI for building queries. ### Getting the latest version ```graphql query LatestVersion { project(key: "paper") { key versions(first: 1, orderBy: {direction: DESC}) { edges { node { key } } } } } ```
Example response ```json { "data": { "project": { "key": "paper", "versions": { "edges": [ { "node": { "key": "1.21.11" } } ] } } } } ```
### Getting the latest stable build number ```graphql query LatestStableBuild { project(key: "paper") { key versions(first: 1, orderBy: {direction: DESC}) { edges { node { key builds(filterBy: { channel: STABLE }, first: 1, orderBy: { direction: DESC }) { edges { node { number } } } } } } } } ```
Example response ```json { "data": { "project": { "key": "paper", "versions": { "edges": [ { "node": { "key": "1.21.10", "builds": { "edges": [ { "node": { "number": 48 } } ] } } } ] } } } } ```
### Getting the latest stable build download URL ```graphql query LatestStableBuildDownloadURL { project(key: "paper") { key versions(first: 1, orderBy: {direction: DESC}) { edges { node { key builds(filterBy: { channel: STABLE }, first: 1, orderBy: { direction: DESC }) { edges { node { number download(key: "server:default") { name url checksums { sha256 } size } } } } } } } } } ```
Example response ```json { "data": { "project": { "key": "paper", "versions": { "edges": [ { "node": { "key": "1.21.10", "builds": { "edges": [ { "node": { "number": 48, "download": { "name": "paper-1.21.10-48.jar", "url": "https://fill-data.papermc.io/v1/objects/bfca155b4a6b45644bfc1766f4e02a83c736e45fcc060e8788c71d6e7b3d56f6/paper-1.21.10-48.jar", "checksums": { "sha256": "bfca155b4a6b45644bfc1766f4e02a83c736e45fcc060e8788c71d6e7b3d56f6" }, "size": 54185955 } } } ] } } } ] } } } } ```
--- # Hangar auto-publishing How to automatically publish your plugin to Hangar on commits. If you want to automatically publish your plugin to [Hangar](https://hangar.papermc.io/) on pushes, you can use our [Gradle plugin](https://github.com/HangarMC/hangar-publish-plugin). After you have added the required `hangarPublish` configuration, you can manually publish it by running `./gradlew build publishPluginPublicationToHangar`, or have GitHub Actions automatically publish a version on every commit. ## Prerequisites ### Gradle Your plugin project needs to use Gradle as its build tooling. :::tip If you are using Maven, [switching to a Gradle setup is easy](https://docs.gradle.org/current/userguide/migrating_from_maven.html) and in general recommended due to higher configurability and support for other plugins, such as when [compiling against an unobfuscated Minecraft server](/paper/dev/userdev). The provided examples use Kotlin DSL, but you can also do the same using Groovy. Online converters (even ChatGPT) are able to convert the example code. ::: ### Creating the `Snapshot` release channel The builds script below will publish non-release builds under a `Snapshot` channel. You need to create this channel in your Hangar project's channel page first. ![Create a Snapshot channel](./assets/hangar-create-snapshot-channel.png) ### Adding the `HANGAR_API_TOKEN` repository secret First, you need to create a Hangar API token. Go to your Hangar settings in the profile dropdown and click on "Api keys" on the left. Then, tick the `create_version` permission box, give the key a name and create it. **At the top**, you should be given your secret API token. **Do not share this with anyone**; you will need it in the next step. The GitHub Actions workflow provided uses a repository secret to store your Hangar API key. Go to your GitHub project settings, then click on "Actions" under the Security tab and click the "New repository secret" button. Name the secret `HANGAR_API_TOKEN` and paste the Hangar API token from the previous step into the Secret field. ![Action secrets](./assets/github-secrets-actions.png) ## Project files The files below are simple examples that require little manual changes for you to use, but you can still adapt them depending on your needs. Take a look at the comments and especially the TODOs to figure out what you still need to change. ### `gradle.properties` Create a `gradle.properties` file in your project root directory if it does not already exist. In there, you define the platform versions your plugin is compatible with. Simply remove the platforms you don't need and put in the correct versions. Hangar allows version ranges (such as `1.19-1.20.2`) and wildcards (such as `1.20.x`). ```properties # Specify the platform versions for Paper and Velocity. # Hangar also allows version ranges (such as 1.19-1.20.2) and wildcards (such as 1.20.x). # TODO: Remove the platforms you don't need and put in the correct versions. paperVersion=1.12.2, 1.16.5, 1.19-1.20.2 velocityVersion=3.2 waterfallVersion=1.20 ``` ### `build.gradle.kts` In the plugins block of your `build.gradle.kts` build script, add the publish plugin: ```kotlin title="build.gradle.kts" plugins { id("io.papermc.hangar-publish-plugin") version "0.1.2" } ``` Then you simply need to add the `hangarPublish` configuration block and make sure you do the following: - If your plugin is not a Paper plugin, or supports Velocity/Waterfall as well, copy the register block with a different platform and change the property used instead of `paperVersion` (as declared in the `gradle.properties` file). - Insert the correct project namespace - Insert your plugin dependencies, if any - You need to have the `HANGAR_API_TOKEN` repository secret set up if you are using the Actions file below, otherwise add the API key in some other way. ```kotlin import io.papermc.hangarpublishplugin.model.Platforms // ... hangarPublish { publications.register("plugin") { version.set(project.version as String) channel.set("Snapshot") // We're using the 'Snapshot' channel // TODO: Edit the project name to match your Hangar project id.set("hangar-project") apiKey.set(System.getenv("HANGAR_API_TOKEN")) platforms { // TODO: Use the correct platform(s) for your plugin register(Platforms.PAPER) { // TODO: If you're using ShadowJar, replace the jar lines with the appropriate task: // jar.set(tasks.shadowJar.flatMap { it.archiveFile }) // Set the JAR file to upload jar.set(tasks.jar.flatMap { it.archiveFile }) // Set platform versions from gradle.properties file val versions: List = (property("paperVersion") as String) .split(",") .map { it.trim() } platformVersions.set(versions) // TODO: Configure your plugin dependencies, if any dependencies { // Example for a dependency found on Hangar hangar("Maintenance") { required.set(false) } // Example for an external dependency url("Debuggery", "https://github.com/PaperMC/Debuggery") { required.set(true) } } } } } } ``` ## GitHub Actions workflow You don't necessarily need to publish via GitHub Actions, but it is an easy way to do so. If you want to use it, create a `publish.yml` file in the `.github/workflows` directory of your project root folder and make sure you [add the repository secret](#adding-the-hangar_api_token-repository-secret). You can add and remove branches to be published by editing the `branches` section. ```yaml name: Publish to Hangar on: push: branches: # Add any additional branches you want to automatically publish from - main # Assuming your main branch is called 'main' jobs: publish: # TODO: Optional, make sure the task only runs on pushes to your repository and doesn't fail on forks. Uncomment the line below and put the repo owner into the quotes # if: github.repository_owner == '' runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v7 - name: Set up JDK 25 uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 25 - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - name: Publish env: # Make sure you have added a repository secret in the repository's settings HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }} run: ./gradlew build publishPluginPublicationToHangar --stacktrace ``` ## Optional: Handling multiple channels and an automatic changelog With the following code, any version that contains a hyphen (`-`) will be published under the `Snapshot` channel (that you need to create on Hangar) and the others on the `Release` channel. By editing the `channel.set(...)` line, you can change this to any channel you would like. For example, you could further split builds depending on the branch you are currently on into `Alpha` builds. :::caution Make sure you never publish ongoing development builds to the `Release` channel. ::: ```kotlin import java.io.ByteArrayOutputStream // ... // Helper methods fun executeGitCommand(vararg command: String): String { val byteOut = ByteArrayOutputStream() exec { commandLine = listOf("git", *command) standardOutput = byteOut } return byteOut.toString(Charsets.UTF_8.name()).trim() } fun latestCommitMessage(): String { return executeGitCommand("log", "-1", "--pretty=%B") } val versionString: String = version as String val isRelease: Boolean = !versionString.contains('-') val suffixedVersion: String = if (isRelease) { versionString } else { // Give the version a unique name by using the GitHub Actions run number versionString + "+" + System.getenv("GITHUB_RUN_NUMBER") } // Use the commit description for the changelog val changelogContent: String = latestCommitMessage() // If you would like to publish releases with their proper changelogs manually, simply add an if statement with the `isRelease` variable here. hangarPublish { publications.register("plugin") { version.set(suffixedVersion) channel.set(if (isRelease) "Release" else "Snapshot") changelog.set(changelogContent) // ... (see above) } } ``` ## Optional: Updating the resource page A notable part of publishing a new version might be updating the resource page (e.g. the plugin's home page) with new content from your plugin's repository. In this example, we're using a README file, but you can use any text you want, as long as it is in Markdown format. ```kotlin val pageContent = project.file("README.md").readText() hangarPublish { publications.register("plugin") { // ... (see above) pages.resourcePage(pageContent) } } ``` You can invoke the `syncPluginPublicationMainResourcePagePageToHangar` task to update the resource page on Hangar. This will not publish a new version, but simply update the page content on Hangar. If you're using a GitHub Actions workflow, you should also add the task invocation to the `Publish` step in your workflow: ```yaml - name: Publish # ... run: ./gradlew build publishPluginPublicationToHangar syncPluginPublicationMainResourcePagePageToHangar --stacktrace ``` --- # Installing or updating Java How to install or update to Java 25 on Linux (apt/rpm), Windows, or Mac. Installing Java is a critical first step to using or developing plugins for Paper and Velocity. This guide will walk you through the recommended installation steps for most major platforms. :::caution[Do not use headless variants of Java!] There are `headless` variants of Java which usually have a suffix of `-headless` in their package name. Those variants miss required dependencies for Paper. Therefore, using them is not recommended. ::: :::tip This guide focuses on Amazon's Corretto OpenJDK distribution. This is because it offers the best installation experience on the most platforms. Corretto is, however, not the only OpenJDK vendor to choose from. Many alternatives exist such as [Eclipse Adoptium](https://adoptium.net/), [Microsoft](https://www.microsoft.com/openjdk) and [Azul Zulu](https://www.azul.com/downloads/?package=jdk). Note that the JDK Oracle distributes, while functionally identical, is **not** recommended due to an extremely unfriendly installer and previous hostile licensing. ::: ## Linux ### Ubuntu/Debian Installing Java 25 on Debian-based Linux distributions is very simple. First, ensure your system has all required tools to successfully install Java. ```bash sudo apt-get update sudo apt-get install ca-certificates apt-transport-https gnupg wget ``` Second, import the Amazon Corretto public key and apt repository. ```bash wget -O - https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg && \ echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | sudo tee /etc/apt/sources.list.d/corretto.list ``` Then, install Java 25 and other dependencies using the following command: ```bash sudo apt-get update sudo apt-get install -y java-25-amazon-corretto-jdk libxi6 libxtst6 libxrender1 ``` Proceed to [verify your installation](#verifying-installation). ### RPM-based To install Java 25 on CentOS, RHEL, Fedora, openSUSE, SLES, or any other RPM-based Linux distribution, execute the following commands depending on your package manager. Once you have finished, precede to [verify your installation](#verifying-installation). #### DNF DNF is used on Fedora, CentOS/RHEL 7+, and related distributions. ```bash sudo rpm --import https://yum.corretto.aws/corretto.key sudo curl -Lo /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo sudo dnf -y install java-25-amazon-corretto-devel ``` #### Zypper Zypper is used on openSUSE, SLES, and related distributions. ```bash sudo zypper addrepo https://yum.corretto.aws/corretto.repo sudo zypper refresh sudo zypper install java-25-amazon-corretto-devel ``` #### YUM YUM is used on older releases of CentOS/RHEL, and excessively old releases of Fedora. ```bash sudo rpm --import https://yum.corretto.aws/corretto.key sudo curl -Lo /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo sudo yum -y install java-25-amazon-corretto-devel ``` ## Windows 10 & 11 If you're on Windows 10 or 11, installing Java is just like installing any other program. Download the Amazon Corretto installer from [their website](https://corretto.aws/downloads/latest/amazon-corretto-25-x64-windows-jdk.msi). Once you have run the installer, it is safe to click "next" through the whole process. No additional bloatware or toolbars will be installed, and all the required features are enabled out of the box. Now, open a command prompt and precede to [verify your installation](#verifying-installation). ## macOS/OS X If you're on macOS, the best way to manage Java installations is with a tool called [Homebrew](https://brew.sh). Follow the instructions on their homepage to install it. Then, in your terminal run the following command: ```bash brew install openjdk@25 ``` Once this command has completed, continue to [verify your installation](#verifying-installation). ## Pterodactyl :::note On Pterodactyl versions lower than `1.2.0`, an administrator account is required to change the Java version. These instructions will not apply. ::: If you have started a Paper server with an incorrect Java version, Pterodactyl will automatically prompt you to update like this: ![Pterodactyl Automatic Prompt](./assets/pterodactyl-prompt.png) If this does not show up for you, the Java version can be manually changed. Navigate to the "Startup" tab of your server, select `Java 25` from the "Docker Image" dropdown as shown in the image below. ![Pterodactyl Manual Java Version Change](./assets/pterodactyl-manual.png) :::note If you don't see `Java 25` in the dropdown, an administrator account is required to update the Paper egg. ::: ## Pufferpanel (Docker) :::note You will need **Admin** permissions on the panel to change your Java version. ::: At the server you want to change the Java version for, go to the **Admin** tab and open **Edit Server Definition**. In the **Environment** tab, the **Docker Image** field allows you edit the docker image used. ![Pufferpanel Environment Tab](./assets/pufferpanel-environment.png) In this case, you can simply replace the version (the number after the ":") with `25`. You can find alternative JDK docker images on [Docker Hub](https://hub.docker.com/search?badges=official). ## Verifying installation :::note This section does not apply when running on Pterodactyl or Pufferpanel. ::: Now that you have installed Java 25, run this command in your terminal to ensure the process was successful. ```bash java -version ``` The output should be similar to this. The important parts to look out for is that it starts with `openjdk 25` and contains `64-Bit` in the last line. If the output you get is similar to `java: command not found`, try creating a new terminal session. ``` openjdk version "25" 2025-09-16 OpenJDK Runtime Environment (build 25+36-3489) OpenJDK 64-Bit Server VM (build 25+36-3489, mixed mode, sharing) ``` If your installation has failed, do not hesitate to reach out in the `#paper-help` channel of our [Discord](https://discord.gg/papermc) for support. --- # Tools import PageCards from "/src/components/PageCards.astro"; Collection of useful tools for server admins or plugin developers. --- # Diff viewer Multi-file rich diff viewer for GitHub and diff/patch files. Multi-file rich diff viewer with support for GitHub commits, PRs, and comparisons, as well as diff and patch files. The GitHub diff viewer is a great tool for most use cases, but it has a variety of limitations, especially when dealing with large diffs where the viewer will perform poorly or even crash. The diffs.dev Diff Viewer aims to solve this problem while also providing a variety of additional features useful for PaperMC and generally, such as special handling for second-order diffs. We have also built a browser extension for Firefox and Chrome to streamline opening GitHub diffs in the viewer. - https://diffs.dev - The diff viewer itself - [Chrome Extension](https://chromewebstore.google.com/detail/patch-roulette/feaaoepdocmiibjilhoahgldkaajfnhb) - [Firefox Add-on](https://addons.mozilla.org/en-US/firefox/addon/patch-roulette/) - https://github.com/PaperMC/diff-viewer - GitHub repository --- # Item command converter A tool to update commands from the old NBT-based item format to item components. import ItemCommandConverter from "/src/components/tools/ItemCommandConverter.svelte"; In 1.20.5, Mojang has moved from unstructured NBT to the so-called item components. While Minecraft will automatically update all existing items for you, only Paper will update commands and text components containing items where possible. This tool helps you convert old item commands to the new format externally. Note that you need to select the *Entity Argument* mode if you want to convert entity data outside of the summon command, as we require the entity type to convert data correctly.
--- # MiniMessage web editor A web-based editor for creating and previewing MiniMessage-formatted text. The MiniMessage Web Editor is a web-based editor for creating and previewing MiniMessage-formatted text. It is hosted here: [MiniMessage Web Editor](https://webui.advntr.dev/). ![Image of adventure webui](./assets/adventure-webui.png) --- # Start script generator A start script generator for PaperMC projects. import StartScriptGenerator from "/src/components/tools/StartScriptGenerator.svelte";
What flags do I choose? If you're running on a modern JVM, i.e. 17, 21 or higher, try with no flags first. Modern JVMs are very good at handling various applications with default GC settings. If you have GC problems or are running an older version of Minecraft/an older version of Java, try [Aikar's flags](/paper/aikars-flags), which are optimized specifically for Minecraft. If you're generating a script for starting [Velocity](https://papermc.io/software/velocity), choose "Velocity".

--- # Paper Documentation import { CardGrid, LinkCard } from "@astrojs/starlight/components"; Paper is a Minecraft: Java Edition game server, designed to greatly improve performance and offer more advanced features and API. --- # Adding plugins Plugins are the most powerful way to extend the functionality of Paper beyond the configuration files. Plugins are the most powerful way to extend the functionality of Paper beyond the configuration files. Functionality added by plugins can range from making milk restore hunger or dead bushes grow, to adding entirely new and original game modes or items. :::danger[Malicious Plugins] Ensure you fully trust the source of any plugin before installing it. Plugins are given **full and unrestricted** access to not only your server but also the machine that it runs on. Because of this, it is imperative that plugins only be installed from trusted sources. Be careful! ::: ## Finding plugins Before installing a plugin, you'll need to find what you want to install. The best place to find plugins is [Hangar](https://hangar.papermc.io), Paper's plugin repository, but you can also find plugins on [Modrinth](https://modrinth.com/discover/plugins?g=categories:paper) or [BukkitDev](https://dev.bukkit.org/bukkit-plugins). Many plugins may release on [GitHub](https://github.com). Instead of checking each site directly, you can also use a search engine. Searching for the function you desire, followed by `Paper plugin` will often yield good results. ## Installing plugins 1. Once you've found the plugin you'd like to install, download it. Ensure the file you have downloaded ends in `.jar`. Some plugins also distribute as `.zip` files, in which case you will need to extract the file and locate the `.jar` for your platform, often labeled `bukkit` or `paper`. 2. Once you have the plugin downloaded locally, locate the `plugins` folder from the root directory of your Paper server. 3. Drag and drop the plugin file (`.jar`) into the `plugins` folder. If you are using a shared hosting service, you may need to use their web panel or SFTP to upload the plugin; however, the procedure will be the same. 4. Restart your server. The plugin should load. 5. Check your work. Once the server has finished loading, run the `/plugins` command in-game or type `plugins` into the console. You should see your freshly installed plugin listed in green. If it is not listed or is colored red, continue to [troubleshooting](#troubleshooting). A plugin listed in red means that it is not currently enabled. For a freshly installed plugin, this often means that the plugin failed to load. ## Troubleshooting ### Check your logs The first step to troubleshooting installing plugins is to check the log of your server. Your server's most recent logs will be stored to the `logs/latest.log` file. You may need to scroll near the beginning of this file to see when plugins were loaded. #### Missing dependencies If you see something like this: ```log [00:00:00] [Server thread/WARN] Could not load 'plugins/MyAwesomePlugin-1.0.0.jar' in folder 'plugins' [00:00:00] [Server thread/WARN] org.bukkit.plugin.UnknownDependencyException: Unknown/missing dependency plugins: [Vault]. Please download and install these plugins to run 'MyAwesomePlugin'. ``` This means that the plugin you tried to install is missing a dependency. A dependency, in this case, is another plugin that you must install for the first to function. While you will get a big scary error, the important line to look at is: ```log [00:00:00] [Server thread/WARN] Unknown/missing dependency plugins: [Vault]. Please download and install these plugins to run 'MyAwesomePlugin'. ``` This is telling you that in order to load `MyAwesomePlugin`, you must first install `Vault`. #### Invalid `plugin.yml` If you see something closer to this: ```log [00:00:00] [Server thread/WARN] Could not load 'plugins/MyAwesomePlugin-1.0.0.jar' in folder 'plugins' [00:00:00] [Server thread/WARN] org.bukkit.plugin.InvalidDescriptionException: Invalid plugin.yml ``` This means that what you have downloaded isn't a valid Paper plugin. This is generally caused by one of the following: 1. The plugin you downloaded isn't a plugin at all, but instead a mod for Forge, Fabric, or similar. These will not run on Paper. 2. The plugin failed to download completely. Especially when using tools such as `curl` or `wget`, you can easily download error pages rather than the plugin you intended. This may also be caused by a network issue. Attempt to download the plugin again. If you are using FTP (not SFTP or a web panel) to upload your plugin to a shared hosting service, ensure your FTP client is in `binary` and not `ASCII` mode. Consult the documentation for your FTP client for details. #### Ambiguous plugin name If you see something like this: ```log [00:00:00] [Server thread/WARN] Ambiguous plugin name `Essentials' for files `plugins/EssentialsX-2.19.4.jar' and `plugins/Essentialsx-2.20.0-dev.jar' in `plugins' ``` This means you have two plugins with the same name, which is not supported. In this case, two versions of EssentialsX are installed. Both the release `2.19.4`, and a development build of `2.20.0`. Ensure you only have one version of each plugin installed at one time. Delete the older version of the duplicate plugin, and restart your server. To prevent accidentally installing two versions of one plugin while updating, you can use the `update` folder as described in the [Update Guide](/paper/updating#step-2-update-plugins). #### Something else If you see an error, but it isn't similar to one of the above, attempt to read it yourself. While the full error may be large and scary, you likely only have to read the first one or two lines to understand what is going on. If you're not sure, do not hesitate to reach out for support on our [Discord](https://discord.gg/papermc) in the `#paper-help` channel. ### If nothing is logged If nothing is logged, your server is likely not attempting to load any plugins. The conditions needed for the server to load a plugin are as follows: 1. The file is at the root of the `plugins` folder, relative to its working directory. This is usually the same folder as the server JAR file. **Subdirectories of the `plugins` folder will not be checked.** All plugins must be in the root folder. 2. The file ends in `.jar`. If your plugin does not end in `.jar`, what you have downloaded may not be a plugin. Note that some plugins distribute multiple JARs as `.zip` files. If this is the case, you have to extract them before installing the plugin. If both of these are true, and you still see no logs, please reach out for support on our [Discord](https://discord.gg/papermc) server in the `#paper-help` channel. We will be happy to assist you. --- # Administration import PageCards from "/src/components/PageCards.astro"; Welcome to the Paper administration guide! This guide includes information and tutorials regarding the administration of a Paper server. #### Getting started #### How-to guides #### Reference #### Miscellaneous --- # Getting started import PageCards from "/src/components/PageCards.astro"; Guides for getting started with running a Paper server. --- # How-to guides import PageCards from "/src/components/PageCards.astro"; How-to guides for setting up specific features of Paper and managing the server. --- # Miscellaneous import PageCards from "/src/components/PageCards.astro"; Miscellaneous documentation pertaining to Paper. --- # Reference import PageCards from "/src/components/PageCards.astro"; Reference of Paper's configuration options, system properties and other features. --- # Aikar's flags Aikar's flags are a set of JVM flags designed to improve the performance of your Paper server. ## Recommended JVM startup flags :::caution[Script Generator] **This page only serves as an explanation page.** If you want to generate a start script, please visit our **[Script Generator](/misc/tools/start-script-gen)**. ::: ```bash java -Xms10G -Xmx10G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true -jar paper.jar --nogui ``` :::danger[Do not allocate all of your available memory on a shared host!] When setting the `Xms` and `Xmx` values, if your host says you have 8GB of memory, **do not use 8GB**! Minecraft (and Java) needs additional memory on top of that `Xmx` parameter. It is recommended to **reduce your `Xmx` and `Xms` by about 1000-1500MB** to avoid running out of memory or `OOMKiller` killing your server. This also leaves room for the operating system to use memory too. Do you have 8GB of memory? Use 6500MB for safety. _But you may also ask your host if they will cover this overhead for you and give you 9500M instead. Some hosts will! Just ask._ ::: ## Recommended memory **We recommend using at least 6-10GB**, no matter how few players! If you can't afford 10GB of memory, give as much as you can, but ensure you leave the operating system some memory too. G1GC operates better with more memory. However, more memory does not mean better performance above a certain point. Eventually you will hit a point of diminishing returns. Going out and getting 32GB of RAM for a server will only waste your money with minimal returns. ## Java GC logging Are you having old gen issues with these flags? Add the following flags based on your Java version to enable GC logging: **Java 8-10** ```bash -Xloggc:gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=1M ``` **Java 11+** ```bash -Xlog:gc*:logs/gc.log:time,uptime:filecount=5,filesize=1M ``` GC logging does not hurt your performance and can be left on at all times. The files will not take up much space (5MB) ## Technical explanation of the flags 1. **-Xms matching -Xmx - why:** You should never run your server with the case that `Xmx` can run the system completely out of memory. Your server should always be expected to use the entire `Xmx`! You should then ensure the OS has extra memory on top of that `Xmx` for non-Minecraft/OS level things. Therefore, you should never run Minecraft with `Xmx` settings you can't support if Java uses it all. Now, that means **if `Xms` is lower than `Xmx` you have unused memory**! Unused memory is wasted memory. G1 operates better with the more memory it's given. G1 adaptively chooses how much memory to give to each region to optimize pause time. If you have more memory than it needs to reach an optimal pause time, G1 will simply push that extra into the old generation, and it will not hurt you. The fundamental idea of improving GC behavior is to ensure short-lived objects die young and never get promoted. With the more memory G1 has, the better assurance you will get that objects are not getting prematurely promoted to the old generation. G1 operates differently than previous collectors and is able to handle larger heaps more efficiently. If it does not need the memory given to it, it will not use it. The entire engine operates differently and does not suffer from too large of heaps, and this is industry-wide accepted information that under G1 to keep Xms and Xmx the same! 2. **UnlockExperimentalVMOptions:** needed for some the below options 3. **G1NewSizePercent:** These are the important ones. You now can specify percentages of an overall desired range for the new generation. With these settings, we tell G1 to not use its default 5% for new gen, and instead give it 40%! **Minecraft has an extremely high memory allocation rate**, ranging to at least 800MB/second on a 30 player server! And this is mostly short-lived objects (Block Position). Now, this means Minecraft **really** needs more focus on new gen to be able to even support this allocation rate. If your new gen is too small, you will be running new gen collections 1-2+ times per second, which is awful. You will have so many pauses that TPS has risk of suffering, and the server will not be able to keep up with the cost of GCs. Then combine the fact that objects will now promote faster, resulting in your old gen growing faster. Given more new gen, we are able to slow down the intervals of young gen collections, resulting in more time for short-lived objects to die young and overall more efficient GC behavior. 4. **G1MixedGCLiveThresholdPercent:** Controls when to include regions in mixed GCs in the young GC collection, keeping old gen tidy without doing a normal old gen GC collection. When your memory is less than this percent, old gen won't even be included in 'mixed' collections. Mixed are not as heavy as a full old collection, so having small incremental cleanups of old keeps memory usage light. Default is 65 to 85 depending on the Java version, we are setting that to 90 to ensure we reclaim garbage in old gen as fast as possible to retain as much free regions as we can. 5. **G1ReservePercent=20:** Minecraft memory allocation rate in up-to-date versions is really insane. We run the risk of a dreaded "to-space exhaustion" not having enough memory free to move data around. This ensures more memory is waiting to be used for this operation. Default is 10, so we are giving another 10 to it. 6. **MaxTenuringThreshold=1:** Minecraft has a really high allocation rate of memory. Of that memory, most is reclaimed in the eden generation. However, transient data will overflow into survivor. Initially played with completely removing survivor and had decent results, but does result in transient data making its way to old which is not good. Max Tenuring 1 ensures that we do not promote transient data to old generation, but anything that survives 2 passes of GC is just going to be assumed as longer-lived. Doing this greatly reduces pause times in young collections as copying data up to 15 times in survivor space for a tenured object really takes a lot of time for actually old memory. Ideally the GC engine would track average age for objects instead and tenure out data faster, but that is not how it works. Considering average GC rate is 10s to the upwards of minutes per young collection, this does not result in any 'garbage' being promoted, and just delays longer lived memory to be collected in mixed GCs. 7. **SurvivorRatio=32:** Because we drastically reduced MaxTenuringThreshold, we will be reducing use of survivor space drastically. This frees up more regions to be used by eden instead. 8. **AlwaysPreTouch:** AlwaysPreTouch gets the memory setup and reserved at process start ensuring it is contiguous, improving the efficiency of it more. This improves the operating systems memory access speed. Mandatory to use Transparent Huge Pages 9. **+DisableExplicitGC:** Many plugins think they know how to control memory, and try to invoke garbage collection. Plugins that do this trigger a full garbage collection, triggering a massive lag spike. This flag disables plugins from trying to do this, protecting you from their bad code. 10. **MaxGCPauseMillis=200:** This setting controls how much memory is used in between the minimum and maximum ranges specified for your new generation. This is a "goal" for how long you want your server to pause for collections. 200 is aiming for at most loss of 4 ticks. This will result in a short TPS drop, however the server can make up for this drop instantly, meaning it will have no meaningful impact on your TPS. 200ms is lower than players can recognize. In testing, having this value constrained to an even lower number results in G1 not recollecting memory fast enough and potentially running out of old gen triggering a full collection. Just because this number is 200 does not mean every collection will be 200. It means it can use up to 200 if it really needs it, and we need to let it do its job when there is memory to collect. 11. **+ParallelRefProcEnabled:** Optimizes the GC process to use multiple threads for weak reference checking. Not sure why this isn't default... 12. **G1RSetUpdatingPauseTimePercent=5:** Default is 10% of time spent during pause updating RSets, reduce this to 5% to make more of it concurrent to reduce pause durations. 13. **G1MixedGCCountTarget=4:** Default is 8. Because we are aiming to collect slower, with less old gen usage, try to reclaim old gen memory faster to avoid running out of old. 14. **G1HeapRegionSize=8M+:** Default is auto calculated. Super important for Minecraft, especially 1.15, as with low memory situations, the default calculation will in most times be too low. Any memory allocation half of this size (4MB) will be treated as "Humongous" and promote straight to old generation and is harder to free. If you allow Java to use the default, you will be destroyed with a significant chunk of your memory getting treated as Humongous. 15. **+PerfDisableSharedMem:** Causes GC to write to file system which can cause major latency if disk IO is high - see https://www.evanjones.ca/jvm-mmap-pause.html ### Transparent huge pages Controversial feature but may be usable if you can not configure your host for real HugeTLBFS. Try adding `-XX:+UseTransparentHugePages` but it's extremely important you also have `AlwaysPreTouch` set. Otherwise, THP will likely hurt you. We have not measured how THP works for Minecraft or its impact with `AlwaysPreTouch`, so this section is for the advanced users who want to experiment. --- # Configuring Anti-Xray Paper ships an obfuscation-based Anti-Xray system by default. Learn how to configure it here. > Originally written and maintained by [stonar96](https://github.com/stonar96). Paper includes an obfuscation-based Anti-Xray with three modes, configurable on a per world basis. :::note[Per World Configuration] If you aren't already familiar with per world configuration, please take a moment to familiarize yourself with the [Configuration Guide](/paper/reference/configuration). ::: This guide is a step-by-step walk-through for configuring Anti-Xray. For reference documentation, refer to the Anti-Xray section of the [Per-World Configuration Reference](/paper/reference/world-configuration#anticheat_anti_xray). Anti-Xray has three different modes. `engine-mode: 1` replaces specified blocks (`hidden-blocks`) with other "fake" blocks, `stone` (`deepslate` at y < 0), `netherrack`, or `end_stone` based on the dimension. In contrast, `engine-mode: 2` will replace both `hidden-blocks` and `replacement-blocks` with randomly generated `hidden-blocks`. `engine-mode: 3` works similarly to `engine-mode: 2`, but instead of randomizing every block, it randomizes the block for each layer of a chunk. The following images[^1] show how each mode will look for a player using Xray with the recommended configuration in both the overworld and nether. [^1]: Image design by `Oberfail`, initially posted in the [PaperMC Discord](https://discord.gg/papermc). {/* Seed: -7943468717341609647 # Overworld: /tp @p -581.976 67.85076 -4924.106 47 36 # Nether: /tp @p 789.437 117.38012 -319.064 -137.4 28 */} ![Overworld Anti-Xray Comparison](assets/anti-xray-overworld.png) ![Nether Anti-Xray Comparison](assets/anti-xray-nether.png) Especially on the client side, `engine-mode: 1` is much less computationally intensive, while `engine-mode: 2` may better prevent Xray. With `engine-mode: 1`, only ores that are entirely covered by solid blocks will be hidden. Ores exposed to air in caves or water from a lake will not be hidden. With `engine-mode: 2`, fake ores obstruct the view of real blocks. If `air` is added to `hidden-blocks`, `engine-mode: 2` will effectively hide all ores, even those exposed to air. `engine-mode: 3` can reduce network load when joining by a factor of ~2 and helps with chunk packet compression. :::caution[Anti-Xray Bypasses] **Range Extension**: While Anti-Xray alone will prevent the majority of users from Xraying on your server, it is not by any means infallible. Because of how Anti-Xray is (and has to be) implemented, it is possible to, on a default server, extend the range of real ores you can see by a not insignificant amount. This can be mitigated by any competent anti-cheat plugin; however, this is not included out of the box. **Seed Reversing**: Another attack vector is the deterministic nature of Minecraft's world generation. If the client is able to obtain the world seed, it is able to know the real location of every generated ore, completely bypassing Anti-Xray. This can be partially worked around by making it harder for the client to reverse the world seed with the [`feature-seeds` configuration](/paper/reference/world-configuration#feature_seeds), in conjunction with the structure seed options in `spigot.yml`. Note that this is not a complete solution, and it may still be possible for a client to obtain the server's world seed. Using a different seed for each world may also be beneficial. **Ores Exposed to Air**: In `engine-mode: 1`, `engine-mode: 2` and `engine-mode: 3`, it is possible for a client to view ores that are exposed to air. This can be mitigated in `engine-mode: 2` and `engine-mode: 3` by adding `air` to the `hidden-blocks` list. However, doing this may cause client performance issues (FPS drops) for some players. ::: ## Recommended configuration The recommended configuration for `engine-mode: 1`, `engine-mode: 2` and `engine-mode: 3` is as follows: :::tip[Spacing] YAML cares about whitespace! The example configuration below is already formatted correctly. Ensure formatting and indentation remains unchanged by using the "copy" button in the top right of each example. Especially ensure that no tabulators are accidentally inserted. Check your editor's options for using spaces instead of tabulators for indentation. If your configuration file already contains other important changes, it is recommended to make a backup before editing it. ::: ### `engine-mode: 1`
Default World Configuration Replace the existing `anticheat.anti-xray` block in `paper-world-defaults.yml` with the following: ```yaml title="paper-world-defaults.yml" anticheat: anti-xray: enabled: true engine-mode: 1 hidden-blocks: # There's no chance to hide dungeon chests as they are entirely surrounded by air, but buried treasures will be hidden. - chest - coal_ore - deepslate_coal_ore - copper_ore - deepslate_copper_ore - raw_copper_block - diamond_ore - deepslate_diamond_ore - emerald_ore - deepslate_emerald_ore - gold_ore - deepslate_gold_ore - iron_ore - deepslate_iron_ore - raw_iron_block - lapis_ore - deepslate_lapis_ore - redstone_ore - deepslate_redstone_ore lava-obscures: false # As of 1.18 some ores are generated much higher. # Please adjust the max-block-height setting at your own discretion. # https://minecraft.wiki/w/Ore might be helpful. max-block-height: 64 # The replacement-blocks list is not used in engine-mode: 1. Changing this will have no effect. replacement-blocks: [] update-radius: 2 use-permission: false ```
Nether Configuration Copy and paste into your `paper-world.yml` within your nether world folder. See the [Configuration Guide](/paper/reference/configuration) for more information. ```yml title="world/dimensions/minecraft/the_nether/paper-world.yml" anticheat: anti-xray: enabled: true engine-mode: 1 hidden-blocks: - ancient_debris - nether_gold_ore - nether_quartz_ore lava-obscures: false max-block-height: 128 # The replacement-blocks list is not used in engine-mode: 1. Changing this will have no effect. replacement-blocks: [] update-radius: 2 use-permission: false ```
End Configuration Copy and paste into your `paper-world.yml` within your end world folder. See the [Configuration Guide](/paper/reference/configuration) for more information. ```yml title="world/dimensions/minecraft/the_end/paper-world.yml" anticheat: anti-xray: enabled: false ```
### `engine-mode: 2`
Default World Configuration Replace the existing `anticheat.anti-xray` block in `paper-world-defaults.yml` with the following: ```yaml title="paper-world-defaults.yml" anticheat: anti-xray: enabled: true engine-mode: 2 hidden-blocks: # You can add air here such that many holes are generated. # This works well against cave finders but may cause client FPS drops for all players. - air - copper_ore - deepslate_copper_ore - raw_copper_block - diamond_ore - deepslate_diamond_ore - gold_ore - deepslate_gold_ore - iron_ore - deepslate_iron_ore - raw_iron_block - lapis_ore - deepslate_lapis_ore - redstone_ore - deepslate_redstone_ore lava-obscures: false # As of 1.18 some ores are generated much higher. # Please adjust the max-block-height setting at your own discretion. # https://minecraft.wiki/w/Ore might be helpful. max-block-height: 64 replacement-blocks: # Chest is a tile entity and can't be added to hidden-blocks in engine-mode: 2. # But adding chest here will hide buried treasures, if max-block-height is increased. - chest - amethyst_block - andesite - budding_amethyst - calcite - coal_ore - deepslate_coal_ore - deepslate - diorite - dirt - emerald_ore - deepslate_emerald_ore - granite - gravel - oak_planks - smooth_basalt - stone - tuff update-radius: 2 use-permission: false ```
Nether Configuration Copy and paste into your `paper-world.yml` within your nether world folder. See the [Configuration Guide](/paper/reference/configuration) for more information. ```yml title="world/dimensions/minecraft/the_nether/paper-world.yml" anticheat: anti-xray: enabled: true engine-mode: 2 hidden-blocks: # See note about air and possible client performance issues above. - air - ancient_debris - bone_block - glowstone - magma_block - nether_bricks - nether_gold_ore - nether_quartz_ore - polished_blackstone_bricks lava-obscures: false max-block-height: 128 replacement-blocks: - basalt - blackstone - gravel - netherrack - soul_sand - soul_soil update-radius: 2 use-permission: false ```
End Configuration Copy and paste into your `paper-world.yml` within your end world folder. See the [Configuration Guide](/paper/reference/configuration) for more information. ```yml title="world/dimensions/minecraft/the_end/paper-world.yml" anticheat: anti-xray: enabled: false ```
### `engine-mode: 3`
Default World Configuration Replace the existing `anticheat.anti-xray` block in `paper-world-defaults.yml` with the following: ```yaml title="paper-world-defaults.yml" anticheat: anti-xray: enabled: true engine-mode: 3 hidden-blocks: # You can add air here such that many holes are generated. # This works well against cave finders but may cause client FPS drops for all players. - air - copper_ore - deepslate_copper_ore - raw_copper_block - diamond_ore - deepslate_diamond_ore - gold_ore - deepslate_gold_ore - iron_ore - deepslate_iron_ore - raw_iron_block - lapis_ore - deepslate_lapis_ore - redstone_ore - deepslate_redstone_ore lava-obscures: false # As of 1.18 some ores are generated much higher. # Please adjust the max-block-height setting at your own discretion. # https://minecraft.wiki/w/Ore might be helpful. max-block-height: 64 replacement-blocks: # Chest is a tile entity and can't be added to hidden-blocks in engine-mode: 2. # But adding chest here will hide buried treasures, if max-block-height is increased. - chest - amethyst_block - andesite - budding_amethyst - calcite - coal_ore - deepslate_coal_ore - deepslate - diorite - dirt - emerald_ore - deepslate_emerald_ore - granite - gravel - oak_planks - smooth_basalt - stone - tuff update-radius: 2 use-permission: false ```
Nether Configuration Copy and paste into your `paper-world.yml` within your nether world folder. See the [Configuration Guide](/paper/reference/configuration) for more information. ```yml title="world/dimensions/minecraft/the_nether/paper-world.yml" anticheat: anti-xray: enabled: true engine-mode: 3 hidden-blocks: # See note about air and possible client performance issues above. - air - ancient_debris - bone_block - glowstone - magma_block - nether_bricks - nether_gold_ore - nether_quartz_ore - polished_blackstone_bricks lava-obscures: false max-block-height: 128 replacement-blocks: - basalt - blackstone - gravel - netherrack - soul_sand - soul_soil update-radius: 2 use-permission: false ```
End Configuration Copy and paste into your `paper-world.yml` within your end world folder. See the [Configuration Guide](/paper/reference/configuration) for more information. ```yml title="world/dimensions/minecraft/the_end/paper-world.yml" anticheat: anti-xray: enabled: false ```
## FAQ, common pitfalls and support
I can still see (some) ores / use X-ray As described above, there are several reasons why you might still see (some) ores even though you have enabled Anti-Xray: * The ores are above the configured `max-block-height` value. * Anti-Xray cannot hide ores exposed to air or other transparent blocks (in caves for example). In principle this is also the case for `engine-mode: 2` and `engine-mode: 3`, however, usually the fake ores obstruct the view of real blocks. Hiding those exposed ores too requires additional plugins. * The `use-permission` option is enabled and you have the Anti-Xray bypass permission (`paper.antixray.bypass`) or you have operator status. * The block type is missing in the configured block lists. This can be the result of using an outdated configuration file.
I have added fake blocks but X-ray doesn't show them If you use `engine-mode: 2` or `engine-mode: 3` and you have added fake blocks to the `hidden-blocks` list but you can't see them in-game using X-ray, this can have the following reasons: * The added block types are tile entities. Anti-Xray can hide (replace) tile entities (such as chests), provided that they are not exposed to air or other transparent blocks. However, Anti-Xray can't place tile entities as fake blocks into the chunk. * The block is disabled in your client's X-ray mod or not shown by your X-ray resource pack.
It doesn't work below y = 0 or in certain other places. * Your configuration file is probably outdated and missing important blocks in the `replacement-blocks` list, such as `deepslate` or biome-specific blocks, such as `basalt`. You might also want to check if the `hidden-blocks` list includes all important ores and their `deepslate` variants. * If it doesn't work above a certain y-level, check your `max-block-height` setting.
It still doesn't work, further troubleshooting * Make sure to always restart your server after making changes to the Anti-Xray configuration. Changes won't be applied automatically. * Do not use the `/reload` command. To apply Anti-Xray configuration changes a restart is required. * After restarting the server, verify that the configuration is applied correctly by inspecting the config sections with timings or spark.
How and where do I ask for support if it still doesn't work? If the above bullet points don't solve your problem or if you have further questions about Anti-Xray, please don't hesitate to ask us on the [PaperMC Discord](https://discord.gg/papermc) using the #paper-help channel. Please try to provide as much detail as possible about your problem. "It doesn't work" isn't very helpful when asking for support. Describe what you want to achieve, what you have tried, what you expect and what you observe. Ideally include a timings or spark link and a picture what you observe in-game.
--- # Basic troubleshooting This guide will help you diagnose your server's problem before reporting it to PaperMC or the plugin's author. This guide will help you diagnose your server's problem before reporting it to PaperMC or the plugin's author. :::caution[Stop Your Server And Take A Backup] Before following this guide, stop your server first. Modifying server files while it is still running will corrupt them. Only a full server shutdown can prevent this. Also, if you don't follow this guide carefully or make a mistake while following it, you might corrupt your server. It is highly advised to back up your server before following this guide. Archiving your server folder as a .zip is good enough, or if you prefer, use backup software such as [borg](https://www.borgbackup.org/) or [kopia](https://kopia.io/). It would be ideal to create a test server by copying your production server's file, but that's not always possible. ::: If your server encounters a problem, it will either print an error message on the server console, create a crash report and close itself, or do both. If your server crashes, the crash report will be saved in the crash-report directory. If your server didn't crash, those error messages will be stored in the log directory along with other messages. Note that the logs older than the latest will be compressed and not stored as plain text files. The first thing you have to do is diagnose those messages. Almost every problem you encounter will print error message lines, which are called a "stack trace", on the server console. Examining the stack trace will help you find out what is causing problems on your server. The stack trace starts with the error message, exception type, and exception message. Both error messages and exception messages were put there by the developer of either your plugin or Paper. These messages tell you what problem your server experienced. An exception type like `java.lang.RuntimeException` tells you the type of the exception. This will help the developer (and you) understand the type of problem. A good starting point is to search the exception type and message in the [Paper Discord](https://discord.gg/papermc). Many lines beginning with `at` may appear beneath the exception message. These are the body of the stack trace. These lines tell you where the problem starts. The top line of the body of the stack trace will tell you exactly where the problem occurred and, if possible, display where it came from. Issues are often plugin-induced, and are the first possible thing you should check. # Common issues ## Plugin-induced issues If you find any plugin's name in a stack trace in your logs, head to [Check Plugin Updates](#check-plugin-updates) and read from there. In most cases, the plugin, whose name is found on the stack trace, is causing the problem. You can disable all of your plugins by renaming the plugins directory to something else, such as plugins-disabled, or by archiving the plugins directory and deleting it. After that, try to run your server. If the problem is resolved after removing the plugins, you know that it was a plugin that caused the issue. ### Binary search In case you've determined a plugin is causing issues but cannot narrow it down, try a binary search. 1. Split your plugins into two groups. The size of the two groups can be different, but it is ideal if the difference is minimal. Make sure that plugins that depend on each other are grouped together. 2. Disable one of the two groups of plugins. You can disable them by changing their extension from .jar to something else, such as .jar-disabled, or move them outside the plugins directory and into a temporary directory. 3. Run your server and check if the problem still exists. If the problem is resolved, the plugin that caused the issue is one of the disabled plugins. If the problem is not resolved, the plugin that is causing the issue is one of the active plugins. 4. Repeat from the start with the suspect plugin group. Repeat the steps above with groups that have the plugin that is causing the issue. :::caution[Library Plugin Dependencies] Some plugins that you install are not a typical plugin, but a library. These are installed like plugins, however tend to offer few user-facing features and are relied upon by other plugins for their functionality. If you disable a library, plugins that depend on it will not work properly. Common examples of these libraries are ProtocolLib, Vault providers, permission plugins, etc. ::: ### Check plugin updates There is a chance that your problem is already fixed in the latest release or latest build of the plugin. Head to your plugin's official download page and check if you are using the latest build or the latest release of the plugin. If not, update it to the latest version and try to run your server again to see if the problem is resolved. ### Update library plugins Many plugins use library plugins like ProtocolLib, and you have to download them and put them in the plugins directory. If you don't update them to the latest version or latest build, you might experience problems related to plugins that use the library plugin. Some library plugins tell their users to use their latest development build for support of the latest Minecraft version. You should look carefully at the requirements of your plugin. ### Check documentation If you misconfigured your plugin or your server, it can also cause problems on your server. Many plugins provide their own documentation about how to set them up properly. Read those documents carefully and check if there is something wrong with the plugin's configuration. If your problem is related to a plugin you use, and you still don't know how to solve it, you can try to reach out to the plugin's author. Many plugins have a way to contact their author, like a GitHub issue tracker, Discord support guild, Gitter, IRC, etc. Below, we list other issues that may happen when running a server. ## Server does not start When this happens, always check your `latest.log` file in your `logs` folder, you may find your issue listed here. If logs are not generating, check your startup script, as described below: ### Checking your startup script The recommended way to start a server is via a startup script, that you can generate [here](/misc/tools/start-script-gen). Don't double click the .jar! If you're on Windows and your terminal disappears quickly after you run, make sure there's a line at the end of the file containing just `pause`. In case you get an error similar to `Error: Unable to access jarfile server.jar`, make sure that the .jar name in your startup script is the same as the file you downloaded. Note that Windows, by default, hides extensions, so you may need re-enable that in the Folder and Search Options in the file explorer to see the correct name of the file, extension included. ### Failed to bind to port This may happen in two cases: 1. A server is already running, check your task manager app for Java processes. 2. `server-ip`, in `server.properties`, is configured incorrectly. Note that this option is not a placeholder for your external IP, it controls which network interfaces your server will bind to. Most of the time, it should be left empty. ### Attempted to load chunk saved with newer version {/* spellchecker:off */} ``` java.lang.RuntimeException: Server attempted to load chunk saved with newer version of minecraft! 3955 > 3465 [18:23:38 WARN]: at net.minecraft.world.level.chunk.storage.ChunkRegionLoader.loadChunk(ChunkRegionLoader.java:149) [18:23:38 WARN]: at io.papermc.paper.chunk.system.scheduling.ChunkLoadTask$ChunkDataLoadTask.runOffMain(ChunkLoadTask.java:338) [18:23:38 WARN]: at io.papermc.paper.chunk.system.scheduling.GenericDataLoadTask$ProcessOffMainTask.run(GenericDataLoadTask.java:307) [18:23:38 WARN]: at ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue$PrioritisedTask.executeInternal(PrioritisedThreadedTaskQueue.java:351) [18:23:38 WARN]: at ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue.executeTask(PrioritisedThreadedTaskQueue.java:118) [18:23:38 WARN]: at ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool$PrioritisedThread.pollTasks(PrioritisedThreadPool.java:274) [18:23:38 WARN]: at ca.spottedleaf.concurrentutil.executor.standard.PrioritisedQueueExecutorThread.run(PrioritisedQueueExecutorThread.java:50) ``` {/* spellchecker:on */} That error means that your world was created or opened in a server version that's newer than one you're currently running. Downgrading your world is not supported, so make sure to use the latest supported version of Paper. Even if you haven't joined the server, by loading your world in a newer version, it is upgraded automatically. :::danger[Forcing the server to try to load a newer world] The server will start if you use the `-DPaper.ignoreWorldDataVersion=true` flag. However, this is **highly not recommended, completely unsupported and may permanently corrupt your world**. If you're going to attempt this, take a backup. ::: ### Circular plugin loading ``` [15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] Circular plugin loading detected! [15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] Circular load order: [15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] Plugin1 -> Plugin2 -> Plugin3 -> Plugin4 -> Plugin1 [15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] Please report this to the plugin authors of the first plugin of each loop or join the PaperMC Discord server for further help. [15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] If you would like to still load these plugins, acknowledging that there may be unexpected plugin loading issues, run the server with -Dpaper.useLegacyPluginLoading=true ``` That means your plugins are configured in a way such that they want to start before (or after) each other, which is impossible -- one has to go first. Plugins usually have reasons to want to start before each other, so when such a conflict happens, rather than picking randomly and risking issues, the server warns you about the issue and shuts down. There's often a problematic plugin involved, and to solve this, it's preferable that you report the issue to its authors. Removing it should also fix the issue. As a last resort, you can use the `-Dpaper.useLegacyPluginLoading=true` startup flag, but it may cause hard to debug issues. ### Outdated version of Java ``` Exception in thread "ServerMain" java.lang.UnsupportedClassVersionError: org/bukkit/craftbukkit/Main has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0 at java.base/java.lang.ClassLoader.defineClass1(Native Method) at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017) at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150) at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:524) at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:427) at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:421) at java.base/java.security.AccessController.doPrivileged(AccessController.java:712) at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:420) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Class.java:467) at io.papermc.paperclip.Paperclip.lambda$main$0(Paperclip.java:38) at java.base/java.lang.Thread.run(Thread.java:842) ``` Your version of Java is outdated, check [our guide on updating it](/misc/java-install). To avoid possibly having to do more tweaks, uninstall your current version of Java, if any. If you do have the correct version installed, your operating system may be not picking it up. Make sure you've closed and opened your terminal after installing it, and that Java is present in your `PATH` environment variable. ## Server crashes or exits unexpectedly :::caution[Update!] Always keep your server up to date (and take a backup before updating). Older versions are known to have on-demand crashes that can be triggered by players at any time. ::: ### Unexpected graceful shutdown If your server shuts down normally as if you typed `/stop` or pressed a stop button in your panel, enable `debug` in `server.properties`. The next time the server shuts down, you will get a stack trace that will help you debug. ### Watchdog dump ("DO NOT REPORT THIS TO PAPER") ``` [02:04:00] [Paper Watchdog Thread/ERROR]: --- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - 1.21.3-66-afb5b13 (MC: 1.21.3) --- [02:04:00] [Paper Watchdog Thread/ERROR]: The server has not responded for 10 seconds! Creating thread dump [02:04:00] [Paper Watchdog Thread/ERROR]: ------------------------------ [02:04:00] [Paper Watchdog Thread/ERROR]: Server thread dump (Look for plugins here before reporting to Paper!): [02:04:00] [Paper Watchdog Thread/ERROR]: ------------------------------ [02:04:00] [Paper Watchdog Thread/ERROR]: Current Thread: Server thread [02:04:00] [Paper Watchdog Thread/ERROR]: PID: 129 | Suspended: false | Native: true | State: RUNNABLE [02:04:00] [Paper Watchdog Thread/ERROR]: Stack: [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.UnixFileDispatcherImpl.write0(Native Method) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.UnixFileDispatcherImpl.write(UnixFileDispatcherImpl.java:65) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:137) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.IOUtil.write(IOUtil.java:102) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.IOUtil.write(IOUtil.java:72) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:300) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.ChannelOutputStream.writeFully(ChannelOutputStream.java:68) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.ChannelOutputStream.write(ChannelOutputStream.java:105) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:125) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/java.io.BufferedOutputStream.implFlush(BufferedOutputStream.java:252) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/java.io.BufferedOutputStream.flush(BufferedOutputStream.java:240) [02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/java.io.FilterOutputStream.close(FilterOutputStream.java:184) ``` That message shows up when your server is taking very long (10+ seconds) to finish the current tick -- it's not a bug or crash, it's simply warning you of severe lag, that can eventually lead to a crash. A good rule of thumb is checking the first lines of the stack trace, as that shows where the main thread was stuck at the time it was printed. Many times, that points to the root cause of the issue. However, sometimes the issue may not be obvious or appear in the stack trace. If possible, analyze a [spark report](#spark-report) for more details, and, if you're in doubt about how to proceed, feel free to visit the [Paper Discord](https://discord.gg/papermc) for help. ### Crash without logs If you have access to a Linux shell, run `dmesg -T | grep -i killed`. That should show how your server process was killed. A common cause (but not the only one), if it was killed due to OOM, is that your server panel is configured with a memory limit that's too close to your `-Xmx`. Either reduce `-Xmx` (by 1-2GB is a good initial rule of thumb) or increase/disable the memory limits in your panel. If you're using a hosting company that only provides you with a panel, you likely won't have the tools to get to the bottom of the problem. You should make a ticket with your host in this case. ## Performance and gameplay issues Unfortunately, Paper can't replicate Vanilla behavior 100%, but it is a goal (except when it comes to exploits). If you're still experiencing a bug that cannot be reproduced in vanilla **multiplayer**, please apply a [Vanilla-like configuration](/paper/vanilla) and check if there's already an open issue in our GitHub. If not, feel free to create one. ### Strange entity/farm/redstone/spawning behavior If you copied values of a pre-made configuration or optimization guide, now is a good time to revert the changes. Keep a copy of your current configs if you prefer, and delete the originals so they can re-generate to default values. In case you're still experiencing such issues with default configurations, try our [Vanilla-like configurations](/paper/vanilla) but do note that this comes at a performance cost. Also, keep in mind that singleplayer does not behave the same as multiplayer, both when it comes to spawning and certain entity behavior. For example, mobs despawn if they're over 128 blocks away from a player, and this becomes more apparent in multiplayer, especially if you're making farms where monsters go to another dimension via a nether portal. If there are players in the target dimension and they're all very far from the portal, the mob will instantly despawn -- this is intended Vanilla behavior. ### Dupes not working Paper has some [unsupported settings](/paper/reference/global-configuration#unsupported_settings) that allow certain dupes. However, a few of them cannot be re-introduced because that would break other aspects of the server. Paper also will not re-add dupes that no longer exist in the game. ## Performance issues ### Spark report Paper has the [spark](https://spark.lucko.me/) profiler built-in, in order to diagnose the root cause of performance issues. For example, you can generate a report for 300 seconds by running `/spark profiler start --timeout 300` If you want to diagnose lag spikes that last more than, for example, 100 ms, you can run `/spark profiler start --only-ticks-over 100 --timeout 300` Look into the [spark docs](https://spark.lucko.me/docs) for a more in-depth guide on how to use it and read reports. If you're still unsure about what's the cause of your problem, you can send us the link via the [Paper Discord](https://discord.gg/papermc). ### High RAM usage Unless you're experiencing out of memory crashes or bad garbage collection (GC) times, high memory usage is expected. Java programs store objects in memory, in an area where we call the heap. The heap grows over time, you can set its initial value with `-Xms` and the maximum value with `-Xmx`. It's normal that the Java process will use the RAM it's given, sometimes using a little more than `-Xmx`. This number won't go down over time, as the garbage collector (GC) rarely returns RAM to the operating system in servers. This is by itself not a problem, in fact it's beneficial: by having more heap memory to work with, GC will have to worry less about disposing of garbage, as that takes valuable processing time. This is not a memory leak and will not cause out of memory crashes, which are commonly caused by not leaving enough RAM for your OS or improperly configured memory limits in a container. There are several different memory metrics that can be measured. For example, imagine a server in a 16GB container, running with `-Xms1G -Xmx14G`. In this case: * **Container memory limit:** 16GB * **Maximum heap:** 14GB * **Current heap size:** Starts at 1GB and expands quickly. Will be between 1-14GB at any given point * **Current heap usage:** will be smaller than the number above, and will grow and shrink in a sawtooth pattern under normal conditions Keep in mind that different tools choose different metrics out of these to display, so the usage meter in your panel, at a glance, might not look like what a plugin will display. ### Low CPU usage Paper is able to make use of multiple cores, but this does not necessarily mean that you will have several cores at near 100% usage. A major source of load in the server comes from the tick loop, which uses a single thread. Thus, at a certain point, more cores will not give you a performance benefit, so it's advisable to go for a CPU with high single-threaded performance and allocate a sufficient amount of threads to your server (at least 4). In servers with a high core count, this situation can translate to a low CPU usage relative to the total amount of cores. However, if your server (especially if large) is really adamant on using only a single core, check your panel's CPU allocation setting. In certain panels, a number like `4` doesn't mean it'll use 4 cores, but instead that it will use the one core with ID 4. --- # Contributing import PageCards from "/src/components/PageCards.astro"; Welcome to the Paper contributing guide! This guide includes information and tutorials for developers wishing to contribute to the Paper project. --- # Events A guide on how to add new events to Paper. There are several requirements for events in the Paper API. :::note Note that while not all existing events may follow these guidelines, all new and modified events should adhere to them. ::: All new events should go in the package (sub-package of) `io.papermc.paper.event`. ### Constructors All new constructors added should be annotated with [`@ApiStatus.Internal`](https://javadoc.io/doc/org.jetbrains/annotations/latest/org/jetbrains/annotations/ApiStatus.Internal.html) to signify that they are not considered API and can change at any time without warning. Constructors that are being replaced, if they aren't being removed, should be marked with [`@Deprecated`](jd:java:java.lang.Deprecated) and [`@DoNotUse`](jd:paper:io.papermc.paper.annotation.DoNotUse). ### Mutability Certain API types are "mutable" which can lead to unexpected behavior within events. Mutable types like [`Location`](jd:paper:org.bukkit.Location) and [`Vector`](jd:paper:org.bukkit.util.Vector) should therefore be cloned when returned from a "getter" in an event. ### `HandlerList` For an event class or any subclass of it to be listened to, a [`HandlerList`](jd:paper:org.bukkit.event.HandlerList) field must be present with an instance and static method to retrieve it. See the docs for [`Event`](jd:paper:org.bukkit.event.Event) for specifics. This field should be static and final and named `HANDLER_LIST`. Also consider not putting a `HandlerList` on every event, just a "common parent" event so that a plugin can listen to the parent event and capture any child events but also listen to the child event separately. ### Miscellaneous * New parameters or method returns of type [`ItemStack`](jd:paper:org.bukkit.inventory.ItemStack) should not be [`@Nullable`](https://javadoc.io/doc/org.jspecify/jspecify/latest/org/jspecify/annotations/Nullable.html) in most case and instead accept an empty itemStack. --- # Development import PageCards from "/src/components/PageCards.astro"; Welcome to the Paper development guide! This guide includes information and tutorials for developers on how to create and expand on Paper plugins. #### Getting started #### API #### Miscellaneous --- # API import PageCards from "/src/components/PageCards.astro"; Welcome to the Paper API guide! This guide includes information for developers about how to use specific parts of the Paper API. --- # Command API import PageCards from "/src/components/PageCards.astro"; #### Basics #### Arguments #### Miscellaneous --- # Arguments import PageCards from "/src/components/PageCards.astro"; --- # Basics import PageCards from "/src/components/PageCards.astro"; --- # Miscellaneous import PageCards from "/src/components/PageCards.astro"; --- # Component API import PageCards from "/src/components/PageCards.astro"; --- # Entity API import PageCards from "/src/components/PageCards.astro"; --- # Event API import PageCards from "/src/components/PageCards.astro"; --- # Inventories import PageCards from "/src/components/PageCards.astro"; --- # Lifecycle API import PageCards from "/src/components/PageCards.astro"; --- # Chat events An outline on AsyncChatEvent and how to handle it. The chat event has evolved a few times over the years. This guide will explain how to properly use the new [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent) and its [](jd:paper:io.papermc.paper.chat.ChatRenderer). The [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent) is an improved version of the old [](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent) that allows you to render chat messages individually for each player. :::note[`AsyncChatEvent` vs `ChatEvent`] The key difference between [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent) and [](jd:paper:io.papermc.paper.event.player.ChatEvent) is that [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent) is fired asynchronously. This means that it does not block the main thread and sends the chat message when the listener has completed. Be aware that using the Bukkit API in an asynchronous context (i.e. the event handler) is unsafe and exceptions may be thrown. If you need to use the Bukkit API, you can use [](jd:paper:io.papermc.paper.event.player.ChatEvent). However, we recommend using [`BukkitScheduler`](/paper/dev/scheduler). ::: ## Understanding the renderer Before we can start using the new chat event, we need to understand how the new renderer works. The renderer is Paper's way of allowing plugins to modify the chat message before it is sent to the player. This is done by using the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface with its [](jd:paper:io.papermc.paper.chat.ChatRenderer#render(org.bukkit.entity.Player,net.kyori.adventure.text.Component,net.kyori.adventure.text.Component,net.kyori.adventure.audience.Audience)) method. Previously, this was done by using the [](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent) with its [](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent#setFormat(java.lang.String)) method. ```java title="ChatRenderer#render" public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) { // ... } ``` - The [`render`](jd:paper:io.papermc.paper.chat.ChatRenderer#render(org.bukkit.entity.Player,net.kyori.adventure.text.Component,net.kyori.adventure.text.Component,net.kyori.adventure.audience.Audience)) method is called when a chat message is sent to the player. - The `source` parameter is the player that sent the message. - The `sourceDisplayName` parameter is the display name of the player that sent the message. - The `message` parameter is the message that was sent. - The `viewer` parameter is the player that is receiving the message. :::tip[`ChatRenderer.ViewerUnaware`] If your renderer does not need to know about the viewer, you can use the [](jd:paper:io.papermc.paper.chat.ChatRenderer$ViewerUnaware) interface instead of the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface. This will benefit performance as the message will only be rendered once instead of each individual player. ::: ## Using the renderer There are two ways to use the renderer. 1. Implementing the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface in a class. 2. Using a lambda expression. Depending on the complexity of your renderer, you may want to use one or the other. ### Implementing the `ChatRenderer` interface The first way of using the renderer is by implementing the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface in a class. In this example, we will be using our `ChatListener` class. Next, we need to tell the event to use the renderer by using the [](jd:paper:io.papermc.paper.event.player.AbstractChatEvent#renderer()) method. ```java title="ChatListener.java" public class ChatListener implements Listener, ChatRenderer { // Implement the ChatRenderer and Listener interface // Listen for the AsyncChatEvent @EventHandler public void onChat(AsyncChatEvent event) { event.renderer(this); // Tell the event to use our renderer } // Override the render method @Override public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) { // ... } } ``` :::note If you decide to create a separate class for your renderer, it is important to know that you don't need to instantiate the class every time the event is called. In this case, you can use [the singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to create a single instance of the class. ::: ### Using a lambda expression Another way of using the renderer is by using a lambda expression. ```java title="ChatListener.java" public class ChatListener implements Listener { @EventHandler public void onChat(AsyncChatEvent event) { event.renderer((source, sourceDisplayName, message, viewer) -> { // ... }); } } ``` ## Rendering the message Now that we have our renderer, we can start rendering the message. Let's say we want to render our chat to look like this: ![](./assets/plain-message-rendering.png) To do this, we need to return a new [`Component`](jd:adventure:net.kyori.adventure.text.Component) that contains the message we want to send. ```java title="ChatListener.java" public class ChatListener implements Listener, ChatRenderer { // Listener logic @Override public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) { return sourceDisplayName .append(Component.text(": ")) .append(message); } } ``` Now you can see that the message is rendered as we wanted it to be. ## Conclusion That is all you need to know about the new chat event and its renderer. Of course there are many more things you can do with components in general. If you want to learn more about components, you can read the [Component Documentation](/adventure/text/). --- # Adventure Documentation for all arguments returning Adventure API objects. import ComponentMp4 from "./assets/vanilla-arguments/component.mp4?url"; import KeyMp4 from "./assets/vanilla-arguments/key.mp4?url"; import NamedColorMp4 from "./assets/vanilla-arguments/namedcolor.mp4?url"; import StyleMp4 from "./assets/vanilla-arguments/style.mp4?url"; import SignedMessageMp4 from "./assets/vanilla-arguments/signedmessage.mp4?url"; import Video from "/src/components/Video.astro"; These arguments return a class from the `net.kyori` package. They are technically not native to Minecraft or Bukkit, but as Paper includes the Adventure library, they are widely used in the Paper ecosystem. ## Component argument :::note This argument is very technical. Following the same format as the `/tellraw ` command for its second argument, it expects the JSON representation of a text component, making it inappropriate for general user input. ::: The result is returned as an Adventure component to work with. ### Example usage ```java public static LiteralCommandNode componentArgument() { return Commands.literal("componentargument") .then(Commands.argument("arg", ArgumentTypes.component()) .executes(ctx -> { final Component component = ctx.getArgument("arg", Component.class); ctx.getSource().getSender().sendRichMessage( "Your message: ", Placeholder.component("input", component) ); return Command.SINGLE_SUCCESS; })) .build(); } ``` ### In-game preview