From e8297c4ccadae4885520def7dfbb545dc0233017 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 22 Sep 2024 14:28:45 -0700 Subject: [PATCH] Expand out datapack API (#10828) * Expand out datapack API * add getTitle * better enable check --- patches/api/0270-Add-basic-Datapack-API.patch | 160 ++++++++++++++++-- .../server/0529-Add-basic-Datapack-API.patch | 138 ++++++++++++--- 2 files changed, 256 insertions(+), 42 deletions(-) diff --git a/patches/api/0270-Add-basic-Datapack-API.patch b/patches/api/0270-Add-basic-Datapack-API.patch index bbf88037ca..0aa103eff0 100644 --- a/patches/api/0270-Add-basic-Datapack-API.patch +++ b/patches/api/0270-Add-basic-Datapack-API.patch @@ -3,71 +3,201 @@ From: Connor Linfoot Date: Sun, 16 May 2021 15:07:34 +0100 Subject: [PATCH] Add basic Datapack API +Co-authored-by: Jake Potrebic diff --git a/src/main/java/io/papermc/paper/datapack/Datapack.java b/src/main/java/io/papermc/paper/datapack/Datapack.java new file mode 100644 -index 0000000000000000000000000000000000000000..7b2ab0be10a21e0496ad1d485ff8cb2c0b92a2cb +index 0000000000000000000000000000000000000000..233a31afa9673c9cb8d9eb52551425ff15f79661 --- /dev/null +++ b/src/main/java/io/papermc/paper/datapack/Datapack.java -@@ -0,0 +1,32 @@ +@@ -0,0 +1,98 @@ +package io.papermc.paper.datapack; + ++import java.util.Set; ++import net.kyori.adventure.text.Component; ++import org.bukkit.FeatureFlag; +import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.Unmodifiable; + ++/** ++ * This is a snapshot of a datapack on the server. It ++ * won't be updated as datapacks are updated. ++ */ +public interface Datapack { + + /** ++ * Gets the name/id of this datapack. ++ * + * @return the name of the pack + */ -+ @NonNull -+ String getName(); ++ @Contract(pure = true) ++ @NonNull String getName(); + + /** ++ * Gets the title component of this datapack. ++ * ++ * @return the title ++ */ ++ @NonNull Component getTitle(); ++ ++ /** ++ * Gets the description component of this datapack. ++ * ++ * @return the description ++ */ ++ @NonNull Component getDescription(); ++ ++ /** ++ * Gets if this datapack is required to be enabled. ++ * ++ * @return true if the pack is required ++ */ ++ boolean isRequired(); ++ ++ /** ++ * Gets the compatibility status of this pack. ++ * + * @return the compatibility of the pack + */ -+ @NonNull -+ Compatibility getCompatibility(); ++ @NonNull Compatibility getCompatibility(); + + /** -+ * @return whether or not the pack is currently enabled ++ * Gets the set of required features for this datapack. ++ * ++ * @return the set of required features ++ */ ++ @NonNull @Unmodifiable Set getRequiredFeatures(); ++ ++ /** ++ * Gets the enabled state of this pack. ++ * ++ * @return whether the pack is currently enabled + */ + boolean isEnabled(); + ++ /** ++ * Changes the enabled state of this pack. Will ++ * cause a reload of resources ({@code /minecraft:reload}) if ++ * any change happens. ++ * ++ * @param enabled true to enable, false to disable ++ * @apiNote This method may be deprecated in the future as setters on a "snapshot" type are undesirable. ++ */ + void setEnabled(boolean enabled); + ++ /** ++ * Gets the source for this datapack. ++ * ++ * @return the pack source ++ */ ++ @NonNull DatapackSource getSource(); ++ ++ /** ++ * Computes the component vanilla Minecraft uses ++ * to display this datapack. Includes the {@link #getSource()}, ++ * {@link #getDescription()}, {@link #getName()}, and the enabled state. ++ * ++ * @return a new component ++ */ ++ @Contract(pure = true, value = "-> new") ++ @NonNull Component computeDisplayName(); ++ + enum Compatibility { + TOO_OLD, + TOO_NEW, + COMPATIBLE, + } -+ +} diff --git a/src/main/java/io/papermc/paper/datapack/DatapackManager.java b/src/main/java/io/papermc/paper/datapack/DatapackManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..58f78d5e91beacaf710f62461cf869f70d08b2a2 +index 0000000000000000000000000000000000000000..bbb81a8058a67fd554c781dbb4908434ad339655 --- /dev/null +++ b/src/main/java/io/papermc/paper/datapack/DatapackManager.java -@@ -0,0 +1,21 @@ +@@ -0,0 +1,43 @@ +package io.papermc.paper.datapack; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Collection; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.jetbrains.annotations.Unmodifiable; + +public interface DatapackManager { + + /** -+ * @return all the packs known to the server ++ * Triggers a refresh of the available and selected datapacks. This ++ * can find new datapacks, remove old ones, and update the metadata for ++ * existing datapacks. Some of these changes will only take effect ++ * after the next {@link org.bukkit.Server#reloadData()} or {@code /minecraft:reload}. + */ -+ @NonNull -+ Collection getPacks(); ++ void refreshPacks(); + + /** ++ * Gets a datapack by name. May require calling {@link #refreshPacks()} before ++ * to get the latest pack information. ++ * ++ * @param name the name/id of the datapack ++ * @return the datapack, or null if not found ++ */ ++ @Nullable Datapack getPack(@NonNull String name); ++ ++ /** ++ * Gets the available datapacks. May require calling {@link #refreshPacks()} before ++ * to get the latest pack information. ++ * ++ * @return all the packs known to the server ++ */ ++ @NonNull @Unmodifiable Collection getPacks(); ++ ++ /** ++ * Gets the enabled datapacks. May require calling {@link #refreshPacks()} before ++ * to get the latest pack information. ++ * + * @return all the packs which are currently enabled + */ -+ @NonNull -+ Collection getEnabledPacks(); ++ @NonNull @Unmodifiable Collection getEnabledPacks(); ++} +diff --git a/src/main/java/io/papermc/paper/datapack/DatapackSource.java b/src/main/java/io/papermc/paper/datapack/DatapackSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1679cbd78920005475343092857e13906ab73f82 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/DatapackSource.java +@@ -0,0 +1,17 @@ ++package io.papermc.paper.datapack; + ++/** ++ * Source of a datapack. ++ */ ++public sealed interface DatapackSource permits DatapackSourceImpl { ++ ++ DatapackSource DEFAULT = create("default"); ++ DatapackSource BUILT_IN = create("built_in"); ++ DatapackSource FEATURE = create("feature"); ++ DatapackSource WORLD = create("world"); ++ DatapackSource SERVER = create("server"); ++ ++ private static DatapackSource create(final String name) { ++ return new DatapackSourceImpl(name); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/datapack/DatapackSourceImpl.java b/src/main/java/io/papermc/paper/datapack/DatapackSourceImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3eb4d1df8187fdeab74948d261d9c8e03e55605c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/DatapackSourceImpl.java +@@ -0,0 +1,12 @@ ++package io.papermc.paper.datapack; ++ ++import org.jetbrains.annotations.ApiStatus; ++ ++@ApiStatus.Internal ++record DatapackSourceImpl(String name) implements DatapackSource { ++ ++ @Override ++ public String toString() { ++ return this.name; ++ } +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java index b558fa73dbcf3747690933e6aadf7061a0de2630..be68351555bde59a4e55bf1bad261e9f6bc9f704 100644 diff --git a/patches/server/0529-Add-basic-Datapack-API.patch b/patches/server/0529-Add-basic-Datapack-API.patch index fbd2c201df..9578a2cc2d 100644 --- a/patches/server/0529-Add-basic-Datapack-API.patch +++ b/patches/server/0529-Add-basic-Datapack-API.patch @@ -3,92 +3,176 @@ From: Connor Linfoot Date: Sun, 16 May 2021 15:07:34 +0100 Subject: [PATCH] Add basic Datapack API +Co-authored-by: Jake Potrebic diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java new file mode 100644 -index 0000000000000000000000000000000000000000..9b7dd8a0fba4547f5268b3f99e21ddbe6b5bf566 +index 0000000000000000000000000000000000000000..8bd8263b51fb2bb364353565b1ba26b3b0d1d55e --- /dev/null +++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java -@@ -0,0 +1,50 @@ +@@ -0,0 +1,103 @@ +package io.papermc.paper.datapack; + ++import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.event.server.ServerResourcesReloadedEvent; ++import io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.packs.repository.Pack; -+import java.util.List; -+import java.util.stream.Collectors; ++import net.minecraft.server.packs.repository.PackSource; ++import org.bukkit.FeatureFlag; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; + ++@DefaultQualifier(NonNull.class) +public class PaperDatapack implements Datapack { -+ private final String name; -+ private final Compatibility compatibility; ++ ++ private static final Map PACK_SOURCES = new ConcurrentHashMap<>(); ++ static { ++ PACK_SOURCES.put(PackSource.DEFAULT, DatapackSource.DEFAULT); ++ PACK_SOURCES.put(PackSource.BUILT_IN, DatapackSource.BUILT_IN); ++ PACK_SOURCES.put(PackSource.FEATURE, DatapackSource.FEATURE); ++ PACK_SOURCES.put(PackSource.WORLD, DatapackSource.WORLD); ++ PACK_SOURCES.put(PackSource.SERVER, DatapackSource.SERVER); ++ } ++ ++ private final Pack pack; + private final boolean enabled; + -+ PaperDatapack(Pack loader, boolean enabled) { -+ this.name = loader.getId(); -+ this.compatibility = Compatibility.valueOf(loader.getCompatibility().name()); ++ PaperDatapack(final Pack pack, final boolean enabled) { ++ this.pack = pack; + this.enabled = enabled; + } + + @Override + public String getName() { -+ return name; ++ return this.pack.getId(); ++ } ++ ++ @Override ++ public Component getTitle() { ++ return PaperAdventure.asAdventure(this.pack.getTitle()); ++ } ++ ++ @Override ++ public Component getDescription() { ++ return PaperAdventure.asAdventure(this.pack.getDescription()); ++ } ++ ++ @Override ++ public boolean isRequired() { ++ return this.pack.isRequired(); + } + + @Override + public Compatibility getCompatibility() { -+ return compatibility; ++ return Datapack.Compatibility.valueOf(this.pack.getCompatibility().name()); ++ } ++ ++ @Override ++ public Set getRequiredFeatures() { ++ return PaperFeatureFlagProviderImpl.fromNms(this.pack.getRequestedFeatures()); + } + + @Override + public boolean isEnabled() { -+ return enabled; ++ return this.enabled; + } + + @Override -+ public void setEnabled(boolean enabled) { -+ if (enabled == this.enabled) { ++ public void setEnabled(final boolean enabled) { ++ final MinecraftServer server = MinecraftServer.getServer(); ++ final List enabledPacks = new ArrayList<>(server.getPackRepository().getSelectedPacks()); ++ final @Nullable Pack packToChange = server.getPackRepository().getPack(this.getName()); ++ if (packToChange == null) { ++ throw new IllegalStateException("Cannot toggle state of pack that doesn't exist: " + this.getName()); ++ } ++ if (enabled == enabledPacks.contains(packToChange)) { + return; + } -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ List enabledKeys = server.getPackRepository().getSelectedPacks().stream().map(Pack::getId).collect(Collectors.toList()); + if (enabled) { -+ enabledKeys.add(this.name); ++ packToChange.getDefaultPosition().insert(enabledPacks, packToChange, Pack::selectionConfig, false); // modeled off the default /datapack enable logic + } else { -+ enabledKeys.remove(this.name); ++ enabledPacks.remove(packToChange); + } -+ server.reloadResources(enabledKeys, ServerResourcesReloadedEvent.Cause.PLUGIN); ++ server.reloadResources(enabledPacks.stream().map(Pack::getId).toList(), ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++ ++ @Override ++ public DatapackSource getSource() { ++ return PACK_SOURCES.computeIfAbsent(this.pack.location().source(), source -> new DatapackSourceImpl(source.toString())); ++ } ++ ++ @Override ++ public Component computeDisplayName() { ++ return PaperAdventure.asAdventure(this.pack.getChatLink(this.enabled)); + } +} diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..cf4374493c11057451a62a655514415cf6b298e0 +index 0000000000000000000000000000000000000000..caa41c525d2b36b5a9f9942380f06c97d5781a93 --- /dev/null +++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java -@@ -0,0 +1,25 @@ +@@ -0,0 +1,55 @@ +package io.papermc.paper.datapack; + ++import com.google.common.collect.Collections2; +import java.util.Collection; -+import java.util.stream.Collectors; ++import java.util.Collections; ++import java.util.function.Predicate; +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.server.packs.repository.PackRepository; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; + ++@DefaultQualifier(NonNull.class) +public class PaperDatapackManager implements DatapackManager { ++ + private final PackRepository repository; + -+ public PaperDatapackManager(PackRepository repository) { ++ public PaperDatapackManager(final PackRepository repository) { + this.repository = repository; + } + + @Override ++ public void refreshPacks() { ++ this.repository.reload(); ++ } ++ ++ @Override ++ public @Nullable Datapack getPack(final @NonNull String name) { ++ final @Nullable Pack pack = this.repository.getPack(name); ++ if (pack == null) { ++ return null; ++ } ++ return new PaperDatapack(pack, this.repository.getSelectedPacks().contains(pack)); ++ } ++ ++ @Override + public Collection getPacks() { -+ Collection enabledPacks = repository.getSelectedPacks(); -+ return repository.getAvailablePacks().stream().map(loader -> new PaperDatapack(loader, enabledPacks.contains(loader))).collect(Collectors.toList()); ++ final Collection enabledPacks = this.repository.getSelectedPacks(); ++ return this.transformPacks(this.repository.getAvailablePacks(), enabledPacks::contains); + } + + @Override + public Collection getEnabledPacks() { -+ return repository.getSelectedPacks().stream().map(loader -> new PaperDatapack(loader, true)).collect(Collectors.toList()); ++ return this.transformPacks(this.repository.getSelectedPacks(), pack -> true); ++ } ++ ++ private Collection transformPacks(final Collection packs, final Predicate enabled) { ++ return Collections.unmodifiableCollection( ++ Collections2.transform( ++ packs, ++ pack -> new PaperDatapack(pack, enabled.test(pack)) ++ ) ++ ); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java