diff --git a/CHANGELOG.md b/CHANGELOG.md
index af2a8e1..10e875e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# v1.2.4
+## Tinkers' Construct Integration
+- Fixed GT main-hand + TiC off-hand combination dropping items twice
+
+* * *
+
# v1.2.3
## Tinkers' Construct Integration
- GT tool in main-hand + TiC tool in off-hand (and vice versa) now mines blocks using the off-hand tool
diff --git a/src/main/java/com/github/gtexpert/gtmt/integration/tic/DualToolHandler.java b/src/main/java/com/github/gtexpert/gtmt/integration/tic/DualToolHandler.java
index 7fe581c..6099117 100644
--- a/src/main/java/com/github/gtexpert/gtmt/integration/tic/DualToolHandler.java
+++ b/src/main/java/com/github/gtexpert/gtmt/integration/tic/DualToolHandler.java
@@ -18,14 +18,14 @@
import slimeknights.tconstruct.library.utils.ToolHelper;
/**
- * Extends TiC's off-hand mining to work with GT tools.
+ * Extends TiC's off-hand mining to cover GT + TiC cross-hand combinations.
*
*
- * - GT main-hand + TiC off-hand: uses TiC dig speed and generates drops when GT cannot harvest.
- * - TiC main-hand + GT off-hand: uses GT dig speed when TiC cannot harvest.
+ * - GT main + TiC off: applies TiC dig speed and drop logic when GT cannot harvest.
+ * - TiC main + GT off: applies GT dig speed when TiC cannot harvest.
*
*
- * Runs at {@link EventPriority#LOW} so TiC's own {@code BreakSpeed} handler fires first.
+ * Registered at {@link EventPriority#LOW} so TiC's own {@code BreakSpeed} handler fires first.
*/
public final class DualToolHandler {
@@ -46,6 +46,8 @@ public static void onBreakSpeed(net.minecraftforge.event.entity.player.PlayerEve
float speed = ToolHelper.calcDigSpeed(offhand, state);
BlockPos pos = event.getPos();
if (pos == null || !ForgeHooks.canHarvestBlock(state.getBlock(), player, player.world, pos)) {
+ // Block hardness is divided by 100 instead of 30 when canHarvestBlock
+ // returns false; multiply to restore TiC's intended mining speed.
speed *= (100.0f / 30.0f);
}
event.setNewSpeed(speed);
@@ -66,10 +68,11 @@ public static void onBreakSpeed(net.minecraftforge.event.entity.player.PlayerEve
/**
* Damages the off-hand tool after a block is broken via off-hand mining.
- * For Case 1, also spawns drops using the off-hand TiC tool when
- * {@code ForgeHooks.canHarvestBlock} returns {@code false} for the GT main-hand tool.
+ * Drops are handled by vanilla: {@code MixinEntityPlayer} makes
+ * {@code player.canHarvestBlock()} return {@code true} for GT+TiC combos,
+ * so {@code tryHarvestBlock} calls {@code harvestBlock} exactly once.
*
- * Runs at {@link EventPriority#LOWEST} so cancellations by other mods are resolved first.
+ * Registered at {@link EventPriority#LOWEST} so other mods' cancellations are resolved first.
*/
@SubscribeEvent(priority = EventPriority.LOWEST)
public static void onBlockBreak(BlockEvent.BreakEvent event) {
@@ -87,10 +90,9 @@ public static void onBlockBreak(BlockEvent.BreakEvent event) {
if (mainhand.getItem() instanceof IGTTool && offhand.getItem() instanceof TinkerToolCore) {
if (!canHarvestForDrops(player, mainhand, state) && ToolHelper.canHarvest(offhand, state)) {
- if (!ForgeHooks.canHarvestBlock(state.getBlock(), player, player.world, event.getPos())) {
- state.getBlock().harvestBlock(player.world, player, event.getPos(), state,
- player.world.getTileEntity(event.getPos()), offhand);
- }
+ // Drops are provided by vanilla: MixinEntityPlayer makes player.canHarvestBlock()
+ // return true for this combo, so tryHarvestBlock's harvestBlock() call fires normally.
+ // Only damage the TiC off-hand tool for its contribution to the harvest.
offhand.getItem().onBlockDestroyed(offhand, player.world, state,
event.getPos(), player);
}
@@ -111,7 +113,10 @@ private static boolean canHarvestForDrops(EntityPlayer player, ItemStack stack,
if (stack.isEmpty() || toolType == null) {
return player.canHarvestBlock(state);
}
- int level = stack.getItem().getHarvestLevel(stack, toolType, player, state);
+ // Null player: bypasses MixinItemGTTool's off-hand elevation so the raw level of
+ // this specific tool is returned. The fallback only fires when the tool itself
+ // cannot harvest.
+ int level = stack.getItem().getHarvestLevel(stack, toolType, null, state);
if (level < 0) return player.canHarvestBlock(state);
return level >= block.getHarvestLevel(state);
}
diff --git a/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ToolMaterialRegistrar.java b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ToolMaterialRegistrar.java
index f843956..6170daf 100644
--- a/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ToolMaterialRegistrar.java
+++ b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ToolMaterialRegistrar.java
@@ -239,9 +239,9 @@ private static void registerHarvestLevelNames() {
// Override TiC's native names (0–4) with vanilla/GT naming convention
// so that the same harvest level shows the same name in both GT and TiC tooltips.
java.util.Map ticNames = slimeknights.tconstruct.library.utils.HarvestLevels.harvestLevelNames;
- ticNames.put(0, TextFormatting.DARK_GREEN + "Wood");
- ticNames.put(1, TextFormatting.GRAY + "Stone");
- ticNames.put(2, TextFormatting.WHITE + "Iron");
+ ticNames.put(0, TextFormatting.GOLD + "Wood");
+ ticNames.put(1, TextFormatting.DARK_GRAY + "Stone");
+ ticNames.put(2, TextFormatting.GRAY + "Iron");
ticNames.put(3, TextFormatting.AQUA + "Diamond");
// Level 4 (Cobalt) — no vanilla equivalent, keep TiC's name
diff --git a/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinDualToolHarvestUtils.java b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinDualToolHarvestUtils.java
index 24ef634..4ccd1b7 100644
--- a/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinDualToolHarvestUtils.java
+++ b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinDualToolHarvestUtils.java
@@ -40,7 +40,7 @@ private static void gtOffHandSupportState(EntityLivingBase entity, IBlockState s
(tool.getItem() instanceof TinkerToolCore && offhand.getItem() instanceof IGTTool);
if (!isCrossCombo) return;
- if (!gtmt$canHarvest(tool, state, player) && gtmt$canHarvest(offhand, state, player)) {
+ if (gtmt$shouldPreferOffhand(tool, offhand, state)) {
cir.setReturnValue(true);
}
}
@@ -61,23 +61,29 @@ private static void gtOffHandSupportPos(EntityLivingBase entity, BlockPos pos,
(tool.getItem() instanceof TinkerToolCore && offhand.getItem() instanceof IGTTool);
if (!isCrossCombo) return;
- if (!gtmt$canHarvest(tool, state, player) && gtmt$canHarvest(offhand, state, player)) {
+ if (gtmt$shouldPreferOffhand(tool, offhand, state)) {
cir.setReturnValue(true);
}
}
- /**
- * Always passes {@code null} for player to {@code getHarvestLevel} to get the raw level,
- * bypassing both TiC's {@code shouldUseOffhand} recursion guard and GT's harvest-level
- * elevation from {@link MixinItemGTTool}.
- */
@Unique
- private static boolean gtmt$canHarvest(ItemStack stack, IBlockState state, EntityPlayer player) {
- if (state.getMaterial().isToolNotRequired()) return true;
+ private static boolean gtmt$shouldPreferOffhand(ItemStack main, ItemStack offhand, IBlockState state) {
+ if (state.getMaterial().isToolNotRequired()) return false;
Block block = state.getBlock();
String toolType = block.getHarvestTool(state);
+ if (toolType == null) return false;
int requiredLevel = block.getHarvestLevel(state);
- if (toolType == null || requiredLevel < 0) return true;
- return stack.getItem().getHarvestLevel(stack, toolType, null, state) >= requiredLevel;
+
+ int mainRaw = main.getItem().getHarvestLevel(main, toolType, null, state);
+ int offRaw = offhand.getItem().getHarvestLevel(offhand, toolType, null, state);
+
+ // Offhand can harvest, main cannot.
+ if (requiredLevel >= 0 && offRaw >= requiredLevel && mainRaw < requiredLevel) return true;
+
+ // Main is wrong type, offhand is correct type — prefer the appropriate tool
+ // even when neither can produce drops (mirrors TiC+TiC cross-type behaviour).
+ if (mainRaw < 0 && offRaw >= 0) return true;
+
+ return false;
}
}
diff --git a/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinForgeHooks.java b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinForgeHooks.java
index 1c10d02..6f10314 100644
--- a/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinForgeHooks.java
+++ b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinForgeHooks.java
@@ -19,20 +19,20 @@
import slimeknights.tconstruct.library.utils.ToolHelper;
/**
- * Allows GT main-hand + TiC off-hand to pass the harvest check.
- * NOTE: ForgeHooks is loaded before MixinBooter's late phase, so this Mixin
- * may not apply in all environments. {@link MixinItemGTTool} is the primary fix.
+ * Allows GT main-hand + TiC off-hand to pass {@code ForgeHooks.canHarvestBlock}.
+ * ForgeHooks loads before MixinBooter's late phase, so this Mixin may not apply
+ * in all environments; {@link MixinItemGTTool} is the primary coverage.
*/
@Mixin(value = ForgeHooks.class, remap = false)
public class MixinForgeHooks {
- @Inject(
- method = "canHarvestBlock(Lnet/minecraft/block/Block;Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/world/IBlockAccess;Lnet/minecraft/util/math/BlockPos;)Z",
+ @Inject(method = "canHarvestBlock(Lnet/minecraft/block/Block;Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/world/IBlockAccess;Lnet/minecraft/util/math/BlockPos;)Z",
at = @At("RETURN"),
cancellable = true)
private static void checkOffHandTool(Block block, EntityPlayer player, IBlockAccess world,
BlockPos pos, CallbackInfoReturnable cir) {
if (Boolean.TRUE.equals(cir.getReturnValue())) return;
+ if (pos == null) return;
ItemStack mainhand = player.getHeldItemMainhand();
ItemStack offhand = player.getHeldItemOffhand();
diff --git a/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinItemGTTool.java b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinItemGTTool.java
index ab7d985..dcdba8e 100644
--- a/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinItemGTTool.java
+++ b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinItemGTTool.java
@@ -21,7 +21,6 @@
/**
* Replicates the hand-swap logic from {@code ToolCore.onBlockStartBreak} for GT tools.
- *
*/
@Mixin(value = ItemGTTool.class, remap = false)
public class MixinItemGTTool {