From 42bf4ebf35ac0ae0b93c56e775e8abe25b0a6de7 Mon Sep 17 00:00:00 2001
From: Clark Fischer <clark.fischer@gmail.com>
Date: Wed, 11 Oct 2023 14:22:21 -0700
Subject: [PATCH] Account for exterior block hardness in break speed
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Players were able to cheese hard block collection by surrounding hard
blocks with softer blocks. For example, it was possible to mine up to 8
obsidian at cobblestone break-speed by targeting cobblestone when
breaking. To solve this, a new `AbstractBlockState` mixin was
introduced. I tried to solve this at other levels (preferably without a
mixin), but wasn't able to get all the necessary information anywhere
other than at the BlockState level.

The minimal breaking delta (which is effectively the same as maximum
block hardness) is used instead of the targeted block hardness. Using
the average basically didn't solve the issue—it was possible to surround
a hard block with softer blocks to 'dilute' the hardness.

Fixes https://github.com/Draylar/magna/issues/14

Signed-off-by: Clark Fischer <clark.fischer@gmail.com>
---
 .../magna/mixin/AbstractBlockStateMixin.java  | 62 +++++++++++++++++++
 src/main/resources/magna.mixins.json          |  5 +-
 .../dev/draylar/magna/test/MagnaTest.java     | 13 ++++
 3 files changed, 78 insertions(+), 2 deletions(-)
 create mode 100644 src/main/java/dev/draylar/magna/mixin/AbstractBlockStateMixin.java

diff --git a/src/main/java/dev/draylar/magna/mixin/AbstractBlockStateMixin.java b/src/main/java/dev/draylar/magna/mixin/AbstractBlockStateMixin.java
new file mode 100644
index 0000000..9fd5e81
--- /dev/null
+++ b/src/main/java/dev/draylar/magna/mixin/AbstractBlockStateMixin.java
@@ -0,0 +1,62 @@
+package dev.draylar.magna.mixin;
+
+import dev.draylar.magna.api.MagnaTool;
+import net.minecraft.block.AbstractBlock;
+import net.minecraft.block.BlockState;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.BlockView;
+import net.minecraft.world.World;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import java.util.List;
+
+/**
+ * A mixin that calculates the block breaking delta for {@link MagnaTool MagnaTool}s.
+ */
+@Mixin(AbstractBlock.AbstractBlockState.class)
+public class AbstractBlockStateMixin {
+    /**
+     * Compute the block breaking delta for {@link MagnaTool}s. If the held item is not a MagnaTool, this mixin
+     * does nothing.
+     *
+     * <p>The delta is computed by taking the minimum delta of the affected blocks. Mining speed is effectively limited
+     * by the hardest block being broken.
+     */
+    @Inject(
+            method = "calcBlockBreakingDelta",
+            at = @At("HEAD"),
+            cancellable = true
+    )
+    public void calcBlockBreakingDelta(PlayerEntity player, BlockView world, BlockPos pos, CallbackInfoReturnable<Float> cir) {
+        ItemStack stack = player.getInventory().getMainHandStack();
+        if (stack.getItem() instanceof MagnaTool tool) {
+            cir.cancel();
+            int radius = tool.getRadius(stack);
+            cir.setReturnValue(calculateDelta(tool, player, radius));
+        }
+    }
+
+    @Unique
+    private static float calculateDelta(final MagnaTool tool, final PlayerEntity player, final int radius) {
+        World world = player.getWorld();
+
+        // Even though we already have the BlockPosition that the player is targeting, the side information wasn't
+        // passed along, which is necessary for figuring out how to expand the radius. Just throw that away and
+        // recompute.
+        List<BlockPos> blocks = tool.getBlockFinder().findPositions(world, player, radius);
+
+        return blocks.stream()
+                .map(pos -> {
+                    BlockState state = world.getBlockState(pos);
+                    //noinspection deprecation
+                    return state.getBlock().calcBlockBreakingDelta(state, player, world, pos);
+                })
+                .min(Float::compare).orElseThrow();
+    }
+}
diff --git a/src/main/resources/magna.mixins.json b/src/main/resources/magna.mixins.json
index 833c6a8..3f02a67 100644
--- a/src/main/resources/magna.mixins.json
+++ b/src/main/resources/magna.mixins.json
@@ -3,14 +3,15 @@
   "package": "dev.draylar.magna.mixin",
   "compatibilityLevel": "JAVA_8",
   "mixins": [
+    "AbstractBlockStateMixin",
     "BlockMixin",
     "PlayerInventoryMixin",
     "ServerPlayerInteractionManagerMixin",
     "WorldMixin"
   ],
   "client": [
-    "WorldRendererMixin",
-    "ClientPlayerInteractionManagerMixin"
+    "ClientPlayerInteractionManagerMixin",
+    "WorldRendererMixin"
   ],
   "injectors": {
     "defaultRequire": 1
diff --git a/src/testmod/java/dev/draylar/magna/test/MagnaTest.java b/src/testmod/java/dev/draylar/magna/test/MagnaTest.java
index b190af5..286a582 100644
--- a/src/testmod/java/dev/draylar/magna/test/MagnaTest.java
+++ b/src/testmod/java/dev/draylar/magna/test/MagnaTest.java
@@ -4,6 +4,7 @@
 import dev.draylar.magna.item.ExcavatorItem;
 import dev.draylar.magna.item.HammerItem;
 import net.fabricmc.api.ModInitializer;
+import net.minecraft.block.BlockState;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
@@ -31,6 +32,18 @@ public void onInitialize() {
                 new HammerItem(ToolMaterials.DIAMOND, 0, 0, new Item.Settings())
         );
 
+        // Standard hammer with a particularly slow mining speed. Useful for testing break speed.
+        Registry.register(
+                Registries.ITEM,
+                new Identifier("magna", "hammer_slow_test"),
+                new HammerItem(ToolMaterials.DIAMOND, 0, 0, new Item.Settings()) {
+                    @Override
+                    public float getMiningSpeedMultiplier(ItemStack stack, BlockState state) {
+                        return 1.0f;
+                    }
+                }
+        );
+
         // Standard Hammer with a tool material of Diamond and a modified depth.
         Registry.register(
                 Registries.ITEM,