Finish block entity

This commit is contained in:
Bjarne Koll
2024-12-14 05:05:32 +01:00
parent 368d2116ba
commit f25c1a33a0
41 changed files with 1904 additions and 2396 deletions

View File

@@ -0,0 +1,295 @@
--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
@@ -20,7 +_,6 @@
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.WorldlyContainer;
import net.minecraft.world.entity.ExperienceOrb;
-import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedItemContents;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.RecipeCraftingHolder;
@@ -99,11 +_,44 @@
};
public final Reference2IntOpenHashMap<ResourceKey<Recipe<?>>> recipesUsed = new Reference2IntOpenHashMap<>();
private final RecipeManager.CachedCheck<SingleRecipeInput, ? extends AbstractCookingRecipe> quickCheck;
+ public final RecipeType<? extends AbstractCookingRecipe> recipeType; // Paper - cook speed multiplier API
+ public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API
protected AbstractFurnaceBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState, RecipeType<? extends AbstractCookingRecipe> recipeType) {
super(type, pos, blockState);
this.quickCheck = RecipeManager.createCheck(recipeType);
- }
+ this.recipeType = recipeType; // Paper - cook speed multiplier API
+ }
+
+ // CraftBukkit start - add fields and methods
+ private int maxStack = MAX_STACK;
+ public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
+
+ public List<ItemStack> getContents() {
+ return this.items;
+ }
+
+ public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.add(who);
+ }
+
+ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.remove(who);
+ }
+
+ public List<org.bukkit.entity.HumanEntity> getViewers() {
+ return this.transaction;
+ }
+
+ @Override
+ public int getMaxStackSize() {
+ return this.maxStack;
+ }
+
+ public void setMaxStackSize(int size) {
+ this.maxStack = size;
+ }
+ // CraftBukkit end
private boolean isLit() {
return this.litTimeRemaining > 0;
@@ -121,8 +_,19 @@
CompoundTag compound = tag.getCompound("RecipesUsed");
for (String string : compound.getAllKeys()) {
- this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(string)), compound.getInt(string));
- }
+ // Paper start - Validate ResourceLocation
+ final ResourceLocation resourceLocation = ResourceLocation.tryParse(string);
+ if (resourceLocation != null) {
+ this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, resourceLocation), tag.getInt(string));
+ }
+ // Paper end - Validate ResourceLocation
+ }
+
+ // Paper start - cook speed multiplier API
+ if (tag.contains("Paper.CookSpeedMultiplier")) {
+ this.cookSpeedMultiplier = tag.getDouble("Paper.CookSpeedMultiplier");
+ }
+ // Paper end - cook speed multiplier API
}
@Override
@@ -132,6 +_,7 @@
tag.putShort("cooking_total_time", (short)this.cookingTotalTime);
tag.putShort("lit_time_remaining", (short)this.litTimeRemaining);
tag.putShort("lit_total_time", (short)this.litTotalTime);
+ tag.putDouble("Paper.CookSpeedMultiplier", this.cookSpeedMultiplier); // Paper - cook speed multiplier API
ContainerHelper.saveAllItems(tag, this.items, registries);
CompoundTag compoundTag = new CompoundTag();
this.recipesUsed.forEach((recipe, count) -> compoundTag.putInt(recipe.location().toString(), count));
@@ -160,11 +_,22 @@
int maxStackSize = furnace.getMaxStackSize();
if (!furnace.isLit() && canBurn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, maxStackSize)) {
- furnace.litTimeRemaining = furnace.getBurnDuration(level.fuelValues(), itemStack);
+ // CraftBukkit start
+ org.bukkit.craftbukkit.inventory.CraftItemStack fuel = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
+
+ org.bukkit.event.inventory.FurnaceBurnEvent furnaceBurnEvent = new org.bukkit.event.inventory.FurnaceBurnEvent(
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
+ fuel,
+ furnace.getBurnDuration(level.fuelValues(), itemStack)
+ );
+ if (!furnaceBurnEvent.callEvent()) return;
+ // CraftBukkit end
+
+ furnace.litTimeRemaining = furnaceBurnEvent.getBurnTime(); // CraftBukkit - respect event output
furnace.litTotalTime = furnace.litTimeRemaining;
- if (furnace.isLit()) {
+ if (furnace.isLit() && furnaceBurnEvent.isBurning()) { // CraftBukkit - respect event output
flag = true;
- if (flag2) {
+ if (flag2 && furnaceBurnEvent.willConsumeFuel()) { // Paper - add consumeFuel to FurnaceBurnEvent
Item item = itemStack.getItem();
itemStack.shrink(1);
if (itemStack.isEmpty()) {
@@ -175,11 +_,28 @@
}
if (furnace.isLit() && canBurn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, maxStackSize)) {
+ // CraftBukkit start
+ if (recipeHolder != null && furnace.cookingTimer == 0) {
+ org.bukkit.craftbukkit.inventory.CraftItemStack source = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(furnace.items.get(0));
+ org.bukkit.inventory.CookingRecipe<?> recipe = (org.bukkit.inventory.CookingRecipe<?>) recipeHolder.toBukkitRecipe();
+
+ org.bukkit.event.inventory.FurnaceStartSmeltEvent event = new org.bukkit.event.inventory.FurnaceStartSmeltEvent(
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
+ source,
+ recipe,
+ getTotalCookTime(level, furnace, furnace.recipeType, furnace.cookSpeedMultiplier) // Paper - cook speed multiplier API
+ );
+ event.callEvent();
+
+ furnace.cookingTotalTime = event.getTotalCookTime();
+ }
+ // CraftBukkit end
+
furnace.cookingTimer++;
- if (furnace.cookingTimer == furnace.cookingTotalTime) {
+ if (furnace.cookingTimer >= furnace.cookingTotalTime) { // Paper - cook speed multiplier API
furnace.cookingTimer = 0;
- furnace.cookingTotalTime = getTotalCookTime(level, furnace);
- if (burn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, maxStackSize)) {
+ furnace.cookingTotalTime = getTotalCookTime(level, furnace, furnace.recipeType, furnace.cookSpeedMultiplier); // Paper - cook speed multiplier API
+ if (burn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, maxStackSize, level, furnace.worldPosition)) { // CraftBukkit
furnace.setRecipeUsed(recipeHolder);
}
@@ -233,17 +_,47 @@
@Nullable RecipeHolder<? extends AbstractCookingRecipe> recipe,
SingleRecipeInput recipeInput,
NonNullList<ItemStack> items,
- int maxStackSize
+ int maxStackSize,
+ net.minecraft.world.level.Level level, // CraftBukkit
+ BlockPos blockPos // CraftBukkit
) {
if (recipe != null && canBurn(registryAccess, recipe, recipeInput, items, maxStackSize)) {
- ItemStack itemStack = items.get(0);
- ItemStack itemStack1 = recipe.value().assemble(recipeInput, registryAccess);
- ItemStack itemStack2 = items.get(2);
+ ItemStack itemStack = items.get(0); final ItemStack ingredient = itemStack; // Paper - OBFHELPER
+ ItemStack itemStack1 = recipe.value().assemble(recipeInput, registryAccess); ItemStack result = itemStack1; // Paper - OBFHELPER
+ ItemStack itemStack2 = items.get(2); final ItemStack existingResults = itemStack2; // Paper - OBFHELPER
+ // CraftBukkit start - fire FurnaceSmeltEvent
+ org.bukkit.craftbukkit.inventory.CraftItemStack apiIngredient = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(ingredient);
+ org.bukkit.inventory.ItemStack apiResult = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(result);
+
+ org.bukkit.event.inventory.FurnaceSmeltEvent furnaceSmeltEvent = new org.bukkit.event.inventory.FurnaceSmeltEvent(
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos),
+ apiIngredient,
+ apiResult,
+ (org.bukkit.inventory.CookingRecipe<?>) recipe.toBukkitRecipe() // Paper - Add recipe to cook events
+ );
+ if (!furnaceSmeltEvent.callEvent()) return false;
+
+ apiResult = furnaceSmeltEvent.getResult();
+ itemStack1 = result = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(apiResult);
+
+ if (!result.isEmpty()) {
+ if (existingResults.isEmpty()) {
+ items.set(2, result.copy());
+ } else if (org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(existingResults).isSimilar(apiResult)) {
+ existingResults.grow(result.getCount());
+ } else {
+ return false;
+ }
+ }
+
+ /*
if (itemStack2.isEmpty()) {
items.set(2, itemStack1.copy());
} else if (ItemStack.isSameItemSameComponents(itemStack2, itemStack1)) {
itemStack2.grow(1);
}
+ */
+ // CraftBukkit end
if (itemStack.is(Blocks.WET_SPONGE.asItem()) && !items.get(1).isEmpty() && items.get(1).is(Items.BUCKET)) {
items.set(1, new ItemStack(Items.WATER_BUCKET));
@@ -260,9 +_,16 @@
return fuelValues.burnDuration(stack);
}
- public static int getTotalCookTime(ServerLevel level, AbstractFurnaceBlockEntity furnace) {
+ private static int getTotalCookTime(@Nullable ServerLevel level, AbstractFurnaceBlockEntity furnace, RecipeType<? extends AbstractCookingRecipe> recipeType, double cookSpeedMultiplier) { // Paper - cook speed multiplier API
SingleRecipeInput singleRecipeInput = new SingleRecipeInput(furnace.getItem(0));
- return furnace.quickCheck.getRecipeFor(singleRecipeInput, level).map(recipe -> recipe.value().cookingTime()).orElse(200);
+ // Paper start - cook speed multiplier API
+ /* Scale the recipe's cooking time to the current cookSpeedMultiplier */
+ int cookTime = level != null
+ ? furnace.quickCheck.getRecipeFor(singleRecipeInput, level).map(holder -> holder.value().cookingTime()).orElse(200)
+ /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */
+ : (net.minecraft.server.MinecraftServer.getServer().getRecipeManager().getRecipeFor(recipeType, singleRecipeInput, level).map(holder -> holder.value().cookingTime()).orElse(200));
+ return (int) Math.ceil (cookTime / cookSpeedMultiplier);
+ // Paper end - cook speed multiplier AP
}
@Override
@@ -306,7 +_,7 @@
this.items.set(index, stack);
stack.limitSize(this.getMaxStackSize(stack));
if (index == 0 && !flag && this.level instanceof ServerLevel serverLevel) {
- this.cookingTotalTime = getTotalCookTime(serverLevel, this);
+ this.cookingTotalTime = getTotalCookTime(serverLevel, this, this.recipeType, this.cookSpeedMultiplier); // Paper - cook speed multiplier API
this.cookingTimer = 0;
this.setChanged();
}
@@ -339,11 +_,11 @@
}
@Override
- public void awardUsedRecipes(Player player, List<ItemStack> items) {
+ public void awardUsedRecipes(net.minecraft.world.entity.player.Player player, List<ItemStack> items) {
}
- public void awardUsedRecipesAndPopExperience(ServerPlayer player) {
- List<RecipeHolder<?>> recipesToAwardAndPopExperience = this.getRecipesToAwardAndPopExperience(player.serverLevel(), player.position());
+ public void awardUsedRecipesAndPopExperience(ServerPlayer player, ItemStack itemstack, int amount) { // CraftBukkit
+ List<RecipeHolder<?>> recipesToAwardAndPopExperience = this.getRecipesToAwardAndPopExperience(player.serverLevel(), player.position(), this.worldPosition, player, itemstack, amount); // CraftBukkit - overload for exp spawn events
player.awardRecipes(recipesToAwardAndPopExperience);
for (RecipeHolder<?> recipeHolder : recipesToAwardAndPopExperience) {
@@ -356,26 +_,52 @@
}
public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel level, Vec3 popVec) {
+ // CraftBukkit start
+ return this.getRecipesToAwardAndPopExperience(level, popVec, this.worldPosition, null, null, 0);
+ }
+ public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel level, Vec3 popVec, BlockPos blockPos, ServerPlayer serverPlayer, ItemStack itemStack, int amount) {
+ // CraftBukkit end
List<RecipeHolder<?>> list = Lists.newArrayList();
for (Entry<ResourceKey<Recipe<?>>> entry : this.recipesUsed.reference2IntEntrySet()) {
level.recipeAccess().byKey(entry.getKey()).ifPresent(recipe -> {
+ if (!(recipe.value() instanceof AbstractCookingRecipe)) return; // Paper - don't process non-cooking recipes
list.add((RecipeHolder<?>)recipe);
- createExperience(level, popVec, entry.getIntValue(), ((AbstractCookingRecipe)recipe.value()).experience());
+ createExperience(level, popVec, entry.getIntValue(), ((AbstractCookingRecipe)recipe.value()).experience(), blockPos, serverPlayer, itemStack, amount);
});
}
return list;
}
- private static void createExperience(ServerLevel level, Vec3 popVec, int recipeIndex, float experience) {
+ private static void createExperience(ServerLevel level, Vec3 popVec, int recipeIndex, float experience, BlockPos blockPos, ServerPlayer serverPlayer, ItemStack itemStack, int amount) { // CraftBukkit
int floor = Mth.floor(recipeIndex * experience);
float fraction = Mth.frac(recipeIndex * experience);
if (fraction != 0.0F && Math.random() < fraction) {
floor++;
}
- ExperienceOrb.award(level, popVec, floor);
+ // CraftBukkit start - fire FurnaceExtractEvent / BlockExpEvent
+ org.bukkit.event.block.BlockExpEvent event;
+ if (amount != 0) {
+ event = new org.bukkit.event.inventory.FurnaceExtractEvent(
+ serverPlayer.getBukkitEntity(),
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos),
+ org.bukkit.craftbukkit.inventory.CraftItemType.minecraftToBukkit(itemStack.getItem()),
+ amount,
+ floor
+ );
+ } else {
+ event = new org.bukkit.event.block.BlockExpEvent(
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos),
+ floor
+ );
+ }
+ event.callEvent();
+ floor = event.getExpToDrop();
+ // CraftBukkit end
+
+ ExperienceOrb.award(level, popVec, floor, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, serverPlayer); // Paper
}
@Override

View File

@@ -0,0 +1,71 @@
--- a/net/minecraft/world/level/block/entity/BannerBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BannerBlockEntity.java
@@ -23,7 +_,7 @@
public static final int MAX_PATTERNS = 6;
private static final String TAG_PATTERNS = "patterns";
@Nullable
- private Component name;
+ public Component name; // Paper - AT public
public DyeColor baseColor;
private BannerPatternLayers patterns = BannerPatternLayers.EMPTY;
@@ -50,7 +_,7 @@
@Override
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.saveAdditional(tag, registries);
- if (!this.patterns.equals(BannerPatternLayers.EMPTY)) {
+ if (!this.patterns.equals(BannerPatternLayers.EMPTY) || serialisingForNetwork.get()) { // Paper - always send patterns to client
tag.put("patterns", BannerPatternLayers.CODEC.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), this.patterns).getOrThrow());
}
@@ -70,7 +_,7 @@
BannerPatternLayers.CODEC
.parse(registries.createSerializationContext(NbtOps.INSTANCE), tag.get("patterns"))
.resultOrPartial(string -> LOGGER.error("Failed to parse banner patterns: '{}'", string))
- .ifPresent(bannerPatternLayers -> this.patterns = bannerPatternLayers);
+ .ifPresent(bannerPatternLayers -> this.setPatterns(bannerPatternLayers)); // CraftBukkit - apply limits
}
}
@@ -79,9 +_,18 @@
return ClientboundBlockEntityDataPacket.create(this);
}
+ // Paper start - always send patterns to client
+ ThreadLocal<Boolean> serialisingForNetwork = ThreadLocal.withInitial(() -> Boolean.FALSE);
@Override
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
+ final Boolean wasSerialisingForNetwork = serialisingForNetwork.get();
+ try {
+ serialisingForNetwork.set(Boolean.TRUE);
return this.saveWithoutMetadata(registries);
+ } finally {
+ serialisingForNetwork.set(wasSerialisingForNetwork);
+ }
+ // Paper end - always send patterns to client
}
public BannerPatternLayers getPatterns() {
@@ -101,7 +_,7 @@
@Override
protected void applyImplicitComponents(BlockEntity.DataComponentInput componentInput) {
super.applyImplicitComponents(componentInput);
- this.patterns = componentInput.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY);
+ this.setPatterns(componentInput.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY)); // CraftBukkit - apply limits
this.name = componentInput.get(DataComponents.CUSTOM_NAME);
}
@@ -117,4 +_,13 @@
tag.remove("patterns");
tag.remove("CustomName");
}
+
+ // CraftBukkit start
+ public void setPatterns(BannerPatternLayers bannerpatternlayers) {
+ if (bannerpatternlayers.layers().size() > 20) {
+ bannerpatternlayers = new BannerPatternLayers(java.util.List.copyOf(bannerpatternlayers.layers().subList(0, 20)));
+ }
+ this.patterns = bannerpatternlayers;
+ }
+ // CraftBukkit end
}

View File

@@ -24,7 +24,7 @@
+ }
+
+ @Override
+ public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
+ public List<HumanEntity> getViewers() {
+ return this.transaction;
+ }
+

View File

@@ -0,0 +1,242 @@
--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
@@ -106,6 +_,51 @@
return 3;
}
};
+ // CraftBukkit start - add fields and methods
+ public org.bukkit.potion.PotionEffect getPrimaryEffect() {
+ return (this.primaryPower != null)
+ ? org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(new MobEffectInstance(
+ this.primaryPower,
+ BeaconBlockEntity.getLevel(this.levels),
+ BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower),
+ true,
+ true
+ ))
+ : null;
+ }
+
+ public org.bukkit.potion.PotionEffect getSecondaryEffect() {
+ return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower))
+ ? org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(new MobEffectInstance(
+ this.secondaryPower,
+ BeaconBlockEntity.getLevel(this.levels),
+ BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower),
+ true,
+ true
+ ))
+ : null;
+ }
+ // CraftBukkit end
+ // Paper start - Custom beacon ranges
+ private final String PAPER_RANGE_TAG = "Paper.Range";
+ private double effectRange = -1;
+
+ public double getEffectRange() {
+ if (this.effectRange < 0) {
+ return this.levels * 10 + 10;
+ } else {
+ return effectRange;
+ }
+ }
+
+ public void setEffectRange(double range) {
+ this.effectRange = range;
+ }
+
+ public void resetEffectRange() {
+ this.effectRange = -1;
+ }
+ // Paper end - Custom beacon ranges
@Nullable
static Holder<MobEffect> filterEffect(@Nullable Holder<MobEffect> effect) {
@@ -163,17 +_,26 @@
blockEntity.lastCheckY++;
}
- int i = blockEntity.levels;
+ int i = blockEntity.levels; final int originalLevels = i; // Paper - OBFHELPER
if (level.getGameTime() % 80L == 0L) {
if (!blockEntity.beamSections.isEmpty()) {
blockEntity.levels = updateBase(level, x, y, z);
}
if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) {
- applyEffects(level, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower);
+ applyEffects(level, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges
playSound(level, pos, SoundEvents.BEACON_AMBIENT);
}
}
+ // Paper start - beacon activation/deactivation events
+ if (originalLevels <= 0 && blockEntity.levels > 0) {
+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
+ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent();
+ } else if (originalLevels > 0 && blockEntity.levels <= 0) {
+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
+ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
+ }
+ // Paper end - beacon activation/deactivation events
if (blockEntity.lastCheckY >= height) {
blockEntity.lastCheckY = level.getMinY() - 1;
@@ -224,36 +_,99 @@
@Override
public void setRemoved() {
+ // Paper start - beacon activation/deactivation events
+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition);
+ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
+ // Paper end - beacon activation/deactivation events
+ // Paper start - fix MC-153086
+ if (this.levels > 0 && !this.beamSections.isEmpty()) {
playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE);
+ }
+ // Paper end
super.setRemoved();
}
+ @io.papermc.paper.annotation.DoNotUse // Paper - pass beacon block entity
private static void applyEffects(
Level level, BlockPos pos, int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect
) {
+ // Paper start - pass beacon block entity
+ applyEffects(level, pos, beaconLevel, primaryEffect, secondaryEffect, null);
+ }
+ private static void applyEffects(
+ Level level, BlockPos pos, int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect, @Nullable BeaconBlockEntity blockEntity
+ ) {
+ // Paper emd - pass beacon block entity
if (!level.isClientSide && primaryEffect != null) {
- double d = beaconLevel * 10 + 10;
- int i = 0;
- if (beaconLevel >= 4 && Objects.equals(primaryEffect, secondaryEffect)) {
- i = 1;
- }
-
- int i1 = (9 + beaconLevel * 2) * 20;
- AABB aabb = new AABB(pos).inflate(d).expandTowards(0.0, level.getHeight(), 0.0);
- List<Player> entitiesOfClass = level.getEntitiesOfClass(Player.class, aabb);
-
- for (Player player : entitiesOfClass) {
- player.addEffect(new MobEffectInstance(primaryEffect, i1, i, true, true));
- }
-
- if (beaconLevel >= 4 && !Objects.equals(primaryEffect, secondaryEffect) && secondaryEffect != null) {
- for (Player player : entitiesOfClass) {
- player.addEffect(new MobEffectInstance(secondaryEffect, i1, 0, true, true));
+ double d = computeBeaconRange(beaconLevel); // Paper - diff out applyEffects logic components - see below
+ int i = computeEffectAmplifier(beaconLevel, primaryEffect, secondaryEffect); // Paper - diff out applyEffects logic components - see below
+
+ int i1 = computeEffectDuration(beaconLevel); // Paper - diff out applyEffects logic components - see below
+ List<Player> entitiesOfClass = getHumansInRange(level, pos, beaconLevel, blockEntity); // Paper - diff out applyEffects logic components - see below
+
+ applyEffectsAndCallEvent(level, pos, entitiesOfClass, new MobEffectInstance(primaryEffect, i1, i, true, true), true); // Paper - BeaconEffectEvent
+
+ if (hasSecondaryEffect(beaconLevel, primaryEffect, secondaryEffect)) { // Paper - diff out applyEffects logic components - see below
+ applyEffectsAndCallEvent(level, pos, entitiesOfClass, new MobEffectInstance(secondaryEffect, i1, 0, true, true), false); // Paper - BeaconEffectEvent
+ }
+ }
+ }
+
+ // Paper start - diff out applyEffects logic components
+ // Generally smarter than spigot trying to split the logic up, as that diff is giant.
+ private static int computeEffectDuration(final int beaconLevel) {
+ return (9 + beaconLevel * 2) * 20; // Diff from applyEffects
+ }
+
+ private static int computeEffectAmplifier(final int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect) {
+ int i = 0;
+ if (beaconLevel >= 4 && Objects.equals(primaryEffect, secondaryEffect)) {
+ i = 1;
+ }
+ return i;
+ }
+
+ private static double computeBeaconRange(final int beaconLevel) {
+ return beaconLevel * 10 + 10; // Diff from applyEffects
+ }
+
+ public static List<Player> getHumansInRange(final Level level, final BlockPos pos, final int beaconLevel, final @Nullable BeaconBlockEntity blockEntity) {
+ final double d = blockEntity != null ? blockEntity.getEffectRange() : computeBeaconRange(beaconLevel);
+ AABB aabb = new AABB(pos).inflate(d).expandTowards(0.0, level.getHeight(), 0.0); // Diff from applyEffects
+ // Improve performance of human lookup by switching to a global player iteration when searching over 128 blocks
+ List<Player> list;
+ if (d <= 128.0) {
+ list = level.getEntitiesOfClass(Player.class, aabb); // Diff from applyEffect
+ } else {
+ list = new java.util.ArrayList<>();
+ for (final Player player : level.players()) {
+ if (!net.minecraft.world.entity.EntitySelector.NO_SPECTATORS.test(player)) continue;
+ if (player.getBoundingBox().intersects(aabb)) {
+ list.add(player);
}
}
}
- }
-
+ return list;
+ }
+
+ private static boolean hasSecondaryEffect(final int beaconLevel, final Holder<MobEffect> primaryEffect, final @Nullable Holder<MobEffect> secondaryEffect) {
+ return beaconLevel >= 4 && !Objects.equals(primaryEffect, secondaryEffect) && secondaryEffect != null;
+ }
+ // Paper end - diff out applyEffects logic components
+
+ // Paper start - BeaconEffectEvent
+ private static void applyEffectsAndCallEvent(final Level level, final BlockPos position, final List<Player> players, final MobEffectInstance mobEffectInstance, final boolean isPrimary) {
+ final org.bukkit.potion.PotionEffect apiEffect = org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(mobEffectInstance);
+ final org.bukkit.craftbukkit.block.CraftBlock apiBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, position);
+ for (final Player player : players) {
+ final com.destroystokyo.paper.event.block.BeaconEffectEvent event = new com.destroystokyo.paper.event.block.BeaconEffectEvent(
+ apiBlock, apiEffect, (org.bukkit.entity.Player) player.getBukkitEntity(), isPrimary
+ );
+ if (!event.callEvent()) continue;
+ player.addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(event.getEffect()));
+ }
+ }
+ // Paper end - BeaconEffectEvent
public static void playSound(Level level, BlockPos pos, SoundEvent sound) {
level.playSound(null, pos, sound, SoundSource.BLOCKS, 1.0F, 1.0F);
}
@@ -282,7 +_,7 @@
private static Holder<MobEffect> loadEffect(CompoundTag tag, String key) {
if (tag.contains(key, 8)) {
ResourceLocation resourceLocation = ResourceLocation.tryParse(tag.getString(key));
- return resourceLocation == null ? null : BuiltInRegistries.MOB_EFFECT.get(resourceLocation).map(BeaconBlockEntity::filterEffect).orElse(null);
+ return resourceLocation == null ? null : BuiltInRegistries.MOB_EFFECT.get(resourceLocation).orElse(null); // CraftBukkit - persist manually set non-default beacon effects (SPIGOT-3598)
} else {
return null;
}
@@ -293,11 +_,13 @@
super.loadAdditional(tag, registries);
this.primaryPower = loadEffect(tag, "primary_effect");
this.secondaryPower = loadEffect(tag, "secondary_effect");
+ this.levels = tag.getInt("Levels"); // CraftBukkit - SPIGOT-5053, use where available
if (tag.contains("CustomName", 8)) {
this.name = parseCustomNameSafe(tag.getString("CustomName"), registries);
}
this.lockKey = LockCode.fromTag(tag, registries);
+ this.effectRange = tag.contains(PAPER_RANGE_TAG, 6) ? tag.getDouble(PAPER_RANGE_TAG) : -1; // Paper - Custom beacon ranges
}
@Override
@@ -311,6 +_,7 @@
}
this.lockKey.addToTag(tag, registries);
+ tag.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper - Custom beacon ranges
}
public void setCustomName(@Nullable Component name) {
@@ -326,7 +_,7 @@
@Nullable
@Override
public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName())
+ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this) // Paper - Add BlockLockCheckEvent
? new BeaconMenu(containerId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos()))
: null;
}

View File

@@ -0,0 +1,244 @@
--- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
@@ -83,6 +_,7 @@
private List<BeehiveBlockEntity.BeeData> stored = Lists.newArrayList();
@Nullable
public BlockPos savedFlowerPos;
+ public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold
public BeehiveBlockEntity(BlockPos pos, BlockState blockState) {
super(BlockEntityType.BEEHIVE, pos, blockState);
@@ -116,7 +_,7 @@
}
public boolean isFull() {
- return this.stored.size() == 3;
+ return this.stored.size() == this.maxBees; // CraftBukkit
}
public void emptyAllLivingFromHive(@Nullable Player player, BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus) {
@@ -127,7 +_,7 @@
Bee bee = (Bee)entity;
if (player.position().distanceToSqr(entity.position()) <= 16.0) {
if (!this.isSedated()) {
- bee.setTarget(player);
+ bee.setTarget(player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit
} else {
bee.setStayOutOfHiveCountdown(400);
}
@@ -138,8 +_,14 @@
}
private List<Entity> releaseAllOccupants(BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus) {
+ // CraftBukkit start - This allows us to bypass the night/rain/emergency check
+ return this.releaseBees(state, releaseStatus, false);
+ }
+
+ public List<Entity> releaseBees(BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus, boolean force) {
+ // CraftBukkit end - This allows us to bypass t he night/rain/emergecny check
List<Entity> list = Lists.newArrayList();
- this.stored.removeIf(data -> releaseOccupant(this.level, this.worldPosition, state, data.toOccupant(), list, releaseStatus, this.savedFlowerPos));
+ this.stored.removeIf(data -> releaseOccupant(this.level, this.worldPosition, state, data.toOccupant(), list, releaseStatus, this.savedFlowerPos, force)); // CraftBukkit - This allows us to bypass t he night/rain/emergecny check
if (!list.isEmpty()) {
super.setChanged();
}
@@ -152,6 +_,11 @@
return this.stored.size();
}
+ // Paper start - Add EntityBlockStorage clearEntities
+ public void clearBees() {
+ this.stored.clear();
+ }
+ // Paper end - Add EntityBlockStorage clearEntities
public static int getHoneyLevel(BlockState state) {
return state.getValue(BeehiveBlock.HONEY_LEVEL);
}
@@ -162,7 +_,16 @@
}
public void addOccupant(Bee bee) {
- if (this.stored.size() < 3) {
+ if (this.stored.size() < this.maxBees) { // CraftBukkit
+ // CraftBukkit start
+ if (this.level != null) {
+ org.bukkit.event.entity.EntityEnterBlockEvent event = new org.bukkit.event.entity.EntityEnterBlockEvent(bee.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.getBlockPos()));
+ if (!event.callEvent()) {
+ bee.setStayOutOfHiveCountdown(400);
+ return;
+ }
+ }
+ // CraftBukkit end
bee.stopRiding();
bee.ejectPassengers();
bee.dropLeash();
@@ -187,7 +_,7 @@
this.level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(bee, this.getBlockState()));
}
- bee.discard();
+ bee.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.ENTER_BLOCK); // CraftBukkit - add Bukkit remove cause
super.setChanged();
}
}
@@ -205,7 +_,21 @@
BeehiveBlockEntity.BeeReleaseStatus releaseStatus,
@Nullable BlockPos storedFlowerPos
) {
- if (Bee.isNightOrRaining(level) && releaseStatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
+ // CraftBukkit start
+ return releaseOccupant(level, pos, state, occupant, storedInHives, releaseStatus, storedFlowerPos, false);
+ }
+ private static boolean releaseOccupant(
+ Level level,
+ BlockPos pos,
+ BlockState state,
+ BeehiveBlockEntity.Occupant occupant,
+ @Nullable List<Entity> storedInHives,
+ BeehiveBlockEntity.BeeReleaseStatus releaseStatus,
+ @Nullable BlockPos storedFlowerPos,
+ boolean force
+ ) {
+ if (!force && Bee.isNightOrRaining(level) && releaseStatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
+ // CraftBukkit end
return false;
} else {
Direction direction = state.getValue(BeehiveBlock.FACING);
@@ -216,6 +_,17 @@
} else {
Entity entity = occupant.createEntity(level, pos);
if (entity != null) {
+ // CraftBukkit start
+ if (entity instanceof Bee) {
+ float bbWidth = entity.getBbWidth();
+ double d = flag ? 0.0 : 0.55 + bbWidth / 2.0F;
+ double d1 = pos.getX() + 0.5 + d * direction.getStepX();
+ double d2 = pos.getY() + 0.5 - entity.getBbHeight() / 2.0F;
+ double d3 = pos.getZ() + 0.5 + d * direction.getStepZ();
+ entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot());
+ }
+ if (!level.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BEEHIVE)) return false; // CraftBukkit - SpawnReason, moved from below
+ // CraftBukkit end
if (entity instanceof Bee bee) {
if (storedFlowerPos != null && !bee.hasSavedFlowerPos() && level.random.nextFloat() < 0.9F) {
bee.setSavedFlowerPos(storedFlowerPos);
@@ -231,7 +_,13 @@
i--;
}
- level.setBlockAndUpdate(pos, state.setValue(BeehiveBlock.HONEY_LEVEL, Integer.valueOf(honeyLevel + i)));
+ // Paper start - Fire EntityChangeBlockEvent in more places
+ BlockState newBlockState = state.setValue(BeehiveBlock.HONEY_LEVEL, Integer.valueOf(honeyLevel + i));
+
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, newBlockState)) {
+ level.setBlockAndUpdate(pos, newBlockState);
+ }
+ // Paper end - Fire EntityChangeBlockEvent in more places
}
}
}
@@ -240,17 +_,19 @@
storedInHives.add(bee);
}
+ /* CraftBukkit start - move up
float bbWidth = entity.getBbWidth();
double d = flag ? 0.0 : 0.55 + bbWidth / 2.0F;
double d1 = pos.getX() + 0.5 + d * direction.getStepX();
double d2 = pos.getY() + 0.5 - entity.getBbHeight() / 2.0F;
double d3 = pos.getZ() + 0.5 + d * direction.getStepZ();
entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot());
+ */ // CraftBukkit end
}
level.playSound(null, pos, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F);
level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(entity, level.getBlockState(pos)));
- return level.addFreshEntity(entity);
+ return true; // CraftBukkit - moved up
} else {
return false;
}
@@ -276,6 +_,11 @@
flag = true;
iterator.remove();
}
+ // Paper start - Fix bees aging inside; use exitTickCounter to keep actual bee life
+ else {
+ beeData.exitTickCounter = beeData.occupant.minTicksInHive / 2;
+ }
+ // Paper end - Fix bees aging inside; use exitTickCounter to keep actual bee life
}
}
@@ -299,7 +_,7 @@
@Override
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.loadAdditional(tag, registries);
- this.stored.clear();
+ this.stored = Lists.newArrayList(); // CraftBukkit - SPIGOT-7790: create new copy (may be modified in physics event triggered by honey change)
if (tag.contains("bees")) {
BeehiveBlockEntity.Occupant.LIST_CODEC
.parse(NbtOps.INSTANCE, tag.get("bees"))
@@ -308,6 +_,11 @@
}
this.savedFlowerPos = NbtUtils.readBlockPos(tag, "flower_pos").orElse(null);
+ // CraftBukkit start
+ if (tag.contains("Bukkit.MaxEntities")) {
+ this.maxBees = tag.getInt("Bukkit.MaxEntities");
+ }
+ // CraftBukkit end
}
@Override
@@ -317,12 +_,13 @@
if (this.hasSavedFlowerPos()) {
tag.put("flower_pos", NbtUtils.writeBlockPos(this.savedFlowerPos));
}
+ tag.putInt("Bukkit.MaxEntities", this.maxBees); // CraftBukkit
}
@Override
protected void applyImplicitComponents(BlockEntity.DataComponentInput componentInput) {
super.applyImplicitComponents(componentInput);
- this.stored.clear();
+ this.stored = Lists.newArrayList(); // CraftBukkit - SPIGOT-7790: create new copy (may be modified in physics event triggered by honey change)
List<BeehiveBlockEntity.Occupant> list = componentInput.getOrDefault(DataComponents.BEES, List.of());
list.forEach(this::storeBee);
}
@@ -345,15 +_,18 @@
static class BeeData {
private final BeehiveBlockEntity.Occupant occupant;
+ private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts
private int ticksInHive;
BeeData(BeehiveBlockEntity.Occupant occupant) {
this.occupant = occupant;
this.ticksInHive = occupant.ticksInHive();
+ this.exitTickCounter = this.ticksInHive; // Paper - Fix bees aging inside hives
}
public boolean tick() {
- return this.ticksInHive++ > this.occupant.minTicksInHive;
+ this.ticksInHive++; // Paper - Fix bees aging inside hives
+ return this.exitTickCounter++ > this.occupant.minTicksInHive; // Paper - Fix bees aging inside hives
}
public BeehiveBlockEntity.Occupant toOccupant() {
@@ -424,6 +_,7 @@
}
private static void setBeeReleaseData(int ticksInHive, Bee bee) {
+ if (!bee.ageLocked) { // Paper - Honor ageLock
int age = bee.getAge();
if (age < 0) {
bee.setAge(Math.min(0, age + ticksInHive));
@@ -432,6 +_,7 @@
}
bee.setInLoveTime(Math.max(0, bee.getInLoveTime() - ticksInHive));
+ } // Paper - Honor ageLock
}
}
}

View File

@@ -0,0 +1,133 @@
--- a/net/minecraft/world/level/block/entity/BlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BlockEntity.java
@@ -26,6 +_,10 @@
import org.slf4j.Logger;
public abstract class BlockEntity {
+ // CraftBukkit start - data containers
+ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
+ public org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer;
+ // CraftBukkit end
private static final Logger LOGGER = LogUtils.getLogger();
private final BlockEntityType<?> type;
@Nullable
@@ -40,6 +_,7 @@
this.worldPosition = pos.immutable();
this.validateBlockState(blockState);
this.blockState = blockState;
+ this.persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init
}
private void validateBlockState(BlockState state) {
@@ -70,6 +_,14 @@
}
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
+ // Paper start - read persistent data container
+ this.persistentDataContainer.clear(); // Paper - clear instead of init
+
+ net.minecraft.nbt.Tag persistentDataTag = tag.get("PublicBukkitValues");
+ if (persistentDataTag instanceof CompoundTag) {
+ this.persistentDataContainer.putAll((CompoundTag) persistentDataTag);
+ }
+ // Paper end - read persistent data container
}
public final void loadWithComponents(CompoundTag tag, HolderLookup.Provider registries) {
@@ -106,12 +_,22 @@
.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), this.components)
.resultOrPartial(string -> LOGGER.warn("Failed to save components: {}", string))
.ifPresent(tag -> compoundTag.merge((CompoundTag)tag));
+ // CraftBukkit start - store container
+ if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) {
+ compoundTag.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound());
+ }
+ // CraftBukkit end
return compoundTag;
}
public final CompoundTag saveCustomOnly(HolderLookup.Provider registries) {
CompoundTag compoundTag = new CompoundTag();
this.saveAdditional(compoundTag, registries);
+ // Paper start - store PDC here as well
+ if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) {
+ compoundTag.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound());
+ }
+ // Paper end
return compoundTag;
}
@@ -220,7 +_,12 @@
public void fillCrashReportCategory(CrashReportCategory reportCategory) {
reportCategory.setDetail("Name", this::getNameForReporting);
if (this.level != null) {
- CrashReportCategory.populateBlockDetails(reportCategory, this.level, this.worldPosition, this.getBlockState());
+ // Paper start - Prevent block entity and entity crashes
+ BlockState block = this.getBlockState();
+ if (block != null) {
+ CrashReportCategory.populateBlockDetails(reportCategory, this.level, this.worldPosition, block);
+ }
+ // Paper end - Prevent block entity and entity crashes
CrashReportCategory.populateBlockDetails(reportCategory, this.level, this.worldPosition, this.level.getBlockState(this.worldPosition));
}
}
@@ -247,10 +_,16 @@
}
public final void applyComponents(DataComponentMap components, DataComponentPatch patch) {
+ // CraftBukkit start
+ this.applyComponentsSet(components, patch);
+ }
+
+ public final Set<DataComponentType<?>> applyComponentsSet(DataComponentMap components, DataComponentPatch patch) {
+ // CraftBukkit end
final Set<DataComponentType<?>> set = new HashSet<>();
set.add(DataComponents.BLOCK_ENTITY_DATA);
set.add(DataComponents.BLOCK_STATE);
- final DataComponentMap dataComponentMap = PatchedDataComponentMap.fromPatch(components, patch);
+ final PatchedDataComponentMap dataComponentMap = PatchedDataComponentMap.fromPatch(components, patch);
this.applyImplicitComponents(new BlockEntity.DataComponentInput() {
@Nullable
@Override
@@ -267,6 +_,10 @@
});
DataComponentPatch dataComponentPatch = patch.forget(set::contains);
this.components = dataComponentPatch.split().added();
+ // CraftBukkit start
+ set.remove(DataComponents.BLOCK_ENTITY_DATA); // Remove as never actually added by applyImplicitComponents
+ return set;
+ // CraftBukkit end
}
protected void collectImplicitComponents(DataComponentMap.Builder components) {
@@ -300,6 +_,30 @@
return null;
}
}
+
+ // CraftBukkit start - add method
+ public org.bukkit.inventory.InventoryHolder getOwner() {
+ // Paper start
+ return getOwner(true);
+ }
+ public org.bukkit.inventory.InventoryHolder getOwner(boolean useSnapshot) {
+ // Paper end
+ if (this.level == null) return null;
+ org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ());
+ // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists
+ org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper
+ return state instanceof final org.bukkit.inventory.InventoryHolder inventoryHolder ? inventoryHolder : null;
+ }
+ // CraftBukkit end
+
+ // Paper start - Sanitize sent data
+ public CompoundTag sanitizeSentNbt(CompoundTag tag) {
+ tag.remove("PublicBukkitValues");
+
+ return tag;
+ }
+ // Paper end - Sanitize sent data
+
static class ComponentHelper {
public static final Codec<DataComponentMap> COMPONENTS_CODEC = DataComponentMap.CODEC.optionalFieldOf("components", DataComponentMap.EMPTY).codec();

View File

@@ -0,0 +1,186 @@
--- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
@@ -8,7 +_,6 @@
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
-import net.minecraft.tags.ItemTags;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.Containers;
import net.minecraft.world.WorldlyContainer;
@@ -36,6 +_,7 @@
public static final int NUM_DATA_VALUES = 2;
private NonNullList<ItemStack> items = NonNullList.withSize(5, ItemStack.EMPTY);
public int brewTime;
+ public int recipeBrewTime = 400; // Paper - Add recipeBrewTime
private boolean[] lastPotionCount;
private Item ingredient;
public int fuel;
@@ -45,6 +_,7 @@
return switch (index) {
case 0 -> BrewingStandBlockEntity.this.brewTime;
case 1 -> BrewingStandBlockEntity.this.fuel;
+ case 2 -> BrewingStandBlockEntity.this.recipeBrewTime; // Paper - Add recipeBrewTime
default -> 0;
};
}
@@ -57,14 +_,50 @@
break;
case 1:
BrewingStandBlockEntity.this.fuel = value;
+ // Paper start - Add recipeBrewTime
+ break;
+ case 2:
+ BrewingStandBlockEntity.this.recipeBrewTime = value;
+ break;
+ // Paper end - Add recipeBrewTime
}
}
@Override
public int getCount() {
- return 2;
+ return 3; // Paper - Add recipeBrewTime
}
};
+ // CraftBukkit start - add fields and methods
+ // private int lastTick = MinecraftServer.currentTick; // Paper - remove anti tick skipping measures / wall time
+ public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
+ private int maxStack = MAX_STACK;
+
+ public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.add(who);
+ }
+
+ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.remove(who);
+ }
+
+ public List<HumanEntity> getViewers() {
+ return this.transaction;
+ }
+
+ public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
+ return this.items;
+ }
+
+ @Override
+ public int getMaxStackSize() {
+ return this.maxStack;
+ }
+
+ public void setMaxStackSize(int size) {
+ this.maxStack = size;
+ }
+ // CraftBukkit end
public BrewingStandBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.BREWING_STAND, pos, state);
@@ -92,9 +_,22 @@
public static void serverTick(Level level, BlockPos pos, BlockState state, BrewingStandBlockEntity blockEntity) {
ItemStack itemStack = blockEntity.items.get(4);
- if (blockEntity.fuel <= 0 && itemStack.is(ItemTags.BREWING_FUEL)) {
- blockEntity.fuel = 20;
- itemStack.shrink(1);
+ if (blockEntity.fuel <= 0 && itemStack.is(net.minecraft.tags.ItemTags.BREWING_FUEL)) {
+ // CraftBukkit start
+ org.bukkit.event.inventory.BrewingStandFuelEvent event = new org.bukkit.event.inventory.BrewingStandFuelEvent(
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
+ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack),
+ 20
+ );
+ if (!event.callEvent()) {
+ return;
+ }
+
+ blockEntity.fuel = event.getFuelPower();
+ if (blockEntity.fuel > 0 && event.isConsuming()) {
+ itemStack.shrink(1);
+ }
+ // CraftBukkit end
setChanged(level, pos, state);
}
@@ -105,7 +_,7 @@
blockEntity.brewTime--;
boolean flag1 = blockEntity.brewTime == 0;
if (flag1 && isBrewable) {
- doBrew(level, pos, blockEntity.items);
+ doBrew(level, pos, blockEntity.items, blockEntity); // CraftBukkit
} else if (!isBrewable || !itemStack1.is(blockEntity.ingredient)) {
blockEntity.brewTime = 0;
}
@@ -114,6 +_,14 @@
} else if (isBrewable && blockEntity.fuel > 0) {
blockEntity.fuel--;
blockEntity.brewTime = 400;
+ // CraftBukkit start
+ org.bukkit.event.block.BrewingStartEvent event = new org.bukkit.event.block.BrewingStartEvent(
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
+ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack1), 400);
+ event.callEvent();
+ blockEntity.recipeBrewTime = event.getRecipeBrewTime(); // Paper - use recipe brew time from event
+ blockEntity.brewTime = event.getBrewingTime(); // 400 -> event.getTotalBrewTime() // Paper - use brewing time from event
+ // CraftBukkit end
blockEntity.ingredient = itemStack1.getItem();
setChanged(level, pos, state);
}
@@ -164,13 +_,37 @@
}
}
- private static void doBrew(Level level, BlockPos pos, NonNullList<ItemStack> items) {
+ private static void doBrew(Level level, BlockPos pos, NonNullList<ItemStack> items, BrewingStandBlockEntity brewingStandBlockEntity) { // CraftBukkit
ItemStack itemStack = items.get(3);
PotionBrewing potionBrewing = level.potionBrewing();
+ // CraftBukkit start
+ org.bukkit.inventory.InventoryHolder owner = brewingStandBlockEntity.getOwner();
+ java.util.List<org.bukkit.inventory.ItemStack> brewResults = new java.util.ArrayList<>(3);
for (int i = 0; i < 3; i++) {
- items.set(i, potionBrewing.mix(itemStack, items.get(i)));
- }
+ brewResults.add(i, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potionBrewing.mix(itemStack, items.get(i))));
+ }
+
+ if (owner != null) {
+ org.bukkit.event.inventory.BrewEvent event = new org.bukkit.event.inventory.BrewEvent(
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
+ (org.bukkit.inventory.BrewerInventory) owner.getInventory(),
+ brewResults,
+ brewingStandBlockEntity.fuel
+ );
+ if (!event.callEvent()) {
+ return;
+ }
+
+ for (int i = 0; i < 3; i++) {
+ if (i < brewResults.size()) {
+ items.set(i, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(brewResults.get(i)));
+ } else {
+ items.set(i, ItemStack.EMPTY);
+ }
+ }
+ }
+ // CraftBukkit end
itemStack.shrink(1);
ItemStack craftingRemainder = itemStack.getItem().getCraftingRemainder();
@@ -209,13 +_,13 @@
@Override
public boolean canPlaceItem(int index, ItemStack stack) {
+ PotionBrewing potionBrewing = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; // Paper - move up
if (index == 3) {
- PotionBrewing potionBrewing = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY;
return potionBrewing.isIngredient(stack);
} else {
return index == 4
- ? stack.is(ItemTags.BREWING_FUEL)
- : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE))
+ ? stack.is(net.minecraft.tags.ItemTags.BREWING_FUEL)
+ : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionBrewing.isCustomInput(stack)) // Paper - Custom Potion Mixes
&& this.getItem(index).isEmpty();
}
}

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
@@ -138,7 +_,10 @@
double d5 = blockPos.getZ() + 0.5 * d1 + d2;
ItemEntity itemEntity = new ItemEntity(level, d3, d4, d5, this.item.split(level.random.nextInt(21) + 10));
itemEntity.setDeltaMovement(Vec3.ZERO);
- level.addFreshEntity(itemEntity);
+ // CraftBukkit start
+ org.bukkit.block.Block bblock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.worldPosition);
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, bblock.getState(), (ServerPlayer) player, java.util.List.of(itemEntity));
+ // CraftBukkit end
this.item = ItemStack.EMPTY;
}
}
@@ -167,7 +_,7 @@
private boolean tryLoadLootTable(CompoundTag tag) {
if (tag.contains("LootTable", 8)) {
- this.lootTable = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(tag.getString("LootTable")));
+ this.lootTable = net.minecraft.Optionull.map(ResourceLocation.tryParse(tag.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl)); // Paper - Validate ResourceLocation
this.lootTableSeed = tag.getLong("LootTableSeed");
return true;
} else {

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java
@@ -20,6 +_,12 @@
public VibrationSystem.User createVibrationUser() {
return new CalibratedSculkSensorBlockEntity.VibrationUser(this.getBlockPos());
}
+ // Paper start - Configurable sculk sensor listener range
+ @Override
+ protected void saveRangeOverride(final net.minecraft.nbt.CompoundTag nbt) {
+ if (this.rangeOverride != null && this.rangeOverride != 16) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default
+ }
+ // Paper end - Configurable sculk sensor listener range
protected class VibrationUser extends SculkSensorBlockEntity.VibrationUser {
public VibrationUser(final BlockPos pos1) {
@@ -28,6 +_,7 @@
@Override
public int getListenerRadius() {
+ if (CalibratedSculkSensorBlockEntity.this.rangeOverride != null) return CalibratedSculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range
return 16;
}

View File

@@ -0,0 +1,104 @@
--- a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
@@ -36,6 +_,7 @@
private final NonNullList<ItemStack> items = NonNullList.withSize(4, ItemStack.EMPTY);
public final int[] cookingProgress = new int[4];
public final int[] cookingTime = new int[4];
+ public final boolean[] stopCooking = new boolean[4]; // Paper - Add more Campfire API
public CampfireBlockEntity(BlockPos pos, BlockState blockState) {
super(BlockEntityType.CAMPFIRE, pos, blockState);
@@ -54,14 +_,42 @@
ItemStack itemStack = campfire.items.get(i);
if (!itemStack.isEmpty()) {
flag = true;
+ if (!campfire.stopCooking[i]) { // Paper - Add more Campfire API
campfire.cookingProgress[i]++;
+ } // Paper - Add more Campfire API
if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) {
SingleRecipeInput singleRecipeInput = new SingleRecipeInput(itemStack);
- ItemStack itemStack1 = check.getRecipeFor(singleRecipeInput, level)
+ final var optionalCookingRecipe = check.getRecipeFor(singleRecipeInput, level);
+ ItemStack itemStack1 = optionalCookingRecipe
.map(recipe -> recipe.value().assemble(singleRecipeInput, level.registryAccess()))
.orElse(itemStack);
if (itemStack1.isItemEnabled(level.enabledFeatures())) {
- Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), itemStack1);
+ // CraftBukkit start - fire BlockCookEvent
+ org.bukkit.craftbukkit.inventory.CraftItemStack source = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
+ org.bukkit.inventory.ItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemStack1);
+
+ org.bukkit.event.block.BlockCookEvent blockCookEvent = new org.bukkit.event.block.BlockCookEvent(
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
+ source,
+ result,
+ (org.bukkit.inventory.CookingRecipe<?>) optionalCookingRecipe.map(RecipeHolder::toBukkitRecipe).orElse(null) // Paper -Add recipe to cook events
+ );
+
+ if (!blockCookEvent.callEvent()) {
+ return;
+ }
+
+ result = blockCookEvent.getResult();
+ itemStack1 = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(result);
+ // CraftBukkit end
+ // Paper start - Fix item locations dropped from campfires
+ double deviation = 0.05F * RandomSource.GAUSSIAN_SPREAD_FACTOR;
+ while (!itemStack1.isEmpty()) {
+ net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(level, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemStack1.split(level.random.nextInt(21) + 10));
+ droppedItem.setDeltaMovement(level.random.triangle(0.0D, deviation), level.random.triangle(0.2D, deviation), level.random.triangle(0.0D, deviation));
+ level.addFreshEntity(droppedItem);
+ }
+ // Paper end - Fix item locations dropped from campfires
campfire.items.set(i, ItemStack.EMPTY);
level.sendBlockUpdated(pos, state, state, 3);
level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
@@ -133,6 +_,17 @@
int[] intArray = tag.getIntArray("CookingTotalTimes");
System.arraycopy(intArray, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, intArray.length));
}
+
+ // Paper start - Add more Campfire API
+ if (tag.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) {
+ byte[] abyte = tag.getByteArray("Paper.StopCooking");
+ boolean[] cookingState = new boolean[4];
+ for (int index = 0; index < abyte.length; index++) {
+ cookingState[index] = abyte[index] == 1;
+ }
+ System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length));
+ }
+ // Paper end - Add more Campfire API
}
@Override
@@ -141,6 +_,13 @@
ContainerHelper.saveAllItems(tag, this.items, true, registries);
tag.putIntArray("CookingTimes", this.cookingProgress);
tag.putIntArray("CookingTotalTimes", this.cookingTime);
+ // Paper start - Add more Campfire API
+ byte[] cookingState = new byte[4];
+ for (int index = 0; index < cookingState.length; index++) {
+ cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0);
+ }
+ tag.putByteArray("Paper.StopCooking", cookingState);
+ // Paper end - Add more Campfire API
}
@Override
@@ -165,7 +_,15 @@
return false;
}
- this.cookingTime[i] = recipeFor.get().value().cookingTime();
+ // CraftBukkit start
+ org.bukkit.event.block.CampfireStartEvent event = new org.bukkit.event.block.CampfireStartEvent(
+ org.bukkit.craftbukkit.block.CraftBlock.at(this.level,this.worldPosition),
+ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack),
+ (org.bukkit.inventory.CampfireRecipe) recipeFor.get().toBukkitRecipe()
+ );
+ this.level.getCraftServer().getPluginManager().callEvent(event);
+ this.cookingTime[i] = event.getTotalCookTime(); // i -> event.getTotalCookTime()
+ // CraftBukkit end
this.cookingProgress[i] = 0;
this.items.set(i, stack.consumeAndReturn(1, entity));
level.gameEvent(GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(entity, this.getBlockState()));

View File

@@ -20,7 +20,7 @@
+ this.transaction.remove(who);
+ }
+
+ public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
+ public List<HumanEntity> getViewers() {
+ return this.transaction;
+ }
+

View File

@@ -24,7 +24,7 @@
+ }
+
+ @Override
+ public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
+ public List<HumanEntity> getViewers() {
+ return this.transaction;
+ }
+

View File

@@ -0,0 +1,62 @@
--- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
@@ -9,6 +_,7 @@
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
+import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
@@ -168,8 +_,20 @@
}
private static void applyEffects(Level level, BlockPos pos, List<BlockPos> positions) {
+ // CraftBukkit start
+ ConduitBlockEntity.applyEffects(level, pos, ConduitBlockEntity.getRange(positions));
+ }
+
+ public static int getRange(List<BlockPos> positions) {
+ // CraftBukkit end
int size = positions.size();
int i = size / 7 * 16;
+ // CraftBukkit start
+ return i;
+ }
+
+ private static void applyEffects(Level level, BlockPos pos, int i) { // i = effect range in blocks
+ // CraftBukkit end
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
@@ -185,6 +_,12 @@
}
private static void updateDestroyTarget(Level level, BlockPos pos, BlockState state, List<BlockPos> positions, ConduitBlockEntity blockEntity) {
+ // CraftBukkit start - add "damageTarget" boolean
+ ConduitBlockEntity.updateDestroyTarget(level, pos, state, positions, blockEntity, true);
+ }
+
+ public static void updateDestroyTarget(Level level, BlockPos pos, BlockState state, List<BlockPos> positions, ConduitBlockEntity blockEntity, boolean damageTarget) {
+ // CraftBukkit end
LivingEntity livingEntity = blockEntity.destroyTarget;
int size = positions.size();
if (size < 42) {
@@ -203,7 +_,8 @@
blockEntity.destroyTarget = null;
}
- if (blockEntity.destroyTarget != null) {
+ if (damageTarget && blockEntity.destroyTarget != null) { // CraftBukkit
+ if (blockEntity.destroyTarget.hurtServer((net.minecraft.server.level.ServerLevel) level, level.damageSources().magic(), 4.0F)) // CraftBukkit
level.playSound(
null,
blockEntity.destroyTarget.getX(),
@@ -214,7 +_,6 @@
1.0F,
1.0F
);
- blockEntity.destroyTarget.hurt(level.damageSources().magic(), 4.0F);
}
if (livingEntity != blockEntity.destroyTarget) {

View File

@@ -0,0 +1,76 @@
--- a/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
+++ b/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
@@ -13,6 +_,7 @@
private static final int CHECK_TICK_DELAY = 5;
private int openCount;
private double maxInteractionRange;
+ public boolean opened; // CraftBukki
protected abstract void onOpen(Level level, BlockPos pos, BlockState state);
@@ -20,10 +_,36 @@
protected abstract void openerCountChanged(Level level, BlockPos pos, BlockState state, int count, int openCount);
+ // CraftBukkit start
+ public void onAPIOpen(Level level, BlockPos blockPos, BlockState blockState) {
+ this.onOpen(level, blockPos, blockState);
+ }
+
+ public void onAPIClose(Level level, BlockPos blockPos, BlockState blockState) {
+ this.onClose(level, blockPos, blockState);
+ }
+
+ public void openerAPICountChanged(Level world, BlockPos blockPos, BlockState blockState, int count, int openCount) {
+ this.openerCountChanged(world, blockPos, blockState, count, openCount);
+ }
+ // CraftBukkit en
+
protected abstract boolean isOwnContainer(Player player);
public void incrementOpeners(Player player, Level level, BlockPos pos, BlockState state) {
+ int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
int i = this.openCount++;
+
+ // CraftBukkit start - Call redstone event
+ if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
+ int newPower = Math.max(0, Math.min(15, this.openCount));
+
+ if (oldPower != newPower) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, oldPower, newPower);
+ }
+ }
+ // CraftBukkit en
+
if (i == 0) {
this.onOpen(level, pos, state);
level.gameEvent(player, GameEvent.CONTAINER_OPEN, pos);
@@ -35,7 +_,20 @@
}
public void decrementOpeners(Player player, Level level, BlockPos pos, BlockState state) {
+ int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
+ if (this.openCount == 0) return; // Paper - Prevent ContainerOpenersCounter openCount from going negative
int i = this.openCount--;
+
+ // CraftBukkit start - Call redstone event
+ if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
+ int newPower = Math.max(0, Math.min(15, this.openCount));
+
+ if (oldPower != newPower) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, oldPower, newPower);
+ }
+ }
+ // CraftBukkit end
+
if (this.openCount == 0) {
this.onClose(level, pos, state);
level.gameEvent(player, GameEvent.CONTAINER_CLOSE, pos);
@@ -60,6 +_,7 @@
}
int size = playersWithContainerOpen.size();
+ if (this.opened) size++; // CraftBukkit - add dummy count from API
int i = this.openCount;
if (i != size) {
boolean flag = size != 0;

View File

@@ -0,0 +1,50 @@
--- a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
@@ -56,6 +_,47 @@
}
};
+ // CraftBukkit start - add fields and methods
+ public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
+ private int maxStack = MAX_STACK;
+
+ @Override
+ public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
+ return this.items;
+ }
+
+ @Override
+ public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.add(who);
+ }
+
+ @Override
+ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.remove(who);
+ }
+
+ @Override
+ public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
+ return this.transaction;
+ }
+
+ @Override
+ public int getMaxStackSize() {
+ return this.maxStack;
+ }
+
+ @Override
+ public void setMaxStackSize(int size) {
+ this.maxStack = size;
+ }
+
+ @Override
+ public org.bukkit.Location getLocation() {
+ if (this.level == null) return null;
+ return io.papermc.paper.util.MCUtil.toLocation(this.level, this.worldPosition);
+ }
+ // CraftBukkit en
+
public CrafterBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.CRAFTER, pos, state);
}

View File

@@ -0,0 +1,51 @@
--- a/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java
@@ -20,6 +_,48 @@
import net.minecraft.world.ticks.ContainerSingleItem;
public class DecoratedPotBlockEntity extends BlockEntity implements RandomizableContainer, ContainerSingleItem.BlockContainerSingleItem {
+
+ // CraftBukkit start - add fields and methods
+ public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
+ private int maxStack = MAX_STACK;
+
+ @Override
+ public List<ItemStack> getContents() {
+ return java.util.List.of(this.item);
+ }
+
+ @Override
+ public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.add(who);
+ }
+
+ @Override
+ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.remove(who);
+ }
+
+ @Override
+ public java.util.List<HumanEntity> getViewers() {
+ return this.transaction;
+ }
+
+ @Override
+ public int getMaxStackSize() {
+ return this.maxStack;
+ }
+
+ @Override
+ public void setMaxStackSize(int i) {
+ this.maxStack = i;
+ }
+
+ @Override
+ public org.bukkit.Location getLocation() {
+ if (this.level == null) return null;
+ return org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.worldPosition, this.level.getWorld());
+ }
+ // CraftBukkit end
+
public static final String TAG_SHERDS = "sherds";
public static final String TAG_ITEM = "item";
public static final int EVENT_POT_WOBBLES = 1;

View File

@@ -0,0 +1,39 @@
--- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
@@ -17,6 +_,36 @@
public static final int CONTAINER_SIZE = 9;
private NonNullList<ItemStack> items = NonNullList.withSize(9, ItemStack.EMPTY);
+ // CraftBukkit start - add fields and methods
+ public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
+ private int maxStack = MAX_STACK;
+
+ public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
+ return this.items;
+ }
+
+ public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.add(who);
+ }
+
+ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.remove(who);
+ }
+
+ public List<HumanEntity> getViewers() {
+ return this.transaction;
+ }
+
+ @Override
+ public int getMaxStackSize() {
+ return this.maxStack;
+ }
+
+ public void setMaxStackSize(int size) {
+ this.maxStack = size;
+ }
+ // CraftBukkit end
+
protected DispenserBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState) {
super(type, pos, blockState);
}

View File

@@ -20,7 +20,7 @@
+ this.transaction.remove(who);
+ }
+
+ public List<org.bukkit.entity.HumanEntity> getViewers() {
+ public java.util.List<HumanEntity> getViewers() {
+ return this.transaction;
+ }
+

View File

@@ -0,0 +1,16 @@
--- a/net/minecraft/world/level/block/entity/JigsawBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/JigsawBlockEntity.java
@@ -131,7 +_,12 @@
public void generate(ServerLevel level, int maxDepth, boolean keepJigsaws) {
BlockPos blockPos = this.getBlockPos().relative(this.getBlockState().getValue(JigsawBlock.ORIENTATION).front());
Registry<StructureTemplatePool> registry = level.registryAccess().lookupOrThrow(Registries.TEMPLATE_POOL);
- Holder<StructureTemplatePool> orThrow = registry.getOrThrow(this.pool);
+ // Paper start - Replace getHolderOrThrow with a null check
+ Holder<StructureTemplatePool> orThrow = registry.get(this.pool).orElse(null);
+ if (orThrow == null) {
+ return;
+ }
+ // Paper end - Replace getHolderOrThrow with a null check
JigsawPlacement.generateJigsaw(level, orThrow, this.target, maxDepth, blockPos, keepJigsaws);
}

View File

@@ -0,0 +1,73 @@
--- a/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java
@@ -20,6 +_,44 @@
import net.minecraft.world.ticks.ContainerSingleItem;
public class JukeboxBlockEntity extends BlockEntity implements ContainerSingleItem.BlockContainerSingleItem {
+
+ // CraftBukkit start - add fields and methods
+ public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
+ private int maxStack = MAX_STACK;
+ public boolean opened;
+
+ @Override
+ public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
+ return Collections.singletonList(this.item);
+ }
+
+ @Override
+ public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.add(who);
+ }
+
+ @Override
+ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.remove(who);
+ }
+
+ @Override
+ public List<HumanEntity> getViewers() {
+ return this.transaction;
+ }
+
+ @Override
+ public void setMaxStackSize(int size) {
+ this.maxStack = size;
+ }
+
+ @Override
+ public org.bukkit.Location getLocation() {
+ if (this.level == null) return null;
+ return io.papermc.paper.util.MCUtil.toLocation(this.level, this.worldPosition);
+ }
+ // CraftBukkit end
+
public static final String SONG_ITEM_TAG_ID = "RecordItem";
public static final String TICKS_SINCE_SONG_STARTED_TAG_ID = "ticks_since_song_started";
private ItemStack item = ItemStack.EMPTY;
@@ -126,7 +_,7 @@
@Override
public int getMaxStackSize() {
- return 1;
+ return this.maxStack; // CraftBukkit
}
@Override
@@ -147,9 +_,14 @@
@VisibleForTesting
public void setSongItemWithoutPlaying(ItemStack stack) {
this.item = stack;
- JukeboxSong.fromStack(this.level.registryAccess(), stack)
+ this.jukeboxSongPlayer.song = null; // CraftBukkit - reset
+ JukeboxSong.fromStack(this.level != null ? this.level.registryAccess() : org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry(), stack) // Paper - fallback to other RegistyrAccess if no level
.ifPresent(holder -> this.jukeboxSongPlayer.setSongWithoutPlaying((Holder<JukeboxSong>)holder, 0L));
- this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
+ // CraftBukkit start - add null check for level
+ if (this.level != null) {
+ this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
+ }
+ // CraftBukkit end
this.setChanged();
}

View File

@@ -0,0 +1,139 @@
--- a/net/minecraft/world/level/block/entity/LecternBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/LecternBlockEntity.java
@@ -33,6 +_,51 @@
public static final int SLOT_BOOK = 0;
public static final int NUM_SLOTS = 1;
public final Container bookAccess = new Container() {
+ // CraftBukkit start - add fields and methods
+ public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
+ private int maxStack = 1;
+
+ @Override
+ public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
+ return java.util.List.of(LecternBlockEntity.this.book);
+ }
+
+ @Override
+ public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.add(who);
+ }
+
+ @Override
+ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.remove(who);
+ }
+
+ @Override
+ public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
+ return this.transaction;
+ }
+
+ @Override
+ public void setMaxStackSize(int i) {
+ this.maxStack = i;
+ }
+
+ @Override
+ public org.bukkit.Location getLocation() {
+ if (LecternBlockEntity.this.level == null) return null;
+ return io.papermc.paper.util.MCUtil.toLocation(LecternBlockEntity.this.level, LecternBlockEntity.this.worldPosition);
+ }
+
+ @Override
+ public org.bukkit.inventory.InventoryHolder getOwner() {
+ return LecternBlockEntity.this.getOwner();
+ }
+
+ public LecternBlockEntity getLectern() {
+ return LecternBlockEntity.this;
+ }
+ // CraftBukkit end
+
@Override
public int getContainerSize() {
return 1;
@@ -76,11 +_,19 @@
@Override
public void setItem(int slot, ItemStack stack) {
+ // CraftBukkit start
+ if (slot == 0) {
+ LecternBlockEntity.this.setBook(stack);
+ if (LecternBlockEntity.this.getLevel() != null) {
+ LecternBlock.resetBookState(null, LecternBlockEntity.this.getLevel(), LecternBlockEntity.this.getBlockPos(), LecternBlockEntity.this.getBlockState(), LecternBlockEntity.this.hasBook());
+ }
+ }
+ // CraftBukkit end
}
@Override
public int getMaxStackSize() {
- return 1;
+ return maxStack;
}
@Override
@@ -158,7 +_,7 @@
if (i != this.page) {
this.page = i;
this.setChanged();
- LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState());
+ if (this.level != null) LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState()); // CraftBukkit
}
}
@@ -179,6 +_,36 @@
return stack;
}
+ // CraftBukkit start
+ private final CommandSource commandSource = new CommandSource() {
+
+ @Override
+ public void sendSystemMessage(Component message) {
+ }
+
+ @Override
+ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack commandSourceStack) {
+ return commandSourceStack.getEntity() != null
+ ? commandSourceStack.getEntity().getBukkitEntity()
+ : new org.bukkit.craftbukkit.command.CraftBlockCommandSender(commandSourceStack, LecternBlockEntity.this);
+ }
+
+ @Override
+ public boolean acceptsSuccess() {
+ return false;
+ }
+
+ @Override
+ public boolean acceptsFailure() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldInformAdmins() {
+ return false;
+ }
+ };
+ // CraftBukkit end
private CommandSourceStack createCommandSourceStack(@Nullable Player player, ServerLevel level) {
String string;
Component component;
@@ -191,7 +_,7 @@
}
Vec3 vec3 = Vec3.atCenterOf(this.worldPosition);
- return new CommandSourceStack(CommandSource.NULL, vec3, Vec2.ZERO, level, 2, string, component, level.getServer(), player);
+ return new CommandSourceStack(this.commandSource, vec3, Vec2.ZERO, level, 2, string, component, level.getServer(), player); // CraftBukkit - commandSource
}
@Override
@@ -223,7 +_,7 @@
@Override
public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
- return new LecternMenu(containerId, this.bookAccess, this.dataAccess);
+ return new LecternMenu(containerId, this.bookAccess, this.dataAccess, playerInventory); // CraftBukkit
}
@Override

View File

@@ -0,0 +1,16 @@
--- a/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
@@ -115,4 +_,13 @@
tag.remove("LootTable");
tag.remove("LootTableSeed");
}
+
+ // Paper start - LootTable API
+ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); // Paper
+
+ @Override
+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
+ return this.lootableData;
+ }
+ // Paper end - LootTable API
}

View File

@@ -0,0 +1,50 @@
--- a/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java
@@ -26,6 +_,7 @@
private final VibrationSystem.Listener vibrationListener;
private final VibrationSystem.User vibrationUser = this.createVibrationUser();
public int lastVibrationFrequency;
+ @Nullable public Integer rangeOverride = null; // Paper - Configurable sculk sensor listener range
protected SculkSensorBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState) {
super(type, pos, blockState);
@@ -52,8 +_,16 @@
.resultOrPartial(string -> LOGGER.error("Failed to parse vibration listener for Sculk Sensor: '{}'", string))
.ifPresent(data -> this.vibrationData = data);
}
+ // Paper start - Configurable sculk sensor listener range
+ if (tag.contains(PAPER_LISTENER_RANGE_NBT_KEY)) {
+ this.rangeOverride = tag.getInt(PAPER_LISTENER_RANGE_NBT_KEY);
+ } else {
+ this.rangeOverride = null;
+ }
+ // Paper end - Configurable sculk sensor listener range
}
+ protected static final String PAPER_LISTENER_RANGE_NBT_KEY = "Paper.ListenerRange"; // Paper - Configurable sculk sensor listener range
@Override
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.saveAdditional(tag, registries);
@@ -63,7 +_,13 @@
.encodeStart(registryOps, this.vibrationData)
.resultOrPartial(string -> LOGGER.error("Failed to encode vibration listener for Sculk Sensor: '{}'", string))
.ifPresent(tag1 -> tag.put("listener", tag1));
- }
+ this.saveRangeOverride(tag); // Paper - Configurable sculk sensor listener range
+ }
+ // Paper start - Configurable sculk sensor listener range
+ protected void saveRangeOverride(CompoundTag tag) {
+ if (this.rangeOverride != null && this.rangeOverride != VibrationUser.LISTENER_RANGE) tag.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default
+ }
+ // Paper end - Configurable sculk sensor listener range
@Override
public VibrationSystem.Data getVibrationData() {
@@ -100,6 +_,7 @@
@Override
public int getListenerRadius() {
+ if (SculkSensorBlockEntity.this.rangeOverride != null) return SculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range
return 8;
}

View File

@@ -0,0 +1,25 @@
--- a/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java
@@ -105,6 +_,13 @@
@Nullable
public static ServerPlayer tryGetPlayer(@Nullable Entity entity) {
+ // Paper start - check global player list where appropriate; ensure level is the same for sculk events
+ final ServerPlayer player = tryGetPlayer0(entity);
+ return player != null && player.level() == entity.level() ? player : null;
+ }
+ @Nullable
+ private static ServerPlayer tryGetPlayer0(@Nullable Entity entity) {
+ // Paper end - check global player list where appropriate
if (entity instanceof ServerPlayer) {
return (ServerPlayer)entity;
} else {
@@ -190,7 +_,7 @@
private boolean trySummonWarden(ServerLevel level) {
return this.warningLevel >= 4
&& SpawnUtil.trySpawnMob(
- EntityType.WARDEN, EntitySpawnReason.TRIGGERED, level, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, false
+ EntityType.WARDEN, EntitySpawnReason.TRIGGERED, level, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, null // Paper - Entity#getEntitySpawnReason
)
.isPresent();
}

View File

@@ -0,0 +1,56 @@
--- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
@@ -49,6 +_,37 @@
@Nullable
private final DyeColor color;
+ // CraftBukkit start - add fields and methods
+ public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
+ private int maxStack = MAX_STACK;
+ public boolean opened;
+
+ public List<ItemStack> getContents() {
+ return this.itemStacks;
+ }
+
+ public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.add(who);
+ }
+
+ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
+ this.transaction.remove(who);
+ }
+
+ public List<org.bukkit.entity.HumanEntity> getViewers() {
+ return this.transaction;
+ }
+
+ @Override
+ public int getMaxStackSize() {
+ return this.maxStack;
+ }
+
+ public void setMaxStackSize(int size) {
+ this.maxStack = size;
+ }
+ // CraftBukkit end
+
public ShulkerBoxBlockEntity(@Nullable DyeColor color, BlockPos pos, BlockState blockState) {
super(BlockEntityType.SHULKER_BOX, pos, blockState);
this.color = color;
@@ -167,6 +_,7 @@
}
this.openCount++;
+ if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call
this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount);
if (this.openCount == 1) {
this.level.gameEvent(player, GameEvent.CONTAINER_OPEN, this.worldPosition);
@@ -180,6 +_,7 @@
public void stopOpen(Player player) {
if (!this.remove && !player.isSpectator()) {
this.openCount--;
+ if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call.
this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount);
if (this.openCount <= 0) {
this.level.gameEvent(player, GameEvent.CONTAINER_CLOSE, this.worldPosition);

View File

@@ -0,0 +1,183 @@
--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java
@@ -54,11 +_,16 @@
return new SignText();
}
- public boolean isFacingFrontText(Player player) {
+ public boolean isFacingFrontText(net.minecraft.world.entity.player.Player player) {
+ // Paper start - More Sign Block API
+ return this.isFacingFrontText(player.getX(), player.getZ());
+ }
+ public boolean isFacingFrontText(double x, double z) {
+ // Paper end - More Sign Block API
if (this.getBlockState().getBlock() instanceof SignBlock signBlock) {
Vec3 signHitboxCenterPosition = signBlock.getSignHitboxCenterPosition(this.getBlockState());
- double d = player.getX() - (this.getBlockPos().getX() + signHitboxCenterPosition.x);
- double d1 = player.getZ() - (this.getBlockPos().getZ() + signHitboxCenterPosition.z);
+ double d = x - ((double) this.getBlockPos().getX() + signHitboxCenterPosition.x); // Paper - More Sign Block API
+ double d1 = z - ((double) this.getBlockPos().getZ() + signHitboxCenterPosition.z); // Paper - More Sign Block AP
float yRotationDegrees = signBlock.getYRotationDegrees(this.getBlockState());
float f = (float)(Mth.atan2(d1, d) * 180.0F / (float)Math.PI) - 90.0F;
return Mth.degreesDifferenceAbs(yRotationDegrees, f) <= 90.0F;
@@ -143,11 +_,13 @@
public void updateSignText(Player player, boolean isFrontText, List<FilteredText> filteredText) {
if (!this.isWaxed() && player.getUUID().equals(this.getPlayerWhoMayEdit()) && this.level != null) {
- this.updateText(signText -> this.setMessages(player, filteredText, signText), isFrontText);
+ this.updateText(signText -> this.setMessages(player, filteredText, signText, isFrontText), isFrontText); // CraftBukkit
this.setAllowedPlayerEditor(null);
this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
} else {
LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString());
+ if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < 32 * 32) // Paper - Dont send far away sign update
+ ((net.minecraft.server.level.ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit
}
}
@@ -156,18 +_,40 @@
return this.setText(updater.apply(text), isFrontText);
}
- private SignText setMessages(Player player, List<FilteredText> filteredText, SignText text) {
+ private SignText setMessages(Player player, List<FilteredText> filteredText, SignText text, boolean front) { // CraftBukkit
+ SignText originalText = text; // CraftBukkit
for (int i = 0; i < filteredText.size(); i++) {
FilteredText filteredText1 = filteredText.get(i);
Style style = text.getMessage(i, player.isTextFilteringEnabled()).getStyle();
if (player.isTextFilteringEnabled()) {
- text = text.setMessage(i, Component.literal(filteredText1.filteredOrEmpty()).setStyle(style));
+ text = text.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style)); // Paper - filter sign text to chat only
} else {
text = text.setMessage(
- i, Component.literal(filteredText1.raw()).setStyle(style), Component.literal(filteredText1.filteredOrEmpty()).setStyle(style)
+ i, Component.literal(filteredText1.raw()).setStyle(style), Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style) // Paper - filter sign text to chat only
);
}
}
+
+ // CraftBukkit start
+ org.bukkit.entity.Player apiPlayer = ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity();
+ List<net.kyori.adventure.text.Component> lines = new java.util.ArrayList<>(); // Paper - adventure
+
+ for (int i = 0; i < filteredText.size(); ++i) {
+ lines.add(io.papermc.paper.adventure.PaperAdventure.asAdventure(text.getMessage(i, player.isTextFilteringEnabled()))); // Paper - Adventure
+ }
+
+ org.bukkit.event.block.SignChangeEvent event = new org.bukkit.event.block.SignChangeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.worldPosition), apiPlayer, new java.util.ArrayList<>(lines), (front) ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK); // Paper - Adventure
+ if (!event.callEvent()) {
+ return originalText;
+ }
+
+ Component[] components = org.bukkit.craftbukkit.block.CraftSign.sanitizeLines(event.lines()); // Paper - Adventure
+ for (int i = 0; i < components.length; i++) {
+ if (!java.util.Objects.equals(lines.get(i), event.line(i))) { // Paper - Adventure
+ text = text.setMessage(i, components[i]);
+ }
+ }
+ // CraftBukkit end
return text;
}
@@ -207,7 +_,23 @@
Style style = component.getStyle();
ClickEvent clickEvent = style.getClickEvent();
if (clickEvent != null && clickEvent.getAction() == ClickEvent.Action.RUN_COMMAND) {
- player.getServer().getCommands().performPrefixedCommand(createCommandSourceStack(player, level, pos), clickEvent.getValue());
+ // Paper start - Fix commands from signs not firing command events
+ String command = clickEvent.getValue().startsWith("/") ? clickEvent.getValue() : "/" + clickEvent.getValue();
+ if (org.spigotmc.SpigotConfig.logCommands) {
+ LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command);
+ }
+ io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent(
+ (org.bukkit.entity.Player) player.getBukkitEntity(),
+ command,
+ new org.bukkit.craftbukkit.util.LazyPlayerSet(player.getServer()),
+ (org.bukkit.block.Sign) org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.worldPosition).getState(),
+ frontText ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK
+ );
+ if (!event.callEvent()) {
+ return false;
+ }
+ player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.entity.CraftPlayer) event.getPlayer()).getHandle(), level, pos), event.getMessage());
+ // Paper end - Fix commands from signs not firing command events
flag = true;
}
}
@@ -215,10 +_,55 @@
return flag;
}
- private static CommandSourceStack createCommandSourceStack(@Nullable Player player, Level level, BlockPos pos) {
+ // CraftBukkit start
+ private final CommandSource commandSource = new CommandSource() {
+
+ @Override
+ public void sendSystemMessage(Component message) {}
+
+ @Override
+ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack commandSourceStack) {
+ return commandSourceStack.getEntity() != null ? commandSourceStack.getEntity().getBukkitEntity() : new org.bukkit.craftbukkit.command.CraftBlockCommandSender(commandSourceStack, SignBlockEntity.this);
+ }
+
+ @Override
+ public boolean acceptsSuccess() {
+ return false;
+ }
+
+ @Override
+ public boolean acceptsFailure() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldInformAdmins() {
+ return false;
+ }
+ };
+
+ private CommandSourceStack createCommandSourceStack(@Nullable Player player, Level world, BlockPos pos) {
+ // CraftBukkit end
String string = player == null ? "Sign" : player.getName().getString();
Component component = (Component)(player == null ? Component.literal("Sign") : player.getDisplayName());
- return new CommandSourceStack(CommandSource.NULL, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel)level, 2, string, component, level.getServer(), player);
+
+ // Paper start - Fix commands from signs not firing command events
+ CommandSource commandSource = world.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this.commandSource) {
+ @Override
+ public void sendSystemMessage(Component message) {
+ if (player instanceof final net.minecraft.server.level.ServerPlayer serverPlayer) {
+ serverPlayer.sendSystemMessage(message);
+ }
+ }
+
+ @Override
+ public boolean acceptsFailure() {
+ return true;
+ }
+ } : this.commandSource;
+ // Paper end - Fix commands from signs not firing command events
+ // CraftBukkit - this
+ return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, string, (Component) component, world.getServer(), player); // Paper - Fix commands from signs not firing command events
}
@Override
@@ -237,12 +_,17 @@
@Nullable
public UUID getPlayerWhoMayEdit() {
+ // CraftBukkit start - unnecessary sign ticking removed, so do this lazily
+ if (this.level != null && this.playerWhoMayEdit != null) {
+ this.clearInvalidPlayerWhoMayEdit(this, this.level, this.playerWhoMayEdit);
+ }
+ // CraftBukkit end
return this.playerWhoMayEdit;
}
private void markUpdated() {
this.setChanged();
- this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
+ if (this.level != null) this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); // CraftBukkit - skip notify if world is null (SPIGOT-5122)
}
public boolean isWaxed() {

View File

@@ -0,0 +1,73 @@
--- a/net/minecraft/world/level/block/entity/SkullBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/SkullBlockEntity.java
@@ -41,7 +_,7 @@
@Nullable
private static LoadingCache<String, CompletableFuture<Optional<GameProfile>>> profileCacheByName;
@Nullable
- private static LoadingCache<UUID, CompletableFuture<Optional<GameProfile>>> profileCacheById;
+ private static LoadingCache<com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile>, CompletableFuture<Optional<GameProfile>>> profileCacheById; // Paper - player profile events
public static final Executor CHECKED_MAIN_THREAD_EXECUTOR = runnable -> {
Executor executor = mainThreadExecutor;
if (executor != null) {
@@ -76,9 +_,9 @@
profileCacheById = CacheBuilder.newBuilder()
.expireAfterAccess(Duration.ofMinutes(10L))
.maximumSize(256L)
- .build(new CacheLoader<UUID, CompletableFuture<Optional<GameProfile>>>() {
+ .build(new CacheLoader<>() { // Paper - player profile events
@Override
- public CompletableFuture<Optional<GameProfile>> load(UUID id) {
+ public CompletableFuture<Optional<GameProfile>> load(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> id) { // Paper - player profile events
return SkullBlockEntity.fetchProfileById(id, services, booleanSupplier);
}
});
@@ -89,23 +_,29 @@
.getAsync(name)
.thenCompose(
optional -> {
- LoadingCache<UUID, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById;
+ LoadingCache<com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile>, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById; // Paper - player profile events
return loadingCache != null && !optional.isEmpty()
- ? loadingCache.getUnchecked(optional.get().getId()).thenApply(optional1 -> optional1.or(() -> optional))
+ ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(optional.get().getId(), optional.get())).thenApply(optional1 -> optional1.or(() -> optional)) // Paper - player profile events
: CompletableFuture.completedFuture(Optional.empty());
}
);
}
- static CompletableFuture<Optional<GameProfile>> fetchProfileById(UUID id, Services services, BooleanSupplier cacheUninitialized) {
+ static CompletableFuture<Optional<GameProfile>> fetchProfileById(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> id, Services services, BooleanSupplier cacheUninitialized) { // Pape
return CompletableFuture.supplyAsync(() -> {
if (cacheUninitialized.getAsBoolean()) {
return Optional.empty();
} else {
- ProfileResult profileResult = services.sessionService().fetchProfile(id, true);
+ // Paper start - fill player profile events
+ if (services.sessionService() instanceof com.destroystokyo.paper.profile.PaperMinecraftSessionService paperService) {
+ final GameProfile profile = id.getSecond() != null ? id.getSecond() : new com.mojang.authlib.GameProfile(id.getFirst(), "");
+ return Optional.ofNullable(paperService.fetchProfile(profile, true)).map(ProfileResult::profile);
+ }
+ ProfileResult profileResult = services.sessionService().fetchProfile(id.getFirst(), true);
+ // Paper end - fill player profile events
return Optional.ofNullable(profileResult).map(ProfileResult::profile);
}
- }, Util.backgroundExecutor().forName("fetchProfile"));
+ }, Util.PROFILE_EXECUTOR); // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread
}
public static void clear() {
@@ -210,9 +_,11 @@
: CompletableFuture.completedFuture(Optional.empty());
}
- public static CompletableFuture<Optional<GameProfile>> fetchGameProfile(UUID profileUuid) {
- LoadingCache<UUID, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById;
- return loadingCache != null ? loadingCache.getUnchecked(profileUuid) : CompletableFuture.completedFuture(Optional.empty());
+ // Paper start - player profile events
+ public static CompletableFuture<Optional<GameProfile>> fetchGameProfile(UUID uuid, @Nullable String name) {
+ LoadingCache<com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile>, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById;
+ return loadingCache != null ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(uuid, name != null ? new com.mojang.authlib.GameProfile(uuid, name) : null)) : CompletableFuture.completedFuture(Optional.empty());
+ // Paper end - player profile events
}
@Override