Merge remote-tracking branch 'upstream/main' into update/1.21.4
This commit is contained in:
@@ -30,6 +30,6 @@ public class CraftGameEventTag extends CraftTag<net.minecraft.world.level.gameev
|
||||
|
||||
@Override
|
||||
public @NotNull Set<GameEvent> getValues() {
|
||||
return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.GAME_EVENT.getKey(nms.value()))), nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet());
|
||||
return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.GAME_EVENT.getKey(nms.value()))), () -> nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,20 @@ public class DataComponentValueConverterProviderImpl implements DataComponentVal
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
),
|
||||
DataComponentValueConverterRegistry.Conversion.convert(
|
||||
DataComponentValue.TagSerializable.class,
|
||||
GsonDataComponentValue.class,
|
||||
(key, tagSerializable) -> {
|
||||
Tag decodedSnbt;
|
||||
try {
|
||||
decodedSnbt = tagSerializable.asBinaryTag().get(PaperAdventure.NBT_CODEC);
|
||||
} catch (final CommandSyntaxException e) {
|
||||
throw new IllegalArgumentException("Unable to parse SNBT value", e);
|
||||
}
|
||||
|
||||
return GsonDataComponentValue.gsonDataComponentValue(NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, decodedSnbt));
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ import io.papermc.paper.plugin.provider.ProviderStatus;
|
||||
import io.papermc.paper.plugin.provider.ProviderStatusHolder;
|
||||
import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent;
|
||||
import io.papermc.paper.plugin.provider.type.spigot.SpigotPluginProvider;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
@@ -23,51 +28,38 @@ import org.bukkit.command.defaults.BukkitCommand;
|
||||
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public class PaperPluginsCommand extends BukkitCommand {
|
||||
|
||||
private static final TextColor INFO_COLOR = TextColor.color(52, 159, 218);
|
||||
|
||||
// TODO: LINK?
|
||||
private static final Component SERVER_PLUGIN_INFO = Component.text("ℹ What is a server plugin?", INFO_COLOR)
|
||||
.append(asPlainComponents("""
|
||||
Server plugins can add new behavior to your server!
|
||||
You can find new plugins on Paper's plugin repository, Hangar.
|
||||
|
||||
<link to hangar>
|
||||
"""));
|
||||
|
||||
private static final Component SERVER_INITIALIZER_INFO = Component.text("ℹ What is a server initializer?", INFO_COLOR)
|
||||
.append(asPlainComponents("""
|
||||
Server initializers are ran before your server
|
||||
starts and are provided by paper plugins.
|
||||
"""));
|
||||
Server plugins can add new behavior to your server!
|
||||
You can find new plugins on Paper's plugin repository, Hangar.
|
||||
|
||||
https://hangar.papermc.io/
|
||||
"""));
|
||||
|
||||
private static final Component LEGACY_PLUGIN_INFO = Component.text("ℹ What is a legacy plugin?", INFO_COLOR)
|
||||
.append(asPlainComponents("""
|
||||
A legacy plugin is a plugin that was made on
|
||||
very old unsupported versions of the game.
|
||||
|
||||
It is encouraged that you replace this plugin,
|
||||
as they might not work in the future and may cause
|
||||
performance issues.
|
||||
"""));
|
||||
A legacy plugin is a plugin that was made on
|
||||
very old unsupported versions of the game.
|
||||
|
||||
It is encouraged that you replace this plugin,
|
||||
as they might not work in the future and may cause
|
||||
performance issues.
|
||||
"""));
|
||||
|
||||
private static final Component LEGACY_PLUGIN_STAR = Component.text('*', TextColor.color(255, 212, 42)).hoverEvent(LEGACY_PLUGIN_INFO);
|
||||
private static final Component INFO_ICON_START = Component.text("ℹ ", INFO_COLOR);
|
||||
private static final Component PAPER_HEADER = Component.text("Paper Plugins:", TextColor.color(2, 136, 209));
|
||||
private static final Component BUKKIT_HEADER = Component.text("Bukkit Plugins:", TextColor.color(237, 129, 6));
|
||||
private static final Component PLUGIN_TICK = Component.text("- ", NamedTextColor.DARK_GRAY);
|
||||
private static final Component PLUGIN_TICK_EMPTY = Component.text(" ");
|
||||
|
||||
private static final Component INFO_ICON_SERVER_PLUGIN = INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO).clickEvent(ClickEvent.openUrl("https://docs.papermc.io/paper/adding-plugins"));
|
||||
|
||||
private static final Type JAVA_PLUGIN_PROVIDER_TYPE = new TypeToken<PluginProvider<JavaPlugin>>() {}.getType();
|
||||
|
||||
public PaperPluginsCommand() {
|
||||
@@ -75,17 +67,17 @@ public class PaperPluginsCommand extends BukkitCommand {
|
||||
this.description = "Gets a list of plugins running on the server";
|
||||
this.usageMessage = "/plugins";
|
||||
this.setPermission("bukkit.command.plugins");
|
||||
this.setAliases(Arrays.asList("pl"));
|
||||
this.setAliases(List.of("pl"));
|
||||
}
|
||||
|
||||
private static <T> List<Component> formatProviders(TreeMap<String, PluginProvider<T>> plugins) {
|
||||
List<Component> components = new ArrayList<>(plugins.size());
|
||||
for (PluginProvider<T> entry : plugins.values()) {
|
||||
private static <T> List<Component> formatProviders(final TreeMap<String, PluginProvider<T>> plugins) {
|
||||
final List<Component> components = new ArrayList<>(plugins.size());
|
||||
for (final PluginProvider<T> entry : plugins.values()) {
|
||||
components.add(formatProvider(entry));
|
||||
}
|
||||
|
||||
boolean isFirst = true;
|
||||
List<Component> formattedSublists = new ArrayList<>();
|
||||
final List<Component> formattedSubLists = new ArrayList<>();
|
||||
/*
|
||||
Split up the plugin list for each 10 plugins to get size down
|
||||
|
||||
@@ -93,30 +85,29 @@ public class PaperPluginsCommand extends BukkitCommand {
|
||||
- Plugin 1, Plugin 2, .... Plugin 10,
|
||||
Plugin 11, Plugin 12 ... Plugin 20,
|
||||
*/
|
||||
for (List<Component> componentSublist : Lists.partition(components, 10)) {
|
||||
for (final List<Component> componentSublist : Lists.partition(components, 10)) {
|
||||
Component component = Component.space();
|
||||
if (isFirst) {
|
||||
component = component.append(PLUGIN_TICK);
|
||||
isFirst = false;
|
||||
} else {
|
||||
component = PLUGIN_TICK_EMPTY;
|
||||
//formattedSublists.add(Component.empty()); // Add an empty line, the auto chat wrapping and this makes it quite jarring.
|
||||
}
|
||||
|
||||
formattedSublists.add(component.append(Component.join(JoinConfiguration.commas(true), componentSublist)));
|
||||
formattedSubLists.add(component.append(Component.join(JoinConfiguration.commas(true), componentSublist)));
|
||||
}
|
||||
|
||||
return formattedSublists;
|
||||
return formattedSubLists;
|
||||
}
|
||||
|
||||
private static Component formatProvider(PluginProvider<?> provider) {
|
||||
TextComponent.Builder builder = Component.text();
|
||||
if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) {
|
||||
private static Component formatProvider(final PluginProvider<?> provider) {
|
||||
final TextComponent.Builder builder = Component.text();
|
||||
if (provider instanceof final SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) {
|
||||
builder.append(LEGACY_PLUGIN_STAR);
|
||||
}
|
||||
|
||||
String name = provider.getMeta().getName();
|
||||
Component pluginName = Component.text(name, fromStatus(provider))
|
||||
final String name = provider.getMeta().getName();
|
||||
final Component pluginName = Component.text(name, fromStatus(provider))
|
||||
.clickEvent(ClickEvent.runCommand("/version " + name));
|
||||
|
||||
builder.append(pluginName);
|
||||
@@ -124,9 +115,20 @@ public class PaperPluginsCommand extends BukkitCommand {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Component asPlainComponents(String strings) {
|
||||
net.kyori.adventure.text.TextComponent.Builder builder = Component.text();
|
||||
for (String string : strings.split("\n")) {
|
||||
private static Component header(final String header, final int color, final int count, final boolean showSize) {
|
||||
final TextComponent.Builder componentHeader = Component.text().color(TextColor.color(color))
|
||||
.append(Component.text(header));
|
||||
|
||||
if (showSize) {
|
||||
componentHeader.appendSpace().append(Component.text("(" + count + ")"));
|
||||
}
|
||||
|
||||
return componentHeader.append(Component.text(":")).build();
|
||||
}
|
||||
|
||||
private static Component asPlainComponents(final String strings) {
|
||||
final net.kyori.adventure.text.TextComponent.Builder builder = Component.text();
|
||||
for (final String string : strings.split("\n")) {
|
||||
builder.append(Component.newline());
|
||||
builder.append(Component.text(string, NamedTextColor.WHITE));
|
||||
}
|
||||
@@ -134,13 +136,13 @@ public class PaperPluginsCommand extends BukkitCommand {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static TextColor fromStatus(PluginProvider<?> provider) {
|
||||
if (provider instanceof ProviderStatusHolder statusHolder && statusHolder.getLastProvidedStatus() != null) {
|
||||
ProviderStatus status = statusHolder.getLastProvidedStatus();
|
||||
private static TextColor fromStatus(final PluginProvider<?> provider) {
|
||||
if (provider instanceof final ProviderStatusHolder statusHolder && statusHolder.getLastProvidedStatus() != null) {
|
||||
final ProviderStatus status = statusHolder.getLastProvidedStatus();
|
||||
|
||||
// Handle enabled/disabled game plugins
|
||||
if (status == ProviderStatus.INITIALIZED && GenericTypeReflector.isSuperType(JAVA_PLUGIN_PROVIDER_TYPE, provider.getClass())) {
|
||||
Plugin plugin = Bukkit.getPluginManager().getPlugin(provider.getMeta().getName());
|
||||
final Plugin plugin = Bukkit.getPluginManager().getPlugin(provider.getMeta().getName());
|
||||
// Plugin doesn't exist? Could be due to it being removed.
|
||||
if (plugin == null) {
|
||||
return NamedTextColor.RED;
|
||||
@@ -153,7 +155,7 @@ public class PaperPluginsCommand extends BukkitCommand {
|
||||
case INITIALIZED -> NamedTextColor.GREEN;
|
||||
case ERRORED -> NamedTextColor.RED;
|
||||
};
|
||||
} else if (provider instanceof PaperPluginParent.PaperServerPluginProvider serverPluginProvider && serverPluginProvider.shouldSkipCreation()) {
|
||||
} else if (provider instanceof final PaperPluginParent.PaperServerPluginProvider serverPluginProvider && serverPluginProvider.shouldSkipCreation()) {
|
||||
// Paper plugins will be skipped if their provider is skipped due to their initializer failing.
|
||||
// Show them as red
|
||||
return NamedTextColor.RED;
|
||||
@@ -165,15 +167,14 @@ public class PaperPluginsCommand extends BukkitCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) {
|
||||
public boolean execute(final CommandSender sender, final String currentAlias, final String[] args) {
|
||||
if (!this.testPermission(sender)) return true;
|
||||
|
||||
TreeMap<String, PluginProvider<JavaPlugin>> paperPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
TreeMap<String, PluginProvider<JavaPlugin>> spigotPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
final TreeMap<String, PluginProvider<JavaPlugin>> paperPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
final TreeMap<String, PluginProvider<JavaPlugin>> spigotPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
|
||||
for (PluginProvider<JavaPlugin> provider : LaunchEntryPointHandler.INSTANCE.get(Entrypoint.PLUGIN).getRegisteredProviders()) {
|
||||
PluginMeta configuration = provider.getMeta();
|
||||
for (final PluginProvider<JavaPlugin> provider : LaunchEntryPointHandler.INSTANCE.get(Entrypoint.PLUGIN).getRegisteredProviders()) {
|
||||
final PluginMeta configuration = provider.getMeta();
|
||||
|
||||
if (provider instanceof SpigotPluginProvider) {
|
||||
spigotPlugins.put(configuration.getDisplayName(), provider);
|
||||
@@ -182,34 +183,36 @@ public class PaperPluginsCommand extends BukkitCommand {
|
||||
}
|
||||
}
|
||||
|
||||
Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE);
|
||||
//.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs
|
||||
final int sizePaperPlugins = paperPlugins.size();
|
||||
final int sizeSpigotPlugins = spigotPlugins.size();
|
||||
final int sizePlugins = sizePaperPlugins + sizeSpigotPlugins;
|
||||
final boolean hasAllPluginTypes = (sizePaperPlugins > 0 && sizeSpigotPlugins > 0);
|
||||
|
||||
final Component infoMessage = Component.text().append(INFO_ICON_SERVER_PLUGIN).append(Component.text("Server Plugins (%s):".formatted(sizePlugins), NamedTextColor.WHITE)).build();
|
||||
|
||||
sender.sendMessage(infoMessage);
|
||||
|
||||
if (!paperPlugins.isEmpty()) {
|
||||
sender.sendMessage(PAPER_HEADER);
|
||||
sender.sendMessage(header("Paper Plugins", 0x0288D1, sizePaperPlugins, hasAllPluginTypes));
|
||||
}
|
||||
|
||||
for (Component component : formatProviders(paperPlugins)) {
|
||||
for (final Component component : formatProviders(paperPlugins)) {
|
||||
sender.sendMessage(component);
|
||||
}
|
||||
|
||||
if (!spigotPlugins.isEmpty()) {
|
||||
sender.sendMessage(BUKKIT_HEADER);
|
||||
sender.sendMessage(header("Bukkit Plugins", 0xED8106, sizeSpigotPlugins, hasAllPluginTypes));
|
||||
}
|
||||
|
||||
for (Component component : formatProviders(spigotPlugins)) {
|
||||
|
||||
for (final Component component : formatProviders(spigotPlugins)) {
|
||||
sender.sendMessage(component);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
|
||||
public List<String> tabComplete(final CommandSender sender, final String alias, final String[] args) throws IllegalArgumentException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ public abstract class ApiMirrorRootNode extends RootCommandNode<CommandSourceSta
|
||||
}
|
||||
|
||||
converted = this.unwrapArgumentWrapper(pureArgumentNode, customArgumentType, customArgumentType.getNativeType(), suggestionProvider);
|
||||
} else if (pureArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType<?,?> nativeWrapperArgumentType) {
|
||||
} else if (pureArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType<?, ?> nativeWrapperArgumentType) {
|
||||
converted = this.unwrapArgumentWrapper(pureArgumentNode, nativeWrapperArgumentType, nativeWrapperArgumentType, null); // "null" for suggestion provider so it uses the argument type's suggestion provider
|
||||
|
||||
/*
|
||||
@@ -140,6 +140,8 @@ public abstract class ApiMirrorRootNode extends RootCommandNode<CommandSourceSta
|
||||
// Unknown argument type was passed
|
||||
throw new IllegalArgumentException("Custom unknown argument type was passed, should be wrapped inside an CustomArgumentType.");
|
||||
}
|
||||
} else if (pureNode == this) {
|
||||
return (CommandNode) this.getDispatcher().getRoot();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown command node passed. Don't know how to unwrap this.");
|
||||
}
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
package io.papermc.paper.command.brigadier;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap;
|
||||
import io.papermc.paper.command.brigadier.bukkit.BukkitCommandNode;
|
||||
import java.util.Map;
|
||||
import net.minecraft.commands.CommandSource;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.CommonComponents;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public final class PaperBrigadier {
|
||||
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
@@ -40,7 +35,8 @@ public final class PaperBrigadier {
|
||||
throw new IllegalArgumentException("Unsure how to wrap a " + node);
|
||||
}
|
||||
|
||||
if (!(node instanceof PluginCommandNode pluginCommandNode)) {
|
||||
final PluginCommandMeta meta;
|
||||
if ((meta = node.pluginCommandMeta) == null) {
|
||||
return new VanillaCommandWrapper(null, node);
|
||||
}
|
||||
CommandNode<CommandSourceStack> argumentCommandNode = node;
|
||||
@@ -49,8 +45,8 @@ public final class PaperBrigadier {
|
||||
}
|
||||
|
||||
Map<CommandNode<CommandSourceStack>, String> map = PaperCommands.INSTANCE.getDispatcherInternal().getSmartUsage(argumentCommandNode, DUMMY);
|
||||
String usage = map.isEmpty() ? pluginCommandNode.getUsageText() : pluginCommandNode.getUsageText() + " " + String.join("\n" + pluginCommandNode.getUsageText() + " ", map.values());
|
||||
return new PluginVanillaCommandWrapper(pluginCommandNode.getName(), pluginCommandNode.getDescription(), usage, pluginCommandNode.getAliases(), node, pluginCommandNode.getPlugin());
|
||||
String usage = map.isEmpty() ? node.getUsageText() : node.getUsageText() + " " + String.join("\n" + node.getUsageText() + " ", map.values());
|
||||
return new PluginVanillaCommandWrapper(node.getName(), meta.description(), usage, meta.aliases(), node, meta.plugin());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -70,4 +66,19 @@ public final class PaperBrigadier {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <S> LiteralCommandNode<S> copyLiteral(final String newLiteral, final LiteralCommandNode<S> source) {
|
||||
// logic copied from LiteralCommandNode#createBuilder
|
||||
final LiteralArgumentBuilder<S> copyBuilder = LiteralArgumentBuilder.<S>literal(newLiteral)
|
||||
.requires(source.getRequirement())
|
||||
.forward(source.getRedirect(), source.getRedirectModifier(), source.isFork());
|
||||
if (source.getCommand() != null) {
|
||||
copyBuilder.executes(source.getCommand());
|
||||
}
|
||||
|
||||
for (final CommandNode<S> child : source.getChildren()) {
|
||||
copyBuilder.then(child);
|
||||
}
|
||||
return copyBuilder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
package io.papermc.paper.command.brigadier;
|
||||
|
||||
import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource;
|
||||
import com.google.common.base.Preconditions;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.craftbukkit.entity.CraftEntity;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBrigadierCommandSource {
|
||||
|
||||
net.minecraft.commands.CommandSourceStack getHandle();
|
||||
|
||||
@Override
|
||||
default @NotNull Location getLocation() {
|
||||
default @NonNull Location getLocation() {
|
||||
Vec2 rot = this.getHandle().getRotation();
|
||||
Vec3 pos = this.getHandle().getPosition();
|
||||
Level level = this.getHandle().getLevel();
|
||||
@@ -24,7 +26,7 @@ public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBriga
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@NonNull
|
||||
default CommandSender getSender() {
|
||||
return this.getHandle().getBukkitSender();
|
||||
}
|
||||
@@ -40,6 +42,12 @@ public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBriga
|
||||
return nmsEntity.getBukkitEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
default CommandSourceStack withExecutor(@NonNull Entity executor) {
|
||||
Preconditions.checkNotNull(executor, "Executor cannot be null.");
|
||||
return this.getHandle().withEntity(((CraftEntity) executor).getHandle());
|
||||
}
|
||||
|
||||
// OLD METHODS
|
||||
@Override
|
||||
default org.bukkit.entity.Entity getBukkitEntity() {
|
||||
|
||||
@@ -21,23 +21,20 @@ import java.util.Set;
|
||||
import net.minecraft.commands.CommandBuildContext;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
@NullMarked
|
||||
public class PaperCommands implements Commands, PaperRegistrar<LifecycleEventOwner> {
|
||||
|
||||
public static final PaperCommands INSTANCE = new PaperCommands();
|
||||
|
||||
private @Nullable LifecycleEventOwner currentContext;
|
||||
private @MonotonicNonNull CommandDispatcher<CommandSourceStack> dispatcher;
|
||||
private @MonotonicNonNull CommandBuildContext buildContext;
|
||||
private @Nullable CommandDispatcher<CommandSourceStack> dispatcher;
|
||||
private @Nullable CommandBuildContext buildContext;
|
||||
private boolean invalid = false;
|
||||
|
||||
@Override
|
||||
@@ -93,65 +90,48 @@ public class PaperCommands implements Commands, PaperRegistrar<LifecycleEventOwn
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Unmodifiable Set<String> registerWithFlags(@NotNull final PluginMeta pluginMeta, @NotNull final LiteralCommandNode<CommandSourceStack> node, @org.jetbrains.annotations.Nullable final String description, @NotNull final Collection<String> aliases, @NotNull final Set<CommandRegistrationFlag> flags) {
|
||||
final boolean hasFlattenRedirectFlag = flags.contains(CommandRegistrationFlag.FLATTEN_ALIASES);
|
||||
public @Unmodifiable Set<String> registerWithFlags(final PluginMeta pluginMeta, final LiteralCommandNode<CommandSourceStack> node, final @Nullable String description, final Collection<String> aliases, final Set<CommandRegistrationFlag> flags) {
|
||||
final PluginCommandMeta meta = new PluginCommandMeta(pluginMeta, description);
|
||||
final String identifier = pluginMeta.getName().toLowerCase(Locale.ROOT);
|
||||
final String literal = node.getLiteral();
|
||||
final PluginCommandNode pluginLiteral = new PluginCommandNode(identifier + ":" + literal, pluginMeta, node, description); // Treat the keyed version of the command as the root
|
||||
final LiteralCommandNode<CommandSourceStack> pluginLiteral = PaperBrigadier.copyLiteral(identifier + ":" + literal, node);
|
||||
|
||||
final Set<String> registeredLabels = new HashSet<>(aliases.size() * 2 + 2);
|
||||
|
||||
if (this.registerIntoDispatcher(pluginLiteral, true)) {
|
||||
registeredLabels.add(pluginLiteral.getLiteral());
|
||||
}
|
||||
if (this.registerRedirect(literal, pluginMeta, pluginLiteral, description, true, hasFlattenRedirectFlag)) { // Plugin commands should override vanilla commands
|
||||
if (this.registerIntoDispatcher(node, true)) { // Plugin commands should override vanilla commands
|
||||
registeredLabels.add(literal);
|
||||
}
|
||||
|
||||
// Add aliases
|
||||
final List<String> registeredAliases = new ArrayList<>(aliases.size() * 2);
|
||||
for (final String alias : aliases) {
|
||||
if (this.registerRedirect(alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) {
|
||||
if (this.registerCopy(alias, pluginLiteral, meta)) {
|
||||
registeredAliases.add(alias);
|
||||
}
|
||||
if (this.registerRedirect(identifier + ":" + alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) {
|
||||
if (this.registerCopy(identifier + ":" + alias, pluginLiteral, meta)) {
|
||||
registeredAliases.add(identifier + ":" + alias);
|
||||
}
|
||||
}
|
||||
|
||||
if (!registeredAliases.isEmpty()) {
|
||||
pluginLiteral.setAliases(registeredAliases);
|
||||
}
|
||||
pluginLiteral.pluginCommandMeta = new PluginCommandMeta(pluginMeta, description, registeredAliases);
|
||||
node.pluginCommandMeta = pluginLiteral.pluginCommandMeta;
|
||||
|
||||
registeredLabels.addAll(registeredAliases);
|
||||
return registeredLabels.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(registeredLabels);
|
||||
}
|
||||
|
||||
private boolean registerRedirect(final String aliasLiteral, final PluginMeta plugin, final PluginCommandNode redirectTo, final @Nullable String description, final boolean override, boolean hasFlattenRedirectFlag) {
|
||||
final LiteralCommandNode<CommandSourceStack> redirect;
|
||||
if (redirectTo.getChildren().isEmpty() || hasFlattenRedirectFlag) {
|
||||
redirect = Commands.literal(aliasLiteral)
|
||||
.executes(redirectTo.getCommand())
|
||||
.requires(redirectTo.getRequirement())
|
||||
.build();
|
||||
|
||||
for (final CommandNode<CommandSourceStack> child : redirectTo.getChildren()) {
|
||||
redirect.addChild(child);
|
||||
}
|
||||
} else {
|
||||
redirect = Commands.literal(aliasLiteral)
|
||||
.executes(redirectTo.getCommand())
|
||||
.redirect(redirectTo)
|
||||
.requires(redirectTo.getRequirement())
|
||||
.build();
|
||||
}
|
||||
|
||||
return this.registerIntoDispatcher(new PluginCommandNode(aliasLiteral, plugin, redirect, description), override);
|
||||
private boolean registerCopy(final String aliasLiteral, final LiteralCommandNode<CommandSourceStack> redirectTo, final PluginCommandMeta meta) {
|
||||
final LiteralCommandNode<CommandSourceStack> node = PaperBrigadier.copyLiteral(aliasLiteral, redirectTo);
|
||||
node.pluginCommandMeta = meta;
|
||||
return this.registerIntoDispatcher(node, false);
|
||||
}
|
||||
|
||||
private boolean registerIntoDispatcher(final PluginCommandNode node, boolean override) {
|
||||
final @Nullable CommandNode<CommandSourceStack> existingChild = this.getDispatcher().getRoot().getChild(node.getLiteral());
|
||||
if (existingChild != null && !(existingChild instanceof PluginCommandNode) && !(existingChild instanceof BukkitCommandNode)) {
|
||||
private boolean registerIntoDispatcher(final LiteralCommandNode<CommandSourceStack> node, boolean override) {
|
||||
final CommandNode<CommandSourceStack> existingChild = this.getDispatcher().getRoot().getChild(node.getLiteral());
|
||||
if (existingChild != null && existingChild.pluginCommandMeta == null && !(existingChild instanceof BukkitCommandNode)) {
|
||||
override = true; // override vanilla commands
|
||||
}
|
||||
if (existingChild == null || override) { // Avoid merging behavior. Maybe something to look into in the future
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.papermc.paper.command.brigadier;
|
||||
|
||||
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@NullMarked
|
||||
public record PluginCommandMeta(PluginMeta pluginMeta, @Nullable String description, List<String> aliases) {
|
||||
|
||||
public PluginCommandMeta(final PluginMeta pluginMeta, final @Nullable String description) {
|
||||
this(pluginMeta, description, Collections.emptyList());
|
||||
}
|
||||
|
||||
public PluginCommandMeta {
|
||||
aliases = List.copyOf(aliases);
|
||||
}
|
||||
|
||||
public Plugin plugin() {
|
||||
return Objects.requireNonNull(Bukkit.getPluginManager().getPlugin(this.pluginMeta.getName()));
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package io.papermc.paper.command.brigadier;
|
||||
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class PluginCommandNode extends LiteralCommandNode<CommandSourceStack> {
|
||||
|
||||
private final PluginMeta plugin;
|
||||
private final String description;
|
||||
private List<String> aliases = Collections.emptyList();
|
||||
|
||||
public PluginCommandNode(final @NotNull String literal, final @NotNull PluginMeta plugin, final @NotNull LiteralCommandNode<CommandSourceStack> rootLiteral, final @Nullable String description) {
|
||||
super(
|
||||
literal, rootLiteral.getCommand(), rootLiteral.getRequirement(),
|
||||
rootLiteral.getRedirect(), rootLiteral.getRedirectModifier(), rootLiteral.isFork()
|
||||
);
|
||||
this.plugin = plugin;
|
||||
this.description = description;
|
||||
|
||||
for (CommandNode<CommandSourceStack> argument : rootLiteral.getChildren()) {
|
||||
this.addChild(argument);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Plugin getPlugin() {
|
||||
return Objects.requireNonNull(Bukkit.getPluginManager().getPlugin(this.plugin.getName()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setAliases(List<String> aliases) {
|
||||
this.aliases = aliases;
|
||||
}
|
||||
|
||||
public List<String> getAliases() {
|
||||
return this.aliases;
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,11 @@ import io.papermc.paper.command.brigadier.argument.range.RangeProvider;
|
||||
import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver;
|
||||
import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver;
|
||||
import io.papermc.paper.command.brigadier.argument.resolvers.PlayerProfileListResolver;
|
||||
import io.papermc.paper.command.brigadier.argument.resolvers.RotationResolver;
|
||||
import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver;
|
||||
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver;
|
||||
import io.papermc.paper.entity.LookAnchor;
|
||||
import io.papermc.paper.math.Rotation;
|
||||
import io.papermc.paper.registry.PaperRegistries;
|
||||
import io.papermc.paper.registry.RegistryAccess;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
@@ -61,6 +63,7 @@ import net.minecraft.commands.arguments.TimeArgument;
|
||||
import net.minecraft.commands.arguments.UuidArgument;
|
||||
import net.minecraft.commands.arguments.blocks.BlockStateArgument;
|
||||
import net.minecraft.commands.arguments.coordinates.BlockPosArgument;
|
||||
import net.minecraft.commands.arguments.coordinates.RotationArgument;
|
||||
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
|
||||
import net.minecraft.commands.arguments.item.ItemArgument;
|
||||
import net.minecraft.commands.arguments.item.ItemPredicateArgument;
|
||||
@@ -71,6 +74,7 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.HeightMap;
|
||||
@@ -156,6 +160,15 @@ public class VanillaArgumentProviderImpl implements VanillaArgumentProvider {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgumentType<RotationResolver> rotation() {
|
||||
return this.wrap(RotationArgument.rotation(), (result) -> sourceStack -> {
|
||||
final Vec2 vec2 = result.getRotation((CommandSourceStack) sourceStack);
|
||||
|
||||
return Rotation.rotation(vec2.y, vec2.x);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgumentType<BlockState> blockState() {
|
||||
return this.wrap(BlockStateArgument.block(PaperCommands.INSTANCE.getBuildContext()), (result) -> {
|
||||
@@ -353,6 +366,11 @@ public class VanillaArgumentProviderImpl implements VanillaArgumentProvider {
|
||||
return this.converter.convert(this.nmsBase.parse(reader));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> P parse(final StringReader reader, final S source) throws CommandSyntaxException {
|
||||
return this.converter.convert(this.nmsBase.parse(reader, source));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
|
||||
return this.nmsBase.listSuggestions(context, builder);
|
||||
|
||||
@@ -46,7 +46,7 @@ public class WrappedArgumentCommandNode<NMS, API> extends ArgumentCommandNode<Co
|
||||
@Override
|
||||
public void parse(final StringReader reader, final CommandContextBuilder<CommandSourceStack> contextBuilder) throws CommandSyntaxException {
|
||||
final int start = reader.getCursor();
|
||||
final API result = this.pureArgumentType.parse(reader); // Use the api argument parser
|
||||
final API result = this.pureArgumentType.parse(reader, contextBuilder.getSource()); // Use the api argument parser
|
||||
final ParsedArgument<CommandSourceStack, API> parsed = new ParsedArgument<>(start, reader.getCursor(), result); // Return an API parsed argument instead.
|
||||
|
||||
contextBuilder.withArgument(this.getName(), parsed);
|
||||
|
||||
@@ -80,7 +80,7 @@ public abstract class Configurations<G, W> {
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() {
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) {
|
||||
return this.createLoaderBuilder();
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public abstract class Configurations<G, W> {
|
||||
}
|
||||
|
||||
public G initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException {
|
||||
return this.initializeGlobalConfiguration(creator(this.globalConfigClass, true));
|
||||
return this.initializeGlobalConfiguration(registryAccess, creator(this.globalConfigClass, true));
|
||||
}
|
||||
|
||||
private void trySaveFileNode(YamlConfigurationLoader loader, ConfigurationNode node, String filename) throws ConfigurateException {
|
||||
@@ -117,9 +117,9 @@ public abstract class Configurations<G, W> {
|
||||
}
|
||||
}
|
||||
|
||||
protected G initializeGlobalConfiguration(final CheckedFunction<ConfigurationNode, G, SerializationException> creator) throws ConfigurateException {
|
||||
protected G initializeGlobalConfiguration(final RegistryAccess registryAccess, final CheckedFunction<ConfigurationNode, G, SerializationException> creator) throws ConfigurateException {
|
||||
final Path configFile = this.globalFolder.resolve(this.globalConfigFileName);
|
||||
final YamlConfigurationLoader loader = this.createGlobalLoaderBuilder()
|
||||
final YamlConfigurationLoader loader = this.createGlobalLoaderBuilder(registryAccess)
|
||||
.defaultOptions(this.applyObjectMapperFactory(this.createGlobalObjectMapperFactoryBuilder().build()))
|
||||
.path(configFile)
|
||||
.build();
|
||||
|
||||
@@ -5,10 +5,13 @@ import io.papermc.paper.FeatureHooks;
|
||||
import io.papermc.paper.configuration.constraint.Constraints;
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import io.papermc.paper.util.ItemObfuscationBinding;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
@@ -20,6 +23,7 @@ import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
||||
public class GlobalConfiguration extends ConfigurationPart {
|
||||
@@ -69,7 +73,7 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
)
|
||||
public int playerMaxConcurrentChunkGenerates = 0;
|
||||
}
|
||||
static void set(GlobalConfiguration instance) {
|
||||
static void set(final GlobalConfiguration instance) {
|
||||
GlobalConfiguration.instance = instance;
|
||||
}
|
||||
|
||||
@@ -166,6 +170,8 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public class UnsupportedSettings extends ConfigurationPart {
|
||||
@Comment("This setting allows for exploits related to end portals, for example sand duping")
|
||||
public boolean allowUnsafeEndPortalTeleportation = false;
|
||||
@Comment("This setting controls the ability to enable dupes related to tripwires.")
|
||||
public boolean skipTripwireHookPlacementValidation = false;
|
||||
@Comment("This setting controls if players should be able to break bedrock, end portals and other intended to be permanent blocks.")
|
||||
public boolean allowPermanentBlockBreakExploits = false;
|
||||
@Comment("This setting controls if player should be able to use TNT duplication, but this also allows duplicating carpet, rails and potentially other items")
|
||||
@@ -177,10 +183,15 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public boolean skipVanillaDamageTickWhenShieldBlocked = false;
|
||||
@Comment("This setting controls what compression format is used for region files.")
|
||||
public CompressionFormat compressionFormat = CompressionFormat.ZLIB;
|
||||
@Comment("This setting controls if equipment should be updated when handling certain player actions.")
|
||||
public boolean updateEquipmentOnPlayerActions = true;
|
||||
@Comment("Only checks an item's amount and type instead of its full data during inventory desync checks.")
|
||||
public boolean simplifyRemoteItemMatching = false;
|
||||
|
||||
public enum CompressionFormat {
|
||||
GZIP,
|
||||
ZLIB,
|
||||
LZ4,
|
||||
NONE
|
||||
}
|
||||
}
|
||||
@@ -189,8 +200,9 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
|
||||
public class Commands extends ConfigurationPart {
|
||||
public boolean suggestPlayerNamesWhenNullTabCompletions = true;
|
||||
public boolean fixTargetSelectorTagCompletion = true;
|
||||
public boolean timeCommandAffectsAllWorlds = false;
|
||||
@Comment("Allow mounting entities to a player in the Vanilla '/ride' command.")
|
||||
public boolean rideCommandAllowPlayerAsVehicle = false;
|
||||
}
|
||||
|
||||
public Logging logging;
|
||||
@@ -354,4 +366,41 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public boolean disableChorusPlantUpdates = false;
|
||||
public boolean disableMushroomBlockUpdates = false;
|
||||
}
|
||||
|
||||
public Anticheat anticheat;
|
||||
|
||||
public class Anticheat extends ConfigurationPart {
|
||||
|
||||
public Obfuscation obfuscation;
|
||||
|
||||
public class Obfuscation extends ConfigurationPart {
|
||||
public Items items;
|
||||
|
||||
public class Items extends ConfigurationPart {
|
||||
|
||||
public boolean enableItemObfuscation = false;
|
||||
public ItemObfuscationBinding.AssetObfuscationConfiguration allModels = new ItemObfuscationBinding.AssetObfuscationConfiguration(
|
||||
true,
|
||||
Set.of(DataComponents.LODESTONE_TRACKER),
|
||||
Set.of()
|
||||
);
|
||||
|
||||
public Map<ResourceLocation, ItemObfuscationBinding.AssetObfuscationConfiguration> modelOverrides = Map.of(
|
||||
Objects.requireNonNull(net.minecraft.world.item.Items.ELYTRA.components().get(DataComponents.ITEM_MODEL)),
|
||||
new ItemObfuscationBinding.AssetObfuscationConfiguration(
|
||||
true,
|
||||
Set.of(DataComponents.DAMAGE),
|
||||
Set.of()
|
||||
)
|
||||
);
|
||||
|
||||
public transient ItemObfuscationBinding binding;
|
||||
|
||||
@PostProcess
|
||||
public void bindDataSanitizer() {
|
||||
this.binding = new ItemObfuscationBinding(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.papermc.paper.configuration.serializer.ComponentSerializer;
|
||||
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
||||
import io.papermc.paper.configuration.serializer.NbtPathSerializer;
|
||||
import io.papermc.paper.configuration.serializer.PacketClassSerializer;
|
||||
import io.papermc.paper.configuration.serializer.ResourceLocationSerializer;
|
||||
import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
|
||||
import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer;
|
||||
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
||||
@@ -48,6 +49,7 @@ import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
@@ -180,6 +182,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
.register(Duration.SERIALIZER)
|
||||
.register(DurationOrDisabled.SERIALIZER)
|
||||
.register(NbtPathSerializer.SERIALIZER)
|
||||
.register(ResourceLocationSerializer.INSTANCE)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -193,16 +196,17 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
}
|
||||
|
||||
@Override
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() {
|
||||
return super.createGlobalLoaderBuilder()
|
||||
.defaultOptions(PaperConfigurations::defaultGlobalOptions);
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) {
|
||||
return super.createGlobalLoaderBuilder(registryAccess)
|
||||
.defaultOptions((options) -> defaultGlobalOptions(registryAccess, options));
|
||||
}
|
||||
|
||||
private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options) {
|
||||
private static ConfigurationOptions defaultGlobalOptions(RegistryAccess registryAccess, ConfigurationOptions options) {
|
||||
return options
|
||||
.header(GLOBAL_HEADER)
|
||||
.serializers(builder -> builder
|
||||
.register(new PacketClassSerializer())
|
||||
.register(new RegistryValueSerializer<>(new TypeToken<DataComponentType<?>>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -316,7 +320,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
|
||||
public void reloadConfigs(MinecraftServer server) {
|
||||
try {
|
||||
this.initializeGlobalConfiguration(reloader(this.globalConfigClass, GlobalConfiguration.get()));
|
||||
this.initializeGlobalConfiguration(server.registryAccess(), reloader(this.globalConfigClass, GlobalConfiguration.get()));
|
||||
this.initializeWorldDefaultsConfiguration(server.registryAccess());
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.paperConfig()));
|
||||
@@ -454,9 +458,9 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static ConfigurationNode createForTesting() {
|
||||
static ConfigurationNode createForTesting(RegistryAccess registryAccess) {
|
||||
ObjectMapper.Factory factory = defaultGlobalFactoryBuilder(ObjectMapper.factoryBuilder()).build();
|
||||
ConfigurationOptions options = defaultGlobalOptions(defaultOptions(ConfigurationOptions.defaults()))
|
||||
ConfigurationOptions options = defaultGlobalOptions(registryAccess, defaultOptions(ConfigurationOptions.defaults()))
|
||||
.serializers(builder -> builder.register(type -> ConfigurationPart.class.isAssignableFrom(erase(type)), factory.asTypeSerializer()));
|
||||
return BasicConfigurationNode.root(options);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@ interface RemovedConfigurations {
|
||||
path("fixes", "fix-curing-zombie-villager-discount-exploit"),
|
||||
path("entities", "mob-effects", "undead-immune-to-certain-effects"),
|
||||
path("entities", "entities-target-with-follow-range"),
|
||||
path("environment", "disable-teleportation-suffocation-check")
|
||||
path("environment", "disable-teleportation-suffocation-check"),
|
||||
path("misc", "light-queue-size")
|
||||
};
|
||||
// spawn.keep-spawn-loaded and spawn.keep-spawn-loaded-range are no longer used, but kept
|
||||
// in the world default config for compatibility with old worlds being migrated to use the gamerule
|
||||
@@ -79,7 +80,8 @@ interface RemovedConfigurations {
|
||||
path("warnWhenSettingExcessiveVelocity"),
|
||||
path("logging", "use-rgb-for-named-text-colors"),
|
||||
path("unsupported-settings", "allow-grindstone-overstacking"),
|
||||
path("unsupported-settings", "allow-tripwire-disarming-exploits")
|
||||
path("unsupported-settings", "allow-tripwire-disarming-exploits"),
|
||||
path("commands", "fix-target-selector-tag-completion"),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -88,17 +88,6 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
|
||||
public class Anticheat extends ConfigurationPart {
|
||||
|
||||
public Obfuscation obfuscation;
|
||||
|
||||
public class Obfuscation extends ConfigurationPart {
|
||||
public Items items = new Items();
|
||||
public class Items extends ConfigurationPart {
|
||||
public boolean hideItemmeta = false;
|
||||
public boolean hideDurability = false;
|
||||
public boolean hideItemmetaWithVisualEffects = false;
|
||||
}
|
||||
}
|
||||
|
||||
public AntiXray antiXray;
|
||||
|
||||
public class AntiXray extends ConfigurationPart {
|
||||
@@ -338,6 +327,9 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public int day = 5;
|
||||
}
|
||||
}
|
||||
|
||||
@Comment("Adds a cooldown to bees being released after a failed release, which can occur if the hive is blocked or it being night.")
|
||||
public boolean cooldownFailedBeehiveReleases = true;
|
||||
}
|
||||
|
||||
public TrackingRangeY trackingRangeY;
|
||||
@@ -566,7 +558,6 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public Misc misc;
|
||||
|
||||
public class Misc extends ConfigurationPart {
|
||||
public int lightQueueSize = 20;
|
||||
public boolean updatePathfindingOnBlockUpdate = true;
|
||||
public boolean showSignClickCommandFailureMsgsToPlayer = false;
|
||||
public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.papermc.paper.configuration.serializer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.function.Predicate;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.spongepowered.configurate.serialize.ScalarSerializer;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
public class ResourceLocationSerializer extends ScalarSerializer<ResourceLocation> {
|
||||
|
||||
public static final ScalarSerializer<ResourceLocation> INSTANCE = new ResourceLocationSerializer();
|
||||
|
||||
private ResourceLocationSerializer() {
|
||||
super(ResourceLocation.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation deserialize(final Type type, final Object obj) throws SerializationException {
|
||||
return ResourceLocation.read(obj.toString()).getOrThrow(s -> new SerializationException(ResourceLocation.class, s));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object serialize(final ResourceLocation item, final Predicate<Class<?>> typeSupported) {
|
||||
return item.toString();
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,18 @@ public record DataComponentAdapter<NMS, API>(
|
||||
) {
|
||||
static final Function<Void, Unit> API_TO_UNIT_CONVERTER = $ -> Unit.INSTANCE;
|
||||
|
||||
static final Function API_TO_UNIMPLEMENTED_CONVERTER = $ -> {
|
||||
throw new UnsupportedOperationException("Cannot convert an API value to an unimplemented type");
|
||||
};
|
||||
|
||||
public boolean isValued() {
|
||||
return this.apiToVanilla != API_TO_UNIT_CONVERTER;
|
||||
}
|
||||
|
||||
public boolean isUnimplemented() {
|
||||
return this.apiToVanilla == API_TO_UNIMPLEMENTED_CONVERTER;
|
||||
}
|
||||
|
||||
public NMS toVanilla(final API value) {
|
||||
final NMS nms = this.apiToVanilla.apply(value);
|
||||
if (this.codecValidation) {
|
||||
|
||||
@@ -63,6 +63,10 @@ public final class DataComponentAdapters {
|
||||
throw new UnsupportedOperationException("Cannot convert the Unit type to an API value");
|
||||
};
|
||||
|
||||
static final Function UNIMPLEMENTED_TO_API_CONVERTER = $ -> {
|
||||
throw new UnsupportedOperationException("Cannot convert the an unimplemented type to an API value");
|
||||
};
|
||||
|
||||
static final Map<ResourceKey<DataComponentType<?>>, DataComponentAdapter<?, ?>> ADAPTERS = new HashMap<>();
|
||||
|
||||
public static void bootstrap() {
|
||||
@@ -136,10 +140,9 @@ public final class DataComponentAdapters {
|
||||
// register(DataComponents.LOCK, PaperLockCode::new);
|
||||
register(DataComponents.CONTAINER_LOOT, PaperSeededContainerLoot::new);
|
||||
|
||||
// TODO: REMOVE THIS... we want to build the PR... so lets just make things UNTYPED!
|
||||
for (final Map.Entry<ResourceKey<DataComponentType<?>>, DataComponentType<?>> componentType : BuiltInRegistries.DATA_COMPONENT_TYPE.entrySet()) {
|
||||
if (!ADAPTERS.containsKey(componentType.getKey())) {
|
||||
registerUntyped((DataComponentType<Unit>) componentType.getValue());
|
||||
registerUnimplemented(componentType.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,6 +155,10 @@ public final class DataComponentAdapters {
|
||||
registerInternal(type, Function.identity(), Function.identity(), true);
|
||||
}
|
||||
|
||||
public static <NMS> void registerUnimplemented(final DataComponentType<NMS> type) {
|
||||
registerInternal(type, UNIMPLEMENTED_TO_API_CONVERTER, DataComponentAdapter.API_TO_UNIMPLEMENTED_CONVERTER, false);
|
||||
}
|
||||
|
||||
private static <NMS, API extends Handleable<NMS>> void register(final DataComponentType<NMS> type, final Function<NMS, API> vanillaToApi) {
|
||||
registerInternal(type, vanillaToApi, Handleable::getHandle, false);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import net.minecraft.core.component.DataComponentMap;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
import org.bukkit.craftbukkit.util.Handleable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -23,7 +22,7 @@ public abstract class PaperDataComponentType<T, NMS> implements DataComponentTyp
|
||||
}
|
||||
|
||||
public static DataComponentType minecraftToBukkit(final net.minecraft.core.component.DataComponentType<?> type) {
|
||||
return CraftRegistry.minecraftToBukkit(type, Registries.DATA_COMPONENT_TYPE, Registry.DATA_COMPONENT_TYPE);
|
||||
return CraftRegistry.minecraftToBukkit(type, Registries.DATA_COMPONENT_TYPE);
|
||||
}
|
||||
|
||||
public static Set<DataComponentType> minecraftToBukkit(final Set<net.minecraft.core.component.DataComponentType<?>> nmsTypes) {
|
||||
@@ -78,7 +77,9 @@ public abstract class PaperDataComponentType<T, NMS> implements DataComponentTyp
|
||||
if (adapter == null) {
|
||||
throw new IllegalArgumentException("No adapter found for " + key);
|
||||
}
|
||||
if (adapter.isValued()) {
|
||||
if (adapter.isUnimplemented()) {
|
||||
return new Unimplemented<>(key, type, adapter);
|
||||
} else if (adapter.isValued()) {
|
||||
return new ValuedImpl<>(key, type, adapter);
|
||||
} else {
|
||||
return new NonValuedImpl<>(key, type, adapter);
|
||||
@@ -106,4 +107,15 @@ public abstract class PaperDataComponentType<T, NMS> implements DataComponentTyp
|
||||
super(key, type, adapter);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Unimplemented<T, NMS> extends PaperDataComponentType<T, NMS> {
|
||||
|
||||
public Unimplemented(
|
||||
final NamespacedKey key,
|
||||
final net.minecraft.core.component.DataComponentType<NMS> type,
|
||||
final DataComponentAdapter<NMS, T> adapter
|
||||
) {
|
||||
super(key, type, adapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.papermc.paper.datacomponent.item;
|
||||
|
||||
import io.papermc.paper.registry.RegistryAccess;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.util.MCUtil;
|
||||
import java.util.List;
|
||||
@@ -20,7 +19,7 @@ public record PaperBannerPatternLayers(
|
||||
|
||||
private static List<Pattern> convert(final net.minecraft.world.level.block.entity.BannerPatternLayers nmsPatterns) {
|
||||
return MCUtil.transformUnmodifiable(nmsPatterns.layers(), input -> {
|
||||
final Optional<PatternType> type = CraftRegistry.unwrapAndConvertHolder(RegistryAccess.registryAccess().getRegistry(RegistryKey.BANNER_PATTERN), input.pattern());
|
||||
final Optional<PatternType> type = CraftRegistry.unwrapAndConvertHolder(RegistryKey.BANNER_PATTERN, input.pattern());
|
||||
return new Pattern(Objects.requireNonNull(DyeColor.getByWoolData((byte) input.color().getId())), type.orElseThrow(() -> new IllegalStateException("Inlined banner patterns are not supported yet in the API!")));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public record PaperCustomModelData(
|
||||
|
||||
@Override
|
||||
public List<Color> colors() {
|
||||
return MCUtil.transformUnmodifiable(this.impl.colors(), Color::fromRGB);
|
||||
return MCUtil.transformUnmodifiable(this.impl.colors(), color -> Color.fromRGB(color & 0x00FFFFFF)); // skip alpha channel
|
||||
}
|
||||
|
||||
static final class BuilderImpl implements CustomModelData.Builder {
|
||||
|
||||
@@ -64,6 +64,6 @@ public class PaperDiscoveredDatapack implements DiscoveredDatapack {
|
||||
|
||||
@Override
|
||||
public DatapackSource getSource() {
|
||||
return PACK_SOURCES.computeIfAbsent(this.pack.location().source(), source -> new DatapackSourceImpl(source.toString()));
|
||||
return PACK_SOURCES.get(this.pack.location().source());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.papermc.paper.entity;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public record PaperPlayerGiveResult(
|
||||
@Unmodifiable Collection<ItemStack> leftovers,
|
||||
@Unmodifiable Collection<Item> drops
|
||||
) implements PlayerGiveResult {
|
||||
|
||||
public static final PlayerGiveResult EMPTY = new PaperPlayerGiveResult(
|
||||
Collections.emptyList(), Collections.emptyList()
|
||||
);
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.FlyingMob;
|
||||
import net.minecraft.world.entity.PathfinderMob;
|
||||
import net.minecraft.world.entity.ambient.AmbientCreature;
|
||||
import net.minecraft.world.entity.animal.AgeableWaterCreature;
|
||||
import net.minecraft.world.entity.animal.WaterAnimal;
|
||||
import net.minecraft.world.entity.monster.Enemy;
|
||||
import net.minecraft.world.entity.npc.Villager;
|
||||
@@ -28,7 +29,7 @@ public enum ActivationType {
|
||||
* @return activation type
|
||||
*/
|
||||
public static ActivationType activationTypeFor(final Entity entity) {
|
||||
if (entity instanceof WaterAnimal) {
|
||||
if (entity instanceof WaterAnimal || entity instanceof AgeableWaterCreature) {
|
||||
return ActivationType.WATER;
|
||||
} else if (entity instanceof Villager) {
|
||||
return ActivationType.VILLAGER;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package io.papermc.paper.raytracing;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.EnumSet;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.function.Predicate;
|
||||
import org.bukkit.FluidCollisionMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@NullMarked
|
||||
public class PositionedRayTraceConfigurationBuilderImpl implements PositionedRayTraceConfigurationBuilder {
|
||||
|
||||
public @Nullable Location start;
|
||||
public @Nullable Vector direction;
|
||||
public OptionalDouble maxDistance = OptionalDouble.empty();
|
||||
public FluidCollisionMode fluidCollisionMode = FluidCollisionMode.NEVER;
|
||||
public boolean ignorePassableBlocks;
|
||||
public double raySize = 0.0D;
|
||||
public @Nullable Predicate<? super Entity> entityFilter;
|
||||
public @Nullable Predicate<? super Block> blockFilter;
|
||||
public EnumSet<RayTraceTarget> targets = EnumSet.noneOf(RayTraceTarget.class);
|
||||
|
||||
@Override
|
||||
public PositionedRayTraceConfigurationBuilder start(final Location start) {
|
||||
Preconditions.checkArgument(start != null, "start must not be null");
|
||||
this.start = start.clone();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionedRayTraceConfigurationBuilder direction(final Vector direction) {
|
||||
Preconditions.checkArgument(direction != null, "direction must not be null");
|
||||
this.direction = direction.clone();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionedRayTraceConfigurationBuilder maxDistance(final double maxDistance) {
|
||||
Preconditions.checkArgument(maxDistance >= 0, "maxDistance must be non-negative");
|
||||
this.maxDistance = OptionalDouble.of(maxDistance);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionedRayTraceConfigurationBuilder fluidCollisionMode(final FluidCollisionMode fluidCollisionMode) {
|
||||
Preconditions.checkArgument(fluidCollisionMode != null, "fluidCollisionMode must not be null");
|
||||
this.fluidCollisionMode = fluidCollisionMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionedRayTraceConfigurationBuilder ignorePassableBlocks(final boolean ignorePassableBlocks) {
|
||||
this.ignorePassableBlocks = ignorePassableBlocks;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionedRayTraceConfigurationBuilder raySize(final double raySize) {
|
||||
Preconditions.checkArgument(raySize >= 0, "raySize must be non-negative");
|
||||
this.raySize = raySize;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionedRayTraceConfigurationBuilder entityFilter(final Predicate<? super Entity> entityFilter) {
|
||||
Preconditions.checkArgument(entityFilter != null, "entityFilter must not be null");
|
||||
this.entityFilter = entityFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionedRayTraceConfigurationBuilder blockFilter(final Predicate<? super Block> blockFilter) {
|
||||
Preconditions.checkArgument(blockFilter != null, "blockFilter must not be null");
|
||||
this.blockFilter = blockFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionedRayTraceConfigurationBuilder targets(final RayTraceTarget first, final RayTraceTarget... others) {
|
||||
Preconditions.checkArgument(first != null, "first must not be null");
|
||||
Preconditions.checkArgument(others != null, "others must not be null");
|
||||
this.targets = EnumSet.of(first, others);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -97,21 +97,21 @@ public final class PaperRegistries {
|
||||
start(Registries.MENU, RegistryKey.MENU).craft(MenuType.class, CraftMenuType::new).build(),
|
||||
start(Registries.ATTRIBUTE, RegistryKey.ATTRIBUTE).craft(Attribute.class, CraftAttribute::new).build(),
|
||||
start(Registries.FLUID, RegistryKey.FLUID).craft(Fluid.class, CraftFluid::new).build(),
|
||||
start(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT).craft(Sound.class, CraftSound::new).build(),
|
||||
start(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT).craft(Sound.class, CraftSound::new, true).build(),
|
||||
start(Registries.DATA_COMPONENT_TYPE, RegistryKey.DATA_COMPONENT_TYPE).craft(DataComponentTypes.class, PaperDataComponentType::of).build(),
|
||||
|
||||
// data-drivens
|
||||
start(Registries.BIOME, RegistryKey.BIOME).craft(Biome.class, CraftBiome::new).build().delayed(),
|
||||
start(Registries.STRUCTURE, RegistryKey.STRUCTURE).craft(Structure.class, CraftStructure::new).build().delayed(),
|
||||
start(Registries.TRIM_MATERIAL, RegistryKey.TRIM_MATERIAL).craft(TrimMaterial.class, CraftTrimMaterial::new).build().delayed(),
|
||||
start(Registries.TRIM_PATTERN, RegistryKey.TRIM_PATTERN).craft(TrimPattern.class, CraftTrimPattern::new).build().delayed(),
|
||||
start(Registries.TRIM_MATERIAL, RegistryKey.TRIM_MATERIAL).craft(TrimMaterial.class, CraftTrimMaterial::new, true).build().delayed(),
|
||||
start(Registries.TRIM_PATTERN, RegistryKey.TRIM_PATTERN).craft(TrimPattern.class, CraftTrimPattern::new, true).build().delayed(),
|
||||
start(Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE).craft(DamageType.class, CraftDamageType::new).writable(PaperDamageTypeRegistryEntry.PaperBuilder::new).delayed(),
|
||||
start(Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT).craft(Wolf.Variant.class, CraftWolf.CraftVariant::new).build().delayed(),
|
||||
start(Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT).craft(Enchantment.class, CraftEnchantment::new).serializationUpdater(FieldRename.ENCHANTMENT_RENAME).writable(PaperEnchantmentRegistryEntry.PaperBuilder::new).delayed(),
|
||||
start(Registries.JUKEBOX_SONG, RegistryKey.JUKEBOX_SONG).craft(JukeboxSong.class, CraftJukeboxSong::new).build().delayed(),
|
||||
start(Registries.BANNER_PATTERN, RegistryKey.BANNER_PATTERN).craft(PatternType.class, CraftPatternType::new).writable(PaperBannerPatternRegistryEntry.PaperBuilder::new).delayed(),
|
||||
start(Registries.PAINTING_VARIANT, RegistryKey.PAINTING_VARIANT).craft(Art.class, CraftArt::new).writable(PaperPaintingVariantRegistryEntry.PaperBuilder::new).delayed(),
|
||||
start(Registries.INSTRUMENT, RegistryKey.INSTRUMENT).craft(MusicInstrument.class, CraftMusicInstrument::new).build().delayed(),
|
||||
start(Registries.BANNER_PATTERN, RegistryKey.BANNER_PATTERN).craft(PatternType.class, CraftPatternType::new, true).writable(PaperBannerPatternRegistryEntry.PaperBuilder::new).delayed(),
|
||||
start(Registries.PAINTING_VARIANT, RegistryKey.PAINTING_VARIANT).craft(Art.class, CraftArt::new, true).writable(PaperPaintingVariantRegistryEntry.PaperBuilder::new).delayed(),
|
||||
start(Registries.INSTRUMENT, RegistryKey.INSTRUMENT).craft(MusicInstrument.class, CraftMusicInstrument::new, true).build().delayed(),
|
||||
|
||||
// api-only
|
||||
start(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE).apiOnly(PaperSimpleRegistry::entityType),
|
||||
@@ -153,12 +153,12 @@ public final class PaperRegistries {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <M, T> RegistryKey<T> registryFromNms(final ResourceKey<? extends Registry<M>> registryResourceKey) {
|
||||
return (RegistryKey<T>) Objects.requireNonNull(BY_RESOURCE_KEY.get(registryResourceKey), registryResourceKey + " doesn't have an api RegistryKey").apiKey();
|
||||
return (RegistryKey<T>) Objects.requireNonNull(BY_RESOURCE_KEY.get(registryResourceKey), () -> registryResourceKey + " doesn't have an api RegistryKey").apiKey();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <M, T> ResourceKey<? extends Registry<M>> registryToNms(final RegistryKey<T> registryKey) {
|
||||
return (ResourceKey<? extends Registry<M>>) Objects.requireNonNull(BY_REGISTRY_KEY.get(registryKey), registryKey + " doesn't have an mc registry ResourceKey").mcKey();
|
||||
return (ResourceKey<? extends Registry<M>>) Objects.requireNonNull(BY_REGISTRY_KEY.get(registryKey), () -> registryKey + " doesn't have an mc registry ResourceKey").mcKey();
|
||||
}
|
||||
|
||||
public static <M, T> TypedKey<T> fromNms(final ResourceKey<M> resourceKey) {
|
||||
|
||||
@@ -107,7 +107,7 @@ public class PaperRegistryAccess implements RegistryAccess {
|
||||
|
||||
public <M> void lockReferenceHolders(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey) {
|
||||
final RegistryEntry<M, Keyed> entry = PaperRegistries.getEntry(resourceKey);
|
||||
if (entry == null || !(entry.meta() instanceof final RegistryEntryMeta.ServerSide<M, Keyed> serverSide) || !serverSide.registryTypeMapper().supportsDirectHolders()) {
|
||||
if (entry == null || !(entry.meta() instanceof final RegistryEntryMeta.ServerSide<M, Keyed> serverSide) || !serverSide.registryTypeMapper().constructorUsesHolder()) {
|
||||
return;
|
||||
}
|
||||
final CraftRegistry<?, M> registry = (CraftRegistry<?, M>) this.getRegistry(entry.apiKey());
|
||||
|
||||
@@ -24,7 +24,7 @@ public final class InlinedRegistryBuilderProviderImpl implements InlinedRegistry
|
||||
Preconditions.checkArgument(buildableMeta.registryTypeMapper().supportsDirectHolders(), "Registry type mapper must support direct holders");
|
||||
final PaperRegistryBuilderFactory<M, A, B> builderFactory = new PaperRegistryBuilderFactory<>(Conversions.global(), buildableMeta.builderFiller(), CraftRegistry.getMinecraftRegistry(buildableMeta.mcKey())::getValue);
|
||||
value.accept(builderFactory);
|
||||
return buildableMeta.registryTypeMapper().convertDirectHolder(Holder.direct(builderFactory.requireBuilder().build()));
|
||||
return buildableMeta.registryTypeMapper().createBukkit(Holder.direct(builderFactory.requireBuilder().build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package io.papermc.paper.registry.data;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import io.papermc.paper.registry.PaperRegistryBuilder;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.registry.TypedKey;
|
||||
import io.papermc.paper.registry.data.util.Checks;
|
||||
import io.papermc.paper.registry.data.util.Conversions;
|
||||
import io.papermc.paper.registry.set.PaperRegistrySets;
|
||||
@@ -189,7 +187,6 @@ public class PaperEnchantmentRegistryEntry implements EnchantmentRegistryEntry {
|
||||
|
||||
@Override
|
||||
public Builder anvilCost(final @Range(from = 0, to = Integer.MAX_VALUE) int anvilCost) {
|
||||
Preconditions.checkArgument(anvilCost >= 0, "anvilCost must be non-negative");
|
||||
this.anvilCost = OptionalInt.of(asArgumentMin(anvilCost, "anvilCost", 0));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,11 @@ public class RegistryEntryBuilder<M, A extends Keyed> { // TODO remove Keyed
|
||||
}
|
||||
|
||||
public CraftStage<M, A> craft(final Class<?> classToPreload, final Function<Holder<M>, ? extends A> minecraftToBukkit) {
|
||||
return new CraftStage<>(this.mcKey, this.apiKey, classToPreload, new RegistryTypeMapper<>(minecraftToBukkit));
|
||||
return this.craft(classToPreload, minecraftToBukkit, false);
|
||||
}
|
||||
|
||||
public CraftStage<M, A> craft(final Class<?> classToPreload, final Function<Holder<M>, ? extends A> minecraftToBukkit, final boolean allowDirect) {
|
||||
return new CraftStage<>(this.mcKey, this.apiKey, classToPreload, new RegistryTypeMapper<>(minecraftToBukkit, allowDirect));
|
||||
}
|
||||
|
||||
public static final class CraftStage<M, A extends Keyed> extends RegistryEntryBuilder<M, A> { // TODO remove Keyed
|
||||
|
||||
@@ -60,7 +60,7 @@ public sealed interface RegistryEntryMeta<M, A extends Keyed> permits RegistryEn
|
||||
) implements ServerSide<M, A> { // TODO remove Keyed
|
||||
|
||||
public Craft {
|
||||
Preconditions.checkArgument(!classToPreload.getPackageName().startsWith("net.minecraft"), classToPreload + " should not be in the net.minecraft package as the class-to-preload");
|
||||
Preconditions.checkArgument(!classToPreload.getPackageName().startsWith("net.minecraft"), "%s should not be in the net.minecraft package as the class-to-preload", classToPreload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.papermc.paper.registry.entry;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import io.papermc.paper.util.MCUtil;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import net.minecraft.core.Holder;
|
||||
@@ -9,29 +9,40 @@ import org.bukkit.NamespacedKey;
|
||||
|
||||
public final class RegistryTypeMapper<M, A> {
|
||||
|
||||
final Either<BiFunction<? super NamespacedKey, M, ? extends A>, Function<Holder<M>, ? extends A>> minecraftToBukkit;
|
||||
private static <M, A> Function<Holder<M>, ? extends A> wrap(final BiFunction<? super NamespacedKey, M, ? extends A> byValueCreator) {
|
||||
return holder -> {
|
||||
if (!(holder instanceof final Holder.Reference<M> reference)) {
|
||||
throw new IllegalArgumentException("This type does not support direct holders: " + holder);
|
||||
}
|
||||
return byValueCreator.apply(MCUtil.fromResourceKey(reference.key()), reference.value());
|
||||
};
|
||||
}
|
||||
|
||||
final Either<Function<Holder<M>, ? extends A>, BiFunction<? super NamespacedKey, M, ? extends A>> minecraftToBukkit;
|
||||
private final boolean supportsDirectHolders;
|
||||
|
||||
public RegistryTypeMapper(final BiFunction<? super NamespacedKey, M, ? extends A> byValueCreator) {
|
||||
this.minecraftToBukkit = Either.left(byValueCreator);
|
||||
this.minecraftToBukkit = Either.right(byValueCreator);
|
||||
this.supportsDirectHolders = false;
|
||||
}
|
||||
|
||||
public RegistryTypeMapper(final Function<Holder<M>, ? extends A> byHolderCreator) {
|
||||
this.minecraftToBukkit = Either.right(byHolderCreator);
|
||||
public RegistryTypeMapper(final Function<Holder<M>, ? extends A> byHolderCreator, final boolean supportsDirectHolders) {
|
||||
this.minecraftToBukkit = Either.left(byHolderCreator);
|
||||
this.supportsDirectHolders = supportsDirectHolders;
|
||||
}
|
||||
|
||||
public A createBukkit(final NamespacedKey key, final Holder<M> minecraft) {
|
||||
return this.minecraftToBukkit.map(
|
||||
minecraftToBukkit -> minecraftToBukkit.apply(key, minecraft.value()),
|
||||
minecraftToBukkit -> minecraftToBukkit.apply(minecraft)
|
||||
);
|
||||
public A createBukkit(final Holder<M> minecraft) {
|
||||
return this.minecraftToBukkit.<Function<Holder<M>, ? extends A>>map(
|
||||
Function.identity(),
|
||||
RegistryTypeMapper::wrap
|
||||
).apply(minecraft);
|
||||
}
|
||||
|
||||
public boolean supportsDirectHolders() {
|
||||
return this.minecraftToBukkit.right().isPresent();
|
||||
return this.supportsDirectHolders;
|
||||
}
|
||||
|
||||
public A convertDirectHolder(final Holder<M> directHolder) {
|
||||
Preconditions.checkArgument(this.supportsDirectHolders() && directHolder.kind() == Holder.Kind.DIRECT);
|
||||
return this.minecraftToBukkit.right().orElseThrow().apply(directHolder);
|
||||
public boolean constructorUsesHolder() {
|
||||
return this.minecraftToBukkit.left().isPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public final class RegistryEventMap {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, E extends LifecycleEvent> LifecycleEventType<BootstrapContext, E, ?> getEventType(final RegistryKey<T> registryKey) {
|
||||
return (LifecycleEventType<BootstrapContext, E, ?>) Objects.requireNonNull(this.eventTypes.get(registryKey), "No hook for " + registryKey);
|
||||
return (LifecycleEventType<BootstrapContext, E, ?>) Objects.requireNonNull(this.eventTypes.get(registryKey), () -> "No hook for " + registryKey);
|
||||
}
|
||||
|
||||
public boolean hasHandlers(final RegistryKey<?> registryKey) {
|
||||
|
||||
@@ -49,6 +49,11 @@ public final class DelayedRegistry<T extends Keyed, R extends Registry<T>> imple
|
||||
return this.delegate().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.delegate().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable NamespacedKey getKey(final T value) {
|
||||
return this.delegate().getKey(value);
|
||||
|
||||
@@ -181,7 +181,7 @@ public final class FoliaAsyncScheduler implements AsyncScheduler {
|
||||
|
||||
private void setDelay(final ScheduledFuture<?> delay) {
|
||||
this.delay = delay;
|
||||
this.state = STATE_SCHEDULED_EXECUTOR;
|
||||
this.state = delay == null ? STATE_SCHEDULED_EXECUTOR : STATE_ON_TIMER;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import io.papermc.paper.registry.RegistryAccess;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.resources.RegistryOps;
|
||||
@@ -25,7 +27,8 @@ public interface Holderable<M> extends Handleable<M> {
|
||||
return this.getHolder().value();
|
||||
}
|
||||
|
||||
static <T extends org.bukkit.Keyed, M> @Nullable T fromBukkitSerializationObject(final Object deserialized, final Codec<? extends Holder<M>> codec, final Registry<T> registry) { // TODO remove Keyed
|
||||
static <T extends org.bukkit.Keyed, M> @Nullable T fromBukkitSerializationObject(final Object deserialized, final Codec<M> directCodec, final RegistryKey<T> registryKey) { // TODO remove Keyed
|
||||
final Registry<T> registry = RegistryAccess.registryAccess().getRegistry(registryKey);
|
||||
return switch (deserialized) {
|
||||
case @Subst("key:value") final String string -> {
|
||||
if (!(Key.parseable(string))) {
|
||||
@@ -38,18 +41,18 @@ public interface Holderable<M> extends Handleable<M> {
|
||||
throw new IllegalArgumentException("Cannot deserialize direct holders for " + registry);
|
||||
}
|
||||
final RegistryOps<JsonElement> ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE);
|
||||
final Holder<M> holder = codec.decode(ops, element).getOrThrow().getFirst();
|
||||
yield ((CraftRegistry<T, M>) registry).convertDirectHolder(holder);
|
||||
final M holder = directCodec.decode(ops, element).getOrThrow().getFirst();
|
||||
yield ((CraftRegistry<T, M>) registry).createBukkit(Holder.direct(holder));
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Cannot deserialize " + deserialized);
|
||||
};
|
||||
}
|
||||
|
||||
default Object toBukkitSerializationObject(final Codec<? super Holder<M>> codec) {
|
||||
default Object toBukkitSerializationObject(final Codec<? super M> directCodec) {
|
||||
return switch (this.getHolder()) {
|
||||
case final Holder.Direct<M> direct -> {
|
||||
final RegistryOps<JsonElement> ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE);
|
||||
yield new JsonObjectWrapper(codec.encodeStart(ops, direct).getOrThrow().getAsJsonObject());
|
||||
yield new JsonObjectWrapper(directCodec.encodeStart(ops, direct.value()).getOrThrow().getAsJsonObject());
|
||||
}
|
||||
case final Holder.Reference<M> reference -> reference.key().location().toString();
|
||||
default -> throw new IllegalArgumentException("Cannot serialize " + this.getHolder());
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package io.papermc.paper.util;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.UnaryOperator;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.alchemy.PotionContents;
|
||||
import net.minecraft.world.item.component.LodestoneTracker;
|
||||
import net.minecraft.world.item.enchantment.ItemEnchantments;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class ItemComponentSanitizer {
|
||||
|
||||
/*
|
||||
* This returns for types, that when configured to be serialized, should instead return these objects.
|
||||
* This is possibly because dropping the patched type may introduce visual changes.
|
||||
*/
|
||||
static final Map<DataComponentType<?>, UnaryOperator<?>> SANITIZATION_OVERRIDES = Util.make(ImmutableMap.<DataComponentType<?>, UnaryOperator<?>>builder(), (map) -> {
|
||||
put(map, DataComponents.LODESTONE_TRACKER, empty(new LodestoneTracker(Optional.empty(), false))); // We need it to be present to keep the glint
|
||||
put(map, DataComponents.POTION_CONTENTS, ItemComponentSanitizer::sanitizePotionContents); // Custom situational serialization
|
||||
|
||||
if (MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).size() > 0) {
|
||||
put(map, DataComponents.ENCHANTMENTS, empty(dummyEnchantments())); // We need to keep it present to keep the glint
|
||||
put(map, DataComponents.STORED_ENCHANTMENTS, empty(dummyEnchantments())); // We need to keep it present to keep the glint
|
||||
}
|
||||
}
|
||||
).build();
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private static <T> void put(final ImmutableMap.Builder map, final DataComponentType<T> type, final UnaryOperator<T> object) {
|
||||
map.put(type, object);
|
||||
}
|
||||
|
||||
private static <T> UnaryOperator<T> empty(final T object) {
|
||||
return (unused) -> object;
|
||||
}
|
||||
|
||||
private static PotionContents sanitizePotionContents(final PotionContents potionContents) {
|
||||
// We have a custom color! We can hide everything!
|
||||
if (potionContents.customColor().isPresent()) {
|
||||
return new PotionContents(Optional.empty(), potionContents.customColor(), List.of(), Optional.empty());
|
||||
}
|
||||
|
||||
// WE cannot hide anything really, as the color is a mix of potion/potion contents, which can
|
||||
// possibly be reversed.
|
||||
return potionContents;
|
||||
}
|
||||
|
||||
// We cant use the empty map from enchantments because we want to keep the glow
|
||||
private static ItemEnchantments dummyEnchantments() {
|
||||
final ItemEnchantments.Mutable obj = new ItemEnchantments.Mutable(ItemEnchantments.EMPTY);
|
||||
obj.set(MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getRandom(RandomSource.create()).orElseThrow(), 1);
|
||||
return obj.toImmutable();
|
||||
}
|
||||
|
||||
public static int sanitizeCount(final ItemObfuscationSession obfuscationSession, final ItemStack itemStack, final int count) {
|
||||
if (obfuscationSession.obfuscationLevel() != ItemObfuscationSession.ObfuscationLevel.ALL) return count; // Ignore if we are not obfuscating
|
||||
|
||||
if (GlobalConfiguration.get().anticheat.obfuscation.items.binding.getAssetObfuscation(itemStack).sanitizeCount()) {
|
||||
return 1;
|
||||
} else {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean shouldDrop(final ItemObfuscationSession obfuscationSession, final DataComponentType<?> key) {
|
||||
if (obfuscationSession.obfuscationLevel() != ItemObfuscationSession.ObfuscationLevel.ALL) return false; // Ignore if we are not obfuscating
|
||||
|
||||
final ItemStack targetItemstack = obfuscationSession.context().itemStack();
|
||||
|
||||
// Only drop if configured to do so.
|
||||
return GlobalConfiguration.get().anticheat.obfuscation.items.binding.getAssetObfuscation(targetItemstack).patchStrategy().get(key) == ItemObfuscationBinding.BoundObfuscationConfiguration.MutationType.Drop.INSTANCE;
|
||||
}
|
||||
|
||||
public static Optional<?> override(final ItemObfuscationSession obfuscationSession, final DataComponentType<?> key, final Optional<?> value) {
|
||||
if (obfuscationSession.obfuscationLevel() != ItemObfuscationSession.ObfuscationLevel.ALL) return value; // Ignore if we are not obfuscating
|
||||
|
||||
// Ignore removed values
|
||||
if (value.isEmpty()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
final ItemStack targetItemstack = obfuscationSession.context().itemStack();
|
||||
|
||||
return switch (GlobalConfiguration.get().anticheat.obfuscation.items.binding.getAssetObfuscation(targetItemstack).patchStrategy().get(key)) {
|
||||
case final ItemObfuscationBinding.BoundObfuscationConfiguration.MutationType.Drop ignored -> Optional.empty();
|
||||
case final ItemObfuscationBinding.BoundObfuscationConfiguration.MutationType.Sanitize sanitize -> Optional.of(sanitize.sanitizer().apply(value.get()));
|
||||
case null -> value;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package io.papermc.paper.util;
|
||||
|
||||
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Required;
|
||||
|
||||
/**
|
||||
* The item obfuscation binding is a state bound by the configured item obfuscation.
|
||||
* It only hosts the bound and computed data from the global configuration.
|
||||
*/
|
||||
@NullMarked
|
||||
public final class ItemObfuscationBinding {
|
||||
|
||||
public final ItemObfuscationSession.ObfuscationLevel level;
|
||||
private final BoundObfuscationConfiguration base;
|
||||
private final Map<ResourceLocation, BoundObfuscationConfiguration> overrides;
|
||||
|
||||
public ItemObfuscationBinding(final GlobalConfiguration.Anticheat.Obfuscation.Items items) {
|
||||
this.level = items.enableItemObfuscation ? ItemObfuscationSession.ObfuscationLevel.ALL : ItemObfuscationSession.ObfuscationLevel.OVERSIZED;
|
||||
this.base = bind(items.allModels);
|
||||
final Map<ResourceLocation, BoundObfuscationConfiguration> overrides = new HashMap<>();
|
||||
for (final Map.Entry<ResourceLocation, AssetObfuscationConfiguration> entry : items.modelOverrides.entrySet()) {
|
||||
overrides.put(entry.getKey(), bind(entry.getValue()));
|
||||
}
|
||||
this.overrides = Collections.unmodifiableMap(overrides);
|
||||
}
|
||||
|
||||
public record BoundObfuscationConfiguration(boolean sanitizeCount,
|
||||
Map<DataComponentType<?>, MutationType> patchStrategy) {
|
||||
|
||||
sealed interface MutationType permits MutationType.Drop, MutationType.Sanitize {
|
||||
enum Drop implements MutationType {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
record Sanitize(UnaryOperator sanitizer) implements MutationType {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
public record AssetObfuscationConfiguration(@Required boolean sanitizeCount,
|
||||
Set<DataComponentType<?>> dontObfuscate,
|
||||
Set<DataComponentType<?>> alsoObfuscate) {
|
||||
|
||||
}
|
||||
|
||||
private static BoundObfuscationConfiguration bind(final AssetObfuscationConfiguration config) {
|
||||
final Set<DataComponentType<?>> base = new HashSet<>(BASE_OVERRIDERS);
|
||||
base.addAll(config.alsoObfuscate());
|
||||
base.removeAll(config.dontObfuscate());
|
||||
|
||||
final Map<DataComponentType<?>, BoundObfuscationConfiguration.MutationType> finalStrategy = new HashMap<>();
|
||||
// Configure what path the data component should go through, should it be dropped, or should it be sanitized?
|
||||
for (final DataComponentType<?> type : base) {
|
||||
// We require some special logic, sanitize it rather than dropping it.
|
||||
final UnaryOperator<?> sanitizationOverride = ItemComponentSanitizer.SANITIZATION_OVERRIDES.get(type);
|
||||
if (sanitizationOverride != null) {
|
||||
finalStrategy.put(type, new BoundObfuscationConfiguration.MutationType.Sanitize(sanitizationOverride));
|
||||
} else {
|
||||
finalStrategy.put(type, BoundObfuscationConfiguration.MutationType.Drop.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
return new BoundObfuscationConfiguration(config.sanitizeCount(), finalStrategy);
|
||||
}
|
||||
|
||||
public BoundObfuscationConfiguration getAssetObfuscation(final ItemStack itemStack) {
|
||||
if (this.overrides.isEmpty()) {
|
||||
return this.base;
|
||||
}
|
||||
return this.overrides.getOrDefault(itemStack.get(DataComponents.ITEM_MODEL), this.base);
|
||||
}
|
||||
|
||||
static final Set<DataComponentType<?>> BASE_OVERRIDERS = Set.of(
|
||||
DataComponents.MAX_STACK_SIZE,
|
||||
DataComponents.MAX_DAMAGE,
|
||||
DataComponents.DAMAGE,
|
||||
DataComponents.UNBREAKABLE,
|
||||
DataComponents.CUSTOM_NAME,
|
||||
DataComponents.ITEM_NAME,
|
||||
DataComponents.LORE,
|
||||
DataComponents.RARITY,
|
||||
DataComponents.ENCHANTMENTS,
|
||||
DataComponents.CAN_PLACE_ON,
|
||||
DataComponents.CAN_BREAK,
|
||||
DataComponents.ATTRIBUTE_MODIFIERS,
|
||||
DataComponents.HIDE_ADDITIONAL_TOOLTIP,
|
||||
DataComponents.HIDE_TOOLTIP,
|
||||
DataComponents.REPAIR_COST,
|
||||
DataComponents.USE_REMAINDER,
|
||||
DataComponents.FOOD,
|
||||
DataComponents.DAMAGE_RESISTANT,
|
||||
// Not important on the player
|
||||
DataComponents.TOOL,
|
||||
DataComponents.ENCHANTABLE,
|
||||
DataComponents.REPAIRABLE,
|
||||
DataComponents.GLIDER,
|
||||
DataComponents.TOOLTIP_STYLE,
|
||||
DataComponents.DEATH_PROTECTION,
|
||||
DataComponents.STORED_ENCHANTMENTS,
|
||||
DataComponents.MAP_ID,
|
||||
DataComponents.POTION_CONTENTS,
|
||||
DataComponents.SUSPICIOUS_STEW_EFFECTS,
|
||||
DataComponents.WRITABLE_BOOK_CONTENT,
|
||||
DataComponents.WRITTEN_BOOK_CONTENT,
|
||||
DataComponents.CUSTOM_DATA,
|
||||
DataComponents.ENTITY_DATA,
|
||||
DataComponents.BUCKET_ENTITY_DATA,
|
||||
DataComponents.BLOCK_ENTITY_DATA,
|
||||
DataComponents.INSTRUMENT,
|
||||
DataComponents.OMINOUS_BOTTLE_AMPLIFIER,
|
||||
DataComponents.JUKEBOX_PLAYABLE,
|
||||
DataComponents.LODESTONE_TRACKER,
|
||||
DataComponents.FIREWORKS,
|
||||
DataComponents.NOTE_BLOCK_SOUND,
|
||||
DataComponents.BEES,
|
||||
DataComponents.LOCK,
|
||||
DataComponents.CONTAINER_LOOT
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package io.papermc.paper.util;
|
||||
|
||||
import java.util.function.UnaryOperator;
|
||||
import com.google.common.base.Preconditions;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The item obfuscation session may be started by a thread to indicate that items should be obfuscated when serialized
|
||||
* for network usage.
|
||||
* <p>
|
||||
* A session is persistent throughout an entire thread and will be "activated" by passing an {@link ObfuscationContext}
|
||||
* to start/switch context methods.
|
||||
*/
|
||||
@NullMarked
|
||||
public class ItemObfuscationSession implements SafeAutoClosable {
|
||||
|
||||
static final ThreadLocal<ItemObfuscationSession> THREAD_LOCAL_SESSION = ThreadLocal.withInitial(ItemObfuscationSession::new);
|
||||
|
||||
public static ItemObfuscationSession currentSession() {
|
||||
return THREAD_LOCAL_SESSION.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obfuscation level on a specific context.
|
||||
*/
|
||||
public enum ObfuscationLevel {
|
||||
NONE,
|
||||
OVERSIZED,
|
||||
ALL;
|
||||
|
||||
public boolean obfuscateOversized() {
|
||||
return switch (this) {
|
||||
case OVERSIZED, ALL -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
public boolean isObfuscating() {
|
||||
return this != NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemObfuscationSession start(final ObfuscationLevel level) {
|
||||
final ItemObfuscationSession sanitizer = THREAD_LOCAL_SESSION.get();
|
||||
sanitizer.switchContext(new ObfuscationContext(sanitizer, null, null, level));
|
||||
return sanitizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the context of the currently running session by requiring the unary operator to emit a new context
|
||||
* based on the current one.
|
||||
* The method expects the caller to use the withers on the context.
|
||||
*
|
||||
* @param contextUpdater the operator to construct the new context.
|
||||
* @return the context callback to close once the context expires.
|
||||
*/
|
||||
public static SafeAutoClosable withContext(final UnaryOperator<ObfuscationContext> contextUpdater) {
|
||||
final ItemObfuscationSession session = THREAD_LOCAL_SESSION.get();
|
||||
|
||||
// Don't pass any context if we are not currently sanitizing
|
||||
if (!session.obfuscationLevel().isObfuscating()) return () -> {
|
||||
};
|
||||
|
||||
final ObfuscationContext newContext = contextUpdater.apply(session.context());
|
||||
Preconditions.checkState(newContext != session.context(), "withContext yielded same context instance, this will break the stack on close");
|
||||
session.switchContext(newContext);
|
||||
return newContext;
|
||||
}
|
||||
|
||||
private final ObfuscationContext root = new ObfuscationContext(this, null, null, ObfuscationLevel.NONE);
|
||||
private ObfuscationContext context = root;
|
||||
|
||||
public void switchContext(final ObfuscationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public ObfuscationContext context() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.context = root;
|
||||
}
|
||||
|
||||
public ObfuscationLevel obfuscationLevel() {
|
||||
return this.context.level;
|
||||
}
|
||||
|
||||
public record ObfuscationContext(
|
||||
ItemObfuscationSession parent,
|
||||
@Nullable ObfuscationContext previousContext,
|
||||
@Nullable ItemStack itemStack,
|
||||
ObfuscationLevel level
|
||||
) implements SafeAutoClosable {
|
||||
|
||||
public ObfuscationContext itemStack(final ItemStack itemStack) {
|
||||
return new ObfuscationContext(this.parent, this, itemStack, this.level);
|
||||
}
|
||||
|
||||
public ObfuscationContext level(final ObfuscationLevel obfuscationLevel) {
|
||||
return new ObfuscationContext(this.parent, this, this.itemStack, obfuscationLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Restore the previous context when this context is closed.
|
||||
this.parent().switchContext(this.previousContext);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
package io.papermc.paper.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.UnaryOperator;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.util.Mth;
|
||||
@@ -12,26 +11,38 @@ import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.component.BundleContents;
|
||||
import net.minecraft.world.item.component.ChargedProjectiles;
|
||||
import net.minecraft.world.item.component.ItemContainerContents;
|
||||
import org.apache.commons.lang3.math.Fraction;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class DataSanitizationUtil {
|
||||
public final class OversizedItemComponentSanitizer {
|
||||
|
||||
private static final ThreadLocal<DataSanitizer> DATA_SANITIZER = ThreadLocal.withInitial(DataSanitizer::new);
|
||||
|
||||
public static DataSanitizer start(final boolean sanitize) {
|
||||
final DataSanitizer sanitizer = DATA_SANITIZER.get();
|
||||
if (sanitize) {
|
||||
sanitizer.start();
|
||||
}
|
||||
return sanitizer;
|
||||
}
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, ChargedProjectiles> CHARGED_PROJECTILES = codec(ChargedProjectiles.STREAM_CODEC, DataSanitizationUtil::sanitizeChargedProjectiles);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, BundleContents> BUNDLE_CONTENTS = codec(BundleContents.STREAM_CODEC, DataSanitizationUtil::sanitizeBundleContents);
|
||||
/*
|
||||
These represent codecs that are meant to help get rid of possibly big items by ALWAYS hiding this data.
|
||||
*/
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, ChargedProjectiles> CHARGED_PROJECTILES = codec(ChargedProjectiles.STREAM_CODEC, OversizedItemComponentSanitizer::sanitizeChargedProjectiles);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, ItemContainerContents> CONTAINER = codec(ItemContainerContents.STREAM_CODEC, contents -> ItemContainerContents.EMPTY);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, BundleContents> BUNDLE_CONTENTS = new StreamCodec<>() {
|
||||
@Override
|
||||
public BundleContents decode(final RegistryFriendlyByteBuf buffer) {
|
||||
return BundleContents.STREAM_CODEC.decode(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final RegistryFriendlyByteBuf buffer, final BundleContents value) {
|
||||
if (!ItemObfuscationSession.currentSession().obfuscationLevel().obfuscateOversized()) {
|
||||
BundleContents.STREAM_CODEC.encode(buffer, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable further obfuscation to skip e.g. count.
|
||||
try (final SafeAutoClosable ignored = ItemObfuscationSession.withContext(c -> c.level(ItemObfuscationSession.ObfuscationLevel.OVERSIZED))){
|
||||
BundleContents.STREAM_CODEC.encode(buffer, sanitizeBundleContents(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static <B, A> StreamCodec<B, A> codec(final StreamCodec<B, A> delegate, final UnaryOperator<A> sanitizer) {
|
||||
return new DataSanitizationCodec<>(delegate, sanitizer);
|
||||
}
|
||||
|
||||
private static ChargedProjectiles sanitizeChargedProjectiles(final ChargedProjectiles projectiles) {
|
||||
if (projectiles.isEmpty()) {
|
||||
@@ -39,21 +50,26 @@ public final class DataSanitizationUtil {
|
||||
}
|
||||
|
||||
return ChargedProjectiles.of(List.of(
|
||||
new ItemStack(projectiles.contains(Items.FIREWORK_ROCKET) ? Items.FIREWORK_ROCKET : Items.ARROW)
|
||||
));
|
||||
new ItemStack(
|
||||
projectiles.contains(Items.FIREWORK_ROCKET)
|
||||
? Items.FIREWORK_ROCKET
|
||||
: Items.ARROW
|
||||
)));
|
||||
}
|
||||
|
||||
// Although bundles no longer change their size based on fullness, fullness is exposed in item models.
|
||||
private static BundleContents sanitizeBundleContents(final BundleContents contents) {
|
||||
if (contents.isEmpty()) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
// Bundles change their texture based on their fullness.
|
||||
// A bundles content weight may be anywhere from 0 to, basically, infinity.
|
||||
// A weight of 1 is the usual maximum case
|
||||
int sizeUsed = Mth.mulAndTruncate(contents.weight(), 64);
|
||||
// Early out, *most* bundles should not be overfilled above a weight of one.
|
||||
if (sizeUsed <= 64) return new BundleContents(List.of(new ItemStack(Items.PAPER, Math.max(1, sizeUsed))));
|
||||
if (sizeUsed <= 64) {
|
||||
return new BundleContents(List.of(new ItemStack(Items.PAPER, Math.max(1, sizeUsed))));
|
||||
}
|
||||
|
||||
final List<ItemStack> sanitizedRepresentation = new ObjectArrayList<>(sizeUsed / 64 + 1);
|
||||
while (sizeUsed > 0) {
|
||||
@@ -66,20 +82,19 @@ public final class DataSanitizationUtil {
|
||||
return new BundleContents(sanitizedRepresentation);
|
||||
}
|
||||
|
||||
private static <B, A> StreamCodec<B, A> codec(final StreamCodec<B, A> delegate, final UnaryOperator<A> sanitizer) {
|
||||
return new DataSanitizationCodec<>(delegate, sanitizer);
|
||||
}
|
||||
|
||||
private record DataSanitizationCodec<B, A>(StreamCodec<B, A> delegate, UnaryOperator<A> sanitizer) implements StreamCodec<B, A> {
|
||||
// Codec used to override encoding if sanitization is enabled
|
||||
private record DataSanitizationCodec<B, A>(StreamCodec<B, A> delegate,
|
||||
UnaryOperator<A> sanitizer) implements StreamCodec<B, A> {
|
||||
|
||||
@Override
|
||||
public @NonNull A decode(final @NonNull B buf) {
|
||||
return this.delegate.decode(buf);
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
@Override
|
||||
public void encode(final @NonNull B buf, final @NonNull A value) {
|
||||
if (!DATA_SANITIZER.get().value().get()) {
|
||||
if (!ItemObfuscationSession.currentSession().obfuscationLevel().obfuscateOversized()) {
|
||||
this.delegate.encode(buf, value);
|
||||
} else {
|
||||
this.delegate.encode(buf, this.sanitizer.apply(value));
|
||||
@@ -87,22 +102,4 @@ public final class DataSanitizationUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public record DataSanitizer(AtomicBoolean value) implements AutoCloseable {
|
||||
|
||||
public DataSanitizer() {
|
||||
this(new AtomicBoolean(false));
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.value.compareAndSet(false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.value.compareAndSet(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
private DataSanitizationUtil() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.papermc.paper.util;
|
||||
|
||||
/**
|
||||
* A type of {@link AutoCloseable} that does not throw a checked exception.
|
||||
*/
|
||||
public interface SafeAutoClosable extends AutoCloseable {
|
||||
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
Reference in New Issue
Block a user