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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# v1.3.1
## Tinkers' Construct Integration
- Fixed TiC main-hand + GT off-hand combination not using the GT off-hand tool when it is the appropriate tool for the block
- Fixed duplicate durability loss on both GT and TiC cross-hand combinations

* * *

# v1.3.0
## New: Storage Drawers Integration
- GT materials can now be used to craft _Storage Upgrades_.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Enchantments;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.common.ForgeHooks;
Expand All @@ -18,13 +16,9 @@
import slimeknights.tconstruct.library.utils.ToolHelper;

/**
* Extends TiC's off-hand mining to cover GT + TiC cross-hand combinations.
*
* <ul>
* <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>
* Supplements TiC's off-hand mining for GT main + TiC off combinations.
*
* <p>
* Registered at {@link EventPriority#LOW} so TiC's own {@code BreakSpeed} handler fires first.
*/
public final class DualToolHandler {
Expand Down Expand Up @@ -53,27 +47,8 @@ public static void onBreakSpeed(net.minecraftforge.event.entity.player.PlayerEve
event.setNewSpeed(speed);
}
}

if (mainhand.getItem() instanceof TinkerToolCore && offhand.getItem() instanceof IGTTool) {
if (!ToolHelper.canHarvest(mainhand, state) && canHarvestForDrops(player, offhand, state)) {
float speed = offhand.getItem().getDestroySpeed(offhand, state);
if (speed > 1.0f) {
int efficiency = EnchantmentHelper.getEnchantmentLevel(Enchantments.EFFICIENCY, offhand);
if (efficiency > 0) speed += efficiency * efficiency + 1;
}
event.setNewSpeed(speed);
}
}
}

/**
* Damages the off-hand tool after a block is broken via off-hand mining.
* 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.
*
* Registered at {@link EventPriority#LOWEST} so other mods' cancellations are resolved first.
*/
@SubscribeEvent(priority = EventPriority.LOWEST)
public static void onBlockBreak(BlockEvent.BreakEvent event) {
if (event.isCanceled()) return;
Expand All @@ -90,16 +65,6 @@ 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)) {
// 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);
}
}

if (mainhand.getItem() instanceof TinkerToolCore && offhand.getItem() instanceof IGTTool) {
if (!ToolHelper.canHarvest(mainhand, state) && canHarvestForDrops(player, offhand, state)) {
offhand.getItem().onBlockDestroyed(offhand, player.world, state,
event.getPos(), player);
}
Expand All @@ -113,9 +78,6 @@ private static boolean canHarvestForDrops(EntityPlayer player, ItemStack stack,
if (stack.isEmpty() || toolType == null) {
return player.canHarvestBlock(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
@@ -0,0 +1,38 @@
package com.github.gtexpert.gtmt.integration.tic;

import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

import com.github.gtexpert.gtmt.integration.tic.materials.ToolMaterialRegistrar;

/**
* Client-side event handlers for the TiC integration.
*
* <p>
* Registered only on the client via {@link TiCModule#getEventBusSubscribers()}.
*/
@SideOnly(Side.CLIENT)
public class TiCClientEvents {

@SubscribeEvent
public static void onRegisterModels(ModelRegistryEvent event) {
ToolMaterialRegistrar.registerFluidModels();
}

/**
* Re-inject dynamic material name translations after every resource reload.
*
* <p>
* {@link net.minecraft.util.text.translation.LanguageMap#inject} entries are wiped
* whenever the language manager reloads (F3+T or in-game language change).
* {@link TextureStitchEvent.Post} fires after the language map has already been
* refreshed from disk, so this is the correct point to restore the dynamic entries.
*/
@SubscribeEvent
public static void onTextureStitchPost(TextureStitchEvent.Post event) {
ToolMaterialRegistrar.reinjectTranslations();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.github.gtexpert.gtmt.integration.tic;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.minecraft.block.Block;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

import org.jetbrains.annotations.NotNull;

Expand All @@ -31,7 +30,11 @@ public class TiCModule extends IntegrationSubmodule {
@NotNull
@Override
public List<Class<?>> getEventBusSubscribers() {
return Arrays.asList(TiCModule.class, DualToolHandler.class);
List<Class<?>> subscribers = new ArrayList<>(Arrays.asList(TiCModule.class, DualToolHandler.class));
if (FMLLaunchHandler.side() == Side.CLIENT) {
subscribers.add(TiCClientEvents.class);
}
return subscribers;
}

@Override
Expand All @@ -40,10 +43,4 @@ public void registerBlocks(RegistryEvent.Register<Block> event) {
ElasticMaterialRegistrar.register(event.getRegistry());
TiCSmeltery.register();
}

@SubscribeEvent
@SideOnly(Side.CLIENT)
public static void onRegisterModels(ModelRegistryEvent event) {
ToolMaterialRegistrar.registerFluidModels();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
*/
public final class TiCSmeltery {

// TiC fluid-amount constants (from slimeknights.tconstruct.library.materials.Material)
private static final int VALUE_Ingot = slimeknights.tconstruct.library.materials.Material.VALUE_Ingot; // 144
private static final int VALUE_Nugget = slimeknights.tconstruct.library.materials.Material.VALUE_Nugget; // 16
private static final int VALUE_Block = slimeknights.tconstruct.library.materials.Material.VALUE_Block; // 1296
private static final int VALUE_Ingot = slimeknights.tconstruct.library.materials.Material.VALUE_Ingot;
private static final int VALUE_Nugget = slimeknights.tconstruct.library.materials.Material.VALUE_Nugget;
private static final int VALUE_Block = slimeknights.tconstruct.library.materials.Material.VALUE_Block;

private TiCSmeltery() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import net.minecraft.block.Block;
import net.minecraft.util.text.TextFormatting;
Expand Down Expand Up @@ -48,6 +50,12 @@ public final class ToolMaterialRegistrar {

private static final List<MaterialIntegration> integrations = new ArrayList<>();

/**
* Cache of TiC lang key → GT Material, used to re-inject translations after a resource
* reload (F3+T or language change) clears the runtime LanguageMap entries.
*/
private static final Map<String, Material> translationSources = new LinkedHashMap<>();

private ToolMaterialRegistrar() {}

/**
Expand Down Expand Up @@ -83,10 +91,6 @@ public static void registerFluidModels() {
}
}

// -------------------------------------------------------------------------
// Merge path
// -------------------------------------------------------------------------

/**
* Enhance an already-registered TiC material with GT stats and traits.
* For each stat type the <b>maximum</b> value is kept; bow draw-speed uses the
Expand Down Expand Up @@ -167,10 +171,6 @@ private static void mergeMaterial(slimeknights.tconstruct.library.materials.Mate
MaterialTraitApplier.applyTraits(ticMaterial, gtMaterial, toolProp);
}

// -------------------------------------------------------------------------
// Register path
// -------------------------------------------------------------------------

private static void registerMaterial(Material gtMaterial, IForgeRegistry<Block> blockRegistry) {
String identifier = ModValues.MODID + "." + gtMaterial.getName();
ToolProperty toolProp = gtMaterial.getProperty(PropertyKey.TOOL);
Expand Down Expand Up @@ -231,10 +231,6 @@ private static void registerMaterial(Material gtMaterial, IForgeRegistry<Block>
integrations.add(integration);
}

// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------

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.
Expand All @@ -254,7 +250,25 @@ private static void registerHarvestLevelNames() {

static void injectTranslation(String ticIdentifier, Material gtMaterial) {
String key = "material." + ticIdentifier + ".name";
String entry = key + "=" + gtMaterial.getLocalizedName() + "\n";
translationSources.put(key, gtMaterial);
doInject(key, gtMaterial.getLocalizedName());
}

/**
* Re-injects all cached material name translations into the runtime LanguageMap.
*
* <p>
* Must be called after every resource reload (e.g. F3+T or language change) because
* {@link LanguageMap#inject} entries are cleared when the language manager reloads.
* Calling {@link Material#getLocalizedName()} here (rather than caching the string)
* ensures the correct translation for the currently active language is used.
*/
public static void reinjectTranslations() {
translationSources.forEach((key, mat) -> doInject(key, mat.getLocalizedName()));
}

private static void doInject(String key, String value) {
String entry = key + "=" + value + "\n";
LanguageMap.inject(new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@

import slimeknights.tconstruct.library.tools.DualToolHarvestUtils;
import slimeknights.tconstruct.library.tools.TinkerToolCore;
import slimeknights.tconstruct.library.utils.ToolHelper;

/**
* Extends TiC's {@code shouldUseOffhand} to recognise GT + TiC tool combinations
* in both directions (GT main / TiC off, TiC main / GT off).
* Bridges GT tools into TiC's {@code shouldUseOffhand} system for cross-hand combinations.
*
* <ul>
* <li>GT main + TiC off: returns {@code true} when GT cannot harvest but TiC can, so that
* {@link MixinItemGTTool} performs the hand-swap and TiC mines as main-hand.</li>
* <li>TiC main + GT off: returns {@code true} when TiC cannot harvest but GT can, activating
* TiC's {@code offhandBreakSpeed}, {@code getHarvestLevel}, and {@code onBlockDestroyed}
* delegation.</li>
* </ul>
*/
@Mixin(value = DualToolHarvestUtils.class, remap = false)
public class MixinDualToolHarvestUtils {
Expand All @@ -36,12 +44,21 @@ private static void gtOffHandSupportState(EntityLivingBase entity, IBlockState s
ItemStack offhand = player.getHeldItemOffhand();
if (tool.isEmpty() || offhand.isEmpty() || state == null) return;

boolean isCrossCombo = (tool.getItem() instanceof IGTTool && offhand.getItem() instanceof TinkerToolCore) ||
(tool.getItem() instanceof TinkerToolCore && offhand.getItem() instanceof IGTTool);
if (!isCrossCombo) return;
if (tool.getItem() instanceof TinkerToolCore && offhand.getItem() instanceof IGTTool) {
if (state.getMaterial().isToolNotRequired()) {
float gtSpeed = offhand.getItem().getDestroySpeed(offhand, state);
float ticSpeed = ToolHelper.calcDigSpeed(tool, state);
if (gtSpeed > ticSpeed) cir.setReturnValue(true);
} else if (!ToolHelper.canHarvest(tool, state) && gtmt$gtCanHarvest(offhand, state)) {
cir.setReturnValue(true);
}
return;
}

if (gtmt$shouldPreferOffhand(tool, offhand, state)) {
cir.setReturnValue(true);
if (tool.getItem() instanceof IGTTool && offhand.getItem() instanceof TinkerToolCore) {
if (gtmt$shouldPreferOffhand(tool, offhand, state)) {
cir.setReturnValue(true);
}
}
}

Expand All @@ -57,31 +74,48 @@ private static void gtOffHandSupportPos(EntityLivingBase entity, BlockPos pos,
ItemStack offhand = player.getHeldItemOffhand();
if (tool.isEmpty() || offhand.isEmpty() || state == null) return;

boolean isCrossCombo = (tool.getItem() instanceof IGTTool && offhand.getItem() instanceof TinkerToolCore) ||
(tool.getItem() instanceof TinkerToolCore && offhand.getItem() instanceof IGTTool);
if (!isCrossCombo) return;
if (tool.getItem() instanceof TinkerToolCore && offhand.getItem() instanceof IGTTool) {
if (state.getMaterial().isToolNotRequired()) {
float gtSpeed = offhand.getItem().getDestroySpeed(offhand, state);
float ticSpeed = ToolHelper.calcDigSpeed(tool, state);
if (gtSpeed > ticSpeed) cir.setReturnValue(true);
} else if (!ToolHelper.canHarvest(tool, state) && gtmt$gtCanHarvest(offhand, state)) {
cir.setReturnValue(true);
}
return;
}

if (gtmt$shouldPreferOffhand(tool, offhand, state)) {
cir.setReturnValue(true);
if (tool.getItem() instanceof IGTTool && offhand.getItem() instanceof TinkerToolCore) {
if (gtmt$shouldPreferOffhand(tool, offhand, state)) {
cir.setReturnValue(true);
}
}
}

@Unique
private static boolean gtmt$shouldPreferOffhand(ItemStack main, ItemStack offhand, IBlockState state) {
private static boolean gtmt$gtCanHarvest(ItemStack gt, IBlockState state) {
if (state.getMaterial().isToolNotRequired()) return false;
Block block = state.getBlock();
String toolType = block.getHarvestTool(state);
if (toolType == null) return false;
int level = gt.getItem().getHarvestLevel(gt, toolType, null, state);
return level >= 0 && level >= block.getHarvestLevel(state);
}
Comment thread
tier940 marked this conversation as resolved.

@Unique
private static boolean gtmt$shouldPreferOffhand(ItemStack main, ItemStack offhand, IBlockState state) {
if (state.getMaterial().isToolNotRequired()) {
return offhand.getItem().getDestroySpeed(offhand, state) > main.getItem().getDestroySpeed(main, state);
}
Block block = state.getBlock();
String toolType = block.getHarvestTool(state);
if (toolType == null) return false;
int requiredLevel = block.getHarvestLevel(state);

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
import slimeknights.tconstruct.library.utils.ToolHelper;

/**
* Extends {@code EntityPlayer.canHarvestBlock} to consider the TiC off-hand tool
* when the GT main-hand tool's {@code getHarvestLevel} returns -1 (wrong tool type)
* and {@code ForgeHooks.canHarvestBlock} falls back to this method.
* Extends {@code EntityPlayer.canHarvestBlock} to pass when the TiC off-hand can harvest
* a block that the GT main-hand cannot.
*/
@Mixin(EntityPlayer.class)
public abstract class MixinEntityPlayer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
import slimeknights.tconstruct.library.utils.ToolHelper;

/**
* 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.
* Extends {@code ForgeHooks.canHarvestBlock} to pass when the TiC off-hand can harvest
* a block that the GT main-hand cannot.
*/
@Mixin(value = ForgeHooks.class, remap = false)
public class MixinForgeHooks {
Expand Down