mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-21 18:15:54 +01:00
8c5b837e05
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.
221 lines
11 KiB
Diff
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()
|
|
+ );
|
|
+ }
|
|
+
|
|
+}
|