diff --git a/src/main/java/slimeknights/tconstruct/library/modifiers/Modifier.java b/src/main/java/slimeknights/tconstruct/library/modifiers/Modifier.java
index 71abe81ee82..6a86aed11e5 100644
--- a/src/main/java/slimeknights/tconstruct/library/modifiers/Modifier.java
+++ b/src/main/java/slimeknights/tconstruct/library/modifiers/Modifier.java
@@ -2,6 +2,7 @@
import com.google.gson.JsonObject;
import net.minecraft.ChatFormatting;
+import net.minecraft.core.BlockSource;
import net.minecraft.core.Direction;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
@@ -969,6 +970,18 @@ public void onEquip(IToolStackView tool, int level, EquipmentChangeContext conte
*/
public void onEquipmentChange(IToolStackView tool, int level, EquipmentChangeContext context, EquipmentSlot slotType) {}
+ /**
+ * Called when a stack is being used within a dispenser.
+ *
+ * @param tool Tool instance
+ * @param level Modifier level
+ * @param source Block source
+ * @param stack Stack being dispensed
+ * @return True if dispenser action was taken
+ */
+ public boolean onDispenserUse(IToolStackView tool, int level, BlockSource source, ItemStack stack) {
+ return false;
+ }
/* Display */
diff --git a/src/main/java/slimeknights/tconstruct/library/tools/definition/aoe/CircleAOEIterator.java b/src/main/java/slimeknights/tconstruct/library/tools/definition/aoe/CircleAOEIterator.java
index 86ac7583af1..0735663237f 100644
--- a/src/main/java/slimeknights/tconstruct/library/tools/definition/aoe/CircleAOEIterator.java
+++ b/src/main/java/slimeknights/tconstruct/library/tools/definition/aoe/CircleAOEIterator.java
@@ -59,6 +59,22 @@ public Iterable getBlocks(IToolStackView tool, ItemStack stack, Player
* @return List of block positions
*/
public static Iterable calculate(IToolStackView tool, ItemStack stack, Level world, Player player, BlockPos origin, Direction sideHit, int diameter, boolean is3D, AOEMatchType matchType) {
+ return calculate(tool, stack, world, player.getDirection(), origin, sideHit, diameter, is3D, matchType);
+ }
+
+ /**
+ *
+ * @param tool Tool used for harvest
+ * @param stack Item stack used for harvest (for vanilla hooks)
+ * @param world World containing the block
+ * @param harvestDirection Player harvesting
+ * @param origin Center of harvest
+ * @param sideHit Block side hit
+ * @param diameter Circle diameter
+ * @param matchType Type of harvest being performed
+ * @return List of block positions
+ */
+ public static Iterable calculate(IToolStackView tool, ItemStack stack, Level world, Direction harvestDirection, BlockPos origin, Direction sideHit, int diameter, boolean is3D, AOEMatchType matchType) {
// skip if no work
if (diameter == 1) {
return Collections.emptyList();
@@ -67,11 +83,27 @@ public static Iterable calculate(IToolStackView tool, ItemStack stack,
// math works out that we can leave this an integer and get the radius working still
int radiusSq = diameter * diameter / 4;
Predicate posPredicate = IAreaOfEffectIterator.defaultBlockPredicate(tool, stack, world, origin, matchType);
- ExpansionDirections directions = IBoxExpansion.SIDE_HIT.getDirections(player, sideHit);
+ ExpansionDirections directions = getDirections(harvestDirection, sideHit);
// max needs to be an odd number
return () -> new CircleIterator(origin, directions.width(), directions.height(), directions.traverseDown(), directions.depth(), radiusSq, diameter / 2, is3D, posPredicate);
}
+ private static ExpansionDirections getDirections(Direction harvestDirection, Direction sideHit) {
+ // depth is always direction into the block
+ Direction depth = sideHit.getOpposite();
+ Direction width, height;
+ // for Y, direction is based on facing
+ if (sideHit.getAxis() == Direction.Axis.Y) {
+ height = harvestDirection;
+ width = height.getClockWise();
+ } else {
+ // for X and Z, just rotate from side hit
+ width = sideHit.getCounterClockWise();
+ height = Direction.UP;
+ }
+ return new ExpansionDirections(width, height, depth, true);
+ }
+
/** Iterator used for getting the blocks, secret is a circle is a rectangle */
private static class CircleIterator extends RectangleIterator {
/* Diameter of the area to mine, circular */
diff --git a/src/main/java/slimeknights/tconstruct/library/tools/item/ModifiableItem.java b/src/main/java/slimeknights/tconstruct/library/tools/item/ModifiableItem.java
index 46cb0a9f7a3..56ee8e40028 100644
--- a/src/main/java/slimeknights/tconstruct/library/tools/item/ModifiableItem.java
+++ b/src/main/java/slimeknights/tconstruct/library/tools/item/ModifiableItem.java
@@ -4,6 +4,7 @@
import com.google.common.collect.Multimap;
import lombok.Getter;
import net.minecraft.core.BlockPos;
+import net.minecraft.core.BlockSource;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
@@ -31,8 +32,10 @@
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.ToolAction;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import slimeknights.mantle.client.SafeClientAccess;
@@ -585,4 +588,8 @@ public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStac
public static BlockHitResult blockRayTrace(Level worldIn, Player player, ClipContext.Fluid fluidMode) {
return Item.getPlayerPOVHitResult(worldIn, player, fluidMode);
}
+
+ public static UseOnContext contextFromBlockSource(BlockSource source, ItemStack stack) {
+ return new UseOnContext(source.getLevel(), null, InteractionHand.MAIN_HAND, stack, Util.createTraceResult(source.getPos().relative(source.getBlockState().getValue(DispenserBlock.FACING)), source.getBlockState().getValue(DispenserBlock.FACING), false));
+ }
}
diff --git a/src/main/java/slimeknights/tconstruct/tools/TinkerTools.java b/src/main/java/slimeknights/tconstruct/tools/TinkerTools.java
index f4f66c6ad26..bde3dfa68fa 100644
--- a/src/main/java/slimeknights/tconstruct/tools/TinkerTools.java
+++ b/src/main/java/slimeknights/tconstruct/tools/TinkerTools.java
@@ -1,6 +1,9 @@
package slimeknights.tconstruct.tools;
import net.minecraft.advancements.critereon.ItemPredicate;
+import net.minecraft.core.BlockSource;
+import net.minecraft.core.Registry;
+import net.minecraft.core.dispenser.DispenseItemBehavior;
import net.minecraft.core.particles.SimpleParticleType;
import net.minecraft.data.DataGenerator;
import net.minecraft.world.entity.EntityType;
@@ -8,7 +11,9 @@
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
+import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
+import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.event.RegistryEvent;
@@ -28,6 +33,7 @@
import slimeknights.tconstruct.library.client.data.material.MaterialPartTextureGenerator;
import slimeknights.tconstruct.library.json.AddToolDataFunction;
import slimeknights.tconstruct.library.json.RandomMaterial;
+import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.tools.IndestructibleItemEntity;
import slimeknights.tconstruct.library.tools.SlotType;
import slimeknights.tconstruct.library.tools.ToolPredicate;
@@ -51,6 +57,7 @@
import slimeknights.tconstruct.library.tools.helper.ModifierLootingHandler;
import slimeknights.tconstruct.library.tools.item.ModifiableArmorItem;
import slimeknights.tconstruct.library.tools.item.ModifiableItem;
+import slimeknights.tconstruct.library.tools.nbt.ToolStack;
import slimeknights.tconstruct.library.utils.BlockSideHitListener;
import slimeknights.tconstruct.tools.data.StationSlotLayoutProvider;
import slimeknights.tconstruct.tools.data.ToolDefinitionDataProvider;
@@ -146,6 +153,21 @@ void commonSetup(FMLCommonSetupEvent event) {
EquipmentChangeWatcher.register();
ToolCapabilityProvider.register(ToolFluidCapability.Provider::new);
ToolCapabilityProvider.register(ToolInventoryCapability.Provider::new);
+
+ event.enqueueWork(() -> {
+ DispenseItemBehavior behavior = (source, stack) -> {
+ ToolStack tool = ToolStack.from(stack);
+
+ for (ModifierEntry entry : tool.getModifierList()) {
+ if (entry.getModifier().onDispenserUse(tool, entry.getLevel(), source, stack)) break;
+ }
+
+ return stack;
+ };
+
+ DispenserBlock.registerBehavior(TinkerTools.flintAndBrick, behavior);
+ });
+
for (ConfigurableAction action : Config.COMMON.damageSourceTweaks) {
event.enqueueWork(action);
}
diff --git a/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/interaction/BlockTransformModifier.java b/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/interaction/BlockTransformModifier.java
index 5e37feb632f..502e63d59dc 100644
--- a/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/interaction/BlockTransformModifier.java
+++ b/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/interaction/BlockTransformModifier.java
@@ -3,6 +3,7 @@
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraft.core.BlockPos;
+import net.minecraft.core.BlockSource;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
@@ -19,6 +20,7 @@
import slimeknights.tconstruct.library.modifiers.impl.InteractionModifier;
import slimeknights.tconstruct.library.tools.definition.aoe.IAreaOfEffectIterator;
import slimeknights.tconstruct.library.tools.helper.ToolDamageUtil;
+import slimeknights.tconstruct.library.tools.item.ModifiableItem;
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
import slimeknights.tconstruct.library.utils.MutableUseOnContext;
@@ -127,6 +129,80 @@ public InteractionResult afterBlockUse(IToolStackView tool, int level, UseOnCont
return didTransform ? InteractionResult.sidedSuccess(world.isClientSide) : InteractionResult.PASS;
}
+ @Override
+ public boolean onDispenserUse(IToolStackView tool, int level, BlockSource source, ItemStack stack) {
+ // tool must not be broken
+ if (tool.isBroken()) {
+ return false;
+ }
+
+ UseOnContext context = ModifiableItem.contextFromBlockSource(source, stack);
+
+ // for hoes and shovels, must have nothing but plants above
+ if (requireGround && context.getClickedFace() == Direction.DOWN) {
+ return true;
+ }
+
+ // must actually transform
+ Level world = context.getLevel();
+ BlockPos pos = context.getClickedPos();
+ BlockState original = world.getBlockState(pos);
+
+ boolean didTransform = transform(context, original, true);
+
+ // if we made a successful transform, client can stop early
+ if (didTransform) {
+ if (world.isClientSide) {
+ return true;
+ }
+
+ // if the tool breaks or it was a campfire, we are done
+ if (ToolDamageUtil.damage(tool, 1, null, null)) {
+ return true;
+ }
+ }
+
+ // AOE transforming, run even if we did not transform the center
+ // note we consider anything effective, as hoes are not effective on all tillable blocks
+// if (player != null && !tool.isBroken()) {
+// int totalTransformed = 0;
+// Iterator aoePos = tool.getDefinition().getData().getAOE().getBlocks(tool, stack, player, original, world, pos, context.getClickedFace(), IAreaOfEffectIterator.AOEMatchType.TRANSFORM).iterator();
+// if (aoePos.hasNext()) {
+// MutableUseOnContext offsetContext = new MutableUseOnContext(context);
+// do {
+// BlockPos newPos = aoePos.next();
+// if (pos.equals(newPos)) {
+// continue;
+// }
+//
+// // try interacting with the new position
+// offsetContext.setOffsetPos(newPos);
+//
+// BlockState newTarget = world.getBlockState(newPos);
+//
+// // limit to playing 40 sounds, that's more than enough for most transforms
+// if (transform(offsetContext, newTarget, totalTransformed < 40)) {
+// totalTransformed++;
+// didTransform = true;
+//
+// // stop if the tool broke
+// if (world.isClientSide || ToolDamageUtil.damageAnimated(tool, 1, player, slotType)) {
+// break;
+// }
+// }
+// } while (aoePos.hasNext());
+//
+// // sweep attack if we transformed any
+// if (totalTransformed > 0) {
+// player.sweepAttack();
+// }
+// }
+// }
+
+ // if anything happened, return success
+ return didTransform;
+ }
+
/** Transforms the given block */
protected boolean transform(UseOnContext context, BlockState original, boolean playSound) {
Level level = context.getLevel();
diff --git a/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/interaction/FirestarterModifier.java b/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/interaction/FirestarterModifier.java
index a26122b7e77..d614fbb0e5e 100644
--- a/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/interaction/FirestarterModifier.java
+++ b/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/interaction/FirestarterModifier.java
@@ -3,6 +3,7 @@
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraft.core.BlockPos;
+import net.minecraft.core.BlockSource;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
@@ -21,6 +22,7 @@
import net.minecraft.world.level.block.CampfireBlock;
import net.minecraft.world.level.block.CandleBlock;
import net.minecraft.world.level.block.CandleCakeBlock;
+import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.TntBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
@@ -31,6 +33,8 @@
import slimeknights.tconstruct.library.tools.definition.aoe.IAreaOfEffectIterator;
import slimeknights.tconstruct.library.tools.helper.ToolDamageUtil;
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
+import slimeknights.tconstruct.library.tools.nbt.ToolStack;
+import slimeknights.tconstruct.library.utils.Util;
import slimeknights.tconstruct.tools.TinkerModifiers;
import javax.annotation.Nullable;
@@ -161,4 +165,64 @@ public InteractionResult afterBlockUse(IToolStackView tool, int level, UseOnCont
}
return didIgnite ? InteractionResult.sidedSuccess(world.isClientSide) : InteractionResult.PASS;
}
+
+ /** Ignites the given block */
+ private static boolean igniteDispenser(IToolStackView tool, Level world, BlockPos pos, BlockState state, Direction sideHit, Direction horizontalFacing) {
+ // campfires first
+ if (CampfireBlock.canLight(state) || CandleBlock.canLight(state) || CandleCakeBlock.canLight(state)) {
+ world.playSound(null, pos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, RANDOM.nextFloat() * 0.4F + 0.8F);
+ world.setBlock(pos, state.setValue(BlockStateProperties.LIT, true), 11);
+ world.gameEvent(null, GameEvent.BLOCK_PLACE, pos);
+ return true;
+ }
+
+ // ignite the TNT
+ if (state.getBlock() instanceof TntBlock tnt) {
+ tnt.onCaughtFire(state, world, pos, sideHit, null);
+ world.setBlock(pos, Blocks.AIR.defaultBlockState(), 11);
+ return true;
+ }
+
+ // fire starting
+ BlockPos offset = pos.relative(sideHit);
+ if (BaseFireBlock.canBePlacedAt(world, offset, horizontalFacing)) {
+ world.playSound(null, offset, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, RANDOM.nextFloat() * 0.4F + 0.8F);
+ world.setBlock(offset, BaseFireBlock.getState(world, offset), 11);
+ return true;
+ }
+ return false;
+ }
+
+
+ @Override
+ public boolean onDispenserUse(IToolStackView tool, int level, BlockSource source, ItemStack stack) {
+
+ if (tool.isBroken()) {
+ return false;
+ }
+
+ Level world = source.getLevel();
+
+ Direction interactionDirection = source.getBlockState().getValue(DispenserBlock.FACING);
+ Direction sideHit = interactionDirection.getOpposite();
+ BlockPos pos = source.getPos().relative(interactionDirection);
+ BlockState state = world.getBlockState(pos);
+
+
+ // if targeting fire, offset to behind the fire
+ boolean targetingFire = state.is(BlockTags.FIRE);
+
+ // burn it all in AOE
+ Direction horizontalFacing = interactionDirection.getAxis() != Direction.Axis.Y ? interactionDirection : Direction.NORTH;
+ // first burn the center, unless we already know its fire
+ boolean didIgnite = false;
+ if (!targetingFire) {
+ didIgnite = igniteDispenser(tool, world, pos, state, sideHit, horizontalFacing);
+ if (didIgnite && ToolDamageUtil.damage(tool, 1, null, stack)) {
+ return true;
+ }
+ }
+
+ return didIgnite;
+ }
}
diff --git a/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/tool/GlowingModifier.java b/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/tool/GlowingModifier.java
index 0d5cc504755..b222af4d7d5 100644
--- a/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/tool/GlowingModifier.java
+++ b/src/main/java/slimeknights/tconstruct/tools/modifiers/ability/tool/GlowingModifier.java
@@ -1,16 +1,21 @@
package slimeknights.tconstruct.tools.modifiers.ability.tool;
import net.minecraft.core.BlockPos;
+import net.minecraft.core.BlockSource;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
+import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.DispenserBlock;
import slimeknights.tconstruct.library.modifiers.impl.InteractionModifier.NoLevels;
import slimeknights.tconstruct.library.tools.helper.ToolDamageUtil;
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
+import slimeknights.tconstruct.library.tools.nbt.ToolStack;
import slimeknights.tconstruct.shared.TinkerCommons;
public class GlowingModifier extends NoLevels {
@@ -39,4 +44,21 @@ public InteractionResult afterBlockUse(IToolStackView tool, int level, UseOnCont
}
return InteractionResult.PASS;
}
+
+ @Override
+ public boolean onDispenserUse(IToolStackView tool, int level, BlockSource source, ItemStack stack) {
+
+ if(tool.getCurrentDurability() >= 10) {
+ Level world = source.getLevel();
+
+ Direction facing = source.getBlockState().getValue(DispenserBlock.FACING);
+ if(TinkerCommons.glow.get().addGlow(world, source.getPos().relative(facing), facing)) {
+ ToolDamageUtil.damage(tool, 10, null, stack);
+ world.playSound(null, source.getPos(), source.getBlockState().getSoundType(world, source.getPos(), null).getPlaceSound(), SoundSource.BLOCKS, 1.0f, 10.f);
+ }
+ return true;
+ }
+
+ return false;
+ }
}