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; + } }