diff --git a/src/main/java/org/violetmoon/quark/api/event/SimpleHarvestEvent.java b/src/main/java/org/violetmoon/quark/api/event/SimpleHarvestEvent.java index abbc07a94b..de7acfe821 100644 --- a/src/main/java/org/violetmoon/quark/api/event/SimpleHarvestEvent.java +++ b/src/main/java/org/violetmoon/quark/api/event/SimpleHarvestEvent.java @@ -2,10 +2,14 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.eventbus.api.Cancelable; import net.minecraftforge.eventbus.api.Event; +import org.jetbrains.annotations.Nullable; +import org.violetmoon.quark.content.tweaks.module.SimpleHarvestModule; /** * Used primarily for double crops which need extra checks before they are considered ready. @@ -15,62 +19,58 @@ @Cancelable public class SimpleHarvestEvent extends Event { - public final BlockState blockState; - public final BlockPos pos; - public final InteractionHand hand; - public final Player player; - public final Source type; - private BlockPos newTarget; - private ActionType action; + public final BlockState blockState; + public final BlockPos pos; + public final Level level; + public final @Nullable InteractionHand hand; + public final @Nullable Entity entity; + private BlockPos newTarget; + private ActionType action; - public SimpleHarvestEvent(BlockState blockState, BlockPos pos, InteractionHand hand, - Player player, boolean isHoe, ActionType actionType) { - this.blockState = blockState; - this.pos = pos; - this.hand = hand; - this.player = player; - this.newTarget = pos; - this.type = isHoe ? Source.HOE : Source.RIGHT_CLICK; - this.action = actionType; - } + //Note that entity could be a player or villager + public SimpleHarvestEvent(BlockState blockState, BlockPos pos, Level level, @Nullable InteractionHand hand, + @Nullable Entity entity, ActionType originalActionType) { + this.blockState = blockState; + this.pos = pos; + this.hand = hand; + this.level = level; + this.entity = entity; + this.newTarget = pos; + this.action = originalActionType; + } - /** - * Used for double crops and the like. Pass a new position which should be broken instead - * - * @param pos new target position - */ - public void setTargetPos(BlockPos pos) { - this.newTarget = pos; - } + /** + * Used for double crops and the like. Pass a new position which should be broken instead + * + * @param pos new target position + */ + public void setTargetPos(BlockPos pos) { + this.newTarget = pos; + } - public Source getInteractionSource() { - return type; - } + @Override + public void setCanceled(boolean cancel) { + if (cancel) + action = ActionType.NONE; + super.setCanceled(cancel); + } - @Override - public void setCanceled(boolean cancel) { - if(cancel) - action = ActionType.NONE; - super.setCanceled(cancel); - } + //Click will work just for players! + public enum ActionType { + NONE, CLICK, HARVEST; + } - public enum ActionType { - NONE, CLICK, HARVEST; - } + public ActionType getAction() { + return action; + } - public ActionType getAction() { - return action; - } + public void setAction(ActionType action) { + this.action = action; + } - public void setAction(ActionType action) { - this.action = action; - } + public BlockPos getTargetPos() { + return newTarget; + } - public BlockPos getTargetPos() { - return newTarget; - } - public enum Source { - RIGHT_CLICK, HOE - } } diff --git a/src/main/java/org/violetmoon/quark/content/automation/block/FeedingTroughBlock.java b/src/main/java/org/violetmoon/quark/content/automation/block/FeedingTroughBlock.java index 87355d7f17..abbc775602 100644 --- a/src/main/java/org/violetmoon/quark/content/automation/block/FeedingTroughBlock.java +++ b/src/main/java/org/violetmoon/quark/content/automation/block/FeedingTroughBlock.java @@ -90,10 +90,10 @@ protected void createBlockStateDefinition(StateDefinition.Builder crops = Maps.newHashMap(); + public static final Map crops = Maps.newHashMap(); //max age to base state private static final Set cropBlocks = Sets.newHashSet(); //used for the event public static final Set rightClickCrops = Sets.newHashSet(); @@ -178,41 +181,43 @@ private boolean isVanilla(Block entry) { return loc.getNamespace().equals("minecraft"); } - public static void harvestAndReplant(Level world, BlockPos pos, BlockState inWorld, Entity entity, ItemStack heldItem) { - if(!(world instanceof ServerLevel serverLevel) || entity.isSpectator()) - return; - - int fortune = Quark.ZETA.itemExtensions.get(heldItem).getEnchantmentLevelZeta(heldItem, Enchantments.BLOCK_FORTUNE); + private static boolean harvestAndReplant(Level level, BlockPos pos, BlockState inWorld, + @Nullable LivingEntity entity, @Nullable InteractionHand hand) { - ItemStack copy = heldItem.copy(); - if(copy.isEmpty()) - copy = new ItemStack(Items.STICK); + // This could be modified and called by the event, so we don't actually know if we know how to replant a block. As such we first check that + BlockState newBlock = crops.get(inWorld); + if(newBlock == null) return false; - Map enchMap = EnchantmentHelper.getEnchantments(copy); - enchMap.put(Enchantments.BLOCK_FORTUNE, fortune); - EnchantmentHelper.setEnchantments(enchMap, copy); + if(level instanceof ServerLevel serverLevel) { - MutableBoolean hasTaken = new MutableBoolean(false); - Item blockItem = inWorld.getBlock().asItem(); - Block.getDrops(inWorld, serverLevel, pos, world.getBlockEntity(pos), entity, copy) - .forEach((stack) -> { - if(stack.getItem() == blockItem && !hasTaken.getValue()) { - stack.shrink(1); - hasTaken.setValue(true); - } + ItemStack copy; + if(entity == null || hand == null){ + copy = new ItemStack(Items.STICK); + }else{ + // is this needed? isnt enchantent level handled intenrally? + copy = entity.getItemInHand(hand).copy(); + } - if(!stack.isEmpty()) - Block.popResource(world, pos, stack); - }); - inWorld.spawnAfterBreak(serverLevel, pos, copy, true); // true = is player - - // ServerLevel sets this to `false` in the constructor, do we really need this check? - if(!world.isClientSide && !inWorld.is(simpleHarvestBlacklistedTag)) { - BlockState newBlock = crops.get(inWorld); - world.levelEvent(LevelEvent.PARTICLES_DESTROY_BLOCK, pos, Block.getId(newBlock)); - world.setBlockAndUpdate(pos, newBlock); - world.gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(entity, inWorld)); + MutableBoolean hasTaken = new MutableBoolean(false); + Item blockItem = inWorld.getBlock().asItem(); + Block.getDrops(inWorld, serverLevel, pos, level.getBlockEntity(pos), entity, copy) + .forEach((stack) -> { + if (stack.getItem() == blockItem && !hasTaken.getValue()) { + stack.shrink(1); + hasTaken.setValue(true); + } + + if (!stack.isEmpty()) + Block.popResource(level, pos, stack); + }); + boolean dropXp = entity instanceof Player; + inWorld.spawnAfterBreak(serverLevel, pos, copy, dropXp); + + level.levelEvent(LevelEvent.PARTICLES_DESTROY_BLOCK, pos, Block.getId(newBlock)); + level.setBlockAndUpdate(pos, newBlock); + level.gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(entity, inWorld)); } + return true; } private boolean isHarvesting = false; @@ -229,37 +234,39 @@ public void onClick(ZRightClickBlock event) { isHarvesting = false; } - private static boolean handle(Player player, InteractionHand hand, BlockPos pos, boolean doRightClick, boolean isHoe) { - if(!player.level().mayInteract(player, pos) || player.isSpectator()) + public static boolean tryHarvestOrClickCrop(Level level, BlockPos pos, @Nullable LivingEntity entity, @Nullable InteractionHand hand, + boolean canRightClick) { + if(entity instanceof Player p && (!level.mayInteract(p, pos))) return false; - BlockState worldBlock = player.level().getBlockState(pos); - if(!worldBlock.is(simpleHarvestBlacklistedTag)) { - //prevents firing event for non crop blocks - if(cropBlocks.contains(worldBlock.getBlock()) || (doRightClick && rightClickCrops.contains(worldBlock.getBlock()))) { - //event stuff - ActionType action = getAction(worldBlock, doRightClick); - SimpleHarvestEvent event = new SimpleHarvestEvent(worldBlock, pos, hand, player, isHoe, action); - MinecraftForge.EVENT_BUS.post(event); - if(event.isCanceled()) - return false; - - BlockPos newPos = event.getTargetPos(); - if(newPos != pos) - worldBlock = player.level().getBlockState(newPos); - action = event.getAction(); - - if(action == ActionType.HARVEST) { - harvestAndReplant(player.level(), pos, worldBlock, player, player.getMainHandItem()); - return true; - } else if(action == ActionType.CLICK) { - var hitResult = new BlockHitResult(Vec3.atCenterOf(pos), Direction.UP, pos, true); - if(player instanceof ServerPlayer serverPlayer) { - return serverPlayer.gameMode.useItemOn(serverPlayer, serverPlayer.level(), player.getMainHandItem(), hand, - hitResult).consumesAction(); - } else { - return Quark.proxy.clientUseItem(player, player.level(), hand, hitResult).consumesAction(); - } + BlockState worldBlock = level.getBlockState(pos); + ActionType action = getActionForBlock(worldBlock, canRightClick); + //prevents firing event for non-crop blocks + if(action != ActionType.NONE) { + // event stuff + SimpleHarvestEvent event = new SimpleHarvestEvent(worldBlock, pos, level, hand, entity, action); + MinecraftForge.EVENT_BUS.post(event); + if(event.isCanceled()) return false; + + BlockPos newPos = event.getTargetPos(); + if(newPos != pos) worldBlock = level.getBlockState(newPos); + action = event.getAction(); + // end event stuff + + if(action == ActionType.HARVEST) { + if(entity instanceof Player p && !Quark.FLAN_INTEGRATION.canBreak(p, pos)) return false; + return harvestAndReplant(level, pos, worldBlock, entity, hand); + } else if(action == ActionType.CLICK && entity instanceof Player p) { //Only players can click! + if(!Quark.FLAN_INTEGRATION.canInteract(p, pos)) return false; + + var hitResult = new BlockHitResult(Vec3.atCenterOf(pos), Direction.UP, pos, true); + if(hand == null) hand = InteractionHand.MAIN_HAND; + //TODO: add fake player here for villagers + if(entity instanceof ServerPlayer player) { + return player.gameMode.useItemOn(player, player.level(), player.getItemInHand(hand), hand, + hitResult).consumesAction(); + } else { + return Quark.proxy.clientUseItem(p, level, hand, hitResult).consumesAction(); } } } @@ -267,8 +274,9 @@ private static boolean handle(Player player, InteractionHand hand, BlockPos pos, return false; } - private static ActionType getAction(BlockState state, boolean doRightClick) { - if(doRightClick && rightClickCrops.contains(state.getBlock())) + private static ActionType getActionForBlock(BlockState state, boolean closeEnoughToRightClick) { + if(state.is(simpleHarvestBlacklistedTag)) return ActionType.NONE; + else if(closeEnoughToRightClick && rightClickCrops.contains(state.getBlock())) return ActionType.CLICK; else if(crops.containsKey(state)) return ActionType.HARVEST; @@ -282,10 +290,8 @@ public static boolean click(Player player, InteractionHand hand, BlockPos pos, B if(pick.getType() != HitResult.Type.BLOCK || !pick.getBlockPos().equals(pos)) return false; - if(!Quark.FLAN_INTEGRATION.canBreak(player, pos)) - return false; - - BlockState stateAt = player.level().getBlockState(pos); + Level level = player.level(); + BlockState stateAt = level.getBlockState(pos); //can you till this block? BlockState modifiedState = Quark.ZETA.blockExtensions.get(stateAt).getToolModifiedStateZeta(stateAt, new UseOnContext(player, hand, pick), "hoe_till", true); if(modifiedState != null) @@ -297,12 +303,13 @@ public static boolean click(Player player, InteractionHand hand, BlockPos pos, B if(!emptyHandHarvest && !isHoe) return false; - BlockState stateAbove = player.level().getBlockState(pos.above()); + BlockState stateAbove = level.getBlockState(pos.above()); + //why do we only check this if using hoe? if(isHoe) { - boolean aboveValid = !stateAbove.is(simpleHarvestBlacklistedTag) && (cropBlocks.contains(stateAbove.getBlock()) || rightClickCrops.contains(stateAbove.getBlock())); - boolean atValid = !stateAt.is(simpleHarvestBlacklistedTag) && (cropBlocks.contains(stateAt.getBlock()) || rightClickCrops.contains(stateAt.getBlock())); - if(!aboveValid && !atValid) + //Early check. Check action at the target block + if(getActionForBlock(stateAt, true) == ActionType.NONE && + getActionForBlock(stateAbove, true) == ActionType.NONE) return false; } @@ -314,10 +321,10 @@ public static boolean click(Player player, InteractionHand hand, BlockPos pos, B for(int z = 1 - range; z < range; z++) { BlockPos shiftPos = pos.offset(x, 0, z); - if(!handle(player, hand, shiftPos, range > 1, isHoe)) { + if(!tryHarvestOrClickCrop(level, shiftPos, player, hand, range > 1)) { shiftPos = shiftPos.above(); - if(handle(player, hand, shiftPos, range > 1, isHoe)) + if(tryHarvestOrClickCrop(level, shiftPos, player, hand, range > 1)) hasHarvested = true; } else { hasHarvested = true; @@ -327,7 +334,7 @@ public static boolean click(Player player, InteractionHand hand, BlockPos pos, B if(!hasHarvested) return false; - if(player.level().isClientSide) { + if(level.isClientSide) { if(inHand.isEmpty()) QuarkClient.ZETA_CLIENT.sendToServer(new HarvestMessage(pos, hand)); } else { diff --git a/src/main/java/org/violetmoon/quark/mixin/mixins/HarvestFarmlandMixin.java b/src/main/java/org/violetmoon/quark/mixin/mixins/HarvestFarmlandMixin.java index a15f146378..503263cdd6 100644 --- a/src/main/java/org/violetmoon/quark/mixin/mixins/HarvestFarmlandMixin.java +++ b/src/main/java/org/violetmoon/quark/mixin/mixins/HarvestFarmlandMixin.java @@ -5,8 +5,10 @@ import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.ai.behavior.HarvestFarmland; +import net.minecraft.world.entity.npc.Villager; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; @@ -22,14 +24,12 @@ public class HarvestFarmlandMixin { method = "tick(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/npc/Villager;J)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;destroyBlock(Lnet/minecraft/core/BlockPos;ZLnet/minecraft/world/entity/Entity;)Z") ) - private boolean harvestAndReplant(ServerLevel instance, BlockPos pos, boolean b, Entity entity, Operation original) { + private boolean harvestAndReplant(ServerLevel level, BlockPos pos, boolean b, Entity entity, Operation original) { if(!SimpleHarvestModule.staticEnabled && SimpleHarvestModule.villagersUseSimpleHarvest) { - BlockState state = instance.getBlockState(pos); - SimpleHarvestModule.harvestAndReplant(instance, pos, state, entity, ItemStack.EMPTY); - if(state.equals(instance.getBlockState(pos))) - return original.call(instance, pos, b, entity); - return true; + if(SimpleHarvestModule.tryHarvestOrClickCrop(level, pos, (Villager) entity, InteractionHand.MAIN_HAND, false)){ + return true; + } } - return original.call(instance, pos, b, entity); + return original.call(level, pos, b, entity); } }