Paper/patches/server/0428-Add-Destroy-Speed-API.patch
Spottedleaf 8c5b837e05 Rework async chunk api implementation
Firstly, the old methods all routed to the CompletableFuture method.
However, the CF method could not guarantee that if the caller
was off-main that the future would be "completed" on-main. Since
the callback methods used the CF one, this meant that the callback
methods did not guarantee that the callbacks were to be called on
the main thread.

Now, all methods route to getChunkAtAsync(x, z, gen, urgent, cb)
so that the methods with the callback are guaranteed to invoke
the callback on the main thread. The CF behavior remains unchanged;
it may still appear to complete on main if invoked off-main.

Secondly, remove the scheduleOnMain invocation in the async
chunk completion. This unnecessarily delays the callback
by 1 tick.

Thirdly, add getChunksAtAsync(minX, minZ, maxX, maxZ, ...) which
will load chunks within an area. This method is provided as a helper
as keeping all chunks loaded within an area can be complicated to
implement for plugins (due to the lacking ticket API), and is
already implemented internally anyways.

Fourthly, remove the ticket addition that occured with getChunkAt
and getChunkAtAsync. The ticket addition may delay the unloading
of the chunk unnecessarily. It also fixes a very rare timing bug
where the future/callback would be completed after the chunk
unloads.
2024-11-18 23:00:59 -08:00

221 lines
11 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ineusia <ineusia@yahoo.com>
Date: Mon, 26 Oct 2020 11:48:06 -0500
Subject: [PATCH] Add Destroy Speed API
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
index a2fe7149a837040cf9f23eb426d398a5f90be394..27a7852a5d3f8c8960f098646ff5587c50556aa5 100644
--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
@@ -153,20 +153,20 @@ public class AttributeInstance {
double d = this.getBaseValue();
for (AttributeModifier attributeModifier : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_VALUE)) {
- d += attributeModifier.amount();
+ d += attributeModifier.amount(); // Paper - destroy speed API - diff on change
}
double e = d;
for (AttributeModifier attributeModifier2 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_BASE)) {
- e += d * attributeModifier2.amount();
+ e += d * attributeModifier2.amount(); // Paper - destroy speed API - diff on change
}
for (AttributeModifier attributeModifier3 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)) {
- e *= 1.0 + attributeModifier3.amount();
+ e *= 1.0 + attributeModifier3.amount(); // Paper - destroy speed API - diff on change
}
- return this.attribute.value().sanitizeValue(e);
+ return attribute.value().sanitizeValue(e); // Paper - destroy speed API - diff on change
}
private Collection<AttributeModifier> getModifiersOrEmpty(AttributeModifier.Operation operation) {
diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
index 7f0d87bd43a54885fc521068116888083327f37e..a17c1d1651d4d36c40ef97c1cf0b1e0d61f53418 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
@@ -725,4 +725,35 @@ public class CraftBlockData implements BlockData {
public BlockState createBlockState() {
return CraftBlockStates.getBlockState(this.state, null);
}
+
+ // Paper start - destroy speed API
+ @Override
+ public float getDestroySpeed(final ItemStack itemStack, final boolean considerEnchants) {
+ net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.unwrap(itemStack);
+ float speed = nmsItemStack.getDestroySpeed(this.state);
+ if (speed > 1.0F && considerEnchants) {
+ final net.minecraft.core.Holder<net.minecraft.world.entity.ai.attributes.Attribute> attribute = net.minecraft.world.entity.ai.attributes.Attributes.MINING_EFFICIENCY;
+ // Logic sourced from AttributeInstance#calculateValue
+ final double initialBaseValue = attribute.value().getDefaultValue();
+ final org.apache.commons.lang3.mutable.MutableDouble modifiedBaseValue = new org.apache.commons.lang3.mutable.MutableDouble(initialBaseValue);
+ final org.apache.commons.lang3.mutable.MutableDouble baseValMul = new org.apache.commons.lang3.mutable.MutableDouble(1);
+ final org.apache.commons.lang3.mutable.MutableDouble totalValMul = new org.apache.commons.lang3.mutable.MutableDouble(1);
+
+ net.minecraft.world.item.enchantment.EnchantmentHelper.forEachModifier(
+ nmsItemStack, net.minecraft.world.entity.EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> {
+ switch (attributeModifier.operation()) {
+ case ADD_VALUE -> modifiedBaseValue.add(attributeModifier.amount());
+ case ADD_MULTIPLIED_BASE -> baseValMul.add(attributeModifier.amount());
+ case ADD_MULTIPLIED_TOTAL -> totalValMul.setValue(totalValMul.doubleValue() * (1D + attributeModifier.amount()));
+ }
+ }
+ );
+
+ final double actualModifier = modifiedBaseValue.doubleValue() * baseValMul.doubleValue() * totalValMul.doubleValue();
+
+ speed += (float) attribute.value().sanitizeValue(actualModifier);
+ }
+ return speed;
+ }
+ // Paper end - destroy speed API
}
diff --git a/src/test/java/io/papermc/paper/block/CraftBlockDataDestroySpeedTest.java b/src/test/java/io/papermc/paper/block/CraftBlockDataDestroySpeedTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..32d38205a5a72c3c1838ed28cb83bcea5ad59b6b
--- /dev/null
+++ b/src/test/java/io/papermc/paper/block/CraftBlockDataDestroySpeedTest.java
@@ -0,0 +1,138 @@
+package io.papermc.paper.block;
+
+import java.util.List;
+import java.util.Optional;
+import net.minecraft.core.Holder;
+import net.minecraft.core.HolderSet;
+import net.minecraft.core.component.DataComponentMap;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.EquipmentSlotGroup;
+import net.minecraft.world.entity.ai.attributes.AttributeInstance;
+import net.minecraft.world.entity.ai.attributes.AttributeModifier;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.item.enchantment.Enchantment;
+import net.minecraft.world.item.enchantment.EnchantmentEffectComponents;
+import net.minecraft.world.item.enchantment.EnchantmentHelper;
+import net.minecraft.world.item.enchantment.ItemEnchantments;
+import net.minecraft.world.item.enchantment.LevelBasedValue;
+import net.minecraft.world.item.enchantment.effects.EnchantmentAttributeEffect;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import org.bukkit.craftbukkit.block.data.CraftBlockData;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.support.environment.AllFeatures;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static net.minecraft.resources.ResourceLocation.fromNamespaceAndPath;
+
+/**
+ * CraftBlockData's {@link org.bukkit.craftbukkit.block.data.CraftBlockData#getDestroySpeed(ItemStack, boolean)}
+ * uses a reimplementation of AttributeValue without any map to avoid attribute instance allocation and mutation
+ * for 0 gain.
+ * <p>
+ * This test is responsible for ensuring that said logic emits the expected destroy speed under heavy attribute
+ * modifier use.
+ */
+@AllFeatures
+public class CraftBlockDataDestroySpeedTest {
+
+ @Test
+ public void testCorrectEnchantmentDestroySpeedComputation() {
+ // Construct fake enchantment that has *all and multiple of* operations
+ final Enchantment speedEnchantment = speedEnchantment();
+ final BlockState blockStateToMine = Blocks.STONE.defaultBlockState();
+
+ final ItemEnchantments.Mutable mutable = new ItemEnchantments.Mutable(ItemEnchantments.EMPTY);
+ mutable.set(Holder.direct(speedEnchantment), 1);
+
+ final net.minecraft.world.item.ItemStack itemStack = new net.minecraft.world.item.ItemStack(Items.DIAMOND_PICKAXE);
+ itemStack.set(DataComponents.ENCHANTMENTS, mutable.toImmutable());
+
+ // Compute expected value by running the entire attribute instance chain
+ final AttributeInstance dummyInstance = new AttributeInstance(Attributes.MINING_EFFICIENCY, $ -> {
+ });
+ EnchantmentHelper.forEachModifier(itemStack, EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> {
+ if (attributeHolder.is(Attributes.MINING_EFFICIENCY)) dummyInstance.addTransientModifier(attributeModifier);
+ });
+
+ final double toolSpeed = itemStack.getDestroySpeed(blockStateToMine);
+ final double expectedSpeed = toolSpeed <= 1.0F ? toolSpeed : toolSpeed + dummyInstance.getValue();
+
+ // API stack + computation
+ final CraftItemStack craftMirror = CraftItemStack.asCraftMirror(itemStack);
+ final CraftBlockData data = CraftBlockData.createData(blockStateToMine);
+ final float actualSpeed = data.getDestroySpeed(craftMirror, true);
+
+ Assertions.assertEquals(expectedSpeed, actualSpeed, Vector.getEpsilon());
+ }
+
+ /**
+ * Complex enchantment that holds attribute modifiers for the mining efficiency.
+ * The enchantment holds 2 of each operation to also ensure that such behaviour works correctly.
+ *
+ * @return the enchantment.
+ */
+ private static @NotNull Enchantment speedEnchantment() {
+ return new Enchantment(
+ Component.empty(),
+ new Enchantment.EnchantmentDefinition(
+ HolderSet.empty(),
+ Optional.empty(),
+ 0, 0,
+ Enchantment.constantCost(0),
+ Enchantment.constantCost(0),
+ 0,
+ List.of(EquipmentSlotGroup.ANY)
+ ),
+ HolderSet.empty(),
+ DataComponentMap.builder()
+ .set(EnchantmentEffectComponents.ATTRIBUTES, List.of(
+ new EnchantmentAttributeEffect(
+ fromNamespaceAndPath("paper", "base1"),
+ Attributes.MINING_EFFICIENCY,
+ LevelBasedValue.constant(1),
+ AttributeModifier.Operation.ADD_VALUE
+ ),
+ new EnchantmentAttributeEffect(
+ fromNamespaceAndPath("paper", "base2"),
+ Attributes.MINING_EFFICIENCY,
+ LevelBasedValue.perLevel(3),
+ AttributeModifier.Operation.ADD_VALUE
+ ),
+ new EnchantmentAttributeEffect(
+ fromNamespaceAndPath("paper", "base-mul1"),
+ Attributes.MINING_EFFICIENCY,
+ LevelBasedValue.perLevel(7),
+ AttributeModifier.Operation.ADD_MULTIPLIED_BASE
+ ),
+ new EnchantmentAttributeEffect(
+ fromNamespaceAndPath("paper", "base-mul2"),
+ Attributes.MINING_EFFICIENCY,
+ LevelBasedValue.constant(10),
+ AttributeModifier.Operation.ADD_MULTIPLIED_BASE
+ ),
+ new EnchantmentAttributeEffect(
+ fromNamespaceAndPath("paper", "total-mul1"),
+ Attributes.MINING_EFFICIENCY,
+ LevelBasedValue.constant(.2f),
+ AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL
+ ),
+ new EnchantmentAttributeEffect(
+ fromNamespaceAndPath("paper", "total-mul2"),
+ Attributes.MINING_EFFICIENCY,
+ LevelBasedValue.constant(-.5F),
+ AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL
+ )
+ ))
+ .build()
+ );
+ }
+
+}