Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <ul>
* <li>GT main-hand + TiC off-hand: uses TiC dig speed and generates drops when GT cannot harvest.</li>
* <li>TiC main-hand + GT off-hand: uses GT dig speed when TiC cannot harvest.</li>
* <li>GT main + TiC off: applies TiC dig speed and drop logic when GT cannot harvest.</li>
* <li>TiC main + GT off: applies GT dig speed when TiC cannot harvest.</li>
* </ul>
*
* 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 {

Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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)) {
Comment thread
tier940 marked this conversation as resolved.
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);
}
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Integer, String> 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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;
Comment thread
tier940 marked this conversation as resolved.
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> cir) {
if (Boolean.TRUE.equals(cir.getReturnValue())) return;
if (pos == null) return;

ItemStack mainhand = player.getHeldItemMainhand();
ItemStack offhand = player.getHeldItemOffhand();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down