package net.minecraft.world.item; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableMap.Builder; import java.util.Map; import java.util.Optional; import net.minecraft.advancements.triggers.CriteriaTriggers; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.component.DataComponents; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.ChestBlock; import net.minecraft.world.level.block.RotatedPillarBlock; import net.minecraft.world.level.block.WeatheringCopper; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.ChestType; import net.minecraft.world.level.gameevent.GameEvent; import org.jspecify.annotations.Nullable; public class AxeItem extends Item { protected static final Map STRIPPABLES = new Builder() .put(Blocks.OAK_WOOD, Blocks.STRIPPED_OAK_WOOD) .put(Blocks.OAK_LOG, Blocks.STRIPPED_OAK_LOG) .put(Blocks.DARK_OAK_WOOD, Blocks.STRIPPED_DARK_OAK_WOOD) .put(Blocks.DARK_OAK_LOG, Blocks.STRIPPED_DARK_OAK_LOG) .put(Blocks.PALE_OAK_WOOD, Blocks.STRIPPED_PALE_OAK_WOOD) .put(Blocks.PALE_OAK_LOG, Blocks.STRIPPED_PALE_OAK_LOG) .put(Blocks.ACACIA_WOOD, Blocks.STRIPPED_ACACIA_WOOD) .put(Blocks.ACACIA_LOG, Blocks.STRIPPED_ACACIA_LOG) .put(Blocks.CHERRY_WOOD, Blocks.STRIPPED_CHERRY_WOOD) .put(Blocks.CHERRY_LOG, Blocks.STRIPPED_CHERRY_LOG) .put(Blocks.BIRCH_WOOD, Blocks.STRIPPED_BIRCH_WOOD) .put(Blocks.BIRCH_LOG, Blocks.STRIPPED_BIRCH_LOG) .put(Blocks.JUNGLE_WOOD, Blocks.STRIPPED_JUNGLE_WOOD) .put(Blocks.JUNGLE_LOG, Blocks.STRIPPED_JUNGLE_LOG) .put(Blocks.SPRUCE_WOOD, Blocks.STRIPPED_SPRUCE_WOOD) .put(Blocks.SPRUCE_LOG, Blocks.STRIPPED_SPRUCE_LOG) .put(Blocks.WARPED_STEM, Blocks.STRIPPED_WARPED_STEM) .put(Blocks.WARPED_HYPHAE, Blocks.STRIPPED_WARPED_HYPHAE) .put(Blocks.CRIMSON_STEM, Blocks.STRIPPED_CRIMSON_STEM) .put(Blocks.CRIMSON_HYPHAE, Blocks.STRIPPED_CRIMSON_HYPHAE) .put(Blocks.MANGROVE_WOOD, Blocks.STRIPPED_MANGROVE_WOOD) .put(Blocks.MANGROVE_LOG, Blocks.STRIPPED_MANGROVE_LOG) .put(Blocks.BAMBOO_BLOCK, Blocks.STRIPPED_BAMBOO_BLOCK) .build(); public AxeItem(final ToolMaterial material, final float attackDamageBaseline, final float attackSpeedBaseline, final Item.Properties properties) { super(properties.axe(material, attackDamageBaseline, attackSpeedBaseline)); } @Override public InteractionResult useOn(final UseOnContext context) { Level level = context.getLevel(); BlockPos pos = context.getClickedPos(); Player player = context.getPlayer(); if (playerHasBlockingItemUseIntent(context)) { return InteractionResult.PASS; } else { Optional newBlock = this.evaluateNewBlockState(level, pos, player, level.getBlockState(pos)); if (newBlock.isEmpty()) { return InteractionResult.PASS; } else { ItemStack itemInHand = context.getItemInHand(); if (player instanceof ServerPlayer serverPlayer) { CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(serverPlayer, pos, itemInHand); } level.setBlock(pos, (BlockState)newBlock.get(), 11); level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(player, (BlockState)newBlock.get())); if (player != null) { itemInHand.hurtAndBreak(1, player, context.getHand().asEquipmentSlot()); } return InteractionResult.SUCCESS; } } } private static boolean playerHasBlockingItemUseIntent(final UseOnContext context) { Player player = context.getPlayer(); return context.getHand().equals(InteractionHand.MAIN_HAND) && player.getOffhandItem().has(DataComponents.BLOCKS_ATTACKS) && !player.isSecondaryUseActive(); } private Optional evaluateNewBlockState(final Level level, final BlockPos pos, @Nullable final Player player, final BlockState oldState) { Optional strippedBlock = this.getStripped(oldState); if (strippedBlock.isPresent()) { level.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); return strippedBlock; } else { Optional scrapedBlock = WeatheringCopper.getPrevious(oldState); if (scrapedBlock.isPresent()) { spawnSoundAndParticle(level, pos, player, oldState, SoundEvents.AXE_SCRAPE, 3005); return scrapedBlock; } else { Optional waxoffBlock = Optional.ofNullable((Block)((BiMap)HoneycombItem.WAX_OFF_BY_BLOCK.get()).get(oldState.getBlock())) .map(b -> b.withPropertiesOf(oldState)); if (waxoffBlock.isPresent()) { spawnSoundAndParticle(level, pos, player, oldState, SoundEvents.AXE_WAX_OFF, 3004); return waxoffBlock; } else { return Optional.empty(); } } } } private static void spawnSoundAndParticle( final Level level, final BlockPos pos, @Nullable final Player player, final BlockState oldState, final SoundEvent soundEvent, final int particle ) { level.playSound(player, pos, soundEvent, SoundSource.BLOCKS, 1.0F, 1.0F); level.levelEvent(player, particle, pos, 0); if (oldState.getBlock() instanceof ChestBlock && oldState.getValue(ChestBlock.TYPE) != ChestType.SINGLE) { BlockPos neighborPos = ChestBlock.getConnectedBlockPos(pos, oldState); level.gameEvent(GameEvent.BLOCK_CHANGE, neighborPos, GameEvent.Context.of(player, level.getBlockState(neighborPos))); level.levelEvent(player, particle, neighborPos, 0); } } private Optional getStripped(final BlockState state) { return Optional.ofNullable((Block)STRIPPABLES.get(state.getBlock())) .map(block -> block.defaultBlockState().setValue(RotatedPillarBlock.AXIS, (Direction.Axis)state.getValue(RotatedPillarBlock.AXIS))); } }