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:
+ *
+ * - Composition-based traits (e.g. contains Silver → Holy)
+ * - Property-based traits (e.g. blast temperature ≥ 2500 → HeatResistant)
+ * - Enchantment traits (dynamically derived from the GT tool property)
+ *
+ */
+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"
+ ]
+}