diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d0aaac..af2a8e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 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 + - Mining speed is taken from the off-hand tool + - Blocks drop items even when the main-hand tool has insufficient harvest level, using the off-hand tool + - HUD mods (e.g. HWYLA) now show the correct can-harvest indicator (✓/×) based on the off-hand tool + +* * * + # v1.2.2 ## Tinkers' Construct Integration - GT material fluids are now registered as TiC Smeltery melting recipes (ingot, nugget, block, dust, gem families always; ore/crushed variants behind `smelteryOreMelting` config). Raw ore doubling is disabled by default (`smelteryOreDoubling=false`) to preserve GT ore-processing progression. 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 new file mode 100644 index 0000000..7fe581c --- /dev/null +++ b/src/main/java/com/github/gtexpert/gtmt/integration/tic/DualToolHandler.java @@ -0,0 +1,118 @@ +package com.github.gtexpert.gtmt.integration.tic; + +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; +import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import gregtech.api.items.toolitem.IGTTool; + +import slimeknights.tconstruct.library.tools.TinkerToolCore; +import slimeknights.tconstruct.library.utils.ToolHelper; + +/** + * Extends TiC's off-hand mining to work with GT tools. + * + * + * + * Runs at {@link EventPriority#LOW} so TiC's own {@code BreakSpeed} handler fires first. + */ +public final class DualToolHandler { + + private DualToolHandler() {} + + @SubscribeEvent(priority = EventPriority.LOW) + public static void onBreakSpeed(net.minecraftforge.event.entity.player.PlayerEvent.BreakSpeed event) { + EntityPlayer player = event.getEntityPlayer(); + ItemStack mainhand = player.getHeldItemMainhand(); + ItemStack offhand = player.getHeldItemOffhand(); + + if (offhand.isEmpty()) return; + + IBlockState state = event.getState(); + + if (mainhand.getItem() instanceof IGTTool && offhand.getItem() instanceof TinkerToolCore) { + if (!canHarvestForDrops(player, mainhand, state) && ToolHelper.canHarvest(offhand, state)) { + float speed = ToolHelper.calcDigSpeed(offhand, state); + BlockPos pos = event.getPos(); + if (pos == null || !ForgeHooks.canHarvestBlock(state.getBlock(), player, player.world, pos)) { + speed *= (100.0f / 30.0f); + } + 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. + * 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. + * + * Runs at {@link EventPriority#LOWEST} so cancellations by other mods are resolved first. + */ + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onBlockBreak(BlockEvent.BreakEvent event) { + if (event.isCanceled()) return; + + EntityPlayer player = event.getPlayer(); + if (player == null || player.world.isRemote) return; + + ItemStack mainhand = player.getHeldItemMainhand(); + ItemStack offhand = player.getHeldItemOffhand(); + + if (offhand.isEmpty()) return; + + IBlockState state = event.getState(); + + 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); + } + 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); + } + } + } + + private static boolean canHarvestForDrops(EntityPlayer player, ItemStack stack, IBlockState state) { + if (state.getMaterial().isToolNotRequired()) return true; + Block block = state.getBlock(); + String toolType = block.getHarvestTool(state); + if (stack.isEmpty() || toolType == null) { + return player.canHarvestBlock(state); + } + int level = stack.getItem().getHarvestLevel(stack, toolType, player, 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/TiCModule.java b/src/main/java/com/github/gtexpert/gtmt/integration/tic/TiCModule.java index eef7660..317b52c 100644 --- a/src/main/java/com/github/gtexpert/gtmt/integration/tic/TiCModule.java +++ b/src/main/java/com/github/gtexpert/gtmt/integration/tic/TiCModule.java @@ -1,6 +1,6 @@ package com.github.gtexpert.gtmt.integration.tic; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import net.minecraft.block.Block; @@ -16,6 +16,8 @@ import com.github.gtexpert.gtmt.api.modules.TModule; import com.github.gtexpert.gtmt.api.util.Mods; import com.github.gtexpert.gtmt.integration.IntegrationSubmodule; +import com.github.gtexpert.gtmt.integration.tic.materials.ElasticMaterialRegistrar; +import com.github.gtexpert.gtmt.integration.tic.materials.ToolMaterialRegistrar; import com.github.gtexpert.gtmt.modules.Modules; @TModule( @@ -29,18 +31,19 @@ public class TiCModule extends IntegrationSubmodule { @NotNull @Override public List> getEventBusSubscribers() { - return Collections.singletonList(TiCModule.class); + return Arrays.asList(TiCModule.class, DualToolHandler.class); } @Override public void registerBlocks(RegistryEvent.Register event) { - TiCMaterials.register(event.getRegistry()); + ToolMaterialRegistrar.register(event.getRegistry()); + ElasticMaterialRegistrar.register(event.getRegistry()); TiCSmeltery.register(); } @SubscribeEvent @SideOnly(Side.CLIENT) public static void onRegisterModels(ModelRegistryEvent event) { - TiCMaterials.registerFluidModels(); + ToolMaterialRegistrar.registerFluidModels(); } } diff --git a/src/main/java/com/github/gtexpert/gtmt/integration/tic/api/ElasticMaterials.java b/src/main/java/com/github/gtexpert/gtmt/integration/tic/api/ElasticMaterials.java new file mode 100644 index 0000000..3088e46 --- /dev/null +++ b/src/main/java/com/github/gtexpert/gtmt/integration/tic/api/ElasticMaterials.java @@ -0,0 +1,137 @@ +package com.github.gtexpert.gtmt.integration.tic.api; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import gregtech.api.unification.material.Material; +import gregtech.api.unification.material.Materials; + +/** + * Registry for TiC BowString and Fletching material stats derived from GT polymer materials. + * + *

+ * GTMT automatically registers every GT material that carries the {@code POLYMER} property + * as a TiC BowString and Fletching material. Registrations made here take priority over + * GTMT's auto-assigned default stats (modifier = 1.0 / accuracy = 1.0). + * + *

+ * All registrations must occur before GTMT's {@code registerBlocks} phase + * (your mod's {@code preInit} or {@code init}). + */ +public final class ElasticMaterials { + + // ------------------------------------------------------------------------- + // Entry type + // ------------------------------------------------------------------------- + + /** BowString and Fletching stats for a single GT material. */ + public static final class Entry { + + private final float stringModifier; + private final float fletchingAccuracy; + private final float fletchingModifier; + + public Entry(float stringModifier, float fletchingAccuracy, float fletchingModifier) { + this.stringModifier = stringModifier; + this.fletchingAccuracy = fletchingAccuracy; + this.fletchingModifier = fletchingModifier; + } + + /** BowString draw-speed modifier (1.0 = neutral). */ + public float stringModifier() { + return stringModifier; + } + + /** Fletching accuracy (1.0 = perfect, lower = spread). */ + public float fletchingAccuracy() { + return fletchingAccuracy; + } + + /** Fletching damage modifier (1.0 = neutral). */ + public float fletchingModifier() { + return fletchingModifier; + } + } + + // ------------------------------------------------------------------------- + // Registry + // ------------------------------------------------------------------------- + + private static final Map ENTRIES = new LinkedHashMap<>(); + + static { + // Natural rubber — slimeball-tier baseline + registerIfAbsent(Materials.Rubber, 1.0f, 1.0f, 1.0f); + // Vulcanised silicone — more durable + registerIfAbsent(Materials.SiliconeRubber, 1.05f, 1.0f, 1.05f); + // SBR — best synthetic rubber + registerIfAbsent(Materials.StyreneButadieneRubber, 1.1f, 1.0f, 1.1f); + // Polyethylene — stiffer plastic, lower tier + registerIfAbsent(Materials.Polyethylene, 0.8f, 0.9f, 0.8f); + } + + /** + * Returns an unmodifiable view of the elastic-material registry. + * Used internally by {@code TiCMaterials} when registering BowString/Fletching materials. + */ + public static Map getEntries() { + return Collections.unmodifiableMap(ENTRIES); + } + + /** + * Registers BowString and Fletching stats for a GT polymer material. + * + *

+ * Call this before GTMT's {@code registerBlocks} phase to override the stats + * GTMT would otherwise auto-assign. + * + *

+ * Example — register a custom high-grade polymer: + * + *

+     * {@code
+     * ElasticMaterials.register(MyMaterials.KEVLAR, 1.3f, 1.0f, 1.2f);
+     * }
+     * 
+ * + * @param material GT material (must carry the {@code POLYMER} property, + * or be a recognised elastic material) + * @param stringModifier BowString draw-speed modifier (must be > 0) + * @param fletchingAccuracy Fletching accuracy (must be > 0 and ≤ 1) + * @param fletchingModifier Fletching damage modifier (must be > 0) + * @throws IllegalArgumentException if material is null or any stat is out of range + */ + public static void register(Material material, + float stringModifier, + float fletchingAccuracy, + float fletchingModifier) { + validate(material, stringModifier, fletchingAccuracy, fletchingModifier); + ENTRIES.put(material, new Entry(stringModifier, fletchingAccuracy, fletchingModifier)); + } + + /** + * Fills stats only if the material has not already been registered externally. + * Called internally by the static initializer and by {@code TiCMaterials} for + * auto-detected POLYMER materials. + */ + public static void registerIfAbsent(Material material, + float stringModifier, + float fletchingAccuracy, + float fletchingModifier) { + validate(material, stringModifier, fletchingAccuracy, fletchingModifier); + ENTRIES.putIfAbsent(material, new Entry(stringModifier, fletchingAccuracy, fletchingModifier)); + } + + private static void validate(Material material, + float stringModifier, + float fletchingAccuracy, + float fletchingModifier) { + if (material == null) throw new IllegalArgumentException("material must not be null"); + if (stringModifier <= 0) throw new IllegalArgumentException("stringModifier must be > 0"); + if (fletchingAccuracy <= 0) throw new IllegalArgumentException("fletchingAccuracy must be > 0"); + if (fletchingModifier <= 0) throw new IllegalArgumentException("fletchingModifier must be > 0"); + } + + private ElasticMaterials() {} +} diff --git a/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ElasticMaterialRegistrar.java b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ElasticMaterialRegistrar.java new file mode 100644 index 0000000..4715195 --- /dev/null +++ b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ElasticMaterialRegistrar.java @@ -0,0 +1,102 @@ +package com.github.gtexpert.gtmt.integration.tic.materials; + +import net.minecraft.block.Block; +import net.minecraftforge.registries.IForgeRegistry; + +import gregtech.api.GregTechAPI; +import gregtech.api.unification.OreDictUnifier; +import gregtech.api.unification.material.Material; +import gregtech.api.unification.material.properties.PropertyKey; +import gregtech.api.unification.ore.OrePrefix; + +import com.github.gtexpert.gtmt.api.ModValues; +import com.github.gtexpert.gtmt.integration.tic.api.ElasticMaterials; + +import slimeknights.tconstruct.library.MaterialIntegration; +import slimeknights.tconstruct.library.TinkerRegistry; +import slimeknights.tconstruct.library.materials.BowStringMaterialStats; +import slimeknights.tconstruct.library.materials.FletchingMaterialStats; + +/** + * Registers GT polymer materials as TiC BowString and Fletching part materials. + * + *

+ * Every GT material carrying the {@code POLYMER} property is registered automatically. + * Stats are taken from {@link ElasticMaterials}: external mods may call + * {@link ElasticMaterials#register} before this phase to override them, or to add + * custom polymer materials that GTMT does not know about. + * Materials not explicitly registered receive neutral defaults (modifier = 1.0 / accuracy = 1.0). + */ +public final class ElasticMaterialRegistrar { + + private ElasticMaterialRegistrar() {} + + /** + * Entry point called during {@code registerBlocks}. + * Must be called after {@link ToolMaterialRegistrar#register} so the shared + * {@code integrations} list and translation helper are already initialised. + */ + public static void register(IForgeRegistry blockRegistry) { + for (Material gtMaterial : GregTechAPI.materialManager.getRegisteredMaterials()) { + if (!gtMaterial.hasProperty(PropertyKey.POLYMER)) continue; + + ElasticMaterials.Entry entry = ElasticMaterials.getEntries().get(gtMaterial); + float strMod, fletchAcc, fletchMod; + if (entry != null) { + strMod = entry.stringModifier(); + fletchAcc = entry.fletchingAccuracy(); + fletchMod = entry.fletchingModifier(); + } else { + strMod = 1.0f; + fletchAcc = 1.0f; + fletchMod = 1.0f; + } + registerElastic(gtMaterial, strMod, fletchAcc, fletchMod, blockRegistry); + } + } + + private static void registerElastic(Material gtMaterial, + float stringMod, + float fletchAcc, float fletchMod, + IForgeRegistry blockRegistry) { + String identifier = ModValues.MODID + "." + gtMaterial.getName(); + slimeknights.tconstruct.library.materials.Material ticMaterial = new slimeknights.tconstruct.library.materials.Material( + identifier, gtMaterial.getMaterialRGB(), true); + + ToolMaterialRegistrar.injectTranslation(identifier, gtMaterial); + + TinkerRegistry.addMaterialStats(ticMaterial, new BowStringMaterialStats(stringMod)); + TinkerRegistry.addMaterialStats(ticMaterial, + new FletchingMaterialStats(fletchAcc, fletchMod)); + + // Register whichever GT item forms exist for this material + String suffix = gtMaterial.toCamelCaseString(); + for (OrePrefix prefix : new OrePrefix[] { OrePrefix.plate, OrePrefix.foil, OrePrefix.ring }) { + if (!OreDictUnifier.get(prefix, gtMaterial).isEmpty()) { + ticMaterial.addItem(prefix.name + suffix, 1, + slimeknights.tconstruct.library.materials.Material.VALUE_Ingot); + } + } + + var fluid = ToolMaterialRegistrar.getFluid(gtMaterial); + MaterialIntegration integration; + if (fluid != null) { + integration = new MaterialIntegration(ticMaterial, fluid, suffix); + } else { + integration = new MaterialIntegration(ticMaterial); + for (OrePrefix prefix : new OrePrefix[] { OrePrefix.plate, OrePrefix.foil }) { + if (!OreDictUnifier.get(prefix, gtMaterial).isEmpty()) { + String rep = prefix.name + suffix; + integration = new MaterialIntegration(rep, ticMaterial, null, null); + integration.setRepresentativeItem(rep); + break; + } + } + } + + TinkerRegistry.integrate(integration); + integration.preInit(); + integration.registerFluidBlock(blockRegistry); + ToolMaterialRegistrar.getIntegrations().add(integration); + } +} diff --git a/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/MaterialStatCalc.java b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/MaterialStatCalc.java new file mode 100644 index 0000000..b1023c9 --- /dev/null +++ b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/MaterialStatCalc.java @@ -0,0 +1,64 @@ +package com.github.gtexpert.gtmt.integration.tic.materials; + +import gregtech.api.unification.material.properties.ToolProperty; + +import slimeknights.tconstruct.library.materials.ArrowShaftMaterialStats; +import slimeknights.tconstruct.library.materials.BowMaterialStats; + +/** + * Pure stat-calculation helpers for converting GT tool properties + * into TiC material stat values. + */ +final class MaterialStatCalc { + + private MaterialStatCalc() {} + + /** + * Maps GT harvest level to TiC harvest level. + * + *

+ * Passes through the GT level directly so that TiC tools made from GT + * materials have the same mining capability as their GT counterparts. + * (e.g. GT Titanium can mine obsidian → TiC Titanium can mine obsidian) + */ + static int mapHarvestLevel(int gtLevel) { + return gtLevel; + } + + static float calcHandleModifier(ToolProperty toolProp) { + float modifier = 0.5f + (toolProp.getToolDurability() / 2000.0f); + return Math.max(0.1f, Math.min(modifier, 2.0f)); + } + + static int calcHandleDurability(ToolProperty toolProp) { + return (int) (toolProp.getToolDurability() * 0.1f); + } + + static int calcExtraDurability(int durability) { + return (int) (durability * 0.15f); + } + + /** + * Calculates arrow shaft stats from GT tool properties. + * Reference (TiC native): wood modifier=1.0/bonus=0.0, prismarine modifier=1.5/bonus=0.5. + */ + static ArrowShaftMaterialStats calcShaftStats(ToolProperty toolProp) { + float attack = toolProp.getToolAttackDamage(); + float modifier = Math.max(0.5f, Math.min(3.0f, 0.8f + attack * 0.04f)); + int bonusAmmo = Math.min(10, (int) (attack / 5f)); + return new ArrowShaftMaterialStats(modifier, bonusAmmo); + } + + /** + * Calculates bow stats from GT tool properties. + * Reference (TiC native): iron drawspeed=0.5, range=1.5, bonusDamage=7. + */ + static BowMaterialStats calcBowStats(ToolProperty toolProp) { + float speed = toolProp.getToolSpeed(); + float attack = toolProp.getToolAttackDamage(); + float drawspeed = Math.max(0.2f, Math.min(1.5f, 1.0f / (1.0f + speed * 0.1f))); + float range = Math.max(0.4f, Math.min(3.0f, 0.5f + speed * 0.15f)); + float bonusDamage = Math.max(0f, Math.min(15f, attack * 1.2f)); + return new BowMaterialStats(drawspeed, range, bonusDamage); + } +} diff --git a/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/MaterialTraitApplier.java b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/MaterialTraitApplier.java new file mode 100644 index 0000000..287e25b --- /dev/null +++ b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/MaterialTraitApplier.java @@ -0,0 +1,65 @@ +package com.github.gtexpert.gtmt.integration.tic.materials; + +import gregtech.api.unification.material.Material; +import gregtech.api.unification.material.properties.ToolProperty; +import gregtech.api.unification.stack.MaterialStack; + +import com.github.gtexpert.gtmt.integration.tic.api.TraitRegistry; + +/** + * Applies TiC traits to a material using the {@link TraitRegistry} API. + * + *

+ * Handles three sources of traits: + *

    + *
  1. Composition-based traits (e.g. contains Silver → Holy)
  2. + *
  3. Property-based traits (e.g. blast temperature ≥ 2500 → HeatResistant)
  4. + *
  5. Enchantment traits (dynamically derived from the GT tool property)
  6. + *
+ */ +final class MaterialTraitApplier { + + private MaterialTraitApplier() {} + + /** + * Assigns TiC traits from the {@link TraitRegistry} registries, then handles + * per-enchantment traits from the GT tool property. + */ + static void applyTraits(slimeknights.tconstruct.library.materials.Material ticMaterial, + Material gtMaterial, ToolProperty toolProp) { + // Composition-based traits (e.g. contains Silver → Holy, contains Gold → Moonlit) + TraitRegistry.getCompositionTraits().forEach((component, entries) -> { + if (containsMaterial(gtMaterial, component)) { + for (TraitRegistry.TraitEntry entry : entries) { + if (entry.slot() != null) ticMaterial.addTrait(entry.trait(), entry.slot()); + else ticMaterial.addTrait(entry.trait()); + } + } + }); + + // Property-based traits (e.g. blast temp ≥ 2500 → HeatResistant) + for (TraitRegistry.PropertyEntry entry : TraitRegistry.getPropertyTraits()) { + if (entry.condition().test(gtMaterial, toolProp)) { + if (entry.slot() != null) ticMaterial.addTrait(entry.trait(), entry.slot()); + else ticMaterial.addTrait(entry.trait()); + } + } + + // Enchantment traits — dynamic per-enchantment, not suitable for the static registry + toolProp.getEnchantments().forEach((enchantment, enchLevel) -> { + int level = enchLevel.getLevel(toolProp.getToolHarvestLevel()); + if (level > 0) { + ticMaterial.addTrait( + TraitRegistry.getOrCreateEnchantmentTrait(enchantment, level), "head"); + } + }); + } + + /** Check whether a material's direct composition contains the given target material. */ + private static boolean containsMaterial(Material material, Material target) { + for (MaterialStack stack : material.getMaterialComponents()) { + if (stack.material == target) return true; + } + return false; + } +} diff --git a/src/main/java/com/github/gtexpert/gtmt/integration/tic/TiCMaterials.java b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ToolMaterialRegistrar.java similarity index 51% rename from src/main/java/com/github/gtexpert/gtmt/integration/tic/TiCMaterials.java rename to src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ToolMaterialRegistrar.java index 581a546..f843956 100644 --- a/src/main/java/com/github/gtexpert/gtmt/integration/tic/TiCMaterials.java +++ b/src/main/java/com/github/gtexpert/gtmt/integration/tic/materials/ToolMaterialRegistrar.java @@ -1,4 +1,4 @@ -package com.github.gtexpert.gtmt.integration.tic; +package com.github.gtexpert.gtmt.integration.tic.materials; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; @@ -16,23 +16,28 @@ import gregtech.api.unification.material.Material; import gregtech.api.unification.material.properties.PropertyKey; import gregtech.api.unification.material.properties.ToolProperty; -import gregtech.api.unification.stack.MaterialStack; import com.github.gtexpert.gtmt.api.ModValues; import com.github.gtexpert.gtmt.integration.tic.api.HarvestLevels; -import com.github.gtexpert.gtmt.integration.tic.api.TraitRegistry; import slimeknights.tconstruct.library.MaterialIntegration; import slimeknights.tconstruct.library.TinkerRegistry; -import slimeknights.tconstruct.library.materials.ArrowShaftMaterialStats; -import slimeknights.tconstruct.library.materials.BowMaterialStats; import slimeknights.tconstruct.library.materials.ExtraMaterialStats; import slimeknights.tconstruct.library.materials.HandleMaterialStats; import slimeknights.tconstruct.library.materials.HeadMaterialStats; -public final class TiCMaterials { - - private static final List integrations = new ArrayList<>(); +/** + * Registers GT tool materials as TiC materials and manages the fluid-block integration list. + * + *

+ * For each GT material that carries both {@code TOOL} and {@code INGOT}/{@code GEM}: + *

    + *
  • If TiC already knows a material with the same name → merge: take the max of + * each stat and add GT traits to the existing material.
  • + *
  • Otherwise → register: create a new TiC material from scratch.
  • + *
+ */ +public final class ToolMaterialRegistrar { /** TiC harvest level colors for levels beyond Cobalt (4). */ private static final TextFormatting[] EXTRA_LEVEL_COLORS = { @@ -41,24 +46,19 @@ public final class TiCMaterials { TextFormatting.WHITE, // level 7+ }; - private TiCMaterials() {} + private static final List integrations = new ArrayList<>(); + + private ToolMaterialRegistrar() {} /** - * Called during registerBlocks when GT materials are available. + * Entry point called during {@code registerBlocks} when GT materials are available. * Also registers fluid blocks into the block registry. - * - *

- * For each GT tool material: - *

    - *
  • If TiC already has a material with the same name → merge: take max stats - * and add GT enchantments/traits to the existing material.
  • - *
  • Otherwise → register: create a new TiC material.
  • - *
*/ public static void register(IForgeRegistry blockRegistry) { for (Material gtMaterial : GregTechAPI.materialManager.getRegisteredMaterials()) { if (!gtMaterial.hasProperty(PropertyKey.TOOL)) continue; if (!gtMaterial.hasProperty(PropertyKey.INGOT) && !gtMaterial.hasProperty(PropertyKey.GEM)) continue; + slimeknights.tconstruct.library.materials.Material existing = TinkerRegistry .getMaterial(gtMaterial.getName()); @@ -74,7 +74,8 @@ public static void register(IForgeRegistry blockRegistry) { } /** - * Called during ModelRegistryEvent to register fluid block models. + * Called during {@code ModelRegistryEvent} to register fluid block models. + * Must be called after {@link #register}. */ public static void registerFluidModels() { for (MaterialIntegration integration : integrations) { @@ -83,13 +84,13 @@ public static void registerFluidModels() { } // ------------------------------------------------------------------------- - // Merge path — GT material matches an existing TiC native material + // Merge path // ------------------------------------------------------------------------- /** * Enhance an already-registered TiC material with GT stats and traits. - * For each stat type, the maximum of the TiC and GT values is kept. - * Bow drawspeed uses the minimum (faster draw = better). + * For each stat type the maximum value is kept; bow draw-speed uses the + * minimum (lower = faster draw). */ private static void mergeMaterial(slimeknights.tconstruct.library.materials.Material ticMaterial, Material gtMaterial) { @@ -97,7 +98,7 @@ private static void mergeMaterial(slimeknights.tconstruct.library.materials.Mate // Head stats HeadMaterialStats existingHead = (HeadMaterialStats) ticMaterial.getStats("head"); - int ticHL = mapHarvestLevel(toolProp.getToolHarvestLevel()); + int ticHL = MaterialStatCalc.mapHarvestLevel(toolProp.getToolHarvestLevel()); if (existingHead != null) { TinkerRegistry.addMaterialStats(ticMaterial, new HeadMaterialStats( Math.max(existingHead.durability, toolProp.getToolDurability()), @@ -112,8 +113,8 @@ private static void mergeMaterial(slimeknights.tconstruct.library.materials.Mate // Handle stats HandleMaterialStats existingHandle = (HandleMaterialStats) ticMaterial.getStats("handle"); - float gtHandleMod = calcHandleModifier(toolProp); - int gtHandleDur = calcHandleDurability(toolProp); + float gtHandleMod = MaterialStatCalc.calcHandleModifier(toolProp); + int gtHandleDur = MaterialStatCalc.calcHandleDurability(toolProp); if (existingHandle != null) { TinkerRegistry.addMaterialStats(ticMaterial, new HandleMaterialStats( Math.max(existingHandle.modifier, gtHandleMod), @@ -125,7 +126,7 @@ private static void mergeMaterial(slimeknights.tconstruct.library.materials.Mate // Extra stats ExtraMaterialStats existingExtra = (ExtraMaterialStats) ticMaterial.getStats("extra"); - int gtExtra = calcExtraDurability(toolProp.getToolDurability()); + int gtExtra = MaterialStatCalc.calcExtraDurability(toolProp.getToolDurability()); if (existingExtra != null) { TinkerRegistry.addMaterialStats(ticMaterial, new ExtraMaterialStats(Math.max(existingExtra.extraDurability, gtExtra))); @@ -134,75 +135,74 @@ private static void mergeMaterial(slimeknights.tconstruct.library.materials.Mate } // Bow stats — lower drawspeed is faster (better) - BowMaterialStats existingBow = (BowMaterialStats) ticMaterial.getStats("bow"); - BowMaterialStats gtBow = calcBowStats(toolProp); + var existingBow = (slimeknights.tconstruct.library.materials.BowMaterialStats) ticMaterial.getStats("bow"); + var gtBow = MaterialStatCalc.calcBowStats(toolProp); if (existingBow != null) { - TinkerRegistry.addMaterialStats(ticMaterial, new BowMaterialStats( - Math.min(existingBow.drawspeed, gtBow.drawspeed), - Math.max(existingBow.range, gtBow.range), - Math.max(existingBow.bonusDamage, gtBow.bonusDamage))); + TinkerRegistry.addMaterialStats(ticMaterial, + new slimeknights.tconstruct.library.materials.BowMaterialStats( + Math.min(existingBow.drawspeed, gtBow.drawspeed), + Math.max(existingBow.range, gtBow.range), + Math.max(existingBow.bonusDamage, gtBow.bonusDamage))); } else { TinkerRegistry.addMaterialStats(ticMaterial, gtBow); } - // Arrow shaft stats — GT bolt as shaft material - ArrowShaftMaterialStats existingShaft = (ArrowShaftMaterialStats) ticMaterial.getStats("shaft"); - ArrowShaftMaterialStats gtShaft = calcShaftStats(toolProp); + // Arrow shaft stats + var existingShaft = (slimeknights.tconstruct.library.materials.ArrowShaftMaterialStats) ticMaterial + .getStats("shaft"); + var gtShaft = MaterialStatCalc.calcShaftStats(toolProp); if (existingShaft != null) { - TinkerRegistry.addMaterialStats(ticMaterial, new ArrowShaftMaterialStats( - Math.max(existingShaft.modifier, gtShaft.modifier), - Math.max(existingShaft.bonusAmmo, gtShaft.bonusAmmo))); + TinkerRegistry.addMaterialStats(ticMaterial, + new slimeknights.tconstruct.library.materials.ArrowShaftMaterialStats( + Math.max(existingShaft.modifier, gtShaft.modifier), + Math.max(existingShaft.bonusAmmo, gtShaft.bonusAmmo))); } else { TinkerRegistry.addMaterialStats(ticMaterial, gtShaft); } - // Harvest level name tracking if (ticHL > 4) { HarvestLevels.registerIfAbsent(ticHL, gtMaterial.getLocalizedName()); } - // Apply GT traits/enchantments on top of the existing TiC traits - applyTraits(ticMaterial, gtMaterial, toolProp); + MaterialTraitApplier.applyTraits(ticMaterial, gtMaterial, toolProp); } // ------------------------------------------------------------------------- - // Register path — new material not already in TiC + // Register path // ------------------------------------------------------------------------- private static void registerMaterial(Material gtMaterial, IForgeRegistry blockRegistry) { String identifier = ModValues.MODID + "." + gtMaterial.getName(); - int color = gtMaterial.getMaterialRGB(); ToolProperty toolProp = gtMaterial.getProperty(PropertyKey.TOOL); slimeknights.tconstruct.library.materials.Material ticMaterial = new slimeknights.tconstruct.library.materials.Material( - identifier, color, true); + identifier, gtMaterial.getMaterialRGB(), true); injectTranslation(identifier, gtMaterial); int durability = toolProp.getToolDurability(); - float speed = toolProp.getToolSpeed(); - float attack = toolProp.getToolAttackDamage(); - int ticHarvestLevel = mapHarvestLevel(toolProp.getToolHarvestLevel()); + int ticHarvestLevel = MaterialStatCalc.mapHarvestLevel(toolProp.getToolHarvestLevel()); if (ticHarvestLevel > 4) { HarvestLevels.registerIfAbsent(ticHarvestLevel, gtMaterial.getLocalizedName()); } TinkerRegistry.addMaterialStats(ticMaterial, - new HeadMaterialStats(durability, speed, attack, ticHarvestLevel), - new HandleMaterialStats(calcHandleModifier(toolProp), calcHandleDurability(toolProp)), - new ExtraMaterialStats(calcExtraDurability(durability)), - calcBowStats(toolProp), - calcShaftStats(toolProp)); + new HeadMaterialStats(durability, toolProp.getToolSpeed(), + toolProp.getToolAttackDamage(), ticHarvestLevel), + new HandleMaterialStats(MaterialStatCalc.calcHandleModifier(toolProp), + MaterialStatCalc.calcHandleDurability(toolProp)), + new ExtraMaterialStats(MaterialStatCalc.calcExtraDurability(durability)), + MaterialStatCalc.calcBowStats(toolProp), + MaterialStatCalc.calcShaftStats(toolProp)); - applyTraits(ticMaterial, gtMaterial, toolProp); + MaterialTraitApplier.applyTraits(ticMaterial, gtMaterial, toolProp); String oreSuffix = gtMaterial.toCamelCaseString(); Fluid fluid = getFluid(gtMaterial); if (gtMaterial.hasProperty(PropertyKey.INGOT) && oreSuffix != null) { ticMaterial.addCommonItems(oreSuffix); - // GT bolt = 1/4 ingot → matches TiC arrow shaft part cost ticMaterial.addItem("bolt" + oreSuffix, 1, slimeknights.tconstruct.library.materials.Material.VALUE_Ingot / 4); } else if (gtMaterial.hasProperty(PropertyKey.GEM) && oreSuffix != null) { @@ -232,129 +232,39 @@ private static void registerMaterial(Material gtMaterial, IForgeRegistry } // ------------------------------------------------------------------------- - // Shared trait / property logic + // Helpers // ------------------------------------------------------------------------- - /** - * Assigns TiC traits from the two {@link TraitRegistry} registries, then handles - * per-enchantment traits from the GT tool property. - * - *

- * All default rules (Holy, HeatResistant, Cryogenic, AntiCorrosion, HeavyBlow, - * Piercer, Magnetic, Unbreakable, Moonlit) are registered in {@link TraitRegistry}'s - * static initialiser and are extensible via - * {@link TraitRegistry#registerCompositionTrait} and - * {@link TraitRegistry#registerPropertyTrait}. - */ - private static void applyTraits(slimeknights.tconstruct.library.materials.Material ticMaterial, - Material gtMaterial, ToolProperty toolProp) { - // Composition-based traits (e.g. contains Silver → Holy, contains Gold → Moonlit) - TraitRegistry.getCompositionTraits().forEach((component, entries) -> { - if (containsMaterial(gtMaterial, component)) { - for (TraitRegistry.TraitEntry entry : entries) { - if (entry.slot() != null) ticMaterial.addTrait(entry.trait(), entry.slot()); - else ticMaterial.addTrait(entry.trait()); - } - } - }); - - // Property-based traits (e.g. blast temp ≥ 2500 → HeatResistant) - for (TraitRegistry.PropertyEntry entry : TraitRegistry.getPropertyTraits()) { - if (entry.condition().test(gtMaterial, toolProp)) { - if (entry.slot() != null) ticMaterial.addTrait(entry.trait(), entry.slot()); - else ticMaterial.addTrait(entry.trait()); - } - } - - // Enchantment traits — dynamic per-enchantment, not suitable for the static registry - toolProp.getEnchantments().forEach((enchantment, enchLevel) -> { - int level = enchLevel.getLevel(toolProp.getToolHarvestLevel()); - if (level > 0) { - ticMaterial.addTrait(TraitRegistry.getOrCreateEnchantmentTrait(enchantment, level), "head"); - } - }); - } - - /** Apply all registered harvest-level names (GTMT auto-assigned + external) to TiC. */ 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(3, TextFormatting.AQUA + "Diamond"); + // Level 4 (Cobalt) — no vanilla equivalent, keep TiC's name + + // Register GT-specific levels above Cobalt (4) HarvestLevels.getNames().forEach((level, name) -> { int colorIndex = Math.min(level - 5, EXTRA_LEVEL_COLORS.length - 1); - slimeknights.tconstruct.library.utils.HarvestLevels.harvestLevelNames.put(level, - EXTRA_LEVEL_COLORS[colorIndex] + name); + ticNames.put(level, EXTRA_LEVEL_COLORS[colorIndex] + name); }); } - /** Check whether a material's direct composition contains the given target material. */ - private static boolean containsMaterial(Material material, Material target) { - for (MaterialStack stack : material.getMaterialComponents()) { - if (stack.material == target) return true; - } - return false; - } - - private static void injectTranslation(String ticIdentifier, Material gtMaterial) { + static void injectTranslation(String ticIdentifier, Material gtMaterial) { String key = "material." + ticIdentifier + ".name"; String entry = key + "=" + gtMaterial.getLocalizedName() + "\n"; LanguageMap.inject(new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8))); } - /** - * Maps GT harvest level to TiC harvest level. - * GT 5 → TiC 4 (Cobalt), GT 6 → TiC 5 (new), GT 7 → TiC 6 (new), … - */ - private static int mapHarvestLevel(int gtLevel) { - return switch (gtLevel) { - case 0, 1 -> 0; - case 2 -> 1; - case 3 -> 2; - case 4 -> 3; - default -> gtLevel - 1; - }; - } - - private static float calcHandleModifier(ToolProperty toolProp) { - float modifier = 0.5f + (toolProp.getToolDurability() / 2000.0f); - return Math.max(0.1f, Math.min(modifier, 2.0f)); - } - - private static int calcHandleDurability(ToolProperty toolProp) { - return (int) (toolProp.getToolDurability() * 0.1f); - } - - private static int calcExtraDurability(int durability) { - return (int) (durability * 0.15f); - } - - /** - * Calculates arrow shaft stats from GT tool properties. - * Reference (TiC native): wood modifier=1.0/bonus=0.0, prismarine modifier=1.5/bonus=0.5. - * GT metals are generally better shafts: modifier scales with attack, bonus damage also scales. - */ - private static ArrowShaftMaterialStats calcShaftStats(ToolProperty toolProp) { - float attack = toolProp.getToolAttackDamage(); - // modifier: 0.8 base + attack contribution, clamped [0.5, 3.0] - float modifier = Math.max(0.5f, Math.min(3.0f, 0.8f + attack * 0.04f)); - // bonusAmmo: bonus arrows per shot, scales with attack, clamped [0, 10] - int bonusAmmo = Math.min(10, (int) (attack / 5f)); - return new ArrowShaftMaterialStats(modifier, bonusAmmo); - } - - /** - * Calculates bow stats from GT tool properties. - * Reference (TiC native): iron drawspeed=0.5, range=1.5, bonusDamage=7. - */ - private static BowMaterialStats calcBowStats(ToolProperty toolProp) { - float speed = toolProp.getToolSpeed(); - float attack = toolProp.getToolAttackDamage(); - float drawspeed = Math.max(0.2f, Math.min(1.5f, 1.0f / (1.0f + speed * 0.1f))); - float range = Math.max(0.4f, Math.min(3.0f, 0.5f + speed * 0.15f)); - float bonusDamage = Math.max(0f, Math.min(15f, attack * 1.2f)); - return new BowMaterialStats(drawspeed, range, bonusDamage); - } - - private static Fluid getFluid(Material material) { + static Fluid getFluid(Material material) { if (!material.hasProperty(PropertyKey.FLUID)) return null; Fluid fluid = material.getFluid(); return (fluid != null && FluidRegistry.isFluidRegistered(fluid)) ? fluid : null; } + + static List getIntegrations() { + return integrations; + } } diff --git a/src/main/java/com/github/gtexpert/gtmt/mixins/ModMixinLoader.java b/src/main/java/com/github/gtexpert/gtmt/mixins/ModMixinLoader.java index 3b0350b..1891b0e 100644 --- a/src/main/java/com/github/gtexpert/gtmt/mixins/ModMixinLoader.java +++ b/src/main/java/com/github/gtexpert/gtmt/mixins/ModMixinLoader.java @@ -21,6 +21,7 @@ public class ModMixinLoader implements ILateMixinLoader { public static final Map modMixinsConfig = new ImmutableMap.Builder() .put(Mods.Names.BETTER_BUILDERS_WANDS, true) .put(Mods.Names.CHISEL, true) + .put(Mods.Names.TINKERS_CONSTRUCT, true) .build(); @SuppressWarnings("SimplifyStreamApiCallChains") 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 new file mode 100644 index 0000000..24ef634 --- /dev/null +++ b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinDualToolHarvestUtils.java @@ -0,0 +1,83 @@ +package com.github.gtexpert.gtmt.mixins.tic; + +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; + +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 gregtech.api.items.toolitem.IGTTool; + +import slimeknights.tconstruct.library.tools.DualToolHarvestUtils; +import slimeknights.tconstruct.library.tools.TinkerToolCore; + +/** + * Extends TiC's {@code shouldUseOffhand} to recognise GT + TiC tool combinations + * in both directions (GT main / TiC off, TiC main / GT off). + */ +@Mixin(value = DualToolHarvestUtils.class, remap = false) +public class MixinDualToolHarvestUtils { + + @Inject(method = "shouldUseOffhand(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/block/state/IBlockState;Lnet/minecraft/item/ItemStack;)Z", + at = @At("HEAD"), + cancellable = true) + private static void gtOffHandSupportState(EntityLivingBase entity, IBlockState state, + ItemStack tool, + CallbackInfoReturnable cir) { + if (!(entity instanceof EntityPlayer)) return; + EntityPlayer player = (EntityPlayer) entity; + 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 (!gtmt$canHarvest(tool, state, player) && gtmt$canHarvest(offhand, state, player)) { + cir.setReturnValue(true); + } + } + + @Inject(method = "shouldUseOffhand(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/item/ItemStack;)Z", + at = @At("HEAD"), + cancellable = true) + private static void gtOffHandSupportPos(EntityLivingBase entity, BlockPos pos, + ItemStack tool, + CallbackInfoReturnable cir) { + if (!(entity instanceof EntityPlayer)) return; + EntityPlayer player = (EntityPlayer) entity; + IBlockState state = entity.getEntityWorld().getBlockState(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 (!gtmt$canHarvest(tool, state, player) && gtmt$canHarvest(offhand, state, player)) { + 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; + Block block = state.getBlock(); + String toolType = block.getHarvestTool(state); + int requiredLevel = block.getHarvestLevel(state); + if (toolType == null || requiredLevel < 0) return true; + return stack.getItem().getHarvestLevel(stack, toolType, null, state) >= requiredLevel; + } +} diff --git a/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinEntityPlayer.java b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinEntityPlayer.java new file mode 100644 index 0000000..4a9efbc --- /dev/null +++ b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinEntityPlayer.java @@ -0,0 +1,43 @@ +package com.github.gtexpert.gtmt.mixins.tic; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import gregtech.api.items.toolitem.IGTTool; + +import slimeknights.tconstruct.library.tools.TinkerToolCore; +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. + */ +@Mixin(EntityPlayer.class) +public abstract class MixinEntityPlayer { + + @Inject( + method = "canHarvestBlock(Lnet/minecraft/block/state/IBlockState;)Z", + at = @At("RETURN"), + cancellable = true) + private void checkOffHandForHarvest(IBlockState state, CallbackInfoReturnable cir) { + if (cir.getReturnValue()) return; + + EntityPlayer player = (EntityPlayer) (Object) this; + ItemStack mainhand = player.getHeldItemMainhand(); + ItemStack offhand = player.getHeldItemOffhand(); + if (offhand.isEmpty()) return; + + if (mainhand.getItem() instanceof IGTTool && offhand.getItem() instanceof TinkerToolCore) { + if (ToolHelper.canHarvest(offhand, state)) { + cir.setReturnValue(true); + } + } + } +} 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 new file mode 100644 index 0000000..1c10d02 --- /dev/null +++ b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinForgeHooks.java @@ -0,0 +1,48 @@ +package com.github.gtexpert.gtmt.mixins.tic; + +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraftforge.common.ForgeHooks; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import gregtech.api.items.toolitem.IGTTool; + +import slimeknights.tconstruct.library.tools.TinkerToolCore; +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. + */ +@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", + 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; + + ItemStack mainhand = player.getHeldItemMainhand(); + ItemStack offhand = player.getHeldItemOffhand(); + if (offhand.isEmpty()) return; + + if (mainhand.getItem() instanceof IGTTool && offhand.getItem() instanceof TinkerToolCore) { + IBlockState state = world.getBlockState(pos); + if (ToolHelper.canHarvest(offhand, state)) { + cir.setReturnValue(true); + } + } + } +} 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 new file mode 100644 index 0000000..ab7d985 --- /dev/null +++ b/src/main/java/com/github/gtexpert/gtmt/mixins/tic/MixinItemGTTool.java @@ -0,0 +1,60 @@ +package com.github.gtexpert.gtmt.mixins.tic; + +import javax.annotation.Nullable; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import gregtech.api.items.toolitem.ItemGTTool; + +import slimeknights.tconstruct.library.tools.DualToolHarvestUtils; +import slimeknights.tconstruct.library.tools.TinkerToolCore; + +/** + * Replicates the hand-swap logic from {@code ToolCore.onBlockStartBreak} for GT tools. + * + */ +@Mixin(value = ItemGTTool.class, remap = false) +public class MixinItemGTTool { + + @Inject(method = "onBlockStartBreak", at = @At("HEAD"), cancellable = true) + private void gtDualToolHarvest(ItemStack stack, BlockPos pos, EntityPlayer player, + CallbackInfoReturnable cir) { + ItemStack offhand = player.getHeldItemOffhand(); + if (offhand.isEmpty() || !(offhand.getItem() instanceof TinkerToolCore)) return; + + if (!DualToolHarvestUtils.shouldUseOffhand(player, pos, stack)) return; + + player.setHeldItem(EnumHand.MAIN_HAND, offhand); + player.setHeldItem(EnumHand.OFF_HAND, stack); + + NBTTagCompound tag = offhand.hasTagCompound() ? offhand.getTagCompound() : new NBTTagCompound(); + tag.setLong("SwitchedHand", player.getEntityWorld().getTotalWorldTime()); + offhand.setTagCompound(tag); + + cir.setReturnValue(false); + } + + @Inject(method = "getHarvestLevel", at = @At("RETURN"), cancellable = true) + private void elevateHarvestLevelFromOffHand(ItemStack stack, String toolClass, + @Nullable EntityPlayer player, @Nullable IBlockState state, + CallbackInfoReturnable cir) { + if (player == null || state == null) return; + ItemStack offhand = player.getHeldItemOffhand(); + if (offhand.isEmpty() || !(offhand.getItem() instanceof TinkerToolCore)) return; + int currentLevel = cir.getReturnValue(); + int ticLevel = offhand.getItem().getHarvestLevel(offhand, toolClass, null, state); + if (ticLevel > currentLevel) { + cir.setReturnValue(ticLevel); + } + } +} diff --git a/src/main/resources/mixins.gtmt.tconstruct.json b/src/main/resources/mixins.gtmt.tconstruct.json new file mode 100644 index 0000000..45c74d6 --- /dev/null +++ b/src/main/resources/mixins.gtmt.tconstruct.json @@ -0,0 +1,13 @@ +{ + "package": "com.github.gtexpert.gtmt.mixins.tic", + "refmap": "mixins.gtmt.refmap.json", + "target": "@env(DEFAULT)", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "MixinDualToolHarvestUtils", + "MixinEntityPlayer", + "MixinForgeHooks", + "MixinItemGTTool" + ] +}