Dialog API (#12671)

This commit is contained in:
Jake Potrebic
2025-07-06 11:49:43 -07:00
committed by GitHub
parent f7d5a0a017
commit b4466ec981
116 changed files with 4404 additions and 465 deletions

View File

@@ -0,0 +1,61 @@
package io.papermc.paper.registry.keys;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.generated.GeneratedFrom;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;
/**
* Vanilla keys for {@link RegistryKey#DIALOG}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@NullMarked
@GeneratedFrom("1.21.7-rc2")
public final class DialogKeys {
/**
* {@code minecraft:custom_options}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Dialog> CUSTOM_OPTIONS = create(key("custom_options"));
/**
* {@code minecraft:quick_actions}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Dialog> QUICK_ACTIONS = create(key("quick_actions"));
/**
* {@code minecraft:server_links}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Dialog> SERVER_LINKS = create(key("server_links"));
private DialogKeys() {
}
/**
* Creates a typed key for {@link Dialog} in the registry {@code minecraft:dialog}.
*
* @param key the value's key in the registry
* @return a new typed key
*/
public static TypedKey<Dialog> create(final Key key) {
return TypedKey.create(RegistryKey.DIALOG, key);
}
}

View File

@@ -0,0 +1,57 @@
package io.papermc.paper.registry.keys.tags;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.generated.GeneratedFrom;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.tag.TagKey;
import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Vanilla tag keys for {@link RegistryKey#DIALOG}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@NullMarked
@GeneratedFrom("1.21.7-rc2")
@ApiStatus.Experimental
public final class DialogTagKeys {
/**
* {@code #minecraft:pause_screen_additions}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Dialog> PAUSE_SCREEN_ADDITIONS = create(key("pause_screen_additions"));
/**
* {@code #minecraft:quick_actions}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Dialog> QUICK_ACTIONS = create(key("quick_actions"));
private DialogTagKeys() {
}
/**
* Creates a tag key for {@link Dialog} in the registry {@code minecraft:dialog}.
*
* @param key the tag key's key
* @return a new tag key
*/
@ApiStatus.Experimental
public static TagKey<Dialog> create(final Key key) {
return TagKey.create(RegistryKey.DIALOG, key);
}
}

View File

@@ -0,0 +1,47 @@
package io.papermc.paper.dialog;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryBuilderFactory;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.InlinedRegistryBuilderProvider;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import java.util.function.Consumer;
import net.kyori.adventure.dialog.DialogLike;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
import org.bukkit.Keyed;
import org.bukkit.Registry;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents a dialog. Can be created during normal server operation via {@link #create(Consumer)}.
* Can also be created during bootstrap via {@link io.papermc.paper.registry.event.RegistryEvents#DIALOG}.
*/
@ApiStatus.NonExtendable
public interface Dialog extends Keyed, DialogLike {
/**
* Creates a new dialog using the provided builder.
*
* @param value the builder to use for creating the dialog
* @return a new dialog instance
*/
@ApiStatus.Experimental
static Dialog create(final Consumer<RegistryBuilderFactory<Dialog, ? extends DialogRegistryEntry.Builder>> value) {
return InlinedRegistryBuilderProvider.instance().createDialog(value);
}
// Start generate - Dialog
// @GeneratedFrom 1.21.7-rc2
Dialog CUSTOM_OPTIONS = getDialog("custom_options");
Dialog QUICK_ACTIONS = getDialog("quick_actions");
Dialog SERVER_LINKS = getDialog("server_links");
// End generate - Dialog
private static Dialog getDialog(@KeyPattern.Value final String value) {
final Registry<Dialog> registry = RegistryAccess.registryAccess().getRegistry(RegistryKey.DIALOG);
return registry.getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, value));
}
}

View File

@@ -0,0 +1,52 @@
package io.papermc.paper.dialog;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
/**
* A view for a possible response to a dialog.
* There are no guarantees that this is an actual response to a
* dialog form. It is on the plugin to validate that the response
* is valid.
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
public interface DialogResponseView {
/**
* Gets the raw payload of the response.
*
* @return the raw payload
*/
@Contract(pure = true)
BinaryTagHolder payload();
/**
* Gets a text value at a key.
*
* @param key the key
* @return the value (or null if it doesn't exist)
*/
@Contract(pure = true)
@Nullable String getText(String key);
/**
* Gets a boolean value at a key.
*
* @param key the key
* @return the value (or null if it doesn't exist)
*/
@Contract(pure = true)
@Nullable Boolean getBoolean(String key);
/**
* Gets a float value at a key.
*
* @param key the key
* @return the value (or null if it doesn't exist)
*/
@Contract(pure = true)
@Nullable Float getFloat(String key);
}

View File

@@ -0,0 +1,7 @@
/**
* This package contains classes and interfaces related to the dialog system in Paper.
*/
@NullMarked
package io.papermc.paper.dialog;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,79 @@
package io.papermc.paper.event.player;
import io.papermc.paper.connection.PlayerCommonConnection;
import io.papermc.paper.dialog.DialogResponseView;
import io.papermc.paper.registry.data.dialog.action.DialogActionCallback;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.event.ClickCallback;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* This event is fired for any custom click events.
* @see net.kyori.adventure.text.event.ClickEvent#custom(Key, BinaryTagHolder)
* @see io.papermc.paper.registry.data.dialog.action.DialogAction#customClick(DialogActionCallback, ClickCallback.Options)
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
@NullMarked
public abstract class PlayerCustomClickEvent extends Event {
private final Key identifier;
private final PlayerCommonConnection commonConnection;
@ApiStatus.Internal
protected PlayerCustomClickEvent(final Key identifier, final PlayerCommonConnection commonConnection) {
this.identifier = identifier;
this.commonConnection = commonConnection;
}
/**
* The identifier of the custom click event.
*
* @return the identifier
*/
public final Key getIdentifier() {
return this.identifier;
}
/**
* The tag payload of the custom click event.
*
* @return the tag (if any)
*/
public abstract @Nullable BinaryTagHolder getTag();
/**
* The dialog response view of the custom click event.
*
* @return the dialog response view
*/
public abstract @Nullable DialogResponseView getDialogResponseView();
/**
* The common connection of the player.
*
* @return the common connection
*/
public final PlayerCommonConnection getCommonConnection() {
return this.commonConnection;
}
@Override
public HandlerList getHandlers() {
// this will be how handler lists will work on interfaces
return PlayerCustomClickEvent.getHandlerList();
}
public static HandlerList getHandlerList() {
final class Holder {
private static final HandlerList HANDLER_LIST = new HandlerList();
}
return Holder.HANDLER_LIST;
}
}

View File

@@ -1,6 +1,7 @@
package io.papermc.paper.registry;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.tag.TagKey;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
@@ -215,7 +216,11 @@ public sealed interface RegistryKey<T> extends Keyed permits RegistryKeyImpl {
* @see io.papermc.paper.registry.keys.PigVariantKeys
*/
RegistryKey<Pig.Variant> PIG_VARIANT = create("pig_variant");
/**
* Data-driven registry for dialogs.
* @see io.papermc.paper.registry.keys.DialogKeys
*/
RegistryKey<Dialog> DIALOG = create("dialog");
/* ******************* *

View File

@@ -1,6 +1,8 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.RegistryBuilderFactory;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Consumer;
@@ -12,11 +14,13 @@ import org.jetbrains.annotations.ApiStatus;
public interface InlinedRegistryBuilderProvider {
static InlinedRegistryBuilderProvider instance() {
class Holder {
final class Holder {
static final Optional<InlinedRegistryBuilderProvider> INSTANCE = ServiceLoader.load(InlinedRegistryBuilderProvider.class).findFirst();
}
return Holder.INSTANCE.orElseThrow();
}
MusicInstrument createInstrument(Consumer<RegistryBuilderFactory<MusicInstrument, ? extends InstrumentRegistryEntry.Builder>> value);
Dialog createDialog(Consumer<RegistryBuilderFactory<Dialog, ? extends DialogRegistryEntry.Builder>> value);
}

View File

@@ -0,0 +1,115 @@
package io.papermc.paper.registry.data.dialog;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
import org.jspecify.annotations.Nullable;
/**
* Represents an action button in a dialog, which can be used to trigger actions or navigate within the dialog.
* Action buttons can have labels, tooltips, and associated actions.
*/
@ApiStatus.NonExtendable
public interface ActionButton {
/**
* Creates a new action button with the specified label, tooltip, width, and action.
*
* @param label the label of the button
* @param tooltip the tooltip to display when hovering over the button, or null if no tooltip is needed
* @param width the width of the button
* @param action the action to perform when the button is clicked, or null if no action is associated
* @return a new ActionButton instance
*/
@Contract(value = "_, _, _, _ -> new", pure = true)
static ActionButton create(final Component label, final @Nullable Component tooltip, final int width, final @Nullable DialogAction action) {
return builder(label).tooltip(tooltip).width(width).action(action).build();
}
/**
* Creates a new action button builder with the specified label.
*
* @param label the label of the button
* @return a new ActionButton.Builder instance
*/
@Contract(pure = true, value = "_ -> new")
static ActionButton.Builder builder(final Component label) {
return DialogInstancesProvider.instance().actionButtonBuilder(label);
}
/**
* Returns the label of the action button.
*
* @return the label of the button
*/
@Contract(pure = true)
Component label();
/**
* Returns the tooltip of the action button, or null if no tooltip is set.
*
* @return the tooltip of the button, or null
*/
@Contract(pure = true)
@Nullable Component tooltip();
/**
* Returns the width of the action button.
*
* @return the width of the button
*/
@Contract(pure = true)
@Range(from = 1, to = 1024) int width();
/**
* Returns the action associated with this button, or null if no action is associated.
*
* @return the action to perform when the button is clicked, or null
*/
@Contract(pure = true)
@Nullable DialogAction action();
/**
* A builder for creating ActionButton instances.
*/
@ApiStatus.NonExtendable
interface Builder {
/**
* Sets the tooltip of the action button, or null if no tooltip is desired.
*
* @param tooltip the tooltip of the button, or null
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder tooltip(@Nullable Component tooltip);
/**
* Sets the width of the action button.
*
* @param width the width of the button
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder width(@Range(from = 1, to = 1024) int width);
/**
* Sets the action associated with this button, or null if no action is desired.
*
* @param action the action to perform when the button is clicked, or null
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder action(@Nullable DialogAction action);
/**
* Builds the ActionButton instance with the configured values.
*
* @return a new ActionButton instance
*/
@Contract(value = "-> new", pure = true)
ActionButton build();
}
}

View File

@@ -0,0 +1,211 @@
package io.papermc.paper.registry.data.dialog;
import io.papermc.paper.registry.data.dialog.body.DialogBody;
import io.papermc.paper.registry.data.dialog.input.DialogInput;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.util.Index;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.Nullable;
/**
* Represents the base of all dialogs.
*/
@ApiStatus.NonExtendable
public interface DialogBase {
/**
* Creates a new dialog base.
*
* @param title the title of the dialog
* @param externalTitle the external title of the dialog, or null if not set
* @param canCloseWithEscape if the dialog can be closed with the "escape" keybind
* @param pause if the dialog should pause the game when opened (single-player only)
* @param afterAction the action to take after the dialog is closed
* @param body the body of the dialog
* @param inputs the inputs of the dialog
* @return a new dialog base instance
*/
@Contract(value = "_, _, _, _, _, _, _ -> new", pure = true)
static DialogBase create(
final Component title,
final @Nullable Component externalTitle,
final boolean canCloseWithEscape,
final boolean pause,
final DialogAfterAction afterAction,
final List<? extends DialogBody> body,
final List<? extends DialogInput> inputs
) {
return builder(title).externalTitle(externalTitle).canCloseWithEscape(canCloseWithEscape).pause(pause).afterAction(afterAction).body(body).inputs(inputs).build();
}
/**
* Creates a new dialog base builder.
*
* @param title the title of the dialog
* @return a new dialog base builder
*/
@Contract(value = "_ -> new", pure = true)
static Builder builder(final Component title) {
return DialogInstancesProvider.instance().dialogBaseBuilder(title);
}
/**
* The title of the dialog.
*
* @return the title
*/
@Contract(pure = true)
Component title();
/**
* The external title of the dialog. This title
* is used on buttons that open this dialog.
*
* @return the external title or null
*/
@Contract(pure = true)
@Nullable Component externalTitle();
/**
* Returns if this dialog can be closed with the "escape" keybind.
*
* @return if the dialog can be closed with "escape"
*/
@Contract(pure = true)
boolean canCloseWithEscape();
/**
* Returns if this dialog should pause the game when opened (single-player only).
*
* @return if the dialog pauses the game
*/
@Contract(pure = true)
boolean pause();
/**
* The action to take after the dialog is closed.
*
* @return the action to take after the dialog is closed
*/
@Contract(pure = true)
DialogAfterAction afterAction();
/**
* The body of the dialog.
* <p>
* The body is a list of {@link DialogBody} elements that will be displayed in the dialog.
*
* @return the body of the dialog
*/
@Contract(pure = true)
@Unmodifiable List<DialogBody> body();
/**
* The inputs of the dialog.
* <p>
* The inputs are a list of {@link DialogInput} elements that will be displayed in the dialog.
*
* @return the inputs of the dialog
*/
@Contract(pure = true)
@Unmodifiable List<DialogInput> inputs();
/**
* Actions to take after the dialog is closed.
*/
enum DialogAfterAction {
/**
* Closes the dialog and returns to the previous non-dialog screen (if any).
*/
CLOSE("close"),
/**
* Does nothing (keeps the current screen open).
*/
NONE("none"),
/**
* Replaces dialog with a "waiting for response" screen.
*/
WAIT_FOR_RESPONSE("wait_for_response");
public static final Index<String, DialogAfterAction> NAMES = Index.create(DialogAfterAction.class, e -> e.name);
private final String name;
DialogAfterAction(final String name) {
this.name = name;
}
}
/**
* Builder interface for creating dialog bases.
*/
@ApiStatus.NonExtendable
interface Builder {
/**
* Sets the external title of the dialog.
* This title is used on buttons that open this dialog.
*
* @param externalTitle the external title of the dialog, or null if not set
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder externalTitle(@Nullable Component externalTitle);
/**
* Sets whether the dialog can be closed with the "escape" keybind.
*
* @param canCloseWithEscape if the dialog can be closed with "escape"
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder canCloseWithEscape(boolean canCloseWithEscape);
/**
* Sets whether the dialog should pause the game when opened (single-player only).
*
* @param pause if the dialog should pause the game
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder pause(boolean pause);
/**
* Sets the action to take after the dialog is closed.
*
* @param afterAction the action to take after the dialog is closed
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder afterAction(DialogAfterAction afterAction);
/**
* Sets the body of the dialog.
*
* @param body the body of the dialog
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder body(List<? extends DialogBody> body);
/**
* Sets the inputs of the dialog.
*
* @param inputs the inputs of the dialog
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder inputs(List<? extends DialogInput> inputs);
/**
* Builds the dialog base.
*
* @return the built dialog base
*/
@Contract(pure = true, value = "-> new")
DialogBase build();
}
}

View File

@@ -0,0 +1,89 @@
package io.papermc.paper.registry.data.dialog;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import io.papermc.paper.registry.data.dialog.action.DialogActionCallback;
import io.papermc.paper.registry.data.dialog.body.ItemDialogBody;
import io.papermc.paper.registry.data.dialog.body.PlainMessageDialogBody;
import io.papermc.paper.registry.data.dialog.input.BooleanDialogInput;
import io.papermc.paper.registry.data.dialog.input.NumberRangeDialogInput;
import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInput;
import io.papermc.paper.registry.data.dialog.input.TextDialogInput;
import io.papermc.paper.registry.data.dialog.type.ConfirmationType;
import io.papermc.paper.registry.data.dialog.type.DialogListType;
import io.papermc.paper.registry.data.dialog.type.MultiActionType;
import io.papermc.paper.registry.data.dialog.type.NoticeType;
import io.papermc.paper.registry.data.dialog.type.ServerLinksType;
import io.papermc.paper.registry.set.RegistrySet;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickCallback;
import net.kyori.adventure.text.event.ClickEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
/**
* @hidden
*/
@SuppressWarnings("MissingJavadoc")
@ApiStatus.Internal
public interface DialogInstancesProvider {
static DialogInstancesProvider instance() {
final class Holder {
static final Optional<DialogInstancesProvider> INSTANCE = ServiceLoader.load(DialogInstancesProvider.class).findFirst();
}
return Holder.INSTANCE.orElseThrow();
}
DialogBase.Builder dialogBaseBuilder(Component title);
ActionButton.Builder actionButtonBuilder(Component label);
// actions
DialogAction.CustomClickAction register(DialogActionCallback callback, ClickCallback.Options options);
DialogAction.StaticAction staticAction(ClickEvent value);
DialogAction.CommandTemplateAction commandTemplate(String template);
DialogAction.CustomClickAction customClick(Key id, @Nullable BinaryTagHolder additions);
// bodies
ItemDialogBody.Builder itemDialogBodyBuilder(ItemStack itemStack);
PlainMessageDialogBody plainMessageDialogBody(Component component);
PlainMessageDialogBody plainMessageDialogBody(Component component, int width);
// inputs
BooleanDialogInput.Builder booleanBuilder(String key, Component label);
NumberRangeDialogInput.Builder numberRangeBuilder(String key, Component label, float start, float end);
SingleOptionDialogInput.Builder singleOptionBuilder(String key, Component label, List<SingleOptionDialogInput.OptionEntry> entries);
SingleOptionDialogInput.OptionEntry singleOptionEntry(String id, @Nullable Component display, boolean initial);
TextDialogInput.Builder textBuilder(String key, Component label);
TextDialogInput.MultilineOptions multilineOptions(@Nullable Integer maxLines, @Nullable Integer height);
// types
ConfirmationType confirmation(ActionButton yesButton, ActionButton noButton);
DialogListType.Builder dialogList(RegistrySet<Dialog> dialogs);
MultiActionType.Builder multiAction(List<ActionButton> actions);
NoticeType notice();
NoticeType notice(ActionButton action);
ServerLinksType serverLinks(@Nullable ActionButton exitAction, int columns, int buttonWidth);
}

View File

@@ -0,0 +1,75 @@
package io.papermc.paper.registry.data.dialog;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.RegistryBuilder;
import io.papermc.paper.registry.data.dialog.type.DialogListType;
import io.papermc.paper.registry.data.dialog.type.DialogType;
import io.papermc.paper.registry.set.RegistryValueSetBuilder;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
/**
* A data-centric version-specific registry entry for the {@link io.papermc.paper.dialog.Dialog} type.
*/
@ApiStatus.NonExtendable
public interface DialogRegistryEntry {
/**
* The base dialog for this entry.
*
* @return the base dialog
*/
@Contract(pure = true)
DialogBase base();
/**
* The type of dialog for this entry.
*
* @return the dialog type
*/
@Contract(pure = true)
DialogType type();
/**
* A mutable builder for the {@link DialogRegistryEntry} plugins may change in applicable registry events.
* <p>
* The following values are required for each builder:
* <ul>
* <li>{@link #base(DialogBase)}</li>
* <li>{@link #type(DialogType)}</li>
* </ul>
*/
@ApiStatus.NonExtendable
interface Builder extends DialogRegistryEntry, RegistryBuilder<Dialog> {
/**
* Provides a builder for dialog {@link io.papermc.paper.registry.set.RegistryValueSet} which
* can be used inside {@link DialogListType}.
* <p>Not a part of the registry entry.</p>
*
* @return a new registry value set builder
*/
@Contract(value = "-> new", pure = true)
RegistryValueSetBuilder<Dialog, DialogRegistryEntry.Builder> registryValueSet();
/**
* Sets the base dialog for this entry.
*
* @param dialogBase the base dialog
* @return this builder instance
* @see DialogRegistryEntry#base()
*/
@Contract(value = "_ -> this", mutates = "this")
Builder base(DialogBase dialogBase);
/**
* Sets the specialty dialog for this entry.
*
* @param dialogType the specialty dialog
* @return this builder instance
* @see DialogRegistryEntry#type()
*/
@Contract(value = "_ -> this", mutates = "this")
Builder type(DialogType dialogType);
}
}

View File

@@ -0,0 +1,122 @@
package io.papermc.paper.registry.data.dialog.action;
import io.papermc.paper.registry.data.dialog.DialogInstancesProvider;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.event.ClickCallback;
import net.kyori.adventure.text.event.ClickEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
/**
* Represents an action that can be performed in a dialog.
*/
public sealed interface DialogAction permits DialogAction.CommandTemplateAction, DialogAction.CustomClickAction, DialogAction.StaticAction {
/**
* Creates a new command template action. The template format
* looks for {@code $(variable_name)} to substitute variables.
* {@code variable_name} should correspond to a {@link io.papermc.paper.registry.data.dialog.input.DialogInput#key()}.
*
* @param template the command template to execute
* @return a new command template action instance
*/
@Contract(pure = true, value = "_ -> new")
static CommandTemplateAction commandTemplate(final String template) {
return DialogInstancesProvider.instance().commandTemplate(template);
}
/**
* Creates a new static action that performs a click event.
*
* @param value the click event to perform
* @return a new static action instance
*/
@Contract(pure = true, value = "_ -> new")
static StaticAction staticAction(final ClickEvent value) {
return DialogInstancesProvider.instance().staticAction(value);
}
/**
* Creates a new custom click action that executes a custom action.
* Each {@link io.papermc.paper.registry.data.dialog.input.DialogInput#key()} is added
* to the compound binary tag holder.
*
* @param id the identifier of the custom action
* @param additions additional data to be sent with the action, or null if not needed
* @return a new custom all action instance
*/
@Contract(pure = true, value = "_, _ -> new")
static CustomClickAction customClick(final Key id, final @Nullable BinaryTagHolder additions) {
return DialogInstancesProvider.instance().customClick(id, additions);
}
/**
* Creates a new custom click action that executes a custom action.
*
* @param callback the custom action to execute
* @param options the options for the custom action
* @return a new custom all action instance
*/
@Contract(pure = true, value = "_, _ -> new")
static CustomClickAction customClick(final DialogActionCallback callback, final ClickCallback.Options options) {
return DialogInstancesProvider.instance().register(callback, options);
}
/**
* Represents an action that executes a command template.
*/
@ApiStatus.NonExtendable
non-sealed interface CommandTemplateAction extends DialogAction {
/**
* The command template to execute.
*
* @return the command template
*/
@Contract(pure = true)
String template();
}
/**
* Represents an action that performs a static click event.
*/
@ApiStatus.NonExtendable
non-sealed interface StaticAction extends DialogAction {
/**
* The click event to perform.
*
* @return the click event
*/
@Contract(pure = true)
ClickEvent value();
}
/**
* Represents an action that executes a custom action with additional data.
*/
@ApiStatus.NonExtendable
non-sealed interface CustomClickAction extends DialogAction {
/**
* The identifier of the custom action.
*
* @return the identifier
*/
@Contract(pure = true)
Key id();
/**
* Additional data to be sent with the action.
* This is a compound binary tag holder that can contain
* various data related to the action.
*
* @return the additional data, or null if not needed
*/
@Contract(pure = true)
@Nullable BinaryTagHolder additions();
}
}

View File

@@ -0,0 +1,21 @@
package io.papermc.paper.registry.data.dialog.action;
import io.papermc.paper.dialog.DialogResponseView;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.ApiStatus;
/**
* A callback for a dialog action.
*/
@FunctionalInterface
public interface DialogActionCallback {
/**
* Handles a dialog action.
*
* @param response the response to the action
* @param audience the audience to send the response to
*/
@ApiStatus.OverrideOnly
void accept(DialogResponseView response, Audience audience);
}

View File

@@ -0,0 +1,9 @@
/**
* This package contains classes related to dialog actions in the Paper API.
*/
@ApiStatus.Experimental
@NullMarked
package io.papermc.paper.registry.data.dialog.action;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,77 @@
package io.papermc.paper.registry.data.dialog.body;
import io.papermc.paper.registry.data.dialog.DialogInstancesProvider;
import net.kyori.adventure.text.Component;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
import org.jspecify.annotations.Nullable;
/**
* Represents the body of a dialog.
*/
public sealed interface DialogBody permits ItemDialogBody, PlainMessageDialogBody {
/**
* Creates an item body for a dialog.
*
* @param item the item to display in the dialog
* @param description the description of the body, or null if not set
* @param showDecorations whether to show decorations around the item
* @param showTooltip whether to show a tooltip for the item
* @param width the width of the item body
* @param height the height of the item body
* @return a new item body instance
*/
@Contract(pure = true, value = "_, _, _, _, _, _ -> new")
static ItemDialogBody item(
final ItemStack item,
final @Nullable PlainMessageDialogBody description,
final boolean showDecorations,
final boolean showTooltip,
final int width,
final int height
) {
return item(item)
.description(description)
.showDecorations(showDecorations)
.showTooltip(showTooltip)
.width(width)
.height(height)
.build();
}
/**
* Creates a new item dialog body builder.
*
* @param item the item to display in the dialog
* @return a new item dialog body builder instance
*/
@Contract(pure = true, value = "_ -> new")
static ItemDialogBody.Builder item(final ItemStack item) {
return DialogInstancesProvider.instance().itemDialogBodyBuilder(item);
}
/**
* Creates a plain message body for a dialog.
*
* @param contents the contents of the message
* @return a new plain message body instance
*/
@Contract(pure = true, value = "_, -> new")
static PlainMessageDialogBody plainMessage(final Component contents) {
return DialogInstancesProvider.instance().plainMessageDialogBody(contents);
}
/**
* Creates a plain message body for a dialog.
*
* @param contents the contents of the message
* @param width the width of the message body
* @return a new plain message body instance
*/
@Contract(pure = true, value = "_, _ -> new")
static PlainMessageDialogBody plainMessage(final Component contents, final @Range(from = 1, to = 1024) int width) {
return DialogInstancesProvider.instance().plainMessageDialogBody(contents, width);
}
}

View File

@@ -0,0 +1,124 @@
package io.papermc.paper.registry.data.dialog.body;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
import org.jspecify.annotations.Nullable;
/**
* An item body for a dialog.
* <p>Created via {@link DialogBody#item(ItemStack, PlainMessageDialogBody, boolean, boolean, int, int)}</p>
*/
@ApiStatus.NonExtendable
public non-sealed interface ItemDialogBody extends DialogBody {
/**
* The item to display in the dialog.
*
* @return the item stack
*/
@Contract(pure = true)
ItemStack item();
/**
* The description of the body, or null if not set.
*
* @return the description body
*/
@Contract(pure = true)
@Nullable PlainMessageDialogBody description();
/**
* Whether to show decorations around the item.
* <p>Decorations include damage, itemstack count, etc.</p>
*
* @return true if decorations should be shown
*/
@Contract(pure = true)
boolean showDecorations();
/**
* Whether to show a tooltip for the item.
*
* @return true if a tooltip should be shown
*/
@Contract(pure = true)
boolean showTooltip();
/**
* The width of the item body.
*
* @return the width
*/
@Contract(pure = true)
int width();
/**
* The height of the item body.
*
* @return the height
*/
@Contract(pure = true)
int height();
/**
* A builder for an item dialog body.
*/
@ApiStatus.NonExtendable
interface Builder {
/**
* Sets the description of the item dialog body, or null if not set.
*
* @param description the description of the body, or null
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder description(@Nullable PlainMessageDialogBody description);
/**
* Sets whether to show decorations around the item.
*
* @param showDecorations true to show decorations, false otherwise
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder showDecorations(boolean showDecorations);
/**
* Sets whether to show a tooltip for the item.
*
* @param showTooltip true to show a tooltip, false otherwise
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder showTooltip(boolean showTooltip);
/**
* Sets the width of the item body.
*
* @param width the width, must be between 1 and 256
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder width(@Range(from = 1, to = 256) int width);
/**
* Sets the height of the item body.
*
* @param height the height, must be between 1 and 256
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder height(@Range(from = 1, to = 256) int height);
/**
* Builds a new instance of {@link ItemDialogBody}.
*
* @return a new item dialog body instance
*/
@Contract(value = "-> new", pure = true)
ItemDialogBody build();
}
}

View File

@@ -0,0 +1,28 @@
package io.papermc.paper.registry.data.dialog.body;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
/**
* A plain message body for a dialog.
* <p>Created via {@link DialogBody#plainMessage(Component, int)}</p>
*/
public non-sealed interface PlainMessageDialogBody extends DialogBody {
/**
* The contents of the plain message body.
*
* @return the component contents
*/
@Contract(pure = true)
Component contents();
/**
* The width of the plain message body.
*
* @return the width
*/
@Contract(pure = true)
@Range(from = 1, to = 1024) int width();
}

View File

@@ -0,0 +1,9 @@
/**
* This package contains classes related to dialog bodies in the Paper API.
*/
@ApiStatus.Experimental
@NullMarked
package io.papermc.paper.registry.data.dialog.body;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,88 @@
package io.papermc.paper.registry.data.dialog.input;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
/**
* A boolean dialog input.
* <p>Created via {@link DialogInput#bool(String, Component, boolean, String, String)}</p>
*/
@ApiStatus.NonExtendable
public non-sealed interface BooleanDialogInput extends DialogInput {
/**
* The label for the input.
*
* @return the label component
*/
@Contract(pure = true)
Component label();
/**
* The initial value of the input.
*
* @return true if the input is initially true, false otherwise
*/
@Contract(pure = true)
boolean initial();
/**
* The input's value in a template when the value is true.
*
* @return the string to use when the input is true
*/
@Contract(pure = true)
String onTrue();
/**
* The input's value in a template when the value is false.
*
* @return the string to use when the input is false
*/
@Contract(pure = true)
String onFalse();
/**
* A builder for a boolean dialog input.
* <p>Created via {@link DialogInput#bool(String, Component)}</p>
*/
@ApiStatus.NonExtendable
interface Builder {
/**
* Sets the initial value of the input.
*
* @param initial true if the input is initially true, false otherwise
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder initial(boolean initial);
/**
* Sets the input's value in a template when the value is true.
*
* @param onTrue the string to use when the input is true
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder onTrue(String onTrue);
/**
* Sets the input's value in a template when the value is false.
*
* @param onFalse the string to use when the input is false
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder onFalse(String onFalse);
/**
* Builds the instance with the configured values.
*
* @return a new instance
*/
@Contract(value = "-> new", pure = true)
BooleanDialogInput build();
}
}

View File

@@ -0,0 +1,142 @@
package io.papermc.paper.registry.data.dialog.input;
import io.papermc.paper.registry.data.dialog.DialogInstancesProvider;
import java.util.List;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
/**
* Represents an input for dialog.
*/
public sealed interface DialogInput permits BooleanDialogInput, NumberRangeDialogInput, SingleOptionDialogInput, TextDialogInput {
/**
* Creates a boolean dialog input.
*
* @param key the key identifier for the input
* @param label the label for the input
* @param initial the initial value of the input
* @param onTrue the input's value in a template when the value is true
* @param onFalse the input's value in a template when the value is false
* @return a new boolean dialog input instance
*/
@Contract(pure = true, value = "_, _, _, _, _ -> new")
static BooleanDialogInput bool(final String key, final Component label, final boolean initial, final String onTrue, final String onFalse) {
return bool(key, label)
.initial(initial)
.onTrue(onTrue)
.onFalse(onFalse)
.build();
}
/**
* Creates a new builder for a boolean dialog input.
*
* @param key the key identifier for the input
* @param label the label for the input
* @return a new builder instance
*/
@Contract(pure = true, value = "_, _ -> new")
static BooleanDialogInput.Builder bool(final String key, final Component label) {
return DialogInstancesProvider.instance().booleanBuilder(key, label);
}
/**
* Creates a number range dialog input.
*
* @param key the key identifier for the input
* @param width the width of the input
* @param label the label for the input
* @param labelFormat the format for the label (a translation key or format string)
* @param start the start of the range
* @param end the end of the range
* @param initial the initial value, or null if not set
* @param step the step size, or null if not set
* @return a new number range dialog input instance
*/
@Contract(pure = true, value = "_, _, _, _, _, _, _, _ -> new")
static NumberRangeDialogInput numberRange(final String key, final int width, final Component label, final String labelFormat, final float start, final float end, final @Nullable Float initial, final @Nullable Float step) {
return numberRange(key, label, start, end).width(width).labelFormat(labelFormat).initial(initial).step(step).build();
}
/**
* Creates a new builder for a number range dialog input.
*
* @param key the key identifier for the input
* @param label the label for the input
* @param start the start of the range
* @param end the end of the range
* @return a new builder instance
*/
@Contract(value = "_, _, _, _ -> new", pure = true)
static NumberRangeDialogInput.Builder numberRange(final String key, final Component label, final float start, final float end) {
return DialogInstancesProvider.instance().numberRangeBuilder(key, label, start, end);
}
/**
* Creates a single option dialog input (radio input).
*
* @param key the key identifier for the input
* @param width the width of the input
* @param entries the list of options for the input
* @param label the label for the input
* @param labelVisible whether the label should be visible
* @return a new single option dialog input instance
*/
@Contract(pure = true, value = "_, _, _, _, _ -> new")
static SingleOptionDialogInput singleOption(final String key, final int width, final List<SingleOptionDialogInput.OptionEntry> entries, final Component label, final boolean labelVisible) {
return singleOption(key, label, entries).width(width).labelVisible(labelVisible).build();
}
/**
* Creates a new builder for a single option dialog input.
*
* @param key the key identifier for the input
* @param label the label for the input
* @param entries the list of options for the input
* @return a new builder instance
*/
@Contract(value = "_, _, _ -> new", pure = true)
static SingleOptionDialogInput.Builder singleOption(final String key, final Component label, final List<SingleOptionDialogInput.OptionEntry> entries) {
return DialogInstancesProvider.instance().singleOptionBuilder(key, label, entries);
}
/**
* Creates a text dialog input.
*
* @param key the key identifier for the input
* @param width the width of the input
* @param label the label for the input
* @param labelVisible whether the label should be visible
* @param initial the initial value of the input
* @param maxLength the maximum length of the input
* @param multilineOptions the multiline options
* @return a new text dialog input instance
*/
@Contract(pure = true, value = "_, _, _, _, _, _, _ -> new")
static TextDialogInput text(final String key, final int width, final Component label, final boolean labelVisible, final String initial, final int maxLength, final TextDialogInput.@Nullable MultilineOptions multilineOptions) {
return text(key, label).width(width).labelVisible(labelVisible).initial(initial).maxLength(maxLength).multiline(multilineOptions).build();
}
/**
* Creates a new builder for a text dialog input.
*
* @param key the key identifier for the input
* @param label the label for the input
* @return a new builder instance
*/
@Contract(value = "_, _ -> new", pure = true)
static TextDialogInput.Builder text(final String key, final Component label) {
return DialogInstancesProvider.instance().textBuilder(key, label);
}
/**
* Gets the key for this input.
* <p>Used in dialog actions to identify this dialog input's value</p>
*
* @return the key
*/
@Contract(pure = true, value = " -> new")
String key();
}

View File

@@ -0,0 +1,126 @@
package io.papermc.paper.registry.data.dialog.input;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.index.qual.Positive;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
/**
* A number range dialog input.
* <p>Created via {@link DialogInput#numberRange(String, int, Component, String, float, float, Float, Float)}</p>
*/
@ApiStatus.NonExtendable
public non-sealed interface NumberRangeDialogInput extends DialogInput {
/**
* The width of the input.
*
* @return the width
*/
@Contract(pure = true)
@Range(from = 1, to = 1024) int width();
/**
* The label for the input.
*
* @return the label component
*/
@Contract(pure = true)
Component label();
/**
* The format for the label, which can be a translation key or a format string.
* <p>Example: {@code "%s: %s"} or {@code "options.generic_value"}</p>
*
* @return the label format
*/
@Contract(pure = true)
String labelFormat();
/**
* The start of the range.
*
* @return the start value
*/
@Contract(pure = true)
float start();
/**
* The end of the range.
*
* @return the end value
*/
@Contract(pure = true)
float end();
/**
* The initial value of the input, or null if not set.
*
* @return the initial value, or null
*/
@Contract(pure = true)
@Nullable Float initial();
/**
* The step size for the input, or null if not set.
*
* @return the step size, or null
*/
@Contract(pure = true)
@Positive @Nullable Float step();
/**
* A builder for creating instances of {@link NumberRangeDialogInput}.
* <p>Created via {@link DialogInput#numberRange(String, Component, float, float)}</p>
*/
@ApiStatus.NonExtendable
interface Builder {
/**
* Sets the width of the input.
*
* @param width the width
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder width(@Range(from = 1, to = 1024) int width);
/**
* Sets the format for the label.
* <p>Example: {@code "%s: %s"} or {@code "options.generic_value"}</p>
*
* @param labelFormat the label format
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder labelFormat(String labelFormat);
/**
* Sets the initial value for the input.
*
* @param initial the initial value, or null if not set
* @return this builder
*/
@Contract(value = "_ -> this", pure = true)
Builder initial(@Nullable Float initial);
/**
* Sets the step of the range.
*
* @param step the step size, or null if not set
* @return this builder
*/
@Contract(value = "_ -> this", pure = true)
Builder step(@Positive @Nullable Float step);
/**
* Builds the instance with the configured values.
*
* @return a new instance
*/
@Contract(pure = true, value = "-> new")
NumberRangeDialogInput build();
}
}

View File

@@ -0,0 +1,130 @@
package io.papermc.paper.registry.data.dialog.input;
import io.papermc.paper.registry.data.dialog.DialogInstancesProvider;
import java.util.List;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import org.jetbrains.annotations.Unmodifiable;
/**
* A single option dialog input.
* <p>Created via {@link DialogInput#singleOption(String, int, List, Component, boolean)}</p>
*/
@ApiStatus.NonExtendable
public non-sealed interface SingleOptionDialogInput extends DialogInput {
/**
* The width of the input.
*
* @return the width
*/
@Contract(pure = true)
@Range(from = 1, to = 1024) int width();
/**
* The list of options for the input.
*
* @return the list of option entries
*/
@Contract(pure = true)
@Unmodifiable List<OptionEntry> entries();
/**
* The label for the input.
*
* @return the label component
*/
@Contract(pure = true)
Component label();
/**
* Whether the label should be visible.
*
* @return true if the label is visible, false otherwise
*/
@Contract(pure = true)
boolean labelVisible();
/**
* Represents a single option entry in a single option dialog input.
* <p>Only 1 option is allowed to have initial selected.</p>
*/
@ApiStatus.NonExtendable
interface OptionEntry {
/**
* Creates a new option entry.
*
* @param id the unique identifier for the option
* @param display the display name for the option, or null if not set
* @param initial whether this option is initially selected
* @return a new option entry instance
*/
@Contract(pure = true, value = "_, _, _ -> new")
static OptionEntry create(final String id, final @Nullable Component display, final boolean initial) {
return DialogInstancesProvider.instance().singleOptionEntry(id, display, initial);
}
/**
* The unique identifier for the option.
*
* @return the option ID
*/
@Contract(pure = true)
String id();
/**
* The display name for the option, or null if not set.
*
* @return the display component, or null
*/
@Contract(pure = true)
@Nullable Component display();
/**
* Whether this option is initially selected.
* <p>Only 1 option is allowed to have initial selected.</p>
*
* @return true if the option is initially selected, false otherwise
*/
@Contract(pure = true)
boolean initial();
}
/**
* A builder for creating a {@link SingleOptionDialogInput}.
* <p>Created via {@link DialogInput#singleOption(String, Component, List)}</p>
*/
@ApiStatus.NonExtendable
interface Builder {
/**
* Sets the width of the input.
*
* @param width the width
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder width(@Range(from = 1, to = 1024) int width);
/**
* Sets whether the label should be visible.
*
* @param labelVisible whether the label should be visible
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder labelVisible(boolean labelVisible);
/**
* Builds the {@link SingleOptionDialogInput}.
*
* @return the built dialog input
*/
@Contract(value = "-> new", pure = true)
SingleOptionDialogInput build();
}
}

View File

@@ -0,0 +1,159 @@
package io.papermc.paper.registry.data.dialog.input;
import io.papermc.paper.registry.data.dialog.DialogInstancesProvider;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
import org.jspecify.annotations.Nullable;
/**
* A text dialog input.
* <p>Created via {@link DialogInput#text(String, int, Component, boolean, String, int, MultilineOptions)}</p>
*/
@ApiStatus.NonExtendable
public non-sealed interface TextDialogInput extends DialogInput {
/**
* The width of the input.
*
* @return the width of the input
*/
@Contract(pure = true)
@Range(from = 1, to = 1024) int width();
/**
* The label for the input.
*
* @return the label component
*/
@Contract(pure = true)
Component label();
/**
* Whether the label should be visible.
*
* @return true if the label is visible, false otherwise
*/
@Contract(pure = true)
boolean labelVisible();
/**
* The initial value of the input.
*
* @return the initial text
*/
@Contract(pure = true)
String initial();
/**
* The format for the label (a translation key or format string).
*
* @return the label format
*/
@Contract(pure = true)
@Range(from = 1, to = Integer.MAX_VALUE) int maxLength();
/**
* The multiline options for the input, or null if not set.
*
* @return the multiline options
*/
@Contract(pure = true)
@Nullable MultilineOptions multiline();
/**
* Represents the multiline options for a text dialog input.
*/
@ApiStatus.NonExtendable
interface MultilineOptions {
/**
* Creates a new multiline options instance.
*
* @param maxLines the maximum number of lines, or null if not set
* @param height the height of the input, or null if not set
* @return a new MultilineOptions instance
*/
static MultilineOptions create(final @Nullable Integer maxLines, final @Nullable Integer height) {
return DialogInstancesProvider.instance().multilineOptions(maxLines, height);
}
/**
* Gets the maximum number of lines.
*
* @return the maximum number of lines, or null if not set
*/
@Contract(pure = true)
@Nullable Integer maxLines();
/**
* Gets the height of the input.
*
* @return the height of the input, or null if not set
*/
@Contract(pure = true)
@Nullable Integer height();
}
/**
* A builder for a text dialog input.
* <p>Created via {@link DialogInput#text(String, Component)}</p>
*/
@ApiStatus.NonExtendable
interface Builder {
/**
* Sets the width of the input.
*
* @param width the width of the input
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder width(final @Range(from = 1, to = 1024) int width);
/**
* Sets whether the label should be visible.
*
* @param labelVisible true if the label should be visible, false otherwise
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder labelVisible(final boolean labelVisible);
/**
* Sets the initial value of the input.
*
* @param initial the initial value of the input
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder initial(final String initial);
/**
* Sets the maximum length of the input.
*
* @param maxLength the maximum length of the input
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder maxLength(final @Range(from = 1, to = Integer.MAX_VALUE) int maxLength);
/**
* Sets the multiline options for the input.
*
* @param multiline the multiline options
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder multiline(final @Nullable MultilineOptions multiline);
/**
* Builds the text dialog input.
*
* @return the text dialog input
*/
@Contract(value = "-> new", pure = true)
TextDialogInput build();
}
}

View File

@@ -0,0 +1,9 @@
/**
* Dialog inputs for Paper API.
*/
@ApiStatus.Experimental
@NullMarked
package io.papermc.paper.registry.data.dialog.input;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,6 @@
@NullMarked
@ApiStatus.Experimental
package io.papermc.paper.registry.data.dialog;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,30 @@
package io.papermc.paper.registry.data.dialog.type;
import io.papermc.paper.registry.data.dialog.ActionButton;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
/**
* Represents a confirmation dialog.
* This interface defines the structure for a confirmation dialog with "confirm" and "deny" buttons.
* @see DialogType#confirmation(ActionButton, ActionButton)
*/
@ApiStatus.NonExtendable
public non-sealed interface ConfirmationType extends DialogType {
/**
* Gets the button for confirming the action.
*
* @return the confirmation button
*/
@Contract(pure = true)
ActionButton yesButton();
/**
* Gets the button for denying the action.
*
* @return the denial button
*/
@Contract(pure = true)
ActionButton noButton();
}

View File

@@ -0,0 +1,91 @@
package io.papermc.paper.registry.data.dialog.type;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.ActionButton;
import io.papermc.paper.registry.set.RegistrySet;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
import org.jspecify.annotations.Nullable;
/**
* Represents a dialog that displays a list of dialogs.
* @see DialogType#dialogList(RegistrySet, ActionButton, int, int)
*/
@ApiStatus.NonExtendable
public non-sealed interface DialogListType extends DialogType {
/**
* Returns the set of dialogs to display in the dialog list.
*
* @return the set of dialogs
*/
@Contract(pure = true)
RegistrySet<Dialog> dialogs();
/**
* Returns the action button to exit the dialog, or null if there is no exit action.
*
* @return the exit action button, or null
*/
@Contract(pure = true)
@Nullable ActionButton exitAction();
/**
* Returns the number of columns to display in the dialog list.
*
* @return the number of columns
*/
@Contract(pure = true)
@Range(from = 1, to = Integer.MAX_VALUE) int columns();
/**
* Returns the width of each button in the dialog list.
*
* @return the width of the buttons
*/
@Contract(pure = true)
@Range(from = 1, to = 1024) int buttonWidth();
/**
* A builder for creating a dialog list type.
*/
@ApiStatus.NonExtendable
interface Builder {
/**
* Sets the action button to exit the dialog, or null if there is no exit action.
*
* @param exitAction the exit action button, or null
* @return the builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder exitAction(final @Nullable ActionButton exitAction);
/**
* Sets the number of columns to display in the dialog list.
*
* @param columns the number of columns
* @return the builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder columns(final @Range(from = 1, to = Integer.MAX_VALUE) int columns);
/**
* Sets the width of each button in the dialog list.
*
* @param buttonWidth the width of the buttons
* @return the builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder buttonWidth(final @Range(from = 1, to = 1024) int buttonWidth);
/**
* Builds the dialog list type.
*
* @return the built dialog list type
*/
@Contract(value = "-> new", pure = true)
DialogListType build();
}
}

View File

@@ -0,0 +1,110 @@
package io.papermc.paper.registry.data.dialog.type;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.ActionButton;
import io.papermc.paper.registry.data.dialog.DialogInstancesProvider;
import io.papermc.paper.registry.set.RegistrySet;
import java.util.List;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
/**
* Represents a type of dialog.
*/
public sealed interface DialogType permits ConfirmationType, DialogListType, MultiActionType, NoticeType, ServerLinksType {
/**
* Creates a confirmation dialog with the specified yes and no buttons.
*
* @param yesButton the button to confirm the action
* @param noButton the button to cancel the action
* @return a new instance
*/
@Contract(value = "_, _ -> new", pure = true)
static ConfirmationType confirmation(final ActionButton yesButton, final ActionButton noButton) {
return DialogInstancesProvider.instance().confirmation(yesButton, noButton);
}
/**
* Creates a dialog list dialog with the specified dialogs, exit action, columns, and button width.
*
* @param dialogs the set of dialogs to display
* @param exitAction the action button to exit the dialog
* @param columns the number of columns to display in the dialog
* @param buttonWidth the width of each button in the dialog
* @return a new instance
*/
@Contract(value = "_, _, _, _ -> new", pure = true)
static DialogListType dialogList(final RegistrySet<Dialog> dialogs, final @Nullable ActionButton exitAction, final int columns, final int buttonWidth) {
return dialogList(dialogs).exitAction(exitAction).columns(columns).buttonWidth(buttonWidth).build();
}
/**
* Creates a dialog list builder with the specified dialogs.
*
* @param dialogs the set of dialogs to display
* @return a new builder instance
*/
@Contract(value = "_ -> new", pure = true)
static DialogListType.Builder dialogList(final RegistrySet<Dialog> dialogs) {
return DialogInstancesProvider.instance().dialogList(dialogs);
}
/**
* Creates a multi-action dialog with the specified actions, exit action, and number of columns.
*
* @param actions the list of action buttons to display
* @param exitAction the action button to exit the dialog
* @param columns the number of columns to display in the dialog
* @return a new instance
*/
@Contract(value = "_, _, _ -> new", pure = true)
static MultiActionType multiAction(final List<ActionButton> actions, final @Nullable ActionButton exitAction, final int columns) {
return multiAction(actions).exitAction(exitAction).columns(columns).build();
}
/**
* Creates a multi-action dialog builder with the specified actions.
*
* @param actions the list of action buttons to display
* @return a new builder instance
*/
@Contract(value = "_ -> new", pure = true)
static MultiActionType.Builder multiAction(final List<ActionButton> actions) {
return DialogInstancesProvider.instance().multiAction(actions);
}
/**
* Creates a notice dialog with the default action button.
*
* @return a new instance
*/
@Contract(value = "-> new", pure = true)
static NoticeType notice() {
return DialogInstancesProvider.instance().notice();
}
/**
* Creates a notice dialog with the specified action button.
*
* @param action the action button to display in the notice
* @return a new instance
*/
@Contract(value = "_ -> new", pure = true)
static NoticeType notice(final ActionButton action) {
return DialogInstancesProvider.instance().notice(action);
}
/**
* Creates a server links dialog with the specified exit action, number of columns, and button width.
*
* @param exitAction the action button to exit the dialog
* @param columns the number of columns to display in the dialog
* @param buttonWidth the width of each button in the dialog
* @return a new instance
*/
@Contract(value = "_, _, _ -> new", pure = true)
static ServerLinksType serverLinks(final @Nullable ActionButton exitAction, final int columns, final int buttonWidth) {
return DialogInstancesProvider.instance().serverLinks(exitAction, columns, buttonWidth);
}
}

View File

@@ -0,0 +1,75 @@
package io.papermc.paper.registry.data.dialog.type;
import io.papermc.paper.registry.data.dialog.ActionButton;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import org.jetbrains.annotations.Unmodifiable;
/**
* Represents a dialog that allows multiple actions to be performed.
* This dialog is used to create dialogs with multiple action buttons, allowing users to choose from several options.
* @see DialogType#multiAction(List, ActionButton, int)
*/
@ApiStatus.NonExtendable
public non-sealed interface MultiActionType extends DialogType {
/**
* Returns the list of action buttons available in this multi-action dialog.
*
* @return an unmodifiable list of action buttons
*/
@Contract(pure = true)
@Unmodifiable List<ActionButton> actions();
/**
* Returns the action button to exit the dialog, or null if there is no exit action.
*
* @return the exit action button, or null
*/
@Contract(pure = true)
@Nullable ActionButton exitAction();
/**
* Returns the number of columns to display in the dialog.
*
* @return the number of columns
*/
@Contract(pure = true)
@Range(from = 1, to = Integer.MAX_VALUE) int columns();
/**
* A builder for creating a multi-action dialog.
*/
@ApiStatus.NonExtendable
interface Builder {
/**
* Sets the action button to exit the dialog, or null if there is no exit action.
*
* @param exitAction the exit action button, or null
* @return the builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder exitAction(final @Nullable ActionButton exitAction);
/**
* Sets the number of columns to display in the dialog.
*
* @param columns the number of columns
* @return the builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder columns(final @Range(from = 1, to = Integer.MAX_VALUE) int columns);
/**
* Builds the multi-action dialog.
*
* @return a new instance
*/
@Contract(value = "-> new", pure = true)
MultiActionType build();
}
}

View File

@@ -0,0 +1,21 @@
package io.papermc.paper.registry.data.dialog.type;
import io.papermc.paper.registry.data.dialog.ActionButton;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
/**
* Represents a notice dialog.
* @see DialogType#notice(ActionButton)
*/
@ApiStatus.NonExtendable
public non-sealed interface NoticeType extends DialogType {
/**
* Returns the action button associated with this notice type.
*
* @return the action button
*/
@Contract(pure = true)
ActionButton action();
}

View File

@@ -0,0 +1,40 @@
package io.papermc.paper.registry.data.dialog.type;
import io.papermc.paper.registry.data.dialog.ActionButton;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
import org.jspecify.annotations.Nullable;
/**
* Represents a server links dialog that displays links.
* @see DialogType#serverLinks(ActionButton, int, int)
*/
@ApiStatus.NonExtendable
public non-sealed interface ServerLinksType extends DialogType {
/**
* Returns the action button to exit the dialog, or null if there is no exit action.
*
* @return the exit action button, or null
*/
@Contract(pure = true)
@Nullable ActionButton exitAction();
/**
* Returns the number of columns to display in the server links dialog.
*
* @return the number of columns
*/
@Contract(pure = true)
@Range(from = 1, to = Integer.MAX_VALUE) int columns();
/**
* Returns the width of each button in the server links dialog.
*
* @return the width of the buttons
*/
@Contract(pure = true)
@Range(from = 1, to = 1024) int buttonWidth();
}

View File

@@ -0,0 +1,7 @@
/**
* Dialog types for the Paper API.
*/
@NullMarked
package io.papermc.paper.registry.data.dialog.type;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,5 +1,6 @@
package io.papermc.paper.registry.event;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.BannerPatternRegistryEntry;
import io.papermc.paper.registry.data.CatTypeRegistryEntry;
@@ -14,6 +15,7 @@ import io.papermc.paper.registry.data.JukeboxSongRegistryEntry;
import io.papermc.paper.registry.data.PaintingVariantRegistryEntry;
import io.papermc.paper.registry.data.PigVariantRegistryEntry;
import io.papermc.paper.registry.data.WolfVariantRegistryEntry;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import org.bukkit.Art;
import org.bukkit.GameEvent;
import org.bukkit.JukeboxSong;
@@ -51,6 +53,7 @@ public final class RegistryEvents {
public static final RegistryEventProvider<Chicken.Variant, ChickenVariantRegistryEntry.Builder> CHICKEN_VARIANT = create(RegistryKey.CHICKEN_VARIANT);
public static final RegistryEventProvider<Cow.Variant, CowVariantRegistryEntry.Builder> COW_VARIANT = create(RegistryKey.COW_VARIANT);
public static final RegistryEventProvider<Pig.Variant, PigVariantRegistryEntry.Builder> PIG_VARIANT = create(RegistryKey.PIG_VARIANT);
public static final RegistryEventProvider<Dialog, DialogRegistryEntry.Builder> DIALOG = create(RegistryKey.DIALOG);
// End generate - RegistryEvents
private RegistryEvents() {

View File

@@ -1,17 +1,29 @@
package io.papermc.paper.registry.set;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.tag.Tag;
import io.papermc.paper.registry.tag.TagKey;
import java.util.Collection;
import java.util.Iterator;
import io.papermc.paper.registry.tag.TagKey;
import org.bukkit.Keyed;
import org.bukkit.Registry;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;
@ApiStatus.Experimental
@NullMarked
/**
* Represents a collection tied to a registry.
* <p>
* There are 2 types of registry key sets:
* <ul>
* <li>{@link Tag} which is a tag from vanilla or a datapack.
* These are obtained via {@link org.bukkit.Registry#getTag(io.papermc.paper.registry.tag.TagKey)}.</li>
* <li>{@link RegistryKeySet} which is a set of keys linked to values that are present in the registry. These are
* created via {@link #keySet(RegistryKey, Iterable)} or {@link #keySetFromValues(RegistryKey, Iterable)}.</li>
* </ul>
*
* @param <T> registry value type
*/
@ApiStatus.NonExtendable
public non-sealed interface RegistryKeySet<T extends Keyed> extends Iterable<TypedKey<T>>, RegistrySet<T> { // TODO remove Keyed

View File

@@ -12,12 +12,9 @@ import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@ApiStatus.Internal
@NullMarked
record RegistryKeySetImpl<T extends @Nullable Keyed>(RegistryKey<T> registryKey, List<TypedKey<T>> values) implements RegistryKeySet<T> { // TODO remove Keyed
record RegistryKeySetImpl<T extends Keyed>(RegistryKey<T> registryKey, List<TypedKey<T>> values) implements RegistryKeySet<T> { // TODO remove Keyed
static <T extends Keyed> RegistryKeySet<T> create(final RegistryKey<T> registryKey, final Iterable<? extends T> values) { // TODO remove Keyed
final Registry<T> registry = RegistryAccess.registryAccess().getRegistry(registryKey);

View File

@@ -5,43 +5,38 @@ import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.tag.Tag;
import org.bukkit.Keyed;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
/**
* Represents a collection tied to a registry.
* <p>
* There are 2<!--3--> types of registry sets:
* There are 3 types of registry sets:
* <ul>
* <li>{@link Tag} which is a tag from vanilla or a datapack.
* These are obtained via {@link org.bukkit.Registry#getTag(io.papermc.paper.registry.tag.TagKey)}.</li>
* <li>{@link RegistryKeySet} which is a set of of keys linked to values that are present in the registry. These are
* <li>{@link RegistryKeySet} which is a set of keys linked to values that are present in the registry. These are
* created via {@link #keySet(RegistryKey, Iterable)} or {@link #keySetFromValues(RegistryKey, Iterable)}.</li>
* <!-- <li>{@link RegistryValueSet} which is a set of values which are anonymous (don't have keys in the registry). These are
* created via {@link #valueSet(RegistryKey, Iterable)}.</li>-->
* <li>{@link RegistryValueSet} which is a set of values which are anonymous (don't have keys in the registry). These are
* created via {@link #valueSet(RegistryKey, Iterable)}.</li>
* </ul>
*
* @param <T> registry value type
*/
@ApiStatus.Experimental
@NullMarked
public sealed interface RegistrySet<T> permits RegistryKeySet, RegistryValueSet {
// TODO uncomment when direct holder sets need to be exposed to the API
// /**
// * Creates a {@link RegistryValueSet} from anonymous values.
// * <p>All values provided <b>must not</b> have keys in the given registry.</p>
// *
// * @param registryKey the registry key for the type of these values
// * @param values the values
// * @return a new registry set
// * @param <T> the type of the values
// */
// @Contract(value = "_, _ -> new", pure = true)
// static <T> RegistryValueSet<T> valueSet(final RegistryKey<T> registryKey, final Iterable<? extends T> values) {
// return RegistryValueSetImpl.create(registryKey, values);
// }
/**
* Creates a {@link RegistryValueSet} from anonymous values.
* <p>All values provided <b>must not</b> have keys in the given registry.</p>
*
* @param registryKey the registry key for the type of these values
* @param values the values
* @return a new registry set
* @param <T> the type of the values
*/
@Contract(value = "_, _ -> new", pure = true)
static <T> RegistryValueSet<T> valueSet(final RegistryKey<T> registryKey, final Iterable<? extends T> values) {
return RegistryValueSetImpl.create(registryKey, values);
}
/**
* Creates a {@link RegistryKeySet} from registry-backed values.

View File

@@ -1,19 +1,18 @@
package io.papermc.paper.registry.set;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import java.util.Collection;
import java.util.Iterator;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;
/**
* A collection of anonymous values relating to a registry. These
* are values of the same type as the registry, but will not be found
* in the registry, hence, anonymous.
* in the registry, hence, anonymous. Created via {@link RegistrySet#valueSet(io.papermc.paper.registry.RegistryKey, Iterable)} or
* in the context of a {@link io.papermc.paper.registry.RegistryBuilder},
* there are methods to create them like {@link DialogRegistryEntry.Builder#registryValueSet()}.
* @param <T> registry value type
*/
@ApiStatus.Experimental
@NullMarked
public sealed interface RegistryValueSet<T> extends Iterable<T>, RegistrySet<T> permits RegistryValueSetImpl {
@Override

View File

@@ -0,0 +1,31 @@
package io.papermc.paper.registry.set;
import io.papermc.paper.registry.RegistryBuilder;
import io.papermc.paper.registry.RegistryBuilderFactory;
import java.util.function.Consumer;
import org.jetbrains.annotations.ApiStatus;
/**
* A builder for a {@link RegistryValueSet}.
*
* @param <API> the API type
* @param <ENTRY_BUILDER> the type of the entry builder,
*/
@ApiStatus.NonExtendable
public interface RegistryValueSetBuilder<API, ENTRY_BUILDER extends RegistryBuilder<API>> {
/**
* Adds a value to the registry value set.
*
* @param builder the builder for the value to add
* @return this builder for chaining
*/
RegistryValueSetBuilder<API, ENTRY_BUILDER> add(Consumer<RegistryBuilderFactory<API, ? extends ENTRY_BUILDER>> builder);
/**
* Builds the {@link RegistryValueSet}.
*
* @return the built registry value set
*/
RegistryValueSet<API> build();
}

View File

@@ -4,10 +4,8 @@ import com.google.common.collect.Lists;
import io.papermc.paper.registry.RegistryKey;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@ApiStatus.Internal
@NullMarked
record RegistryValueSetImpl<T>(RegistryKey<T> registryKey, List<T> values) implements RegistryValueSet<T> {
RegistryValueSetImpl {

View File

@@ -0,0 +1,11 @@
/**
* This package contains the API for registry sets in Paper.
* <p>
* Registry sets are collections of keys or inlined values of a type that has a registry.
*/
@ApiStatus.Experimental
@NullMarked
package io.papermc.paper.registry.set;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,5 +1,9 @@
package org.bukkit.entity;
import io.papermc.paper.connection.PlayerGameConnection;
import io.papermc.paper.entity.LookAnchor;
import io.papermc.paper.entity.PlayerGiveResult;
import io.papermc.paper.math.Position;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.time.Duration;
@@ -10,10 +14,6 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import io.papermc.paper.connection.PlayerGameConnection;
import io.papermc.paper.entity.LookAnchor;
import io.papermc.paper.entity.PlayerGiveResult;
import io.papermc.paper.math.Position;
import org.bukkit.BanEntry;
import org.bukkit.DyeColor;
import org.bukkit.Effect;

View File

@@ -23,6 +23,7 @@ import io.papermc.generator.rewriter.types.simple.trial.VillagerProfessionRewrit
import io.papermc.generator.types.goal.MobGoalNames;
import io.papermc.generator.utils.Formatting;
import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation;
import io.papermc.paper.dialog.Dialog;
import io.papermc.typewriter.preset.EnumCloneRewriter;
import io.papermc.typewriter.preset.model.EnumValue;
import io.papermc.typewriter.replace.SearchMetadata;
@@ -184,6 +185,7 @@ public final class Rewriters {
.register("ChickenVariant", Chicken.Variant.class, new RegistryFieldRewriter<>(Registries.CHICKEN_VARIANT, "getVariant"))
.register("CowVariant", Cow.Variant.class, new RegistryFieldRewriter<>(Registries.COW_VARIANT, "getVariant"))
.register("PigVariant", Pig.Variant.class, new RegistryFieldRewriter<>(Registries.PIG_VARIANT, "getVariant"))
.register("Dialog", Dialog.class, new RegistryFieldRewriter<>(Registries.DIALOG, "getDialog"))
.register("MemoryKey", MemoryKey.class, new MemoryKeyRewriter())
// .register("ItemType", ItemType.class, new ItemTypeRewriter()) - disable for now, lynx want the generic type
.register("BlockType", BlockType.class, new BlockTypeRewriter())

View File

@@ -3,6 +3,7 @@ package io.papermc.generator.registry;
import io.papermc.generator.utils.ClassHelper;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.BannerPatternRegistryEntry;
import io.papermc.paper.registry.data.CatTypeRegistryEntry;
import io.papermc.paper.registry.data.ChickenVariantRegistryEntry;
@@ -17,6 +18,7 @@ import io.papermc.paper.registry.data.PaintingVariantRegistryEntry;
import io.papermc.paper.registry.data.PigVariantRegistryEntry;
import io.papermc.paper.registry.data.SoundEventRegistryEntry;
import io.papermc.paper.registry.data.WolfVariantRegistryEntry;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
@@ -34,6 +36,7 @@ import net.minecraft.core.component.DataComponents;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.dialog.Dialogs;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.damagesource.DamageTypes;
import net.minecraft.world.effect.MobEffects;
@@ -181,7 +184,8 @@ public final class RegistryEntries {
entry(Registries.FROG_VARIANT, FrogVariants.class, Frog.Variant.class).writableApiRegistryBuilder(FrogVariantRegistryEntry.Builder.class, "PaperFrogVariantRegistryEntry.PaperBuilder").delayed(),
entry(Registries.CHICKEN_VARIANT, ChickenVariants.class, Chicken.Variant.class).writableApiRegistryBuilder(ChickenVariantRegistryEntry.Builder.class, "PaperChickenVariantRegistryEntry.PaperBuilder"),
entry(Registries.COW_VARIANT, CowVariants.class, Cow.Variant.class).writableApiRegistryBuilder(CowVariantRegistryEntry.Builder.class, "PaperCowVariantRegistryEntry.PaperBuilder"),
entry(Registries.PIG_VARIANT, PigVariants.class, Pig.Variant.class).writableApiRegistryBuilder(PigVariantRegistryEntry.Builder.class, "PaperPigVariantRegistryEntry.PaperBuilder")
entry(Registries.PIG_VARIANT, PigVariants.class, Pig.Variant.class).writableApiRegistryBuilder(PigVariantRegistryEntry.Builder.class, "PaperPigVariantRegistryEntry.PaperBuilder"),
entry(Registries.DIALOG, Dialogs.class, Dialog.class, "Paper").allowDirect().writableApiRegistryBuilder(DialogRegistryEntry.Builder.class, "PaperDialogRegistryEntry.PaperBuilder")
);
public static final List<RegistryEntry<?>> API_ONLY = List.of(

View File

@@ -23867,7 +23867,7 @@ index 46de98a6bbbae48c4837e1e588ba198a363d2dde..fd3553bdc1c3cdbf6aa3dc00e0a4987f
thread1 -> {
DedicatedServer dedicatedServer1 = new DedicatedServer(
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 75aba65cbe1a943f21c7464ff9465e64f63e8e5b..32475c0958fd7e0f1f9b494b0cc78a4a718d12b8 100644
index 03a616fc9b0325aa163fe4950ec4ce9ffdd3a9ea..b6aee251027cf124d6597137abefc7fd177358c9 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2;
@@ -24059,7 +24059,7 @@ index 75aba65cbe1a943f21c7464ff9465e64f63e8e5b..32475c0958fd7e0f1f9b494b0cc78a4a
return true;
} else {
boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
@@ -2478,6 +2566,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2479,6 +2567,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}

View File

@@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
+ }
+}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index cff9d761dfee8a90b19fb2f3e678f99a39fc000c..0a260fdf6b198a8ab52e60bf6db2fb5eab719c48 100644
index 41e24da7f0c120af96446603d234ddd417b0de60..7c5b6973769f6eda97f5a08a974b43a98f276ac7 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1718,6 +1718,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1719,6 +1719,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
serverLevel.updateLagCompensationTick(); // Paper - lag compensation

View File

@@ -6,10 +6,10 @@ Subject: [PATCH] Optimise collision checking in player move packet handling
Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c8857c064d55 100644
index 08aba415735733f5968fd032ab7ca249cdcf6cde..ee4397711625344622c81424afd11fd6d967efba 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -594,6 +594,7 @@ public class ServerGamePacketListenerImpl
@@ -606,6 +606,7 @@ public class ServerGamePacketListenerImpl
}
rootVehicle.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
@@ -17,7 +17,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885
double verticalDelta = d4;
d3 = d - rootVehicle.getX();
d4 = d1 - rootVehicle.getY();
@@ -605,12 +606,21 @@ public class ServerGamePacketListenerImpl
@@ -617,12 +618,21 @@ public class ServerGamePacketListenerImpl
d7 = d3 * d3 + d4 * d4 + d5 * d5;
boolean flag1 = false;
if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
@@ -42,7 +42,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885
rootVehicle.absSnapTo(x, y, z, f, f1);
this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle));
rootVehicle.removeLatestMovementRecording();
@@ -689,9 +699,32 @@ public class ServerGamePacketListenerImpl
@@ -701,9 +711,32 @@ public class ServerGamePacketListenerImpl
}
private boolean noBlocksAround(Entity entity) {
@@ -78,7 +78,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885
}
@Override
@@ -1467,7 +1500,7 @@ public class ServerGamePacketListenerImpl
@@ -1479,7 +1512,7 @@ public class ServerGamePacketListenerImpl
}
}
@@ -87,7 +87,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885
d3 = d - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above
d4 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above
d5 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above
@@ -1506,6 +1539,7 @@ public class ServerGamePacketListenerImpl
@@ -1518,6 +1551,7 @@ public class ServerGamePacketListenerImpl
boolean flag1 = this.player.verticalCollisionBelow;
this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move
@@ -95,7 +95,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885
// Paper start - prevent position desync
if (this.awaitingPositionFromClient != null) {
return; // ... thanks Mojang for letting move calls teleport across dimensions.
@@ -1538,7 +1572,17 @@ public class ServerGamePacketListenerImpl
@@ -1550,7 +1584,17 @@ public class ServerGamePacketListenerImpl
}
// Paper start - Add fail move event
@@ -114,7 +114,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885
if (!allowMovement) {
io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK,
toX, toY, toZ, toYaw, toPitch, false);
@@ -1674,7 +1718,7 @@ public class ServerGamePacketListenerImpl
@@ -1686,7 +1730,7 @@ public class ServerGamePacketListenerImpl
private boolean updateAwaitingTeleport() {
if (this.awaitingPositionFromClient != null) {
@@ -123,7 +123,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885
this.awaitingTeleportTime = this.tickCount;
this.teleport(
this.awaitingPositionFromClient.x,
@@ -1693,6 +1737,33 @@ public class ServerGamePacketListenerImpl
@@ -1705,6 +1749,33 @@ public class ServerGamePacketListenerImpl
}
}

View File

@@ -100,7 +100,7 @@ index 962084054c0208470d0c3c99c5dca6327c9b8752..2abc21102bbd2da79dc0c50826cff7da
}
}
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index 140f0ac42626662354e9b5386d91f1b096a945a3..2e7c4c74818befd25e296b58ef9f20319544c2fb 100644
index 1b42d5e9f9fd07f99009de6f4483648f416db733..08c59d603fca038fc2dde36384eea1b6c971e659 100644
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -38,12 +38,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@@ -137,7 +137,7 @@ index 140f0ac42626662354e9b5386d91f1b096a945a3..2e7c4c74818befd25e296b58ef9f2031
// Paper end
}
@@ -93,13 +95,41 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@@ -99,13 +101,41 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@Override
public void handleKeepAlive(ServerboundKeepAlivePacket packet) {
@@ -185,7 +185,7 @@ index 140f0ac42626662354e9b5386d91f1b096a945a3..2e7c4c74818befd25e296b58ef9f2031
}
@Override
@@ -220,20 +250,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@@ -232,20 +262,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
protected void keepConnectionAlive() {
Profiler.get().push("keepAlive");
long millis = Util.getMillis();
@@ -223,7 +223,7 @@ index 140f0ac42626662354e9b5386d91f1b096a945a3..2e7c4c74818befd25e296b58ef9f2031
}
}
@@ -413,6 +446,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@@ -425,6 +458,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
}
protected CommonListenerCookie createCookie(ClientInformation clientInformation) {

View File

@@ -20,7 +20,7 @@ index 5f2deeb5cc01d8bbeb7449bd4e59c466b3dfdf57..82824ae7ffbced513a8bcace684af949
@Override
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 0a260fdf6b198a8ab52e60bf6db2fb5eab719c48..52fa5112cd90ba766c94512a02401dd3aee82cc9 100644
index 382d2b6b53bd144f4d56dccdc603ed0da8fe07a7..7aac2a6889af3edaebfaf94deecbf00d00758b68 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1654,33 +1654,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -63,9 +63,9 @@ index 0a260fdf6b198a8ab52e60bf6db2fb5eab719c48..52fa5112cd90ba766c94512a02401dd3
- });
- // Paper end - Folia scheduler API
+ // Paper end - optimise Folia entity scheduler
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
profilerFiller.push("commandFunctions");
this.getFunctions().tick();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index da880f52920b1101f23ef94f3fd0dbdea218c373..3d2c0a4d3a1f9d3e5cc6cd0cdb988ae1205de821 100644
--- a/net/minecraft/world/entity/Entity.java

View File

@@ -984,7 +984,7 @@
ObjectArrayList<GameProfile> list = new ObjectArrayList<>(min);
int randomInt = Mth.nextInt(this.random, 0, players.size() - min);
@@ -1040,17 +_,75 @@
@@ -1040,17 +_,76 @@
protected void tickChildren(BooleanSupplier hasTimeLeft) {
ProfilerFiller profilerFiller = Profiler.get();
this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
@@ -1012,7 +1012,8 @@
+ }
+ });
+ // Paper end - Folia scheduler API
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
profilerFiller.push("commandFunctions");
this.getFunctions().tick();
profilerFiller.popPush("levels");

View File

@@ -0,0 +1,13 @@
--- a/net/minecraft/server/dialog/body/ItemBody.java
+++ b/net/minecraft/server/dialog/body/ItemBody.java
@@ -15,8 +_,8 @@
PlainMessage.CODEC.optionalFieldOf("description").forGetter(ItemBody::description),
Codec.BOOL.optionalFieldOf("show_decorations", true).forGetter(ItemBody::showDecorations),
Codec.BOOL.optionalFieldOf("show_tooltip", true).forGetter(ItemBody::showTooltip),
- ExtraCodecs.intRange(1, 256).optionalFieldOf("width", 16).forGetter(ItemBody::width),
- ExtraCodecs.intRange(1, 256).optionalFieldOf("height", 16).forGetter(ItemBody::height)
+ ExtraCodecs.intRange(1, 256).optionalFieldOf("width", 16).forGetter(ItemBody::width), // Paper - diff on change - update builder defaults/limits
+ ExtraCodecs.intRange(1, 256).optionalFieldOf("height", 16).forGetter(ItemBody::height) // Paper - diff on change - update builder defaults/limits
)
.apply(instance, ItemBody::new)
);

View File

@@ -27,7 +27,7 @@
public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) {
this.server = server;
@@ -52,6 +_,11 @@
@@ -52,7 +_,18 @@
this.keepAliveTime = Util.getMillis();
this.latency = cookie.latency();
this.transferred = cookie.transferred();
@@ -37,8 +37,15 @@
+ this.pluginMessagerChannels = cookie.channels();
+ // Paper end
}
+
+ // Paper start - configuration phase API
+ public abstract io.papermc.paper.connection.PlayerCommonConnection getApiConnection();
+
+ public abstract net.kyori.adventure.audience.Audience getAudience();
+ // Paper end - configuration phase API
private void close() {
if (!this.closed) {
@@ -82,7 +_,7 @@
this.latency = (this.latency * 3 + i) / 4;
this.keepAlivePending = false;
@@ -48,7 +55,7 @@
}
}
@@ -90,8 +_,76 @@
@@ -90,14 +_,88 @@
public void handlePong(ServerboundPongPacket packet) {
}
@@ -124,6 +131,18 @@
+ // Paper end
}
@Override
public void handleCustomClickAction(ServerboundCustomClickActionPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.server);
this.server.handleCustomClickAction(packet.id(), packet.payload());
+ // Paper start - Implement click callbacks with custom click action
+ final io.papermc.paper.event.player.PaperPlayerCustomClickEvent event = new io.papermc.paper.event.player.PaperPlayerCustomClickEvent(io.papermc.paper.adventure.PaperAdventure.asAdventure(packet.id()), this.getApiConnection(), packet.payload().orElse(null));
+ event.callEvent();
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.tryRunCallback(this.getAudience(), packet.id(), packet.payload());
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.tryRunCallback(this.getAudience(), packet.id(), packet.payload());
+ // Paper end - Implement click callbacks with custom click action
}
@Override
@@ -105,21 +_,46 @@
PacketUtils.ensureRunningOnSameThread(packet, this, this.server);

View File

@@ -1,6 +1,6 @@
--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
@@ -47,11 +_,13 @@
@@ -47,12 +_,26 @@
public ClientInformation clientInformation;
@Nullable
private SynchronizeRegistriesTask synchronizeRegistriesTask;
@@ -10,10 +10,24 @@
super(server, connection, cookie);
this.gameProfile = cookie.gameProfile();
this.clientInformation = cookie.clientInformation();
- }
+ this.paperConnection = new io.papermc.paper.connection.PaperPlayerConfigurationConnection(this); // Paper
}
+ }
+
+ // Paper start - configuration phase API
+ @Override
+ public io.papermc.paper.connection.PlayerCommonConnection getApiConnection() {
+ return this.paperConnection;
+ }
+
+ @Override
+ public net.kyori.adventure.audience.Audience getAudience() {
+ return this.paperConnection.getAudience();
+ }
+ // Paper end - configuration phase API
@Override
protected GameProfile playerProfile() {
@@ -61,6 +_,11 @@
@Override

View File

@@ -83,17 +83,31 @@
public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie cookie) {
super(server, connection, cookie);
@@ -268,7 +_,9 @@
@@ -268,8 +_,22 @@
player.connection = this;
player.getTextFilter().join();
this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(player.getUUID(), server::enforceSecureProfile);
- this.chatMessageChain = new FutureChain(server);
- }
+ this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat
+ this.tickEndEvent = new io.papermc.paper.event.packet.ClientTickEndEvent(player.getBukkitEntity()); // Paper - add client tick end event
+ this.playerGameConnection = new io.papermc.paper.connection.PaperPlayerGameConnection(this); // Paper
}
+ }
+
+ // Paper start - configuration phase API
+ @Override
+ public io.papermc.paper.connection.PlayerCommonConnection getApiConnection() {
+ return this.playerGameConnection;
+ }
+
+ @Override
+ public net.kyori.adventure.audience.Audience getAudience() {
+ return this.getCraftPlayer();
+ }
+ // Paper end - configuration phase API
@Override
public void tick() {
@@ -288,8 +_,8 @@
this.knownMovePacketCount = this.receivedMovePacketCount;
if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) {
@@ -2610,7 +2624,7 @@
if (!this.receivedMovementThisTick) {
this.player.setKnownMovement(Vec3.ZERO);
}
@@ -2078,4 +_,92 @@
@@ -2078,4 +_,80 @@
interface EntityInteraction {
InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand);
}
@@ -2628,18 +2642,6 @@
+ }
+ // Paper end - Add fail move event
+
+ // Paper start - Implement click callbacks with custom click action
+ @Override
+ public void handleCustomClickAction(final net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket packet) {
+ super.handleCustomClickAction(packet);
+ if (packet.id().equals(io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CLICK_CALLBACK_RESOURCE_LOCATION)) {
+ packet.payload().ifPresent(tag ->
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.tryRunCallback(this.player.getBukkitEntity(), tag)
+ );
+ }
+ }
+ // Paper end - Implement click callbacks with custom click action
+
+ // Paper start - add utilities
+ public org.bukkit.craftbukkit.entity.CraftPlayer getCraftPlayer() {
+ return this.player == null ? null : this.player.getBukkitEntity();

View File

@@ -5,6 +5,9 @@ import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.PaperDialogCodecs;
import java.net.URI;
import java.util.Collections;
import java.util.List;
@@ -13,7 +16,7 @@ import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.function.Supplier;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.BlockNBTComponent;
@@ -36,12 +39,12 @@ import net.kyori.adventure.text.format.ShadowColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.util.Index;
import net.minecraft.commands.arguments.selector.SelectorPattern;
import net.minecraft.core.UUIDUtil;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.TagParser;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.network.chat.contents.KeybindContents;
@@ -49,7 +52,6 @@ import net.minecraft.network.chat.contents.ScoreContents;
import net.minecraft.network.chat.contents.TranslatableContents;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.item.Item;
@@ -98,7 +100,7 @@ public final class AdventureCodecs {
}
});
static final Codec<Key> KEY_CODEC = Codec.STRING.comapFlatMap(s -> {
public static final Codec<Key> KEY_CODEC = Codec.STRING.comapFlatMap(s -> {
return Key.parseable(s) ? DataResult.success(Key.key(s)) : DataResult.error(() -> "Cannot convert " + s + " to adventure Key");
}, Key::asString);
@@ -129,6 +131,10 @@ public final class AdventureCodecs {
static final MapCodec<ClickEvent> COPY_TO_CLIPBOARD_CODEC = mapCodec((instance) -> instance.group(
Codec.STRING.fieldOf("value").forGetter(TEXT_PAYLOAD_EXTRACTOR)
).apply(instance, ClickEvent::copyToClipboard));
// needs to be lazy loaded due to depending on PaperDialogCodecs static init
static final MapCodec<ClickEvent> SHOW_DIALOG_CODEC = MapCodec.recursive("show_dialog", ignored -> mapCodec((instance) -> instance.group(
PaperDialogCodecs.DIALOG_CODEC.fieldOf("dialog").forGetter(a -> (Dialog) ((ClickEvent.Payload.Dialog) a.payload()).dialog())
).apply(instance, ClickEvent::showDialog)));
static final MapCodec<ClickEvent> CUSTOM_CODEC = mapCodec((instance) -> instance.group(
KEY_CODEC.fieldOf("id").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).key()),
BINARY_TAG_HOLDER_CODEC.fieldOf("payload").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).nbt())
@@ -140,17 +146,19 @@ public final class AdventureCodecs {
static final ClickEventType SUGGEST_COMMAND_CLICK_EVENT_TYPE = new ClickEventType(SUGGEST_COMMAND_CODEC, "suggest_command");
static final ClickEventType CHANGE_PAGE_CLICK_EVENT_TYPE = new ClickEventType(CHANGE_PAGE_CODEC, "change_page");
static final ClickEventType COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE = new ClickEventType(COPY_TO_CLIPBOARD_CODEC, "copy_to_clipboard");
static final ClickEventType SHOW_DIALOG_CLICK_EVENT_TYPE = new ClickEventType(SHOW_DIALOG_CODEC, "show_dialog");
static final ClickEventType CUSTOM_CLICK_EVENT_TYPE = new ClickEventType(CUSTOM_CODEC, "custom");
static final Codec<ClickEventType> CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE, CUSTOM_CLICK_EVENT_TYPE});
public static final Supplier<ClickEventType[]> CLICK_EVENT_TYPES = () -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE, SHOW_DIALOG_CLICK_EVENT_TYPE, CUSTOM_CLICK_EVENT_TYPE};
static final Codec<ClickEventType> CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(CLICK_EVENT_TYPES);
record ClickEventType(MapCodec<ClickEvent> codec, String id) implements StringRepresentable {
public record ClickEventType(MapCodec<ClickEvent> codec, String id) implements StringRepresentable {
@Override
public String getSerializedName() {
return this.id;
}
}
private static final Function<ClickEvent, ClickEventType> GET_CLICK_EVENT_TYPE =
public static final Function<ClickEvent, ClickEventType> GET_CLICK_EVENT_TYPE =
he -> switch (he.action()) {
case OPEN_URL -> OPEN_URL_CLICK_EVENT_TYPE;
case OPEN_FILE -> OPEN_FILE_CLICK_EVENT_TYPE;
@@ -158,7 +166,7 @@ public final class AdventureCodecs {
case SUGGEST_COMMAND -> SUGGEST_COMMAND_CLICK_EVENT_TYPE;
case CHANGE_PAGE -> CHANGE_PAGE_CLICK_EVENT_TYPE;
case COPY_TO_CLIPBOARD -> COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE;
case SHOW_DIALOG -> throw new UnsupportedOperationException(); // todo: dialog codec with dialog "api"
case SHOW_DIALOG -> SHOW_DIALOG_CLICK_EVENT_TYPE;
case CUSTOM -> CUSTOM_CLICK_EVENT_TYPE;
};
@@ -450,6 +458,23 @@ public final class AdventureCodecs {
return component;
}
public static final Codec<BinaryTagHolder> BINARY_TAG_HOLDER_COMPOUND_CODEC = CompoundTag.CODEC.flatComapMap(PaperAdventure::asBinaryTagHolder, api -> {
try {
final Tag tag = api.get(PaperAdventure.NBT_CODEC);
if (!(tag instanceof final CompoundTag compoundTag)) {
return DataResult.error(() -> "Expected a CompoundTag, but got " + tag.getClass().getSimpleName());
}
return DataResult.success(compoundTag);
} catch (CommandSyntaxException e) {
return DataResult.error(e::getMessage);
}
});
public static <T> Codec<T> indexCodec(final Index<String, T> index) {
return Codec.of(Codec.STRING.comap(index::keyOrThrow), Codec.STRING.map(index::valueOrThrow));
}
private AdventureCodecs() {
}
}

View File

@@ -1,15 +1,22 @@
package io.papermc.paper.adventure.providers;
import com.google.common.base.Predicates;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.dialog.PaperDialogResponseView;
import io.papermc.paper.registry.data.dialog.action.DialogActionCallback;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import java.util.function.Predicate;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.event.ClickCallback;
import net.kyori.adventure.text.event.ClickEvent;
import net.minecraft.core.UUIDUtil;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
@@ -17,30 +24,81 @@ import org.jetbrains.annotations.NotNull;
@SuppressWarnings("UnstableApiUsage") // permitted provider
public class ClickCallbackProviderImpl implements ClickCallback.Provider {
private static final Key CLICK_CALLBACK_KEY = Key.key("paper", "click_callback");
private static final String ID_KEY = "id";
private static final Key ADVENTURE_CLICK_CALLBACK_KEY = Key.key("paper", "click_callback");
public static final Key DIALOG_CLICK_CALLBACK_KEY = Key.key("paper", "dialog_click_callback");
public static final String ID_KEY = "id";
public static final ResourceLocation CLICK_CALLBACK_RESOURCE_LOCATION = PaperAdventure.asVanilla(CLICK_CALLBACK_KEY);
public static final CallbackManager CALLBACK_MANAGER = new CallbackManager();
public static final AdventureClick ADVENTURE_CLICK_MANAGER = new AdventureClick();
public static final DialogClickManager DIALOG_CLICK_MANAGER = new DialogClickManager();
@Override
public @NotNull ClickEvent create(final @NotNull ClickCallback<Audience> callback, final ClickCallback.@NotNull Options options) {
final CompoundTag tag = new CompoundTag();
tag.putString(ID_KEY, CALLBACK_MANAGER.addCallback(callback, options).toString());
return ClickEvent.custom(CLICK_CALLBACK_KEY, PaperAdventure.asBinaryTagHolder(tag));
tag.store(ID_KEY, UUIDUtil.CODEC, ADVENTURE_CLICK_MANAGER.addCallback(callback, options));
return ClickEvent.custom(ADVENTURE_CLICK_CALLBACK_KEY, PaperAdventure.asBinaryTagHolder(tag));
}
public static final class CallbackManager {
public static final class AdventureClick extends CallbackManager<ClickCallback<Audience>, UUID> {
private final Map<UUID, StoredCallback> callbacks = new HashMap<>();
private final Queue<StoredCallback> queue = new ConcurrentLinkedQueue<>();
private CallbackManager() {
private AdventureClick() {
super(PaperAdventure.asVanilla(ADVENTURE_CLICK_CALLBACK_KEY)::equals);
}
public UUID addCallback(final @NotNull ClickCallback<Audience> callback, final ClickCallback.@NotNull Options options) {
final UUID id = UUID.randomUUID();
this.queue.add(new StoredCallback(callback, options, id));
return this.addCallback(UUID.randomUUID(), callback, options);
}
@Override
void doRunCallback(final @NotNull Audience audience, final Key key, final Tag tag) {
tag.asCompound().ifPresent(t -> {
final Optional<UUID> id = t.read(ID_KEY, UUIDUtil.CODEC);
if (id.isEmpty()) {
return;
}
this.tryConsumeCallback(id.get(), callback -> {
callback.accept(audience);
});
});
}
}
public static final class DialogClickManager extends CallbackManager<DialogActionCallback, UUID> {
public DialogClickManager() {
super(PaperAdventure.asVanilla(DIALOG_CLICK_CALLBACK_KEY)::equals);
}
@Override
void doRunCallback(final @NotNull Audience audience, final Key key, final Tag tag) {
tag.asCompound().ifPresent(t -> {
final Optional<UUID> id = t.read(ID_KEY, UUIDUtil.CODEC);
if (id.isEmpty()) {
return;
}
this.tryConsumeCallback(id.get(), callback -> {
callback.accept(PaperDialogResponseView.createUnvalidatedResponse(t), audience);
});
});
}
}
abstract static class CallbackManager<C, I> {
private final Predicate<ResourceLocation> locationPredicate;
protected final Map<I, StoredCallback<C, I>> callbacks = new HashMap<>();
private final Queue<StoredCallback<C, I>> queue = new ConcurrentLinkedQueue<>();
protected CallbackManager(final Predicate<ResourceLocation> locationPredicate) {
this.locationPredicate = locationPredicate;
}
protected CallbackManager() {
this.locationPredicate = Predicates.alwaysTrue();
}
public I addCallback(final I id, final @NotNull C callback, final ClickCallback.@NotNull Options options) {
this.queue.add(new StoredCallback<>(callback, options, id));
return id;
}
@@ -51,38 +109,36 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider {
}
// Add entries from queue
StoredCallback callback;
StoredCallback<C, I> callback;
while ((callback = this.queue.poll()) != null) {
this.callbacks.put(callback.id(), callback);
}
}
public void tryRunCallback(final @NotNull Audience audience, final Tag tag) {
tag.asCompound().flatMap(t -> t.getString(ID_KEY)).ifPresent(s -> {
final UUID id;
try {
id = UUID.fromString(s);
} catch (final IllegalArgumentException ignored) {
return;
}
final void tryConsumeCallback(final I key, final Consumer<? super C> callbackConsumer) {
final StoredCallback<C, I> callback = this.callbacks.get(key);
if (callback != null && callback.valid()) {
callback.takeUse();
callbackConsumer.accept(callback.callback);
}
}
final StoredCallback callback = this.callbacks.get(id);
if (callback != null && callback.valid()) {
callback.takeUse();
callback.callback.accept(audience);
}
});
abstract void doRunCallback(final @NotNull Audience audience, final Key key, final Tag tag);
public final void tryRunCallback(final @NotNull Audience audience, final ResourceLocation key, final Optional<? extends Tag> tag) {
if (!this.locationPredicate.test(key) || tag.isEmpty()) return;
this.doRunCallback(audience, PaperAdventure.asAdventure(key), tag.get());
}
}
private static final class StoredCallback {
public static final class StoredCallback<C, I> {
private final long startedAt = System.nanoTime();
private final ClickCallback<Audience> callback;
private final C callback;
private final long lifetime;
private final UUID id;
private final I id;
private int remainingUses;
private StoredCallback(final @NotNull ClickCallback<Audience> callback, final ClickCallback.@NotNull Options options, final UUID id) {
private StoredCallback(final @NotNull C callback, final ClickCallback.@NotNull Options options, final I id) {
long lifetimeValue;
this.callback = callback;
try {
@@ -95,7 +151,7 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider {
this.id = id;
}
public void takeUse() {
private void takeUse() {
if (this.remainingUses != ClickCallback.UNLIMITED_USES) {
this.remainingUses--;
}
@@ -110,11 +166,11 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider {
return System.nanoTime() - this.startedAt >= this.lifetime;
}
public boolean valid() {
return hasRemainingUses() && !expired();
private boolean valid() {
return this.hasRemainingUses() && !this.expired();
}
public UUID id() {
public I id() {
return this.id;
}
}

View File

@@ -3,26 +3,31 @@ package io.papermc.paper.connection;
import com.destroystokyo.paper.profile.CraftPlayerProfile;
import com.destroystokyo.paper.profile.PlayerProfile;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.dialog.PaperDialog;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.dialog.DialogLike;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.resource.ResourcePackCallback;
import net.kyori.adventure.resource.ResourcePackInfo;
import net.kyori.adventure.resource.ResourcePackRequest;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.common.ClientboundResourcePackPopPacket;
import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket;
import net.minecraft.network.protocol.common.ClientboundShowDialogPacket;
import net.minecraft.network.protocol.configuration.ClientboundResetChatPacket;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.network.ConfigurationTask;
import net.minecraft.server.network.ServerConfigurationPacketListenerImpl;
import org.jspecify.annotations.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
public class PaperPlayerConfigurationConnection extends PaperCommonConnection<ServerConfigurationPacketListenerImpl> implements PlayerConfigurationConnection, Audience, PluginMessageBridgeImpl {
private @Nullable Pointers adventurePointers;
@@ -37,13 +42,13 @@ public class PaperPlayerConfigurationConnection extends PaperCommonConnection<Se
}
@Override
public void sendResourcePacks(ResourcePackRequest request) {
final List<ClientboundResourcePackPushPacket> packs = new java.util.ArrayList<>(request.packs().size());
public void sendResourcePacks(final ResourcePackRequest request) {
final List<ClientboundResourcePackPushPacket> packs = new ArrayList<>(request.packs().size());
if (request.replace()) {
this.clearResourcePacks();
}
final net.minecraft.network.chat.Component prompt = PaperAdventure.asVanilla(request.prompt());
for (final java.util.Iterator<ResourcePackInfo> iter = request.packs().iterator(); iter.hasNext(); ) {
final Component prompt = PaperAdventure.asVanilla(request.prompt());
for (final Iterator<ResourcePackInfo> iter = request.packs().iterator(); iter.hasNext(); ) {
final ResourcePackInfo pack = iter.next();
packs.add(new ClientboundResourcePackPushPacket(pack.id(), pack.uri().toASCIIString(), pack.hash(), request.required(), iter.hasNext() ? Optional.empty() : Optional.ofNullable(prompt)));
if (request.callback() != ResourcePackCallback.noOp()) {
@@ -54,7 +59,7 @@ public class PaperPlayerConfigurationConnection extends PaperCommonConnection<Se
}
@Override
public void removeResourcePacks(UUID id, UUID... others) {
public void removeResourcePacks(final UUID id, final UUID... others) {
net.kyori.adventure.util.MonkeyBars.nonEmptyArrayToList(pack -> new ClientboundResourcePackPopPacket(Optional.of(pack)), id, others).forEach(this.handle::send);
}
@@ -63,13 +68,18 @@ public class PaperPlayerConfigurationConnection extends PaperCommonConnection<Se
this.handle.send(new ClientboundResourcePackPopPacket(Optional.empty()));
}
@Override
public void showDialog(final DialogLike dialog) {
this.handle.send(new ClientboundShowDialogPacket(PaperDialog.bukkitToMinecraftHolder((Dialog) dialog)));
}
@Override
public Pointers pointers() {
if (this.adventurePointers == null) {
this.adventurePointers = Pointers.builder()
.withDynamic(Identity.NAME, () -> this.handle.getOwner().getName())
.withDynamic(Identity.UUID, () -> this.handle.getOwner().getId())
.build();
.withDynamic(Identity.NAME, () -> this.handle.getOwner().getName())
.withDynamic(Identity.UUID, () -> this.handle.getOwner().getId())
.build();
}
return this.adventurePointers;
@@ -92,7 +102,7 @@ public class PaperPlayerConfigurationConnection extends PaperCommonConnection<Se
@Override
public void completeReconfiguration() {
ConfigurationTask task = this.handle.currentTask;
final ConfigurationTask task = this.handle.currentTask;
if (task != null) {
// This means that the player is going through the normal configuration process, or is already returning to the game phase.
// Be safe and just ignore, as many plugins may call this.

View File

@@ -0,0 +1,29 @@
package io.papermc.paper.dialog;
import io.papermc.paper.registry.HolderableBase;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import org.bukkit.craftbukkit.CraftRegistry;
public final class PaperDialog extends HolderableBase<net.minecraft.server.dialog.Dialog> implements Dialog {
public static net.minecraft.server.dialog.Dialog bukkitToMinecraft(final Dialog bukkit) {
return CraftRegistry.bukkitToMinecraft(bukkit);
}
public static Holder<net.minecraft.server.dialog.Dialog> bukkitToMinecraftHolder(final Dialog bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public static Dialog minecraftToBukkit(final net.minecraft.server.dialog.Dialog minecraft) {
return CraftRegistry.minecraftToBukkit(minecraft, Registries.DIALOG);
}
public static Dialog minecraftHolderToBukkit(final Holder<net.minecraft.server.dialog.Dialog> minecraft) {
return CraftRegistry.minecraftHolderToBukkit(minecraft, Registries.DIALOG);
}
public PaperDialog(final Holder<net.minecraft.server.dialog.Dialog> holder) {
super(holder);
}
}

View File

@@ -0,0 +1,48 @@
package io.papermc.paper.dialog;
import io.papermc.paper.adventure.PaperAdventure;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.minecraft.nbt.CompoundTag;
import org.jspecify.annotations.Nullable;
public class PaperDialogResponseView implements DialogResponseView {
private final CompoundTag payload;
private PaperDialogResponseView(final CompoundTag payload) {
this.payload = payload;
}
public static DialogResponseView createUnvalidatedResponse(final CompoundTag tag) {
return new PaperDialogResponseView(tag);
}
@Override
public BinaryTagHolder payload() {
return BinaryTagHolder.encode(this.payload, PaperAdventure.NBT_CODEC);
}
@Override
public @Nullable String getText(final String key) {
if (!this.payload.contains(key)) {
return null;
}
return this.payload.getString(key).orElse(null);
}
@Override
public @Nullable Boolean getBoolean(final String key) {
if (!this.payload.contains(key)) {
return null;
}
return this.payload.getBoolean(key).orElse(null);
}
@Override
public @Nullable Float getFloat(final String key) {
if (!this.payload.contains(key)) {
return null;
}
return this.payload.getFloat(key).orElse(null);
}
}

View File

@@ -0,0 +1,44 @@
package io.papermc.paper.event.player;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.connection.PlayerCommonConnection;
import io.papermc.paper.dialog.DialogResponseView;
import io.papermc.paper.dialog.PaperDialogResponseView;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import org.bukkit.entity.Player;
import org.jspecify.annotations.Nullable;
public class PaperPlayerCustomClickEvent extends PlayerCustomClickEvent {
private final @Nullable Tag payload;
private @Nullable BinaryTagHolder apiPayload;
private @Nullable DialogResponseView rawResponse;
public PaperPlayerCustomClickEvent(final Key key, final PlayerCommonConnection commonConnection, final @Nullable Tag payload) {
super(key, commonConnection);
this.payload = payload;
}
@Override
public @Nullable BinaryTagHolder getTag() {
if (this.apiPayload == null && this.payload != null) {
this.apiPayload = BinaryTagHolder.encode(this.payload, PaperAdventure.NBT_CODEC);
}
return this.apiPayload;
}
@Override
public @Nullable DialogResponseView getDialogResponseView() {
if (this.payload == null || !(this.payload instanceof final CompoundTag compoundPayload)) {
return null;
}
if (this.rawResponse == null) {
this.rawResponse = PaperDialogResponseView.createUnvalidatedResponse(compoundPayload);
}
return this.rawResponse;
}
}

View File

@@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.event.player;
import org.jspecify.annotations.NullMarked;

View File

@@ -4,6 +4,8 @@ import com.google.common.base.Preconditions;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.PaperDataComponentType;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.dialog.PaperDialog;
import io.papermc.paper.registry.data.PaperBannerPatternRegistryEntry;
import io.papermc.paper.registry.data.PaperCatTypeRegistryEntry;
import io.papermc.paper.registry.data.PaperChickenVariantRegistryEntry;
@@ -18,6 +20,7 @@ import io.papermc.paper.registry.data.PaperPaintingVariantRegistryEntry;
import io.papermc.paper.registry.data.PaperPigVariantRegistryEntry;
import io.papermc.paper.registry.data.PaperSoundEventRegistryEntry;
import io.papermc.paper.registry.data.PaperWolfVariantRegistryEntry;
import io.papermc.paper.registry.data.dialog.PaperDialogRegistryEntry;
import io.papermc.paper.registry.entry.RegistryEntry;
import io.papermc.paper.registry.entry.RegistryEntryMeta;
import io.papermc.paper.registry.tag.TagKey;
@@ -132,6 +135,7 @@ public final class PaperRegistries {
start(Registries.CHICKEN_VARIANT, RegistryKey.CHICKEN_VARIANT).craft(Chicken.Variant.class, CraftChicken.CraftVariant::new).writable(PaperChickenVariantRegistryEntry.PaperBuilder::new),
start(Registries.COW_VARIANT, RegistryKey.COW_VARIANT).craft(Cow.Variant.class, CraftCow.CraftVariant::new).writable(PaperCowVariantRegistryEntry.PaperBuilder::new),
start(Registries.PIG_VARIANT, RegistryKey.PIG_VARIANT).craft(Pig.Variant.class, CraftPig.CraftVariant::new).writable(PaperPigVariantRegistryEntry.PaperBuilder::new),
start(Registries.DIALOG, RegistryKey.DIALOG).craft(Dialog.class, PaperDialog::new, true).writable(PaperDialogRegistryEntry.PaperBuilder::new),
// api-only
start(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE).apiOnly(PaperSimpleRegistry::entityType),
@@ -161,13 +165,13 @@ public final class PaperRegistries {
}
@SuppressWarnings("unchecked")
public static <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> RegistryEntryMeta.Buildable<M, T, B> getBuildableMeta(final ResourceKey<? extends Registry<M>> resourceKey) {
final RegistryEntry<M, T> entry = getEntry(resourceKey);
public static <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> RegistryEntryMeta.Buildable<M, T, B> getBuildableMeta(final RegistryKey<T> registryKey) {
final RegistryEntry<M, T> entry = getEntry(registryKey);
if (entry == null) {
throw new IllegalArgumentException("No registry entry for " + resourceKey);
throw new IllegalArgumentException("No registry entry for " + registryKey);
}
if (!(entry.meta() instanceof final RegistryEntryMeta.Buildable<M, T, ?> buildableMeta)) {
throw new IllegalArgumentException("Registry entry for " + resourceKey + " is not buildable");
throw new IllegalArgumentException("Registry entry for " + registryKey + " is not buildable");
}
return (RegistryEntryMeta.Buildable<M, T, B>) buildableMeta;
}

View File

@@ -1,15 +1,22 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.RegistryBuilderFactory;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import io.papermc.paper.registry.data.util.Conversions;
import java.util.function.Consumer;
import net.minecraft.core.registries.Registries;
import org.bukkit.MusicInstrument;
public final class InlinedRegistryBuilderProviderImpl implements InlinedRegistryBuilderProvider {
@Override
public MusicInstrument createInstrument(final Consumer<RegistryBuilderFactory<MusicInstrument, ? extends InstrumentRegistryEntry.Builder>> value) {
return Conversions.global().createApiInstanceFromBuilder(Registries.INSTRUMENT, value);
return Conversions.global().createApiInstanceFromBuilder(RegistryKey.INSTRUMENT, value);
}
@Override
public Dialog createDialog(final Consumer<RegistryBuilderFactory<Dialog, ? extends DialogRegistryEntry.Builder>> value) {
return Conversions.global().createApiInstanceFromBuilder(RegistryKey.DIALOG, value);
}
}

View File

@@ -3,13 +3,13 @@ package io.papermc.paper.registry.data;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.RegistryBuilderFactory;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.data.util.Conversions;
import io.papermc.paper.registry.holder.PaperRegistryHolders;
import io.papermc.paper.registry.holder.RegistryHolder;
import java.util.function.Consumer;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.Instrument;
@@ -77,7 +77,7 @@ public class PaperInstrumentRegistryEntry implements InstrumentRegistryEntry {
@Override
public Builder soundEvent(final Consumer<RegistryBuilderFactory<Sound, ? extends SoundEventRegistryEntry.Builder>> soundEvent) {
this.soundEvent = this.conversions.createHolderFromBuilder(Registries.SOUND_EVENT, asArgument(soundEvent, "soundEvent"));
this.soundEvent = this.conversions.createHolderFromBuilder(RegistryKey.SOUND_EVENT, asArgument(soundEvent, "soundEvent"));
return this;
}

View File

@@ -3,6 +3,7 @@ package io.papermc.paper.registry.data;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.RegistryBuilderFactory;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.data.util.Conversions;
import io.papermc.paper.registry.holder.PaperRegistryHolders;
@@ -10,7 +11,6 @@ import io.papermc.paper.registry.holder.RegistryHolder;
import java.util.OptionalInt;
import java.util.function.Consumer;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.JukeboxSong;
@@ -80,7 +80,7 @@ public class PaperJukeboxSongRegistryEntry implements JukeboxSongRegistryEntry {
@Override
public JukeboxSongRegistryEntry.Builder soundEvent(final Consumer<RegistryBuilderFactory<Sound, ? extends SoundEventRegistryEntry.Builder>> soundEvent) {
this.soundEvent = this.conversions.createHolderFromBuilder(Registries.SOUND_EVENT, asArgument(soundEvent, "soundEvent"));
this.soundEvent = this.conversions.createHolderFromBuilder(RegistryKey.SOUND_EVENT, asArgument(soundEvent, "soundEvent"));
return this;
}

View File

@@ -0,0 +1,46 @@
package io.papermc.paper.registry.data.dialog;
import com.google.common.base.Preconditions;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import net.kyori.adventure.text.Component;
import net.minecraft.server.dialog.CommonButtonData;
import org.jspecify.annotations.Nullable;
public record ActionButtonImpl(Component label, @Nullable Component tooltip, int width, @Nullable DialogAction action) implements ActionButton {
public static final class BuilderImpl implements ActionButton.Builder {
private final Component label;
private @Nullable Component tooltip;
private int width = CommonButtonData.DEFAULT_WIDTH;
private @Nullable DialogAction action;
public BuilderImpl(final Component label) {
this.label = label;
}
@Override
public Builder tooltip(final @Nullable Component tooltip) {
this.tooltip = tooltip;
return this;
}
@Override
public Builder width(final int width) {
Preconditions.checkArgument(width >= 1 && width <= 1024, "Width must be between 1 and 1024");
this.width = width;
return this;
}
@Override
public Builder action(final @Nullable DialogAction action) {
this.action = action;
return this;
}
@Override
public ActionButton build() {
return new ActionButtonImpl(this.label, this.tooltip, this.width, this.action);
}
}
}

View File

@@ -0,0 +1,83 @@
package io.papermc.paper.registry.data.dialog;
import com.google.common.base.Preconditions;
import io.papermc.paper.registry.data.dialog.body.DialogBody;
import io.papermc.paper.registry.data.dialog.input.DialogInput;
import java.util.Collections;
import java.util.List;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.Nullable;
public record DialogBaseImpl(
Component title,
@Nullable Component externalTitle,
boolean canCloseWithEscape,
boolean pause,
DialogAfterAction afterAction,
@Unmodifiable List<DialogBody> body,
@Unmodifiable List<DialogInput> inputs
) implements DialogBase {
public DialogBaseImpl {
Preconditions.checkArgument(!pause || afterAction != DialogAfterAction.NONE);
body = List.copyOf(body);
inputs = List.copyOf(inputs);
}
public static final class BuilderImpl implements DialogBase.Builder {
private final Component title;
private @Nullable Component externalTitle;
private boolean canCloseWithEscape = true;
private boolean pause = true;
private DialogAfterAction afterAction = DialogAfterAction.CLOSE;
private List<DialogBody> body = Collections.emptyList();
private List<DialogInput> inputs = Collections.emptyList();
public BuilderImpl(final Component title) {
this.title = title;
}
@Override
public BuilderImpl externalTitle(final @Nullable Component externalTitle) {
this.externalTitle = externalTitle;
return this;
}
@Override
public BuilderImpl canCloseWithEscape(final boolean canCloseWithEscape) {
this.canCloseWithEscape = canCloseWithEscape;
return this;
}
@Override
public BuilderImpl pause(final boolean pause) {
this.pause = pause;
return this;
}
@Override
public BuilderImpl afterAction(final DialogAfterAction afterAction) {
this.afterAction = afterAction;
return this;
}
@Override
public BuilderImpl body(final List<? extends DialogBody> body) {
this.body = List.copyOf(body);
return this;
}
@Override
public BuilderImpl inputs(final List<? extends DialogInput> inputs) {
this.inputs = List.copyOf(inputs);
return this;
}
@Override
public DialogBase build() {
return new DialogBaseImpl(this.title, this.externalTitle, this.canCloseWithEscape, this.pause, this.afterAction, this.body, this.inputs);
}
}
}

View File

@@ -0,0 +1,186 @@
package io.papermc.paper.registry.data.dialog;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Lifecycle;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.papermc.paper.adventure.AdventureCodecs;
import io.papermc.paper.dialog.PaperDialog;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import io.papermc.paper.registry.data.dialog.body.DialogBody;
import io.papermc.paper.registry.data.dialog.body.ItemDialogBody;
import io.papermc.paper.registry.data.dialog.body.PlainMessageDialogBody;
import io.papermc.paper.registry.data.dialog.input.BooleanDialogInput;
import io.papermc.paper.registry.data.dialog.input.DialogInput;
import io.papermc.paper.registry.data.dialog.input.NumberRangeDialogInput;
import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInput;
import io.papermc.paper.registry.data.dialog.input.TextDialogInput;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.minecraft.Util;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.dialog.CommonButtonData;
import net.minecraft.server.dialog.Dialog;
import net.minecraft.server.dialog.action.ParsedTemplate;
import net.minecraft.server.dialog.body.PlainMessage;
import net.minecraft.server.dialog.input.TextInput;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import static io.papermc.paper.util.PaperCodecs.registryFileDecoderFor;
public final class PaperDialogCodecs {
private PaperDialogCodecs() {
}
// dialog actions
private static final MapCodec<DialogAction.CommandTemplateAction> COMMAND_TEMPLATE_ACTION_CODEC = RecordCodecBuilder.mapCodec(instance ->
instance.group(Codec.STRING.fieldOf("template").forGetter(DialogAction.CommandTemplateAction::template))
.apply(instance, DialogAction::commandTemplate)
);
private static final MapCodec<DialogAction.CustomClickAction> CUSTOM_ALL_ACTION_CODEC = RecordCodecBuilder.mapCodec(instance ->
instance.group(AdventureCodecs.KEY_CODEC.fieldOf("id").forGetter(DialogAction.CustomClickAction::id), AdventureCodecs.BINARY_TAG_HOLDER_COMPOUND_CODEC.optionalFieldOf("additions").forGetter(action -> Optional.ofNullable(action.additions())))
.apply(instance, (key, binaryTagHolder) -> DialogAction.customClick(key, binaryTagHolder.orElse(null)))
);
private static final Map<AdventureCodecs.ClickEventType, MapCodec<DialogAction.StaticAction>> STATIC_ACTION_CODECS = Arrays.stream(AdventureCodecs.CLICK_EVENT_TYPES.get()).collect(Collectors.toMap(Function.identity(), type -> type.codec().xmap(DialogAction::staticAction, DialogAction.StaticAction::value)));
private static final Registry<MapCodec<? extends DialogAction>> DIALOG_ACTION_TYPES = Util.make(() -> {
final MappedRegistry<MapCodec<? extends DialogAction>> registry = new MappedRegistry<>(ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ResourceLocation.PAPER_NAMESPACE, "dialog_action_type")), Lifecycle.experimental());
STATIC_ACTION_CODECS.forEach((clickType, actionCodec) -> {
Registry.register(registry, clickType.getSerializedName(), actionCodec);
});
Registry.register(registry, "dynamic/run_command", COMMAND_TEMPLATE_ACTION_CODEC);
Registry.register(registry, "dynamic/custom", CUSTOM_ALL_ACTION_CODEC);
return registry.freeze();
});
private static final Function<DialogAction, MapCodec<? extends DialogAction>> GET_DIALOG_ACTION_TYPE = dialogAction -> switch (dialogAction) {
case DialogAction.CommandTemplateAction $ -> COMMAND_TEMPLATE_ACTION_CODEC;
case DialogAction.CustomClickAction $ -> CUSTOM_ALL_ACTION_CODEC;
case DialogAction.StaticAction action -> STATIC_ACTION_CODECS.get(AdventureCodecs.GET_CLICK_EVENT_TYPE.apply(action.value()));
};
private static final Codec<DialogAction> DIALOG_ACTION_CODEC = DIALOG_ACTION_TYPES.byNameCodec().dispatch(GET_DIALOG_ACTION_TYPE, Function.identity());
// buttons
public static final Codec<ActionButton> ACTION_BUTTON_CODEC = RecordCodecBuilder.create(instance -> instance.group(
AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(ActionButton::label),
AdventureCodecs.COMPONENT_CODEC.optionalFieldOf("tooltip").forGetter(button -> Optional.ofNullable(button.tooltip())),
Dialog.WIDTH_CODEC.optionalFieldOf("width", CommonButtonData.DEFAULT_WIDTH).forGetter(ActionButton::width),
DIALOG_ACTION_CODEC.optionalFieldOf("action").forGetter(button -> Optional.ofNullable(button.action()))
).apply(instance, (label, tooltip, width, action) -> ActionButton.create(label, tooltip.orElse(null), width, action.orElse(null))));
// dialog bodies
private static final MapCodec<PlainMessageDialogBody> PLAIN_MESSAGE_BODY_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
AdventureCodecs.COMPONENT_CODEC.fieldOf("contents").forGetter(PlainMessageDialogBody::contents),
Dialog.WIDTH_CODEC.optionalFieldOf("width", PlainMessage.DEFAULT_WIDTH).forGetter(PlainMessageDialogBody::width)
).apply(instance, DialogBody::plainMessage)
);
private static final Codec<PlainMessageDialogBody> SIMPLE_PLAIN_MESSAGE_BODY_CODEC = Codec.withAlternative(PLAIN_MESSAGE_BODY_CODEC.codec(), AdventureCodecs.COMPONENT_CODEC, component -> DialogBody.plainMessage(component, PlainMessage.DEFAULT_WIDTH));
private static final MapCodec<ItemDialogBody> ITEM_BODY_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
ItemStack.STRICT_CODEC.xmap(CraftItemStack::asBukkitCopy, CraftItemStack::asNMSCopy).fieldOf("item").forGetter(ItemDialogBody::item),
SIMPLE_PLAIN_MESSAGE_BODY_CODEC.optionalFieldOf("description").forGetter(body -> Optional.ofNullable(body.description())),
Codec.BOOL.optionalFieldOf("show_decorations", true).forGetter(ItemDialogBody::showDecorations),
Codec.BOOL.optionalFieldOf("show_tooltip", true).forGetter(ItemDialogBody::showTooltip),
ExtraCodecs.intRange(1, 256).optionalFieldOf("width", 16).forGetter(ItemDialogBody::width),
ExtraCodecs.intRange(1, 256).optionalFieldOf("height", 16).forGetter(ItemDialogBody::height)
).apply(instance, (itemStack, plainMessageBody, showDecorations, showTooltip, width, height) -> DialogBody.item(itemStack, plainMessageBody.orElse(null), showDecorations, showTooltip, width, height))
);
private static final Registry<MapCodec<? extends DialogBody>> DIALOG_BODY_TYPES = Util.make(() -> {
final MappedRegistry<MapCodec<? extends DialogBody>> registry = new MappedRegistry<>(ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ResourceLocation.PAPER_NAMESPACE, "dialog_body_type")), Lifecycle.experimental());
Registry.register(registry, "item", ITEM_BODY_CODEC);
Registry.register(registry, "plain_message", PLAIN_MESSAGE_BODY_CODEC);
return registry.freeze();
});
private static final Function<DialogBody, MapCodec<? extends DialogBody>> GET_DIALOG_BODY_TYPE = dialogAction -> switch (dialogAction) {
case PlainMessageDialogBody $ -> PLAIN_MESSAGE_BODY_CODEC;
case ItemDialogBody $ -> ITEM_BODY_CODEC;
};
private static final Codec<DialogBody> DIALOG_BODY_CODEC = DIALOG_BODY_TYPES.byNameCodec().dispatch(GET_DIALOG_BODY_TYPE, Function.identity());
private static final Codec<List<DialogBody>> DIALOG_BODY_LIST_CODEC = ExtraCodecs.compactListCodec(DIALOG_BODY_CODEC);
// input types
private static final MapCodec<BooleanDialogInput> BOOLEAN_DIALOG_INPUT_TYPE_MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
ParsedTemplate.VARIABLE_CODEC.fieldOf("key").forGetter(DialogInput::key),
AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(BooleanDialogInput::label),
Codec.BOOL.optionalFieldOf("initial", false).forGetter(BooleanDialogInput::initial),
Codec.STRING.optionalFieldOf("on_true", "true").forGetter(BooleanDialogInput::onTrue),
Codec.STRING.optionalFieldOf("on_false", "false").forGetter(BooleanDialogInput::onFalse)
).apply(instance, DialogInput::bool));
private static final MapCodec<NumberRangeDialogInput> NUMBER_RANGE_INPUT_MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
ParsedTemplate.VARIABLE_CODEC.fieldOf("key").forGetter(DialogInput::key),
Dialog.WIDTH_CODEC.optionalFieldOf("width", PlainMessage.DEFAULT_WIDTH).forGetter(NumberRangeDialogInput::width),
AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(NumberRangeDialogInput::label),
Codec.STRING.optionalFieldOf("label_format", "options.generic_value").forGetter(NumberRangeDialogInput::labelFormat),
Codec.FLOAT.fieldOf("start").forGetter(NumberRangeDialogInput::start),
Codec.FLOAT.fieldOf("end").forGetter(NumberRangeDialogInput::end),
Codec.FLOAT.optionalFieldOf("initial").forGetter(type -> Optional.ofNullable(type.initial())),
ExtraCodecs.POSITIVE_FLOAT.optionalFieldOf("step").forGetter(type -> Optional.ofNullable(type.step()))
).apply(instance, (key, width, label, labelFormat, start, end, initial, step) -> DialogInput.numberRange(key, width, label, labelFormat, start, end, initial.orElse(null), step.orElse(null))));
private static final Codec<SingleOptionDialogInput.OptionEntry> SINGLE_OPTION_DIALOG_INPUT_ENTRY_FULL_CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.fieldOf("id").forGetter(SingleOptionDialogInput.OptionEntry::id),
AdventureCodecs.COMPONENT_CODEC.optionalFieldOf("display").forGetter(entry -> Optional.ofNullable(entry.display())),
Codec.BOOL.optionalFieldOf("initial", false).forGetter(SingleOptionDialogInput.OptionEntry::initial)
).apply(instance, (id, display, initial) -> SingleOptionDialogInput.OptionEntry.create(id, display.orElse(null), initial)));
private static final Codec<SingleOptionDialogInput.OptionEntry> SINGLE_OPTION_DIALOG_INPUT_ENTRY_CODEC = Codec.withAlternative(
SINGLE_OPTION_DIALOG_INPUT_ENTRY_FULL_CODEC, Codec.STRING, string -> SingleOptionDialogInput.OptionEntry.create(string, null, false)
);
private static final MapCodec<SingleOptionDialogInput> SINGLE_OPTION_DIALOG_INPUT_TYPE_MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
ParsedTemplate.VARIABLE_CODEC.fieldOf("key").forGetter(DialogInput::key),
Dialog.WIDTH_CODEC.optionalFieldOf("width", PlainMessage.DEFAULT_WIDTH).forGetter(SingleOptionDialogInput::width),
ExtraCodecs.nonEmptyList(SINGLE_OPTION_DIALOG_INPUT_ENTRY_CODEC.listOf()).fieldOf("options").forGetter(SingleOptionDialogInput::entries),
AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(SingleOptionDialogInput::label),
Codec.BOOL.optionalFieldOf("label_visible", true).forGetter(SingleOptionDialogInput::labelVisible)
).apply(instance, DialogInput::singleOption));
private static final Codec<TextDialogInput.MultilineOptions> TEXT_DIALOG_INPUT_MULTILINE_OPTIONS_CODEC = RecordCodecBuilder.create(instance -> instance.group(
ExtraCodecs.POSITIVE_INT.optionalFieldOf("max_lines").forGetter(options -> Optional.ofNullable(options.maxLines())),
ExtraCodecs.intRange(1, TextInput.MultilineOptions.MAX_HEIGHT).optionalFieldOf("height").forGetter(options -> Optional.ofNullable(options.height()))
).apply(instance, (maxLines, height) -> TextDialogInput.MultilineOptions.create(maxLines.orElse(null), height.orElse(null))));
private static final MapCodec<TextDialogInput> TEXT_DIALOG_INPUT_TYPE_MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
ParsedTemplate.VARIABLE_CODEC.fieldOf("key").forGetter(DialogInput::key),
Dialog.WIDTH_CODEC.optionalFieldOf("width", PlainMessage.DEFAULT_WIDTH).forGetter(TextDialogInput::width),
AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(TextDialogInput::label),
Codec.BOOL.optionalFieldOf("label_visible", true).forGetter(TextDialogInput::labelVisible),
Codec.STRING.optionalFieldOf("initial", "").forGetter(TextDialogInput::initial),
ExtraCodecs.POSITIVE_INT.optionalFieldOf("max_length", 32).forGetter(TextDialogInput::maxLength),
TEXT_DIALOG_INPUT_MULTILINE_OPTIONS_CODEC.optionalFieldOf("multiline").forGetter(inputType -> Optional.ofNullable(inputType.multiline()))
).apply(instance, (key, width, label, labelVisible, initial, maxLength, multilineOptions) ->
DialogInput.text(key, width, label, labelVisible, initial, maxLength, multilineOptions.orElse(null))
));
private static final Registry<MapCodec<? extends DialogInput>> DIALOG_INPUT_TYPES = Util.make(() -> {
final MappedRegistry<MapCodec<? extends DialogInput>> registry = new MappedRegistry<>(ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ResourceLocation.PAPER_NAMESPACE, "dialog_input_type")), Lifecycle.experimental());
Registry.register(registry, "boolean", BOOLEAN_DIALOG_INPUT_TYPE_MAP_CODEC);
Registry.register(registry, "number_range", NUMBER_RANGE_INPUT_MAP_CODEC);
Registry.register(registry, "single_option", SINGLE_OPTION_DIALOG_INPUT_TYPE_MAP_CODEC);
Registry.register(registry, "text", TEXT_DIALOG_INPUT_TYPE_MAP_CODEC);
return registry.freeze();
});
private static final Function<DialogInput, MapCodec<? extends DialogInput>> GET_DIALOG_INPUT_TYPE_TYPE = dialogAction -> switch (dialogAction) {
case TextDialogInput $ -> TEXT_DIALOG_INPUT_TYPE_MAP_CODEC;
case SingleOptionDialogInput $ -> SINGLE_OPTION_DIALOG_INPUT_TYPE_MAP_CODEC;
case NumberRangeDialogInput $ -> NUMBER_RANGE_INPUT_MAP_CODEC;
case BooleanDialogInput $ -> BOOLEAN_DIALOG_INPUT_TYPE_MAP_CODEC;
};
private static final Codec<DialogInput> DIALOG_INPUT_CODEC = DIALOG_INPUT_TYPES.byNameCodec().dispatchMap(GET_DIALOG_INPUT_TYPE_TYPE, Function.identity()).codec();
// dialog base / common dialog data
public static final Codec<DialogBase> DIALOG_BASE_CODEC = RecordCodecBuilder.<DialogBase>mapCodec(instance -> instance.group(
AdventureCodecs.COMPONENT_CODEC.fieldOf("title").forGetter(DialogBase::title),
AdventureCodecs.COMPONENT_CODEC.optionalFieldOf("external_title").forGetter(base -> Optional.ofNullable(base.externalTitle())),
Codec.BOOL.optionalFieldOf("can_close_with_escape", true).forGetter(DialogBase::canCloseWithEscape),
Codec.BOOL.optionalFieldOf("pause", true).forGetter(DialogBase::pause),
AdventureCodecs.indexCodec(DialogBase.DialogAfterAction.NAMES).optionalFieldOf("after_action", DialogBase.DialogAfterAction.CLOSE).forGetter(DialogBase::afterAction),
DIALOG_BODY_LIST_CODEC.optionalFieldOf("body", List.of()).forGetter(DialogBase::body),
DIALOG_INPUT_CODEC.listOf().optionalFieldOf("inputs", List.of()).forGetter(DialogBase::inputs)
).apply(instance, (title, externalTitle, canCloseWithEsc, pause, afterAction, body, inputs) -> DialogBase.create(title, externalTitle.orElse(null), canCloseWithEsc, pause, afterAction, body, inputs))
).codec();
public static final Codec<io.papermc.paper.dialog.Dialog> DIALOG_CODEC = Codec.of(Dialog.CODEC.comap(PaperDialog::bukkitToMinecraftHolder), registryFileDecoderFor(Dialog.DIRECT_CODEC, PaperDialog::minecraftHolderToBukkit, RegistryKey.DIALOG, true));
}

View File

@@ -0,0 +1,155 @@
package io.papermc.paper.registry.data.dialog;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.adventure.providers.ClickCallbackProviderImpl;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.action.CommandTemplateActionImpl;
import io.papermc.paper.registry.data.dialog.action.CustomClickActionImpl;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import io.papermc.paper.registry.data.dialog.action.DialogActionCallback;
import io.papermc.paper.registry.data.dialog.action.StaticActionImpl;
import io.papermc.paper.registry.data.dialog.body.ItemDialogBody;
import io.papermc.paper.registry.data.dialog.body.ItemDialogBodyImpl;
import io.papermc.paper.registry.data.dialog.body.PlainMessageBodyImpl;
import io.papermc.paper.registry.data.dialog.body.PlainMessageDialogBody;
import io.papermc.paper.registry.data.dialog.input.BooleanDialogInput;
import io.papermc.paper.registry.data.dialog.input.BooleanDialogInputImpl;
import io.papermc.paper.registry.data.dialog.input.NumberRangeDialogInput;
import io.papermc.paper.registry.data.dialog.input.NumberRangeDialogInputImpl;
import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInput;
import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInputImpl;
import io.papermc.paper.registry.data.dialog.input.TextDialogInput;
import io.papermc.paper.registry.data.dialog.input.TextDialogInputImpl;
import io.papermc.paper.registry.data.dialog.type.ConfirmationType;
import io.papermc.paper.registry.data.dialog.type.ConfirmationTypeImpl;
import io.papermc.paper.registry.data.dialog.type.DialogListType;
import io.papermc.paper.registry.data.dialog.type.DialogListTypeImpl;
import io.papermc.paper.registry.data.dialog.type.MultiActionType;
import io.papermc.paper.registry.data.dialog.type.MultiActionTypeImpl;
import io.papermc.paper.registry.data.dialog.type.NoticeType;
import io.papermc.paper.registry.data.dialog.type.NoticeTypeImpl;
import io.papermc.paper.registry.data.dialog.type.ServerLinksType;
import io.papermc.paper.registry.data.dialog.type.ServerLinksTypeImpl;
import io.papermc.paper.registry.set.RegistrySet;
import java.util.List;
import java.util.UUID;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickCallback;
import net.kyori.adventure.text.event.ClickEvent;
import net.minecraft.core.UUIDUtil;
import net.minecraft.nbt.CompoundTag;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.Nullable;
public final class PaperDialogInstancesProvider implements DialogInstancesProvider {
@Override
public DialogBase.Builder dialogBaseBuilder(final Component title) {
return new DialogBaseImpl.BuilderImpl(title);
}
@Override
public ActionButton.Builder actionButtonBuilder(final Component label) {
return new ActionButtonImpl.BuilderImpl(label);
}
@Override
public DialogAction.CustomClickAction register(final DialogActionCallback callback, final ClickCallback.Options options) {
final UUID id = ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.addCallback(UUID.randomUUID(), callback, options);
final CompoundTag tag = new CompoundTag();
tag.store(ClickCallbackProviderImpl.ID_KEY, UUIDUtil.CODEC, id);
return DialogAction.customClick(ClickCallbackProviderImpl.DIALOG_CLICK_CALLBACK_KEY, BinaryTagHolder.encode(tag, PaperAdventure.NBT_CODEC));
}
@Override
public DialogAction.StaticAction staticAction(final ClickEvent value) {
return new StaticActionImpl(value);
}
@Override
public DialogAction.CommandTemplateAction commandTemplate(final String template) {
return new CommandTemplateActionImpl(template);
}
@Override
public DialogAction.CustomClickAction customClick(final Key id, final @Nullable BinaryTagHolder additions) {
return new CustomClickActionImpl(id, additions);
}
@Override
public ItemDialogBody.Builder itemDialogBodyBuilder(final ItemStack itemStack) {
return new ItemDialogBodyImpl.BuilderImpl(itemStack);
}
@Override
public PlainMessageDialogBody plainMessageDialogBody(final Component component) {
return new PlainMessageBodyImpl(component);
}
@Override
public PlainMessageDialogBody plainMessageDialogBody(final Component component, final int width) {
return new PlainMessageBodyImpl(component, width);
}
@Override
public BooleanDialogInput.Builder booleanBuilder(final String key, final Component label) {
return new BooleanDialogInputImpl.BuilderImpl(key, label);
}
@Override
public NumberRangeDialogInput.Builder numberRangeBuilder(final String key, final Component label, final float start, final float end) {
return new NumberRangeDialogInputImpl.BuilderImpl(key, label, start, end);
}
@Override
public SingleOptionDialogInput.Builder singleOptionBuilder(final String key, final Component label, final List<SingleOptionDialogInput.OptionEntry> entries) {
return new SingleOptionDialogInputImpl.BuilderImpl(key, entries, label);
}
@Override
public SingleOptionDialogInput.OptionEntry singleOptionEntry(final String id, final @Nullable Component display, final boolean initial) {
return new SingleOptionDialogInputImpl.SingleOptionEntryImpl(id, display, initial);
}
@Override
public TextDialogInput.Builder textBuilder(final String key, final Component label) {
return new TextDialogInputImpl.BuilderImpl(key, label);
}
@Override
public TextDialogInput.MultilineOptions multilineOptions(final @Nullable Integer maxLines, final @Nullable Integer height) {
return new TextDialogInputImpl.MultilineOptionsImpl(maxLines, height);
}
@Override
public ConfirmationType confirmation(final ActionButton yesButton, final ActionButton noButton) {
return new ConfirmationTypeImpl(yesButton, noButton);
}
@Override
public DialogListType.Builder dialogList(final RegistrySet<Dialog> dialogs) {
return new DialogListTypeImpl.BuilderImpl(dialogs);
}
@Override
public MultiActionType.Builder multiAction(final List<ActionButton> actions) {
return new MultiActionTypeImpl.BuilderImpl(actions);
}
@Override
public NoticeType notice() {
return new NoticeTypeImpl();
}
@Override
public NoticeType notice(final ActionButton action) {
return new NoticeTypeImpl(action);
}
@Override
public ServerLinksType serverLinks(final @Nullable ActionButton exitAction, final int columns, final int buttonWidth) {
return new ServerLinksTypeImpl(exitAction, columns, buttonWidth);
}
}

View File

@@ -0,0 +1,74 @@
package io.papermc.paper.registry.data.dialog;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.dialog.type.DialogType;
import io.papermc.paper.registry.data.util.Conversions;
import io.papermc.paper.registry.set.RegistryValueSetBuilder;
import io.papermc.paper.registry.set.RegistryValueSetBuilderImpl;
import net.minecraft.server.dialog.CommonDialogData;
import net.minecraft.server.dialog.Dialog;
import org.jspecify.annotations.Nullable;
import static io.papermc.paper.registry.data.util.Checks.asArgument;
import static io.papermc.paper.registry.data.util.Checks.asConfigured;
public class PaperDialogRegistryEntry implements DialogRegistryEntry {
protected @Nullable DialogBase dialogBase;
protected @Nullable DialogType dialogType;
protected final Conversions conversions;
public PaperDialogRegistryEntry(
final Conversions conversions,
final @Nullable Dialog internal
) {
this.conversions = conversions;
if (internal == null) return;
final CommonDialogData common = internal.common();
this.dialogBase = conversions.convert(common, PaperDialogCodecs.DIALOG_BASE_CODEC, CommonDialogData.MAP_CODEC.codec());
this.dialogType = PaperDialogs.extractSpecialty(internal, conversions);
}
@Override
public DialogBase base() {
return asConfigured(this.dialogBase, "dialogBase");
}
@Override
public DialogType type() {
return asConfigured(this.dialogType, "dialogSpecialty");
}
public static final class PaperBuilder extends PaperDialogRegistryEntry implements Builder, PaperRegistryBuilder<Dialog, io.papermc.paper.dialog.Dialog> {
public PaperBuilder(final Conversions conversions, final @Nullable Dialog internal) {
super(conversions, internal);
}
@Override
public RegistryValueSetBuilder<io.papermc.paper.dialog.Dialog, Builder> registryValueSet() {
return new RegistryValueSetBuilderImpl<>(RegistryKey.DIALOG, this.conversions);
}
@Override
public Builder base(final DialogBase dialogBase) {
this.dialogBase = asArgument(dialogBase, "dialogBase");
return this;
}
@Override
public Builder type(final DialogType dialogType) {
this.dialogType = asArgument(dialogType, "dialogSpecialty");
return this;
}
@Override
public Dialog build() {
return PaperDialogs.constructDialog(this.base(), this.type(), this.conversions);
}
}
}

View File

@@ -0,0 +1,84 @@
package io.papermc.paper.registry.data.dialog;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.dialog.type.ConfirmationType;
import io.papermc.paper.registry.data.dialog.type.DialogListType;
import io.papermc.paper.registry.data.dialog.type.DialogType;
import io.papermc.paper.registry.data.dialog.type.MultiActionType;
import io.papermc.paper.registry.data.dialog.type.NoticeType;
import io.papermc.paper.registry.data.dialog.type.ServerLinksType;
import io.papermc.paper.registry.data.util.Conversions;
import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.registry.set.RegistrySet;
import java.util.Optional;
import java.util.function.Function;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.dialog.CommonDialogData;
import net.minecraft.server.dialog.ConfirmationDialog;
import net.minecraft.server.dialog.Dialog;
import net.minecraft.server.dialog.DialogListDialog;
import net.minecraft.server.dialog.MultiActionDialog;
import net.minecraft.server.dialog.NoticeDialog;
import net.minecraft.server.dialog.ServerLinksDialog;
public final class PaperDialogs {
private PaperDialogs() {
}
public static DialogType extractSpecialty(final Dialog nmsDialog, final Conversions conversions) {
final Function<net.minecraft.server.dialog.ActionButton, ActionButton> convertButton = button -> conversions.convert(button, PaperDialogCodecs.ACTION_BUTTON_CODEC, net.minecraft.server.dialog.ActionButton.CODEC);
return switch (nmsDialog) {
case final ConfirmationDialog conf ->
DialogType.confirmation(convertButton.apply(conf.yesButton()), convertButton.apply(conf.noButton()));
case final DialogListDialog list -> {
final RegistrySet<io.papermc.paper.dialog.Dialog> apiSet = PaperRegistrySets.convertToApiWithDirects(RegistryKey.DIALOG, list.dialogs());
yield DialogType.dialogList(
apiSet,
list.exitAction().map(convertButton).orElse(null),
list.columns(),
list.buttonWidth()
);
}
case final MultiActionDialog multi ->
DialogType.multiAction(multi.actions().stream().map(convertButton).toList(), multi.exitAction().map(convertButton).orElse(null), multi.columns());
case final NoticeDialog notice -> DialogType.notice(convertButton.apply(notice.action()));
case final ServerLinksDialog links ->
DialogType.serverLinks(links.exitAction().map(convertButton).orElse(null), links.columns(), links.buttonWidth());
default -> throw new IllegalArgumentException("Unsupported dialog type: " + nmsDialog.getClass().getName());
};
}
public static Dialog constructDialog(final DialogBase dialogBase, final DialogType dialogType, final Conversions conversions) {
final Function<ActionButton, net.minecraft.server.dialog.ActionButton> convertButton = button -> conversions.convert(button, net.minecraft.server.dialog.ActionButton.CODEC, PaperDialogCodecs.ACTION_BUTTON_CODEC);
final CommonDialogData common = conversions.convert(dialogBase, CommonDialogData.MAP_CODEC.codec(), PaperDialogCodecs.DIALOG_BASE_CODEC);
switch (dialogType) {
case final ConfirmationType conf -> {
return new ConfirmationDialog(common, convertButton.apply(conf.yesButton()), convertButton.apply(conf.noButton()));
}
case final DialogListType list -> {
return new DialogListDialog(
common,
PaperRegistrySets.convertToNmsWithDirects(Registries.DIALOG, conversions.lookup(), list.dialogs()),
Optional.ofNullable(list.exitAction()).map(convertButton),
list.columns(),
list.buttonWidth()
);
}
case final MultiActionType multi -> {
return new MultiActionDialog(
common,
multi.actions().stream().map(convertButton).toList(),
Optional.ofNullable(multi.exitAction()).map(convertButton),
multi.columns()
);
}
case final NoticeType notice -> {
return new NoticeDialog(common, convertButton.apply(notice.action()));
}
case final ServerLinksType links -> {
return new ServerLinksDialog(common, Optional.ofNullable(links.exitAction()).map(convertButton), links.columns(), links.buttonWidth());
}
}
}
}

View File

@@ -0,0 +1,10 @@
package io.papermc.paper.registry.data.dialog.action;
import net.minecraft.commands.functions.StringTemplate;
public record CommandTemplateActionImpl(String template) implements DialogAction.CommandTemplateAction {
public CommandTemplateActionImpl {
StringTemplate.fromString(template);
}
}

View File

@@ -0,0 +1,24 @@
package io.papermc.paper.registry.data.dialog.action;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import io.papermc.paper.adventure.PaperAdventure;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import org.jspecify.annotations.Nullable;
public record CustomClickActionImpl(Key id, @Nullable BinaryTagHolder additions) implements DialogAction.CustomClickAction {
public CustomClickActionImpl {
if (additions != null) {
try {
final Tag tag = additions.get(PaperAdventure.NBT_CODEC);
Preconditions.checkArgument(tag instanceof CompoundTag, "Additions must be a compound tag");
} catch (final CommandSyntaxException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -0,0 +1,11 @@
package io.papermc.paper.registry.data.dialog.action;
import com.google.common.base.Preconditions;
import net.kyori.adventure.text.event.ClickEvent;
public record StaticActionImpl(ClickEvent value) implements DialogAction.StaticAction {
public StaticActionImpl {
Preconditions.checkArgument(value.action().readable(), "action must be readable");
}
}

View File

@@ -0,0 +1,63 @@
package io.papermc.paper.registry.data.dialog.body;
import com.google.common.base.Preconditions;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.Nullable;
public record ItemDialogBodyImpl(
ItemStack item, @Nullable PlainMessageDialogBody description, boolean showDecorations, boolean showTooltip, int width, int height
) implements ItemDialogBody {
public static final class BuilderImpl implements ItemDialogBody.Builder {
private final ItemStack item;
private @Nullable PlainMessageDialogBody description;
private boolean showDecorations = true;
private boolean showTooltip = true;
private int width = 16;
private int height = 16;
public BuilderImpl(final ItemStack item) {
net.minecraft.world.item.ItemStack.validateStrict(CraftItemStack.unwrap(item)).getOrThrow();
this.item = item;
}
@Override
public ItemDialogBody.Builder description(final @Nullable PlainMessageDialogBody description) {
this.description = description;
return this;
}
@Override
public ItemDialogBody.Builder showDecorations(final boolean showDecorations) {
this.showDecorations = showDecorations;
return this;
}
@Override
public ItemDialogBody.Builder showTooltip(final boolean showTooltip) {
this.showTooltip = showTooltip;
return this;
}
@Override
public ItemDialogBody.Builder width(final int width) {
Preconditions.checkArgument(width >= 1 && width <= 256, "Width must be between 1 and 256");
this.width = width;
return this;
}
@Override
public ItemDialogBody.Builder height(final int height) {
Preconditions.checkArgument(this.width >= 1 && this.width <= 256, "Width must be between 1 and 256");
this.height = height;
return this;
}
@Override
public ItemDialogBody build() {
return new ItemDialogBodyImpl(this.item, this.description, this.showDecorations, this.showTooltip, this.width, this.height);
}
}
}

View File

@@ -0,0 +1,16 @@
package io.papermc.paper.registry.data.dialog.body;
import com.google.common.base.Preconditions;
import net.kyori.adventure.text.Component;
import net.minecraft.server.dialog.body.PlainMessage;
public record PlainMessageBodyImpl(Component contents, int width) implements PlainMessageDialogBody {
public PlainMessageBodyImpl {
Preconditions.checkArgument(width >= 1 && width <= 1024, "Width must be between 1 and 1024");
}
public PlainMessageBodyImpl(final Component contents) {
this(contents, PlainMessage.DEFAULT_WIDTH);
}
}

View File

@@ -0,0 +1,46 @@
package io.papermc.paper.registry.data.dialog.input;
import com.google.common.base.Preconditions;
import net.kyori.adventure.text.Component;
import net.minecraft.commands.functions.StringTemplate;
public record BooleanDialogInputImpl(String key, Component label, boolean initial, String onTrue, String onFalse) implements BooleanDialogInput {
public static final class BuilderImpl implements BooleanDialogInput.Builder {
private final String key;
private final Component label;
private boolean initial = false;
private String onTrue = "true";
private String onFalse = "false";
public BuilderImpl(final String key, final Component label) {
Preconditions.checkArgument(StringTemplate.isValidVariableName(key), "key must be a valid input name");
this.key = key;
this.label = label;
}
@Override
public BooleanDialogInput.Builder initial(final boolean initial) {
this.initial = initial;
return this;
}
@Override
public BooleanDialogInput.Builder onTrue(final String onTrue) {
this.onTrue = onTrue;
return this;
}
@Override
public BooleanDialogInput.Builder onFalse(final String onFalse) {
this.onFalse = onFalse;
return this;
}
@Override
public BooleanDialogInput build() {
return new BooleanDialogInputImpl(this.key, this.label, this.initial, this.onTrue, this.onFalse);
}
}
}

View File

@@ -0,0 +1,73 @@
package io.papermc.paper.registry.data.dialog.input;
import com.google.common.base.Preconditions;
import net.kyori.adventure.text.Component;
import net.minecraft.commands.functions.StringTemplate;
import net.minecraft.server.dialog.body.PlainMessage;
import org.jspecify.annotations.Nullable;
public record NumberRangeDialogInputImpl(
String key,
int width,
Component label,
String labelFormat,
float start,
float end,
@Nullable Float initial,
@Nullable Float step
) implements NumberRangeDialogInput {
public static final class BuilderImpl implements NumberRangeDialogInput.Builder {
private final String key;
private final Component label;
private final float start;
private final float end;
private int width = PlainMessage.DEFAULT_WIDTH;
private String labelFormat = "options.generic_value";
private @Nullable Float initial = null;
private @Nullable Float step = null;
public BuilderImpl(final String key, final Component label, final float start, final float end) {
Preconditions.checkArgument(StringTemplate.isValidVariableName(key), "key must be a valid input name");
this.key = key;
this.label = label;
this.start = start;
this.end = end;
}
@Override
public BuilderImpl width(final int width) {
Preconditions.checkArgument(width >= 1 && width <= 1024, "width must be between 1 and 1024");
this.width = width;
return this;
}
@Override
public BuilderImpl labelFormat(final String labelFormat) {
this.labelFormat = labelFormat;
return this;
}
@Override
public BuilderImpl initial(final @Nullable Float initial) {
if (initial != null) {
Preconditions.checkArgument(initial >= this.start && initial <= this.end, "initial must be within the range");
}
this.initial = initial;
return this;
}
@Override
public BuilderImpl step(final @Nullable Float step) {
Preconditions.checkArgument(step == null || step > 0, "step must be null or greater than 0");
this.step = step;
return this;
}
@Override
public NumberRangeDialogInput build() {
return new NumberRangeDialogInputImpl(this.key, this.width, this.label, this.labelFormat, this.start, this.end, this.initial, this.step);
}
}
}

View File

@@ -0,0 +1,60 @@
package io.papermc.paper.registry.data.dialog.input;
import com.google.common.base.Preconditions;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.minecraft.commands.functions.StringTemplate;
import net.minecraft.server.dialog.body.PlainMessage;
import org.jetbrains.annotations.Nullable;
public record SingleOptionDialogInputImpl(
String key,
int width,
List<SingleOptionDialogInput.OptionEntry> entries,
Component label,
boolean labelVisible
) implements SingleOptionDialogInput {
public SingleOptionDialogInputImpl {
entries = List.copyOf(entries);
}
public record SingleOptionEntryImpl(String id, @Nullable Component display, boolean initial) implements OptionEntry {
}
public static final class BuilderImpl implements SingleOptionDialogInput.Builder {
private final String key;
private int width = PlainMessage.DEFAULT_WIDTH;
private final List<OptionEntry> entries;
private final Component label;
private boolean labelVisible = true;
public BuilderImpl(final String key, final List<OptionEntry> entries, final Component label) {
Preconditions.checkArgument(StringTemplate.isValidVariableName(key), "key must be a valid input name");
this.key = key;
Preconditions.checkArgument(!entries.isEmpty(), "entries must not be empty");
Preconditions.checkArgument(entries.stream().filter(OptionEntry::initial).count() <= 1, "only 1 option can be initially selected");
this.entries = entries;
this.label = label;
}
@Override
public BuilderImpl width(final int width) {
Preconditions.checkArgument(width >= 1 && width <= 1024, "width must be between 1 and 1024");
this.width = width;
return this;
}
@Override
public BuilderImpl labelVisible(final boolean labelVisible) {
this.labelVisible = labelVisible;
return this;
}
@Override
public SingleOptionDialogInput build() {
return new SingleOptionDialogInputImpl(this.key, this.width, this.entries, this.label, this.labelVisible);
}
}
}

View File

@@ -0,0 +1,81 @@
package io.papermc.paper.registry.data.dialog.input;
import com.google.common.base.Preconditions;
import net.kyori.adventure.text.Component;
import net.minecraft.commands.functions.StringTemplate;
import net.minecraft.server.dialog.body.PlainMessage;
import net.minecraft.server.dialog.input.TextInput;
import org.jspecify.annotations.Nullable;
public record TextDialogInputImpl(
String key,
int width,
Component label,
boolean labelVisible,
String initial,
int maxLength,
TextDialogInput.@Nullable MultilineOptions multiline
) implements TextDialogInput {
public record MultilineOptionsImpl(@Nullable Integer maxLines, @Nullable Integer height) implements MultilineOptions {
public MultilineOptionsImpl {
Preconditions.checkArgument(maxLines == null || maxLines > 0, "maxLines must be null or greater than 0");
Preconditions.checkArgument(height == null || (height >= 1 && height <= TextInput.MultilineOptions.MAX_HEIGHT), "height must be null or between 1 and 512");
}
}
public static final class BuilderImpl implements TextDialogInput.Builder {
private final String key;
private int width = PlainMessage.DEFAULT_WIDTH;
private final Component label;
private boolean labelVisible = true;
private String initial = "";
private int maxLength = 32;
private TextDialogInput.@Nullable MultilineOptions multiline = null;
public BuilderImpl(final String key, final Component label) {
Preconditions.checkArgument(StringTemplate.isValidVariableName(key), "key must be a valid input name");
this.key = key;
this.label = label;
}
@Override
public TextDialogInput.Builder width(final int width) {
Preconditions.checkArgument(width >= 1 && width <= 1024, "width must be between 1 and 1024");
this.width = width;
return this;
}
@Override
public TextDialogInput.Builder labelVisible(final boolean labelVisible) {
this.labelVisible = labelVisible;
return this;
}
@Override
public TextDialogInput.Builder initial(final String initial) {
this.initial = initial;
return this;
}
@Override
public TextDialogInput.Builder maxLength(final int maxLength) {
Preconditions.checkArgument(maxLength > 0, "maxLength must be greater than 0");
this.maxLength = maxLength;
return this;
}
@Override
public TextDialogInput.Builder multiline(final TextDialogInput.@Nullable MultilineOptions multiline) {
this.multiline = multiline;
return this;
}
@Override
public TextDialogInput build() {
Preconditions.checkState(this.initial.length() <= this.maxLength, "The initial value must be less than or equal to the maximum length.");
return new TextDialogInputImpl(this.key, this.width, this.label, this.labelVisible, this.initial, this.maxLength, this.multiline);
}
}
}

View File

@@ -0,0 +1,6 @@
package io.papermc.paper.registry.data.dialog.type;
import io.papermc.paper.registry.data.dialog.ActionButton;
public record ConfirmationTypeImpl(ActionButton yesButton, ActionButton noButton) implements ConfirmationType {
}

View File

@@ -0,0 +1,53 @@
package io.papermc.paper.registry.data.dialog.type;
import com.google.common.base.Preconditions;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.ActionButton;
import io.papermc.paper.registry.set.RegistrySet;
import net.minecraft.server.dialog.CommonButtonData;
import org.jspecify.annotations.Nullable;
public record DialogListTypeImpl(
RegistrySet<Dialog> dialogs,
@Nullable ActionButton exitAction,
int columns,
int buttonWidth
) implements DialogListType {
public static final class BuilderImpl implements DialogListType.Builder {
private final RegistrySet<Dialog> dialogs;
private @Nullable ActionButton exitAction;
private int columns = 2;
private int buttonWidth = CommonButtonData.DEFAULT_WIDTH;
public BuilderImpl(final RegistrySet<Dialog> dialogs) {
this.dialogs = dialogs;
}
@Override
public DialogListType.Builder exitAction(final @Nullable ActionButton exitAction) {
this.exitAction = exitAction;
return this;
}
@Override
public DialogListType.Builder columns(final int columns) {
Preconditions.checkArgument(columns > 0, "columns must be greater than 0");
this.columns = columns;
return this;
}
@Override
public DialogListType.Builder buttonWidth(final int buttonWidth) {
Preconditions.checkArgument(buttonWidth >= 1 && buttonWidth <= 1024, "buttonWidth must be between 1 and 1024");
this.buttonWidth = buttonWidth;
return this;
}
@Override
public DialogListType build() {
return new DialogListTypeImpl(this.dialogs, this.exitAction, this.columns, this.buttonWidth);
}
}
}

View File

@@ -0,0 +1,46 @@
package io.papermc.paper.registry.data.dialog.type;
import com.google.common.base.Preconditions;
import io.papermc.paper.registry.data.dialog.ActionButton;
import java.util.List;
import org.jspecify.annotations.Nullable;
public record MultiActionTypeImpl(
List<ActionButton> actions,
@Nullable ActionButton exitAction,
int columns
) implements MultiActionType {
public MultiActionTypeImpl {
actions = List.copyOf(actions);
}
public static final class BuilderImpl implements MultiActionType.Builder {
private final List<ActionButton> actions;
private @Nullable ActionButton exitAction = null;
private int columns = 2;
public BuilderImpl(final List<ActionButton> actions) {
Preconditions.checkArgument(!actions.isEmpty(), "actions cannot be empty");
this.actions = actions;
}
@Override
public Builder exitAction(final @org.jetbrains.annotations.Nullable ActionButton exitAction) {
this.exitAction = exitAction;
return this;
}
@Override
public Builder columns(final int columns) {
Preconditions.checkArgument(columns > 0, "columns must be greater than 0");
this.columns = columns;
return this;
}
@Override
public MultiActionType build() {
return new MultiActionTypeImpl(this.actions, this.exitAction, this.columns);
}
}
}

View File

@@ -0,0 +1,15 @@
package io.papermc.paper.registry.data.dialog.type;
import io.papermc.paper.registry.data.dialog.ActionButton;
import net.minecraft.server.dialog.CommonButtonData;
import static net.kyori.adventure.text.Component.translatable;
public record NoticeTypeImpl(ActionButton action) implements NoticeType {
public static final ActionButton DEFAULT_ACTION = ActionButton.builder(translatable("gui.ok")).width(CommonButtonData.DEFAULT_WIDTH).build();
public NoticeTypeImpl() {
this(DEFAULT_ACTION);
}
}

View File

@@ -0,0 +1,17 @@
package io.papermc.paper.registry.data.dialog.type;
import com.google.common.base.Preconditions;
import io.papermc.paper.registry.data.dialog.ActionButton;
import org.jspecify.annotations.Nullable;
public record ServerLinksTypeImpl(
@Nullable ActionButton exitAction,
int columns,
int buttonWidth
) implements ServerLinksType {
public ServerLinksTypeImpl {
Preconditions.checkArgument(columns > 0, "columns must be greater than 0");
Preconditions.checkArgument(buttonWidth >= 1 && buttonWidth <= 1024, "buttonWidth must be between 1 and 1024");
}
}

View File

@@ -1,12 +1,14 @@
package io.papermc.paper.registry.data.util;
import com.google.common.base.Preconditions;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JavaOps;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.adventure.WrapperAwareSerializer;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.PaperRegistryBuilderFactory;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.client.ClientTextureAsset;
import io.papermc.paper.registry.entry.RegistryEntryMeta;
import java.util.function.Consumer;
@@ -36,10 +38,20 @@ public class Conversions {
private final RegistryOps.RegistryInfoLookup lookup;
private final WrapperAwareSerializer serializer;
private final RegistryOps<Object> javaOps;
public Conversions(final RegistryOps.RegistryInfoLookup lookup) {
this.lookup = lookup;
this.serializer = new WrapperAwareSerializer(() -> RegistryOps.create(JavaOps.INSTANCE, lookup));
this.javaOps = RegistryOps.create(JavaOps.INSTANCE, lookup);
}
public <OUT, IN> OUT convert(final IN in, final Codec<OUT> outCodec, final Codec<IN> inCodec) {
final Object obj = inCodec.encodeStart(this.javaOps, in)
.getOrThrow(s -> new RuntimeException("Failed to encode input: " + in + "; " + s));
return outCodec.decode(this.javaOps, obj)
.getOrThrow(s -> new RuntimeException("Failed to decode to output: " + obj + "; " + s))
.getFirst();
}
public RegistryOps.RegistryInfoLookup lookup() {
@@ -74,20 +86,20 @@ public class Conversions {
);
}
private static <M, A extends Keyed, B extends PaperRegistryBuilder<M, A>> RegistryEntryMeta.Buildable<M, A, B> getDirectHolderBuildableMeta(final ResourceKey<? extends Registry<M>> registryKey) {
private static <M, A extends Keyed, B extends PaperRegistryBuilder<M, A>> RegistryEntryMeta.Buildable<M, A, B> getDirectHolderBuildableMeta(final RegistryKey<A> registryKey) {
final RegistryEntryMeta.Buildable<M, A, B> buildableMeta = PaperRegistries.getBuildableMeta(registryKey);
Preconditions.checkArgument(buildableMeta.registryTypeMapper().supportsDirectHolders(), "Registry type mapper must support direct holders");
return buildableMeta;
}
public <M, A extends Keyed, B extends PaperRegistryBuilder<M, A>> A createApiInstanceFromBuilder(final ResourceKey<? extends Registry<M>> registryKey, final Consumer<? super PaperRegistryBuilderFactory<M, A, B>> value) {
public <M, A extends Keyed, B extends PaperRegistryBuilder<M, A>> A createApiInstanceFromBuilder(final RegistryKey<A> registryKey, final Consumer<? super PaperRegistryBuilderFactory<M, A, B>> value) {
final RegistryEntryMeta.Buildable<M, A, B> meta = getDirectHolderBuildableMeta(registryKey);
final PaperRegistryBuilderFactory<M, A, B> builderFactory = this.createRegistryBuilderFactory(registryKey, meta);
value.accept(builderFactory);
return meta.registryTypeMapper().createBukkit(Holder.direct(builderFactory.requireBuilder().build()));
}
public <M, A extends Keyed, B extends PaperRegistryBuilder<M, A>> Holder<M> createHolderFromBuilder(final ResourceKey<? extends Registry<M>> registryKey, final Consumer<? super PaperRegistryBuilderFactory<M, A, B>> value) {
public <M, A extends Keyed, B extends PaperRegistryBuilder<M, A>> Holder<M> createHolderFromBuilder(final RegistryKey<A> registryKey, final Consumer<? super PaperRegistryBuilderFactory<M, A, B>> value) {
final RegistryEntryMeta.Buildable<M, A, B> meta = getDirectHolderBuildableMeta(registryKey);
final PaperRegistryBuilderFactory<M, A, B> builderFactory = this.createRegistryBuilderFactory(registryKey, meta);
value.accept(builderFactory);
@@ -95,11 +107,12 @@ public class Conversions {
}
private <M, A extends Keyed, B extends PaperRegistryBuilder<M, A>> PaperRegistryBuilderFactory<M, A, B> createRegistryBuilderFactory(
final ResourceKey<? extends Registry<M>> registryKey,
final RegistryKey<A> registryKey,
final RegistryEntryMeta.Buildable<M, A, B> buildableMeta
) {
final HolderLookup.RegistryLookup<M> lookupForBuilders = this.lookup.lookupForValueCopyViaBuilders().lookupOrThrow(registryKey);
return new PaperRegistryBuilderFactory<>(registryKey, this, buildableMeta.builderFiller(), lookupForBuilders::getValueForCopying);
final ResourceKey<? extends Registry<M>> resourceRegistryKey = PaperRegistries.registryToNms(registryKey);
final HolderLookup.RegistryLookup<M> lookupForBuilders = this.lookup.lookupForValueCopyViaBuilders().lookupOrThrow(resourceRegistryKey);
return new PaperRegistryBuilderFactory<>(resourceRegistryKey, this, buildableMeta.builderFiller(), lookupForBuilders::getValueForCopying);
}
}

View File

@@ -37,10 +37,6 @@ public class RegistryEntryBuilder<M, A extends Keyed> { // TODO remove Keyed
return new RegistryEntryImpl<>(new RegistryEntryMeta.ApiOnly<>(this.mcKey, this.apiKey, apiRegistrySupplier));
}
public CraftStage<M, A> craft(final Class<?> classToPreload, final BiFunction<? super NamespacedKey, M, ? extends A> minecraftToBukkit) {
return new CraftStage<>(this.mcKey, this.apiKey, classToPreload, new RegistryTypeMapper<>(minecraftToBukkit));
}
public CraftStage<M, A> craft(final Class<?> classToPreload, final Function<Holder<M>, ? extends A> minecraftToBukkit) {
return this.craft(classToPreload, minecraftToBukkit, false);
}

View File

@@ -18,9 +18,7 @@ import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public record NamedRegistryKeySetImpl<T extends Keyed, M>( // TODO remove Keyed
TagKey<T> tagKey,
HolderSet.Named<M> namedSet

View File

@@ -3,6 +3,7 @@ package io.papermc.paper.registry.set;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.util.Holderable;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.core.Holder;
@@ -11,6 +12,7 @@ import net.minecraft.core.Registry;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import org.bukkit.Keyed;
import org.bukkit.craftbukkit.CraftRegistry;
public final class PaperRegistrySets {
@@ -25,6 +27,30 @@ public final class PaperRegistrySets {
}
}
public static <A extends Keyed, M> HolderSet<M> convertToNmsWithDirects(final ResourceKey<? extends Registry<M>> resourceKey, final RegistryOps.RegistryInfoLookup lookup, final RegistrySet<A> registrySet) { // TODO remove Keyed
if (registrySet instanceof NamedRegistryKeySetImpl<A, ?>) {
return ((NamedRegistryKeySetImpl<A, M>) registrySet).namedSet();
} else if (registrySet.isEmpty()) {
return HolderSet.empty();
} else if (registrySet instanceof final RegistryValueSet<A> valueSet) {
final List<Holder<M>> directs = new ArrayList<>(valueSet.values().size());
for (final A value : valueSet) {
if (!(value instanceof final Holderable<?> holderable)) {
throw new UnsupportedOperationException("Cannot convert a registry set containing non-holderable values");
}
directs.add(((Holderable<M>) holderable).getHolder());
}
return HolderSet.direct(directs);
} else if (registrySet instanceof final RegistryKeySet<A> keySet) {
final RegistryOps.RegistryInfo<M> registryInfo = lookup.lookup(resourceKey).orElseThrow();
return HolderSet.direct(key -> {
return registryInfo.getter().getOrThrow(PaperRegistries.toNms(key));
}, keySet.values());
} else {
throw new UnsupportedOperationException("Cannot convert a registry set of type " + registrySet.getClass().getName());
}
}
public static <A extends Keyed, M> RegistryKeySet<A> convertToApi(final RegistryKey<A> registryKey, final HolderSet<M> holders) { // TODO remove Keyed
if (holders instanceof final HolderSet.Named<M> named) {
return new NamedRegistryKeySetImpl<>(PaperRegistries.fromNms(named.key()), named);
@@ -40,6 +66,30 @@ public final class PaperRegistrySets {
}
}
public static <A extends Keyed, M> RegistrySet<A> convertToApiWithDirects(final RegistryKey<A> registryKey, final HolderSet<M> holders) { // TODO remove Keyed
if (holders instanceof final HolderSet.Named<M> named) {
return new NamedRegistryKeySetImpl<>(PaperRegistries.fromNms(named.key()), named);
} else {
if (holders.size() == 0) {
return RegistrySet.keySet(registryKey);
}
if (holders.get(0) instanceof Holder.Direct<M>) {
final List<A> directs = new ArrayList<>(holders.size());
final ResourceKey<? extends Registry<M>> nmsRegistryKey = PaperRegistries.registryToNms(registryKey);
for (final Holder<M> holder : holders) {
directs.add(CraftRegistry.minecraftHolderToBukkit(holder, nmsRegistryKey));
}
return RegistrySet.valueSet(registryKey, directs);
} else {
final List<TypedKey<A>> keys = new ArrayList<>(holders.size());
for (final Holder<M> holder : holders) {
keys.add(PaperRegistries.fromNms(((Holder.Reference<M>) holder).key()));
}
return RegistrySet.keySet(registryKey, keys);
}
}
}
private PaperRegistrySets() {
}
}

View File

@@ -0,0 +1,33 @@
package io.papermc.paper.registry.set;
import io.papermc.paper.registry.RegistryBuilder;
import io.papermc.paper.registry.RegistryBuilderFactory;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.util.Conversions;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.bukkit.Keyed;
public class RegistryValueSetBuilderImpl<M, API extends Keyed, BUILDER extends RegistryBuilder<API>> implements RegistryValueSetBuilder<API, BUILDER> { // TODO remove Keyed
private final RegistryKey<API> registryKey;
private final Conversions conversions;
private final List<API> instances = new ArrayList<>();
public RegistryValueSetBuilderImpl(final RegistryKey<API> registryKey, final Conversions conversions) {
this.registryKey = registryKey;
this.conversions = conversions;
}
@Override
public RegistryValueSetBuilder<API, BUILDER> add(final Consumer<RegistryBuilderFactory<API, ? extends BUILDER>> builder) {
this.instances.add(this.conversions.createApiInstanceFromBuilder(this.registryKey, builder));
return this;
}
@Override
public RegistryValueSet<API> build() {
return RegistrySet.valueSet(this.registryKey, this.instances);
}
}

View File

@@ -0,0 +1,54 @@
package io.papermc.paper.util;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.Decoder;
import com.mojang.serialization.DynamicOps;
import io.papermc.paper.adventure.AdventureCodecs;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import java.util.function.Function;
import net.kyori.adventure.key.Key;
import net.minecraft.core.Holder;
import net.minecraft.resources.RegistryOps;
import org.bukkit.Keyed;
import org.bukkit.Registry;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class PaperCodecs {
public static <A extends Keyed, M> Decoder<A> registryFileDecoderFor(final Decoder<? extends M> directNmsDecoder, final Function<? super Holder<M>, A> directHolderConverter, final RegistryKey<A> registryKey, final boolean allowInline) { // TODO remove Keyed
final Decoder.Terminal<A> terminalDecoder = directNmsDecoder.map(nms -> directHolderConverter.apply(Holder.direct(nms))).terminal();
return Decoder.ofTerminal(new Decoder.Terminal<>() {
@Override
public <T> DataResult<A> decode(final DynamicOps<T> ops, final T input) {
// logic based on RegistryFileCodec
if (ops instanceof RegistryOps<?>) {
// Pretty sure we can just use our RegistryAccess here. These codecs aren't ever
// used for deserialization, so we don't need to rely on different implementations
// of HolderGetter like vanilla's RegistryFileCodec does. We are always just
// getting existing dialog elements, never creating empty reference holders.
final Registry<A> registry = RegistryAccess.registryAccess().getRegistry(registryKey);
final DataResult<Pair<Key, T>> keyDataResult = AdventureCodecs.KEY_CODEC.decode(ops, input);
if (keyDataResult.result().isEmpty()) {
return !allowInline ? DataResult.error(() -> "Inline definitions not allowed here") : terminalDecoder.decode(ops, input);
}
final Pair<Key, T> pair = keyDataResult.result().get();
final TypedKey<A> elementKey = TypedKey.create(registryKey, pair.getFirst());
final A value = registry.get(elementKey);
if (value == null) {
return DataResult.error(() -> "Failed to get element " + elementKey);
}
return DataResult.success(value);
} else {
return terminalDecoder.decode(ops, input);
}
}
});
}
private PaperCodecs() {
}
}

View File

@@ -25,7 +25,7 @@ public class CraftArt extends OldEnumHolderable<Art, PaintingVariant> implements
}
public static Holder<PaintingVariant> bukkitToMinecraftHolder(Art bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.PAINTING_VARIANT);
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public CraftArt(Holder<PaintingVariant> paintingVariant) {

View File

@@ -21,7 +21,7 @@ public class CraftJukeboxSong extends HolderableBase<net.minecraft.world.item.Ju
}
public static Holder<net.minecraft.world.item.JukeboxSong> bukkitToMinecraftHolder(JukeboxSong bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.JUKEBOX_SONG);
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public CraftJukeboxSong(final Holder<net.minecraft.world.item.JukeboxSong> holder) {

View File

@@ -28,7 +28,7 @@ public class CraftMusicInstrument extends MusicInstrument implements io.papermc.
}
public static Holder<Instrument> bukkitToMinecraftHolder(MusicInstrument bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.INSTRUMENT);
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public static Object bukkitToString(MusicInstrument bukkit) {

View File

@@ -99,28 +99,18 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
* @param bukkit the bukkit representation
* @return the minecraft representation of the bukkit value
*/
public static <B extends Keyed, M> M bukkitToMinecraft(B bukkit) {
@SuppressWarnings("unchecked")
public static <B extends Keyed, M> M bukkitToMinecraft(final B bukkit) {
Preconditions.checkArgument(bukkit != null);
return ((Handleable<M>) bukkit).getHandle();
}
public static <B extends Keyed, M> Holder<M> bukkitToMinecraftHolder(B bukkit, ResourceKey<net.minecraft.core.Registry<M>> registryKey) {
@SuppressWarnings("unchecked")
public static <B extends Keyed, M> Holder<M> bukkitToMinecraftHolder(final B bukkit) {
Preconditions.checkArgument(bukkit != null);
// Paper start - support direct Holder
if (bukkit instanceof io.papermc.paper.util.Holderable<?>) {
return ((io.papermc.paper.util.Holderable<M>) bukkit).getHolder();
}
// Paper end - support direct Holder
net.minecraft.core.Registry<M> registry = CraftRegistry.getMinecraftRegistry(registryKey);
if (registry.wrapAsHolder(CraftRegistry.bukkitToMinecraft(bukkit)) instanceof Holder.Reference<M> holder) {
return holder;
}
throw new IllegalArgumentException("No Reference holder found for " + bukkit
+ ", this can happen if a plugin creates its own registry entry with out properly registering it.");
return ((Holderable<M>) bukkit).getHolder();
}
// Paper start - fixup upstream being dum

View File

@@ -23,7 +23,7 @@ public class CraftSound extends OldEnumHolderable<Sound, SoundEvent> implements
}
public static Holder<SoundEvent> bukkitToMinecraftHolder(Sound bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.SOUND_EVENT);
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public CraftSound(Holder<SoundEvent> soundEffect) {

View File

@@ -43,7 +43,7 @@ public class CraftAttribute extends OldEnumHolderable<Attribute, net.minecraft.w
}
public static Holder<net.minecraft.world.entity.ai.attributes.Attribute> bukkitToMinecraftHolder(Attribute bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.ATTRIBUTE);
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public static String bukkitToString(Attribute bukkit) {

View File

@@ -36,7 +36,7 @@ public class CraftBiome extends OldEnumHolderable<Biome, net.minecraft.world.lev
if (bukkit == Biome.CUSTOM) {
return null;
}
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.BIOME);
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public CraftBiome(final Holder<net.minecraft.world.level.biome.Biome> holder) {

View File

@@ -24,7 +24,7 @@ public class CraftPatternType extends OldEnumHolderable<PatternType, BannerPatte
}
public static Holder<BannerPattern> bukkitToMinecraftHolder(PatternType bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.BANNER_PATTERN);
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public CraftPatternType(Holder<BannerPattern> bannerPatternType) {

View File

@@ -79,7 +79,7 @@ public class CraftDamageType extends HolderableBase<net.minecraft.world.damageso
}
public static Holder<net.minecraft.world.damagesource.DamageType> bukkitToMinecraftHolder(DamageType bukkitDamageType) {
return CraftRegistry.bukkitToMinecraftHolder(bukkitDamageType, Registries.DAMAGE_TYPE);
return CraftRegistry.bukkitToMinecraftHolder(bukkitDamageType);
}
public static net.minecraft.world.damagesource.DamageType bukkitToMinecraft(DamageType bukkitDamageType) {

Some files were not shown because too many files have changed in this diff Show More