Skip to content
172 changes: 122 additions & 50 deletions src/main/java/com/gregtechceu/gtceu/api/item/tool/ToolHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
import com.gregtechceu.gtceu.common.data.item.GTDataComponents;
import com.gregtechceu.gtceu.common.data.item.GTItemAbilities;
import com.gregtechceu.gtceu.config.ConfigHolder;
import com.gregtechceu.gtceu.core.mixins.LootTableAccessor;
import com.gregtechceu.gtceu.data.recipe.CustomTags;
import com.gregtechceu.gtceu.data.recipe.VanillaRecipeHelper;
import com.gregtechceu.gtceu.utils.DummyRecipeUtils;

import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponents;
import net.minecraft.server.level.ServerLevel;
Expand All @@ -41,6 +43,8 @@
import net.minecraft.stats.Stats;
import net.minecraft.tags.TagKey;
import net.minecraft.util.RandomSource;
import net.minecraft.world.Difficulty;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
Expand All @@ -56,11 +60,17 @@
import net.minecraft.world.item.component.Tool;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.functions.ApplyBonusCount;
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
Expand All @@ -72,6 +82,7 @@
import it.unimi.dsi.fastutil.chars.CharSet;
import it.unimi.dsi.fastutil.chars.CharSets;
import lombok.experimental.ExtensionMethod;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;

Expand All @@ -85,6 +96,10 @@ public class ToolHelper {
// Crafting Symbols
private static final Char2ReferenceMap<GTToolType> symbols = new Char2ReferenceOpenHashMap<>();

// Enchantments aren't reloadable so these are safe to keep around between reloads
private static @Nullable LootItemFunction uniformDropMultiplier;
private static @Nullable LootItemFunction oreDropMultiplier;

private ToolHelper() {/**/}

/**
Expand Down Expand Up @@ -345,59 +360,104 @@ private static boolean isBlockAoEHarvestable(UseOnContext context) {
/**
* Applies Forge Hammer recipes to block broken, used for hammers or tools with hard hammer enchant applied.
*/

@SuppressWarnings("DataFlowIssue")
public static void applyHammerDropConversion(ServerLevel world, BlockPos pos, ItemStack tool, BlockState state,
public static void applyHammerDropConversion(ServerLevel level, BlockPos pos, ItemStack tool, BlockState state,
List<ItemStack> drops, int fortune, float dropChance,
RandomSource random) {
if (is(tool, GTToolType.HARD_HAMMER)) {
List<ItemStack> silkTouchDrops = getSilkTouchDrop(world, pos, state);
for (ItemStack silkTouchDrop : silkTouchDrops) {
if (silkTouchDrop.isEmpty()) continue;
// Stack lists can be immutable going into Recipe#matches barring no rewrites
// Search for forge hammer recipes from all drops individually (only LV or under)

RecipeHandlerList dummyInputs = RecipeHandlerList.of(IO.IN,
new DummyRecipeUtils.DummyEnergyContainer(GTValues.V[GTValues.LV], GTValues.V[GTValues.LV], 1),
new DummyRecipeUtils.DummyItemHandler(IO.IN, NonNullList.of(silkTouchDrop)));

RecipeHandlerList dummyOutputs = RecipeHandlerList.of(IO.OUT,
new DummyRecipeUtils.DummyItemHandler(IO.OUT, 2));
DummyRecipeUtils.DummyRecipeCapabilityHolder capHolder = new DummyRecipeUtils.DummyRecipeCapabilityHolder(
dummyInputs, dummyOutputs);

Iterator<GTRecipe> hammerRecipes = GTRecipeTypes.FORGE_HAMMER_RECIPES.searchRecipe(capHolder,
r -> RecipeHelper.matchContents(capHolder, r).isSuccess());
GTRecipe hammerRecipe = !hammerRecipes.hasNext() ? null : hammerRecipes.next();
if (hammerRecipe != null &&
RecipeHelper.handleRecipeIO(capHolder, hammerRecipe, IO.IN, capHolder.getCacheChances())
.isSuccess()) {
drops.clear();
TagPrefix prefix = ChemicalHelper.getPrefix(silkTouchDrop.getItem());
if (prefix.isEmpty()) {
for (Content output : hammerRecipe.getOutputContents(ItemRecipeCapability.CAP)) {
if (dropChance >= 1.0F || random.nextFloat() <= dropChance) {
drops.add(ItemRecipeCapability.CAP.of(output.content()).copy().getItems()[0]);
}
}
} else if (TagPrefix.ORES.containsKey(prefix)) {
for (Content content : hammerRecipe.getOutputContents(ItemRecipeCapability.CAP)) {
if (dropChance >= 1.0F || random.nextFloat() <= dropChance) {
ItemStack output = ItemRecipeCapability.CAP.of(content.content()).getItems()[0];
// Only apply fortune on ore -> crushed forge hammer recipes
if (ChemicalHelper.getPrefix(output.getItem()) == TagPrefix.crushed) {
output = output.copy();
if (fortune > 0) output.grow(random.nextInt(fortune));
drops.add(output);
}
}
}
}
// || EnchantmentHelper.getEnchantmentLevel(EnchantmentHardHammer.INSTANCE, tool) > 0
if (!is(tool, GTToolType.HARD_HAMMER)) {
return;
}
LootItemFunction fortuneDropMultiplier = null;
LootContext lootContext = null;
boolean cleared = false;

List<ItemStack> silkTouchDrops = getSilkTouchDrop(level, pos, state);
for (ItemStack silkTouchDrop : silkTouchDrops) {
if (silkTouchDrop.isEmpty()) continue;
// Stack lists can be immutable going into Recipe#matches barring no rewrites
// Search for forge hammer recipes from all drops individually (only LV or under)

RecipeHandlerList dummyInputs = RecipeHandlerList.of(IO.IN,
new DummyRecipeUtils.DummyEnergyContainer(GTValues.V[GTValues.LV], GTValues.V[GTValues.LV], 1),
new DummyRecipeUtils.DummyItemHandler(IO.IN, NonNullList.of(silkTouchDrop)));

RecipeHandlerList dummyOutputs = RecipeHandlerList.of(IO.OUT,
new DummyRecipeUtils.DummyItemHandler(IO.OUT, 2));
DummyRecipeUtils.DummyRecipeCapabilityHolder capHolder = new DummyRecipeUtils.DummyRecipeCapabilityHolder(
dummyInputs, dummyOutputs);

Iterator<GTRecipe> hammerRecipes = GTRecipeTypes.FORGE_HAMMER_RECIPES.searchRecipe(capHolder,
r -> RecipeHelper.matchContents(capHolder, r).isSuccess());
GTRecipe hammerRecipe = null;
// find the first valid recipe
while (hammerRecipes.hasNext()) {
GTRecipe recipe = hammerRecipes.next();
if (recipe != null && RecipeHelper.handleRecipeIO(capHolder, hammerRecipe, IO.IN,
capHolder.getCacheChances()).isSuccess()) {
hammerRecipe = recipe;
break;
}
}
if (hammerRecipe == null) {
continue;
}

if (!cleared) {
drops.clear();
cleared = true;
}
TagPrefix prefix = ChemicalHelper.getPrefix(silkTouchDrop.getItem());
boolean isOre = !prefix.isEmpty() && TagPrefix.ORES.containsKey(prefix);

for (Content content : hammerRecipe.getOutputContents(ItemRecipeCapability.CAP)) {
ItemStack output = ItemRecipeCapability.CAP.of(content.content()).getItems()[0];
// only apply hammer drop conversion to ore blocks
if (!isOre) {
drops.add(output);
continue;
}
// Only apply fortune on ore -> crushed forge hammer recipes
if (ChemicalHelper.getPrefix(output.getItem()) != TagPrefix.crushed) {
continue;
}
if (fortuneDropMultiplier == null) {
fortuneDropMultiplier = getOrInitUniformDropMultiplier(level.registryAccess());
}
if (lootContext == null) {
// TODO pass params from BlockMixin
lootContext = createBlockLootContext(level, state, new LootParams.Builder(level)
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
.withParameter(LootContextParams.TOOL, tool));
}
drops.add(fortuneDropMultiplier.apply(output.copy(), lootContext));
}
}
}

public static LootItemFunction getOrInitUniformDropMultiplier(HolderLookup.Provider registries) {
if (uniformDropMultiplier == null) {
var fortuneHolder = registries.holderOrThrow(Enchantments.FORTUNE);
uniformDropMultiplier = ApplyBonusCount.addUniformBonusCount(fortuneHolder).build();
}
return uniformDropMultiplier;
}

public static LootItemFunction getOrInitOreDropMultiplier(HolderLookup.Provider registries) {
if (oreDropMultiplier == null) {
var fortuneHolder = registries.holderOrThrow(Enchantments.FORTUNE);
oreDropMultiplier = ApplyBonusCount.addOreBonusCount(fortuneHolder).build();
}
return oreDropMultiplier;
}

public static LootContext createBlockLootContext(ServerLevel level, BlockState state,
LootParams.Builder lootParams) {
LootParams params = lootParams.withParameter(LootContextParams.BLOCK_STATE, state)
.create(LootContextParamSets.BLOCK);
LootTable lootTable = level.getServer().reloadableRegistries().getLootTable(state.getBlock().getLootTable());
return new LootContext.Builder(params).create(((LootTableAccessor) lootTable).getRandomSequence());
}

public static final ThreadLocal<Boolean> DO_BLOCK_BREAK_SOUND_PARTICLES = ThreadLocal.withInitial(() -> true);
public static final ThreadLocal<Boolean> IS_AOE_BREAKING_BLOCKS = ThreadLocal.withInitial(() -> false);

Expand Down Expand Up @@ -624,22 +684,34 @@ public static float getDestroySpeed(BlockState state, Set<GTToolType> toolClasse
public static final Supplier<ItemStack> SUPPLY_POWER_UNIT_IV = () -> GTItems.POWER_UNIT_IV.get()
.getDefaultInstance();

private static final DifficultyInstance CONSTANT_DIFFICULTY = new DifficultyInstance(Difficulty.HARD, 0L, 0L, 0.0f);

/**
* @param state the BlockState of the block
* @return the silk touch drop
*/
public static List<ItemStack> getSilkTouchDrop(ServerLevel level, BlockPos origin, BlockState state) {
public static List<ItemStack> getSilkTouchDrop(ServerLevel level, BlockPos pos, BlockState state) {
ItemStack tool = GTMaterialItems.TOOL_ITEMS.get(GTMaterials.Neutronium, GTToolType.PICKAXE).get().get();
// oh wow, this exists now. cool!
EnchantmentHelper.enchantItemFromProvider(
tool,
level.registryAccess(),
GTEnchantmentProviders.SILK_TOUCH,
level.getCurrentDifficultyAt(origin),
CONSTANT_DIFFICULTY,
level.getRandom());

return state.getDrops(new LootParams.Builder(level).withParameter(LootContextParams.BLOCK_STATE, state)
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(origin))
.withParameter(LootContextParams.TOOL, tool));
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
.withParameter(LootContextParams.TOOL, tool)
.withOptionalParameter(LootContextParams.BLOCK_ENTITY, level.getBlockEntity(pos)));
}

/**
* For internal use only, do not call!
*/
@ApiStatus.Internal
public static void clearCachedLootModifiers() {
uniformDropMultiplier = null;
oreDropMultiplier = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import net.neoforged.neoforge.event.entity.living.LivingDropsEvent;
import net.neoforged.neoforge.event.entity.player.PlayerDestroyItemEvent;
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;

import java.util.ArrayList;
import java.util.Iterator;
Expand Down Expand Up @@ -205,4 +206,9 @@ public static void onPlayerKilledEntity(LivingDropsEvent event) {
}
}
}

@SubscribeEvent
public static void onServerClosing(ServerStoppingEvent event) {
ToolHelper.clearCachedLootModifiers();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import com.gregtechceu.gtceu.api.data.chemical.ChemicalHelper;
import com.gregtechceu.gtceu.api.data.tag.TagPrefix;
import com.gregtechceu.gtceu.api.item.tool.ToolHelper;
import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.Enchantments;
Expand All @@ -16,16 +16,13 @@
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.functions.ApplyBonusCount;
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;

import lombok.Getter;
import lombok.Setter;

import java.util.List;
import java.util.Optional;

public class LargeMinerLogic extends MinerLogic {

Expand All @@ -45,8 +42,6 @@ public class LargeMinerLogic extends MinerLogic {
@SaveField
private boolean isSilkTouchMode;

private LootItemFunction dropMultiplier;

/**
* Creates the logic for multiblock ore block miners
*
Expand Down Expand Up @@ -129,20 +124,19 @@ protected void dropPostProcessing(NonNullList<ItemStack> blockDrops, List<ItemSt
super.dropPostProcessing(blockDrops, outputs, blockState, builder);
return;
}
ServerLevel level = builder.getLevel();

var fortuneHolder = level.registryAccess().holderOrThrow(Enchantments.FORTUNE);
ItemStack fortunePick = this.pickaxeTool.copy();
var registry = builder.getLevel().registryAccess().registryOrThrow(Registries.ENCHANTMENT);
var fortuneHolder = registry.getHolderOrThrow(Enchantments.FORTUNE);
if (dropMultiplier == null) {
dropMultiplier = ApplyBonusCount.addOreBonusCount(fortuneHolder).build();
}
fortunePick.enchant(fortuneHolder, getDropCountMultiplier());
LootParams params = builder.withParameter(LootContextParams.TOOL, fortunePick)
.create(LootContextParamSets.BLOCK);
LootContext context = new LootContext.Builder(params).create(Optional.empty());

LootItemFunction fortuneDropMultiplier = ToolHelper.getOrInitOreDropMultiplier(level.registryAccess());
LootContext lootContext = ToolHelper.createBlockLootContext(level, blockState,
builder.withParameter(LootContextParams.TOOL, fortunePick));

for (ItemStack outputStack : outputs) {
if (ChemicalHelper.getPrefix(outputStack.getItem()) == TagPrefix.crushed) {
outputStack = dropMultiplier.apply(outputStack, context);
outputStack = fortuneDropMultiplier.apply(outputStack.copy(), lootContext);
}
blockDrops.add(outputStack);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,23 +398,25 @@ protected boolean doPostProcessing(NonNullList<ItemStack> blockDrops, BlockState
break;
}

if (recipe != null) {
long eut = recipe.getInputEUt().getTotalEU();
if (GTUtil.getTierByVoltage(eut) <= getVoltageTier()) {
if (RecipeHelper.handleRecipeIO(this, recipe, IO.OUT, this.chanceCaches).isSuccess()) {
blockDrops.clear();
var result = new ArrayList<ItemStack>();
for (int i = 0; i < outputItemHandler.storage.getSlots(); ++i) {
var stack = outputItemHandler.storage.getStackInSlot(i);
if (stack.isEmpty()) continue;
result.add(stack);
}
dropPostProcessing(blockDrops, result, blockState, builder);
return true;
}
}
if (recipe == null) {
return false;
}
return false;
long eut = recipe.getInputEUt().getTotalEU();
if (GTUtil.getTierByVoltage(eut) > getVoltageTier()) {
return false;
}
if (!RecipeHelper.handleRecipeIO(this, recipe, IO.OUT, this.chanceCaches).isSuccess()) {
return false;
}
blockDrops.clear();
var result = new ArrayList<ItemStack>();
for (int i = 0; i < outputItemHandler.storage.getSlots(); ++i) {
var stack = outputItemHandler.storage.getStackInSlot(i);
if (stack.isEmpty()) continue;
result.add(stack);
}
dropPostProcessing(blockDrops, result, blockState, builder);
return true;
}

protected void dropPostProcessing(NonNullList<ItemStack> blockDrops, List<ItemStack> outputs, BlockState blockState,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.gregtechceu.gtceu.core.mixins;

import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.storage.loot.LootTable;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

import java.util.Optional;

@Mixin(LootTable.class)
public interface LootTableAccessor {

@Accessor
Optional<ResourceLocation> getRandomSequence();
}
Loading
Loading