Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DispenserItemBehavior for flint and brick. #5029

Open
wants to merge 2 commits into
base: 1.18.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <br>
* @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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the item stack parameter? I generally leave it out of modifier hooks as people should be using the tool stack.

Looks like you use it to create a use on context below if I am understanding the code, double check if that is avoidable, if not modify the javadoc to be clear about the relationship between the tool and the stack and that the stack should not be used to create a ToolStack instance.

return false;
}

/* Display */

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ public Iterable<BlockPos> getBlocks(IToolStackView tool, ItemStack stack, Player
* @return List of block positions
*/
public static Iterable<BlockPos> 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<BlockPos> 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();
Expand All @@ -67,11 +83,27 @@ public static Iterable<BlockPos> 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<BlockPos> 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;
Copy link
Member

@KnightMiner KnightMiner Nov 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, the harvest direction parameter does not matter for circles. It matters for a rectangle as it determines the rectangle orientation and was conveient to reuse the rectangle logic for a circle, but if you are going to extract it I would just ditch the side/player argument entirely (could be accomplished by making player nullable in the above method signature then ignoring it here)

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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs javadoc, and probably belongs in ModifierUtils or one of those similar helper classes. Above method is only here as getPlayerPOVHitResult is protected.

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));
}
}
22 changes: 22 additions & 0 deletions src/main/java/slimeknights/tconstruct/tools/TinkerTools.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
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;
import net.minecraft.world.entity.MobCategory;
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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flint and steel fires particles if its unable to do anything. Looks like they use a class called OptionalDispenseItemBehavior for that fallback particle, I suggest using that

});

for (ConfigurableAction action : Config.COMMON.damageSourceTweaks) {
event.enqueueWork(action);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets just hardcode to circle AOE here, using the same logic you were planning to use in fireprimer.

// int totalTransformed = 0;
// Iterator<BlockPos> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you duplicate this whole method just to remove a nullable player argument? Just call the original method by passing null.

If there are any other differences, add the needed parameter to distinguish the two

// 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;
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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;
}
}