From b56273bef901949737986353ea95586f02f06db3 Mon Sep 17 00:00:00 2001 From: I-am-DDang_ Date: Thu, 1 May 2025 16:19:18 +0900 Subject: [PATCH 1/4] make bookshelf power configurable and refresh enchantment hint of menu after editing bookshelf power --- .../enchantment/PrepareItemEnchantEvent.java | 4 +++- .../inventory/EnchantmentMenu.java.patch | 24 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java b/paper-api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java index 4627f5144d31..116b1f31933f 100644 --- a/paper-api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java @@ -24,7 +24,7 @@ public class PrepareItemEnchantEvent extends InventoryEvent implements Cancellab private final Block table; private final ItemStack item; private final EnchantmentOffer[] offers; - private final int bonus; + private int bonus; private boolean cancelled; @@ -111,6 +111,8 @@ public int getEnchantmentBonus() { return this.bonus; } + public void setEnchantmentBonus(int bonus) { this.bonus = bonus; } + @Override public boolean isCancelled() { return this.cancelled; diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch index 80fbd66e2285..a7b236846163 100644 --- a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch @@ -64,7 +64,7 @@ this.access.execute((level, blockPos) -> { IdMap> holderIdMap = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); int i1 = 0; -@@ -119,6 +_,42 @@ +@@ -119,6 +_,64 @@ } } @@ -89,6 +89,28 @@ + return; + } + ++ // Paper Start - refreshing enchantment hint with configuring bookshelf power ++ // refreshing offers with event's bookshelf power if event's bookshelf power value is not equal to previous natural bookshelf power value. ++ if (event.getEnchantmentBonus() != i1) { ++ for (int i = 0; i < 3; i++) { ++ org.bukkit.enchantments.Enchantment enchantment = (this.enchantClue[i] >= 0) ? org.bukkit.craftbukkit.enchantments.CraftEnchantment.minecraftHolderToBukkit(holderIdMap.byId(this.enchantClue[i])) : null; ++ event.getOffers()[i] = (enchantment != null) ? new org.bukkit.enchantments.EnchantmentOffer(enchantment, this.levelClue[i], this.costs[i]) : null; ++ ++ this.costs[i] = EnchantmentHelper.getEnchantmentCost(this.random, i, event.getEnchantmentBonus(), item); ++ this.enchantClue[i] = -1; ++ this.levelClue[i] = -1; ++ if (this.costs[i] < i + 1) continue; ++ ++ List enchantmentList = this.getEnchantmentList(level.registryAccess(), item, i, this.costs[i]); ++ if (!enchantmentList.isEmpty()) { ++ EnchantmentInstance enchantmentInstance = enchantmentList.get(this.random.nextInt(enchantmentList.size())); ++ this.enchantClue[i] = holderIdMap.getId(enchantmentInstance.enchantment()); ++ this.levelClue[i] = enchantmentInstance.level(); ++ } ++ } ++ } ++ // Paper end ++ + for (int j = 0; j < 3; j++) { + org.bukkit.enchantments.EnchantmentOffer offer = event.getOffers()[j]; + if (offer != null) { From 94142092789cf518ca56023ee34994f8295352d5 Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Wed, 7 May 2025 12:40:17 +0200 Subject: [PATCH 2/4] Revert "make bookshelf power configurable and refresh enchantment hint of menu after editing bookshelf power" This reverts commit b56273bef901949737986353ea95586f02f06db3. --- .../enchantment/PrepareItemEnchantEvent.java | 4 +--- .../inventory/EnchantmentMenu.java.patch | 24 +------------------ 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java b/paper-api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java index 116b1f31933f..4627f5144d31 100644 --- a/paper-api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java @@ -24,7 +24,7 @@ public class PrepareItemEnchantEvent extends InventoryEvent implements Cancellab private final Block table; private final ItemStack item; private final EnchantmentOffer[] offers; - private int bonus; + private final int bonus; private boolean cancelled; @@ -111,8 +111,6 @@ public int getEnchantmentBonus() { return this.bonus; } - public void setEnchantmentBonus(int bonus) { this.bonus = bonus; } - @Override public boolean isCancelled() { return this.cancelled; diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch index a7b236846163..80fbd66e2285 100644 --- a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch @@ -64,7 +64,7 @@ this.access.execute((level, blockPos) -> { IdMap> holderIdMap = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); int i1 = 0; -@@ -119,6 +_,64 @@ +@@ -119,6 +_,42 @@ } } @@ -89,28 +89,6 @@ + return; + } + -+ // Paper Start - refreshing enchantment hint with configuring bookshelf power -+ // refreshing offers with event's bookshelf power if event's bookshelf power value is not equal to previous natural bookshelf power value. -+ if (event.getEnchantmentBonus() != i1) { -+ for (int i = 0; i < 3; i++) { -+ org.bukkit.enchantments.Enchantment enchantment = (this.enchantClue[i] >= 0) ? org.bukkit.craftbukkit.enchantments.CraftEnchantment.minecraftHolderToBukkit(holderIdMap.byId(this.enchantClue[i])) : null; -+ event.getOffers()[i] = (enchantment != null) ? new org.bukkit.enchantments.EnchantmentOffer(enchantment, this.levelClue[i], this.costs[i]) : null; -+ -+ this.costs[i] = EnchantmentHelper.getEnchantmentCost(this.random, i, event.getEnchantmentBonus(), item); -+ this.enchantClue[i] = -1; -+ this.levelClue[i] = -1; -+ if (this.costs[i] < i + 1) continue; -+ -+ List enchantmentList = this.getEnchantmentList(level.registryAccess(), item, i, this.costs[i]); -+ if (!enchantmentList.isEmpty()) { -+ EnchantmentInstance enchantmentInstance = enchantmentList.get(this.random.nextInt(enchantmentList.size())); -+ this.enchantClue[i] = holderIdMap.getId(enchantmentInstance.enchantment()); -+ this.levelClue[i] = enchantmentInstance.level(); -+ } -+ } -+ } -+ // Paper end -+ + for (int j = 0; j < 3; j++) { + org.bukkit.enchantments.EnchantmentOffer offer = event.getOffers()[j]; + if (offer != null) { From bc11611cda6d2852c4f9ab377a6021a1c88e4b83 Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Thu, 24 Apr 2025 16:47:37 +0200 Subject: [PATCH 3/4] Roll EnchantmentOffer API Introduces a new API method that allows plugins to simulate the rolling process of an enchantment table for enchantment offers. This is specifically useful to "reroll" an enchantment tables offers during the PrepareItemEnchantEvent to e.g. change the bookshelf bonus or seed. Co-authored-by: I-am-DDang_ --- .../io/papermc/paper/InternalAPIBridge.java | 19 ++++ .../bukkit/enchantments/EnchantmentOffer.java | 35 +++++++ .../inventory/EnchantmentMenu.java.patch | 94 ++++++++++++++++++- .../paper/PaperServerInternalAPIBridge.java | 52 ++++++++++ 4 files changed, 197 insertions(+), 3 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java b/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java index 422fdd93d142..8075050a5fbd 100644 --- a/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java +++ b/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java @@ -6,7 +6,9 @@ import org.bukkit.block.Biome; import org.bukkit.damage.DamageEffect; import org.bukkit.damage.DamageSource; +import org.bukkit.enchantments.EnchantmentOffer; import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -73,5 +75,22 @@ class Holder { * @return combat entry */ CombatEntry createCombatEntry(DamageSource damageSource, float damage, @Nullable FallLocationType fallLocationType, float fallDistance); + + /** + * Rolls a new set of enchantment offers based on a target item stack, a seed and a bookshelf count. + * + * @param targetStack the itemstack for which the enchantment offers are rolled. + * @param seed the seed used for random selection of enchantments. + * Mirrors {@link org.bukkit.inventory.view.EnchantmentView#getEnchantmentSeed()}. + * @param bookshelfCount the number of virtual bookshelves to consider when rolling the enchantments. + * @param offerAmount the amount of enchantment offers to roll. + * @return an array of enchantment offers with size {@code offerAmount}. + */ + @Nullable EnchantmentOffer[] rollEnchantmentOffers( + final ItemStack targetStack, + final int seed, + final int bookshelfCount, + final int offerAmount + ); } diff --git a/paper-api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java b/paper-api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java index 8111acaa1af5..ceac2eb3834e 100644 --- a/paper-api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java +++ b/paper-api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java @@ -1,7 +1,11 @@ package org.bukkit.enchantments; import com.google.common.base.Preconditions; +import io.papermc.paper.InternalAPIBridge; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Objects; /** * A class for the available enchantment offers in the enchantment table. @@ -80,4 +84,35 @@ public void setCost(int cost) { this.cost = cost; } + + @Override + public boolean equals(final Object o) { + if (o == null || getClass() != o.getClass()) return false; + final EnchantmentOffer that = (EnchantmentOffer) o; + return getEnchantmentLevel() == that.getEnchantmentLevel() && getCost() == that.getCost() && Objects.equals(getEnchantment(), that.getEnchantment()); + } + + @Override + public int hashCode() { + return Objects.hash(getEnchantment(), getEnchantmentLevel(), getCost()); + } + + /** + * Rolls a new set of enchantment offers based on a target item stack, a seed and a bookshelf count. + * + * @param targetStack the itemstack for which the enchantment offers are rolled. + * @param seed the seed used for random selection of enchantments. + * Mirrors {@link org.bukkit.inventory.view.EnchantmentView#getEnchantmentSeed()}. + * @param bookshelfCount the number of virtual bookshelves to consider when rolling the enchantments. + * @param offerAmount the amount of enchantment offers to roll. + * @return an array of enchantment offers with size {@code offerAmount}. + */ + public static @Nullable EnchantmentOffer[] rollEnchantmentOffers( + final ItemStack targetStack, + final int seed, + final int bookshelfCount, + final int offerAmount + ) { + return InternalAPIBridge.get().rollEnchantmentOffers(targetStack, seed, bookshelfCount, offerAmount); + } } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch index 80fbd66e2285..1d706d073672 100644 --- a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch @@ -64,10 +64,32 @@ this.access.execute((level, blockPos) -> { IdMap> holderIdMap = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); int i1 = 0; -@@ -119,6 +_,42 @@ +@@ -97,27 +_,42 @@ } } +- this.random.setSeed(this.enchantmentSeed.get()); +- +- for (int i2 = 0; i2 < 3; i2++) { +- this.costs[i2] = EnchantmentHelper.getEnchantmentCost(this.random, i2, i1, item); +- this.enchantClue[i2] = -1; +- this.levelClue[i2] = -1; +- if (this.costs[i2] < i2 + 1) { +- this.costs[i2] = 0; +- } +- } +- +- for (int i2x = 0; i2x < 3; i2x++) { +- if (this.costs[i2x] > 0) { +- List enchantmentList = this.getEnchantmentList(level.registryAccess(), item, i2x, this.costs[i2x]); +- if (enchantmentList != null && !enchantmentList.isEmpty()) { +- EnchantmentInstance enchantmentInstance = enchantmentList.get(this.random.nextInt(enchantmentList.size())); +- this.enchantClue[i2x] = holderIdMap.getId(enchantmentInstance.enchantment()); +- this.levelClue[i2x] = enchantmentInstance.level(); +- } +- } +- } ++ fillEnchantmentOffers(this.random, this.enchantmentSeed.get(), i1, item, level.registryAccess(), this.costs, this.enchantClue, this.levelClue); // Paper - extracted into net.minecraft.world.inventory.EnchantmentMenu.fillEnchantmentOffers + // CraftBukkit start + org.bukkit.craftbukkit.inventory.CraftItemStack craftItemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); + org.bukkit.enchantments.EnchantmentOffer[] offers = new org.bukkit.enchantments.EnchantmentOffer[3]; @@ -103,10 +125,53 @@ + } + } + // CraftBukkit end -+ + this.broadcastChanges(); }); - } else { +@@ -131,6 +_,43 @@ + } + } + ++ // Paper - extract offer generation ++ // Extracted over modifications in place given nearly every line has diff to account for non-instance fields for ++ // cost, enchantment and level clues. ++ public static void fillEnchantmentOffers( ++ final RandomSource random, ++ final int seed, ++ final int i1, ++ final ItemStack item, ++ final RegistryAccess registryAccess, ++ final int[] costs, ++ final int[] enchantClue, ++ final int[] levelClue ++ ) { ++ random.setSeed(seed); ++ ++ for (int i2 = 0; i2 < costs.length; i2++) { ++ costs[i2] = EnchantmentHelper.getEnchantmentCost(random, i2, i1, item); ++ enchantClue[i2] = -1; ++ levelClue[i2] = -1; ++ if (costs[i2] < i2 + 1) { ++ costs[i2] = 0; ++ } ++ } ++ ++ for (int i2x = 0; i2x < costs.length; i2x++) { ++ if (costs[i2x] > 0) { ++ List enchantmentList = getEnchantmentList(registryAccess, item, i2x, costs[i2x], random, seed); ++ if (enchantmentList != null && !enchantmentList.isEmpty()) { ++ EnchantmentInstance enchantmentInstance = enchantmentList.get(random.nextInt(enchantmentList.size())); ++ enchantClue[i2x] = registryAccess.lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap().getId(enchantmentInstance.enchantment()); ++ levelClue[i2x] = enchantmentInstance.level(); ++ } ++ } ++ } ++ } ++ // Paper - extract offer generation ++ + @Override + public boolean clickMenuButton(Player player, int id) { + if (id >= 0 && id < this.costs.length) { @@ -145,19 +_,52 @@ return false; } else { @@ -171,6 +236,29 @@ item1.consume(i, player); if (item1.isEmpty()) { this.enchantSlots.setItem(1, ItemStack.EMPTY); +@@ -183,14 +_,19 @@ + } + + private List getEnchantmentList(RegistryAccess registryAccess, ItemStack stack, int slot, int cost) { +- this.random.setSeed(this.enchantmentSeed.get() + slot); ++ // Paper start - make static and public ++ return getEnchantmentList(registryAccess, stack, slot, cost, this.random, this.enchantmentSeed.get()); ++ } ++ public static List getEnchantmentList(RegistryAccess registryAccess, ItemStack stack, int slot, int cost, RandomSource random, int seed) { ++ // Paper end - make static and public ++ random.setSeed(seed + slot); // Paper - make static and public + Optional> optional = registryAccess.lookupOrThrow(Registries.ENCHANTMENT).get(EnchantmentTags.IN_ENCHANTING_TABLE); + if (optional.isEmpty()) { + return List.of(); + } else { +- List list = EnchantmentHelper.selectEnchantment(this.random, stack, cost, optional.get().stream()); ++ List list = EnchantmentHelper.selectEnchantment(random, stack, cost, optional.get().stream()); // Paper - make static and public + if (stack.is(Items.BOOK) && list.size() > 1) { +- list.remove(this.random.nextInt(list.size())); ++ list.remove(random.nextInt(list.size())); // Paper start - make static and public + } + + return list; @@ -202,6 +_,12 @@ return item.isEmpty() ? 0 : item.getCount(); } diff --git a/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java b/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java index d6933562690e..d3406f4dea70 100644 --- a/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java @@ -5,17 +5,30 @@ import io.papermc.paper.world.damagesource.PaperCombatEntryWrapper; import io.papermc.paper.world.damagesource.PaperCombatTrackerWrapper; import net.minecraft.Optionull; +import net.minecraft.core.Holder; +import net.minecraft.core.IdMap; +import net.minecraft.core.registries.Registries; +import net.minecraft.util.RandomSource; import net.minecraft.world.damagesource.FallLocation; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.item.enchantment.Enchantment; import org.bukkit.block.Biome; +import org.bukkit.craftbukkit.CraftRegistry; import org.bukkit.craftbukkit.block.CraftBiome; import org.bukkit.craftbukkit.damage.CraftDamageEffect; import org.bukkit.craftbukkit.damage.CraftDamageSource; +import org.bukkit.craftbukkit.enchantments.CraftEnchantment; import org.bukkit.craftbukkit.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.RandomSourceWrapper; import org.bukkit.damage.DamageEffect; import org.bukkit.damage.DamageSource; +import org.bukkit.enchantments.EnchantmentOffer; import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.Random; @NullMarked public class PaperServerInternalAPIBridge implements InternalAPIBridge { @@ -71,4 +84,43 @@ private CombatEntry createCombatEntry( damageSource, damage, fallLocation, fallDistance )); } + + @Override + public @Nullable EnchantmentOffer[] rollEnchantmentOffers( + final ItemStack targetStack, + final int seed, + final int bookshelfCount, + final int offerAmount + ) { + final int[] costs = new int[offerAmount]; + final int[] enchantClue = new int[offerAmount]; + final int[] levelClue = new int[offerAmount]; + + final RandomSource randomSource = new RandomSourceWrapper(new Random()); + EnchantmentMenu.fillEnchantmentOffers( + randomSource, + seed, + bookshelfCount, + CraftItemStack.unwrap(targetStack), + CraftRegistry.getMinecraftRegistry(), + costs, + enchantClue, + levelClue + ); + + final IdMap> holderIdMap = CraftRegistry.getMinecraftRegistry() + .lookupOrThrow(Registries.ENCHANTMENT) + .asHolderIdMap(); + final @Nullable EnchantmentOffer[] offers = new EnchantmentOffer[offerAmount]; + for (int i = 0; i < costs.length; i++) { + if (enchantClue[i] < 0) continue; + offers[i] = new EnchantmentOffer( + CraftEnchantment.minecraftHolderToBukkit(holderIdMap.byId(enchantClue[i])), + levelClue[i], + costs[i] + ); + } + + return offers; + } } From ae2d5e5cdf1a3a10259e2fce585c188be76fcb7c Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Wed, 7 May 2025 12:54:57 +0200 Subject: [PATCH 4/4] Missing annotations --- .../main/java/org/bukkit/enchantments/EnchantmentOffer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java b/paper-api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java index ceac2eb3834e..b02e8f208860 100644 --- a/paper-api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java +++ b/paper-api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java @@ -107,8 +107,8 @@ public int hashCode() { * @param offerAmount the amount of enchantment offers to roll. * @return an array of enchantment offers with size {@code offerAmount}. */ - public static @Nullable EnchantmentOffer[] rollEnchantmentOffers( - final ItemStack targetStack, + public static @Nullable EnchantmentOffer @NotNull [] rollEnchantmentOffers( + final @NotNull ItemStack targetStack, final int seed, final int bookshelfCount, final int offerAmount