From e1e294247544b282423b7b467843966573a3d705 Mon Sep 17 00:00:00 2001 From: eggohito <66972816+eggohito@users.noreply.github.com> Date: Sun, 19 May 2024 20:03:11 +0800 Subject: [PATCH] Changed the impl. of the `*_block_use` power types * The behavior has been changed to be consistent with the `*_entity_use` power types * The actions of using items on blocks are no longer prevented if the items are modified to be edible + Added a new `usage_phases` field; an array of strings which determines at which phase the action should be executed/prevented. It accepts these values: * `"block"`; the phase at which the player has interacted with a block (happens first) * `"item"`; the phase at which the player is using an item on a block (happens last) --- .../apace100/apoli/data/ApoliDataTypes.java | 4 + .../ClientPlayerInteractionManagerMixin.java | 245 ++++++++++++++++-- .../apace100/apoli/mixin/ItemStackMixin.java | 17 -- .../ServerPlayerInteractionManagerMixin.java | 244 ++++++++++++++++- .../apoli/power/ActionOnBlockUsePower.java | 53 ++-- .../apoli/power/PreventBlockUsePower.java | 92 ++++++- .../apace100/apoli/power/Prioritized.java | 5 +- .../apace100/apoli/util/ActionResultUtil.java | 12 + .../apace100/apoli/util/BlockUsagePhase.java | 6 + .../apace100/apoli/util/PriorityPhase.java | 25 ++ 10 files changed, 627 insertions(+), 76 deletions(-) create mode 100644 src/main/java/io/github/apace100/apoli/util/ActionResultUtil.java create mode 100644 src/main/java/io/github/apace100/apoli/util/BlockUsagePhase.java create mode 100644 src/main/java/io/github/apace100/apoli/util/PriorityPhase.java diff --git a/src/main/java/io/github/apace100/apoli/data/ApoliDataTypes.java b/src/main/java/io/github/apace100/apoli/data/ApoliDataTypes.java index cc8bf03cb..73b24afff 100644 --- a/src/main/java/io/github/apace100/apoli/data/ApoliDataTypes.java +++ b/src/main/java/io/github/apace100/apoli/data/ApoliDataTypes.java @@ -440,6 +440,10 @@ public class ApoliDataTypes { } ); + public static final SerializableDataType BLOCK_USAGE_PHASE = SerializableDataType.enumValue(BlockUsagePhase.class); + + public static final SerializableDataType> BLOCK_USAGE_PHASE_SET = SerializableDataType.enumSet(BlockUsagePhase.class, BLOCK_USAGE_PHASE); + public static SerializableDataType.Instance> condition(Registry> registry, String name) { return condition(registry, IdentifierAlias.GLOBAL, name); } diff --git a/src/main/java/io/github/apace100/apoli/mixin/ClientPlayerInteractionManagerMixin.java b/src/main/java/io/github/apace100/apoli/mixin/ClientPlayerInteractionManagerMixin.java index 4486688fe..c74da7330 100644 --- a/src/main/java/io/github/apace100/apoli/mixin/ClientPlayerInteractionManagerMixin.java +++ b/src/main/java/io/github/apace100/apoli/mixin/ClientPlayerInteractionManagerMixin.java @@ -1,49 +1,260 @@ package io.github.apace100.apoli.mixin; -import io.github.apace100.apoli.component.PowerHolderComponent; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; import io.github.apace100.apoli.power.ActionOnBlockUsePower; +import io.github.apace100.apoli.power.ActiveInteractionPower; import io.github.apace100.apoli.power.PreventBlockUsePower; +import io.github.apace100.apoli.power.Prioritized; +import io.github.apace100.apoli.util.ActionResultUtil; +import io.github.apace100.apoli.util.BlockUsagePhase; +import io.github.apace100.apoli.util.PriorityPhase; +import net.minecraft.block.BlockState; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.Slice; + +import java.util.List; @Mixin(ClientPlayerInteractionManager.class) public abstract class ClientPlayerInteractionManagerMixin { - @Inject(method = "interactBlockInternal", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;shouldCancelInteraction()Z"), cancellable = true) - private void apoli$preventBlockInteraction(ClientPlayerEntity player, Hand hand, BlockHitResult hitResult, CallbackInfoReturnable cir) { - if(PowerHolderComponent.hasPower(player, PreventBlockUsePower.class, pbup -> pbup.doesPrevent(player.getWorld(), hitResult.getBlockPos()))) { - cir.setReturnValue(ActionResult.PASS); + @WrapOperation(method = "interactBlockInternal", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;onUse(Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/Hand;Lnet/minecraft/util/hit/BlockHitResult;)Lnet/minecraft/util/ActionResult;")) + private ActionResult apoli$beforeUseBlock(BlockState state, World world, PlayerEntity player, Hand hand, BlockHitResult hitResult, Operation original, @Share("zeroPriority$onBlock") LocalRef zeroPriority$onBlockRef, @Share("zeroPriority$itemOnBlock") LocalRef zeroPriority$itemOnBlockRef) { + + ItemStack stackInHand = player.getStackInHand(hand); + BlockUsagePhase usePhase = BlockUsagePhase.BLOCK; + + zeroPriority$onBlockRef.set(ActionResult.PASS); + zeroPriority$itemOnBlockRef.set(ActionResult.PASS); + + if (PreventBlockUsePower.doesPrevent(player, usePhase, hitResult, stackInHand, hand)) { + return ActionResult.FAIL; + } + + Prioritized.CallInstance aipci = new Prioritized.CallInstance<>(); + aipci.add(player, ActionOnBlockUsePower.class, p -> p.shouldExecute(usePhase, PriorityPhase.BEFORE, hitResult, hand, stackInHand)); + + for (int i = aipci.getMaxPriority(); i >= aipci.getMinPriority(); i--) { + + if (!aipci.hasPowers(i)) { + continue; + } + + List aips = aipci.getPowers(i); + ActionResult previousResult = ActionResult.PASS; + + for (ActiveInteractionPower aip : aips) { + + ActionResult currentResult = aip instanceof ActionOnBlockUsePower aobup + ? aobup.executeAction(hitResult, hand) + : ActionResult.PASS; + + if (ActionResultUtil.shouldOverride(previousResult, currentResult)) { + previousResult = currentResult; + } + + } + + if (i == 0) { + zeroPriority$onBlockRef.set(previousResult); + continue; + } + + if (previousResult == ActionResult.PASS) { + continue; + } + + if (previousResult.shouldSwingHand()) { + player.swingHand(hand); + } + + return previousResult; + } + + return original.call(state, world, player, hand, hitResult); + } - @Inject(method = "interactBlockInternal", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;shouldCancelInteraction()Z"), cancellable = true) - private void apoli$executeBlockUseActions(ClientPlayerEntity player, Hand hand, BlockHitResult hitResult, CallbackInfoReturnable cir) { + @ModifyReturnValue(method = "interactBlockInternal", at = @At(value = "RETURN", ordinal = 0), slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/util/ActionResult;isAccepted()Z"))) + private ActionResult apoli$afterUseBlock(ActionResult original, ClientPlayerEntity player, Hand hand, BlockHitResult hitResult, @Share("zeroPriority$onBlock") LocalRef zeroPriority$onBlockRef) { + + ItemStack stackInHand = player.getStackInHand(hand); + + ActionResult zeroPriority$onBlock = zeroPriority$onBlockRef.get(); + ActionResult newResult = ActionResult.PASS; + + if (zeroPriority$onBlock != ActionResult.PASS) { + newResult = zeroPriority$onBlock; + } + + else if (original == ActionResult.PASS) { + + Prioritized.CallInstance aipci = new Prioritized.CallInstance<>(); + aipci.add(player, ActionOnBlockUsePower.class, p -> p.shouldExecute(BlockUsagePhase.BLOCK, PriorityPhase.AFTER, hitResult, hand, stackInHand)); + + for (int i = aipci.getMaxPriority(); i >= aipci.getMinPriority(); i--) { - ActionResult result = ActionResult.PASS; - for (ActionOnBlockUsePower aobup : PowerHolderComponent.getPowers(player, ActionOnBlockUsePower.class)) { + if (!aipci.hasPowers(i)) { + continue; + } - if (!aobup.shouldExecute(hitResult, hand, player.getStackInHand(hand))) { + List aips = aipci.getPowers(i); + ActionResult previousResult = ActionResult.PASS; + + for (ActiveInteractionPower aip : aips) { + + ActionResult currentResult = aip instanceof ActionOnBlockUsePower aobup + ? aobup.executeAction(hitResult, hand) + : ActionResult.PASS; + + if (ActionResultUtil.shouldOverride(previousResult, currentResult)) { + previousResult = currentResult; + } + + } + + if (previousResult != ActionResult.PASS) { + newResult = previousResult; + break; + } + + } + + } + + if (newResult.shouldSwingHand()) { + player.swingHand(hand, true); + } + + return ActionResultUtil.shouldOverride(original, newResult) + ? newResult + : original; + + } + + @WrapOperation(method = "interactBlockInternal", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;useOnBlock(Lnet/minecraft/item/ItemUsageContext;)Lnet/minecraft/util/ActionResult;")) + private ActionResult apoli$beforeItemUseOnBlock(ItemStack stack, ItemUsageContext context, Operation original, ClientPlayerEntity player, Hand hand, BlockHitResult hitResult, @Share("zeroPriority$itemOnBlock") LocalRef zeroPriority$itemOnBlockRef) { + + ItemStack stackInHand = player.getStackInHand(hand); + BlockUsagePhase usePhase = BlockUsagePhase.ITEM; + + zeroPriority$itemOnBlockRef.set(ActionResult.PASS); + if (PreventBlockUsePower.doesPrevent(player, usePhase, hitResult, stackInHand, hand)) { + return ActionResult.FAIL; + } + + Prioritized.CallInstance aipci = new Prioritized.CallInstance<>(); + aipci.add(player, ActionOnBlockUsePower.class, p -> p.shouldExecute(usePhase, PriorityPhase.BEFORE, hitResult, hand, stackInHand)); + + for (int i = aipci.getMaxPriority(); i >= aipci.getMinPriority(); i--) { + + if (!aipci.hasPowers(i)) { continue; } - ActionResult newResult = aobup.executeAction(hitResult, hand); - if ((newResult.isAccepted() && !result.isAccepted()) || (newResult.shouldSwingHand() && !result.shouldSwingHand())) { - result = newResult; + List aips = aipci.getPowers(i); + ActionResult previousResult = ActionResult.PASS; + + for (ActiveInteractionPower aip : aips) { + + ActionResult currentResult = aip instanceof ActionOnBlockUsePower aobup + ? aobup.executeAction(hitResult, hand) + : ActionResult.PASS; + + if (ActionResultUtil.shouldOverride(previousResult, currentResult)) { + previousResult = currentResult; + } + + } + + if (i == 0) { + zeroPriority$itemOnBlockRef.set(previousResult); + continue; } + if (previousResult == ActionResult.PASS) { + continue; + } + + if (previousResult.shouldSwingHand()) { + player.swingHand(hand); + } + + return previousResult; + } - if (result.isAccepted()) { - cir.setReturnValue(result); + return original.call(stack, context); + + } + + @ModifyReturnValue(method = "interactBlockInternal", at = @At("RETURN"), slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getItemCooldownManager()Lnet/minecraft/entity/player/ItemCooldownManager;"))) + private ActionResult apoli$afterItemUseOnBlock(ActionResult original, ClientPlayerEntity player, Hand hand, BlockHitResult hitResult, @Share("zeroPriority$itemOnBlock") LocalRef zeroPriority$itemOnBlockRef) { + + ActionResult zeroPriority$itemOnBlock = zeroPriority$itemOnBlockRef.get(); + ActionResult newResult = ActionResult.PASS; + + if (zeroPriority$itemOnBlock != ActionResult.PASS) { + newResult = zeroPriority$itemOnBlock; + } + + else if (original == ActionResult.PASS) { + + Prioritized.CallInstance aipci = new Prioritized.CallInstance<>(); + aipci.add(player, ActionOnBlockUsePower.class, p -> p.shouldExecute(BlockUsagePhase.ITEM, PriorityPhase.AFTER, hitResult, hand, player.getStackInHand(hand))); + + for (int i = aipci.getMaxPriority(); i >= aipci.getMinPriority(); i--) { + + if (!aipci.hasPowers(i)) { + continue; + } + + List aips = aipci.getPowers(i); + ActionResult previousResult = ActionResult.PASS; + + for (ActiveInteractionPower aip : aips) { + + ActionResult currentResult = aip instanceof ActionOnBlockUsePower aobup + ? aobup.executeAction(hitResult, hand) + : ActionResult.PASS; + + if (ActionResultUtil.shouldOverride(previousResult, currentResult)) { + previousResult = currentResult; + } + + } + + if (previousResult != ActionResult.PASS) { + newResult = previousResult; + break; + } + + } + } + if (newResult.shouldSwingHand()) { + player.swingHand(hand); + } + + return ActionResultUtil.shouldOverride(original, newResult) + ? newResult + : original; + } } diff --git a/src/main/java/io/github/apace100/apoli/mixin/ItemStackMixin.java b/src/main/java/io/github/apace100/apoli/mixin/ItemStackMixin.java index 356181517..a72c2cf06 100644 --- a/src/main/java/io/github/apace100/apoli/mixin/ItemStackMixin.java +++ b/src/main/java/io/github/apace100/apoli/mixin/ItemStackMixin.java @@ -22,9 +22,7 @@ import net.minecraft.item.FoodComponent; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.item.ItemUsageContext; import net.minecraft.sound.SoundEvent; -import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.TypedActionResult; import net.minecraft.util.UseAction; @@ -345,21 +343,6 @@ private void callActionOnUseDuringAfter(Item instance, World world, LivingEntity } - @WrapOperation(method = "useOnBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;useOnBlock(Lnet/minecraft/item/ItemUsageContext;)Lnet/minecraft/util/ActionResult;")) - private ActionResult apoli$consumeUsableOnBlockCustomFood(Item instance, ItemUsageContext context, Operation original) { - - PlayerEntity user = context.getPlayer(); - EdibleItemPower edibleItemPower = this.apoli$getEdiblePower().orElse(null); - - if (user == null || edibleItemPower == null || !user.canConsume(edibleItemPower.getFoodComponent().isAlwaysEdible())) { - return original.call(instance, context); - } - - user.setCurrentHand(context.getHand()); - return ActionResult.CONSUME_PARTIAL; - - } - @WrapOperation(method = "finishUsing", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;finishUsing(Lnet/minecraft/item/ItemStack;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;)Lnet/minecraft/item/ItemStack;")) private ItemStack apoli$finishConsumingCustomFood(Item instance, ItemStack stack, World world, LivingEntity user, Operation original) { diff --git a/src/main/java/io/github/apace100/apoli/mixin/ServerPlayerInteractionManagerMixin.java b/src/main/java/io/github/apace100/apoli/mixin/ServerPlayerInteractionManagerMixin.java index 7ddac917e..9f13f8d11 100644 --- a/src/main/java/io/github/apace100/apoli/mixin/ServerPlayerInteractionManagerMixin.java +++ b/src/main/java/io/github/apace100/apoli/mixin/ServerPlayerInteractionManagerMixin.java @@ -1,17 +1,23 @@ package io.github.apace100.apoli.mixin; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; import com.llamalad7.mixinextras.sugar.ref.LocalRef; import io.github.apace100.apoli.component.PowerHolderComponent; -import io.github.apace100.apoli.power.ActionOnBlockBreakPower; -import io.github.apace100.apoli.power.ActionOnBlockUsePower; -import io.github.apace100.apoli.power.ModifyHarvestPower; -import io.github.apace100.apoli.power.PreventBlockUsePower; +import io.github.apace100.apoli.power.*; +import io.github.apace100.apoli.util.ActionResultUtil; +import io.github.apace100.apoli.util.BlockUsagePhase; +import io.github.apace100.apoli.util.PriorityPhase; import io.github.apace100.apoli.util.SavedBlockPosition; +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerInteractionManager; @@ -28,9 +34,12 @@ import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.List; + @Mixin(ServerPlayerInteractionManager.class) public class ServerPlayerInteractionManagerMixin { @@ -78,18 +87,227 @@ public class ServerPlayerInteractionManagerMixin { aobbp -> aobbp.executeActions(harvestedSuccessfully, pos, apoli$blockBreakDirection)); } - @Inject(method = "interactBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;shouldCancelInteraction()Z"), cancellable = true) - private void apoli$preventBlockInteraction(ServerPlayerEntity player, World world, ItemStack stack, Hand hand, BlockHitResult hitResult, CallbackInfoReturnable cir) { - if (PowerHolderComponent.hasPower(player, PreventBlockUsePower.class, pbup -> pbup.doesPrevent(world, hitResult.getBlockPos()))) { - cir.setReturnValue(ActionResult.FAIL); + @WrapOperation(method = "interactBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;onUse(Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/Hand;Lnet/minecraft/util/hit/BlockHitResult;)Lnet/minecraft/util/ActionResult;")) + private ActionResult apoli$beforeUseBlock(BlockState state, World world, PlayerEntity player, Hand hand, BlockHitResult hitResult, Operation original, @Share("zeroPriority$onBlock") LocalRef zeroPriority$onBlockRef, @Share("zeroPriority$itemOnBlock") LocalRef zeroPriority$itemOnBlockRef) { + + ItemStack stackInHand = player.getStackInHand(hand); + BlockUsagePhase usePhase = BlockUsagePhase.BLOCK; + + zeroPriority$onBlockRef.set(ActionResult.PASS); + zeroPriority$itemOnBlockRef.set(ActionResult.PASS); + + if (PreventBlockUsePower.doesPrevent(player, usePhase, hitResult, stackInHand, hand)) { + return ActionResult.FAIL; + } + + Prioritized.CallInstance aipci = new Prioritized.CallInstance<>(); + aipci.add(player, ActionOnBlockUsePower.class, p -> p.shouldExecute(usePhase, PriorityPhase.BEFORE, hitResult, hand, stackInHand)); + + for (int i = aipci.getMaxPriority(); i >= aipci.getMinPriority(); i--) { + + if (!aipci.hasPowers(i)) { + continue; + } + + List aips = aipci.getPowers(i); + ActionResult previousResult = ActionResult.PASS; + + for (ActiveInteractionPower aip : aips) { + + ActionResult currentResult = aip instanceof ActionOnBlockUsePower aobup + ? aobup.executeAction(hitResult, hand) + : ActionResult.PASS; + + if (ActionResultUtil.shouldOverride(previousResult, currentResult)) { + previousResult = currentResult; + } + + } + + if (i == 0) { + zeroPriority$onBlockRef.set(previousResult); + continue; + } + + if (previousResult == ActionResult.PASS) { + continue; + } + + if (previousResult.shouldSwingHand()) { + player.swingHand(hand); + } + + return previousResult; + + } + + return original.call(state, world, player, hand, hitResult); + + } + + @ModifyReturnValue(method = "interactBlock", at = @At(value = "RETURN", ordinal = 0), slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;getMainHandStack()Lnet/minecraft/item/ItemStack;"))) + private ActionResult apoli$afterUseBlock(ActionResult original, ServerPlayerEntity player, World world, ItemStack stack, Hand hand, BlockHitResult hitResult, @Share("zeroPriority$onBlock") LocalRef zeroPriority$onBlockRef) { + + ActionResult zeroPriority$onBlock = zeroPriority$onBlockRef.get(); + ActionResult newResult = ActionResult.PASS; + + if (zeroPriority$onBlock != ActionResult.PASS) { + newResult = zeroPriority$onBlock; } + + else if (original == ActionResult.PASS) { + + Prioritized.CallInstance aipci = new Prioritized.CallInstance<>(); + aipci.add(player, ActionOnBlockUsePower.class, p -> p.shouldExecute(BlockUsagePhase.BLOCK, PriorityPhase.AFTER, hitResult, hand, stack)); + + for (int i = aipci.getMaxPriority(); i >= aipci.getMinPriority(); i--) { + + if (!aipci.hasPowers(i)) { + continue; + } + + List aips = aipci.getPowers(i); + ActionResult previousResult = ActionResult.PASS; + + for (ActiveInteractionPower aip : aips) { + + ActionResult currentResult = aip instanceof ActionOnBlockUsePower aobup + ? aobup.executeAction(hitResult, hand) + : ActionResult.PASS; + + if (ActionResultUtil.shouldOverride(previousResult, currentResult)) { + previousResult = currentResult; + } + + } + + if (previousResult != ActionResult.PASS) { + newResult = previousResult; + break; + } + + } + + } + + if (newResult.shouldSwingHand()) { + player.swingHand(hand, true); + } + + return ActionResultUtil.shouldOverride(original, newResult) + ? newResult + : original; + } - @Inject(method = "interactBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;copy()Lnet/minecraft/item/ItemStack;")) - private void apoli$executeBlockUseActions(ServerPlayerEntity player, World world, ItemStack stack, Hand hand, BlockHitResult hitResult, CallbackInfoReturnable cir) { - PowerHolderComponent.withPowers(this.player, ActionOnBlockUsePower.class, - aobbp -> aobbp.shouldExecute(hitResult, hand, stack), - aobbp -> aobbp.executeAction(hitResult, hand)); + @WrapOperation(method = "interactBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;useOnBlock(Lnet/minecraft/item/ItemUsageContext;)Lnet/minecraft/util/ActionResult;")) + private ActionResult apoli$beforeItemUseOnBlock(ItemStack stack, ItemUsageContext context, Operation original, ServerPlayerEntity mPlayer, World mWorld, ItemStack mStack, Hand mHand, BlockHitResult mHitResult, @Share("zeroPriority$itemOnBlock") LocalRef zeroPriority$itemOnBlockRef) { + + BlockUsagePhase usePhase = BlockUsagePhase.ITEM; + zeroPriority$itemOnBlockRef.set(ActionResult.PASS); + + if (PreventBlockUsePower.doesPrevent(player, usePhase, mHitResult, mStack, mHand)) { + return ActionResult.FAIL; + } + + Prioritized.CallInstance aipci = new Prioritized.CallInstance<>(); + aipci.add(player, ActionOnBlockUsePower.class, p -> p.shouldExecute(usePhase, PriorityPhase.BEFORE, mHitResult, mHand, mStack)); + + for (int i = aipci.getMaxPriority(); i >= aipci.getMinPriority(); i--) { + + if (!aipci.hasPowers(i)) { + continue; + } + + List aips = aipci.getPowers(i); + ActionResult previousResult = ActionResult.PASS; + + for (ActiveInteractionPower aip : aips) { + + ActionResult currentResult = aip instanceof ActionOnBlockUsePower aobup + ? aobup.executeAction(mHitResult, mHand) + : ActionResult.PASS; + + if (ActionResultUtil.shouldOverride(previousResult, currentResult)) { + previousResult = currentResult; + } + + } + + if (i == 0) { + zeroPriority$itemOnBlockRef.set(previousResult); + continue; + } + + if (previousResult == ActionResult.PASS) { + continue; + } + + if (previousResult.shouldSwingHand()) { + player.swingHand(mHand, true); + } + + return previousResult; + + } + + return original.call(stack, context); + + } + + @ModifyReturnValue(method = "interactBlock", at = @At("RETURN"), slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;getItemCooldownManager()Lnet/minecraft/entity/player/ItemCooldownManager;"))) + private ActionResult apoli$afterItemUseOnBlock(ActionResult original, ServerPlayerEntity player, World world, ItemStack stack, Hand hand, BlockHitResult hitResult, @Share("zeroPriority$itemOnBlock") LocalRef zeroPriority$itemOnBlockRef) { + + ActionResult zeroPriority$itemOnBlock = zeroPriority$itemOnBlockRef.get(); + ActionResult newResult = ActionResult.PASS; + + if (zeroPriority$itemOnBlock != ActionResult.PASS) { + newResult = zeroPriority$itemOnBlock; + } + + else if (original == ActionResult.PASS) { + + Prioritized.CallInstance aipci = new Prioritized.CallInstance<>(); + aipci.add(player, ActionOnBlockUsePower.class, p -> p.shouldExecute(BlockUsagePhase.ITEM, PriorityPhase.AFTER, hitResult, hand, stack)); + + for (int i = aipci.getMaxPriority(); i >= aipci.getMinPriority(); i--) { + + if (!aipci.hasPowers(i)) { + continue; + } + + List aips = aipci.getPowers(i); + ActionResult previousResult = ActionResult.PASS; + + for (ActiveInteractionPower aip : aips) { + + ActionResult currentResult = aip instanceof ActionOnBlockUsePower aobup + ? aobup.executeAction(hitResult, hand) + : ActionResult.PASS; + + if (ActionResultUtil.shouldOverride(previousResult, currentResult)) { + previousResult = currentResult; + } + + } + + if (previousResult != ActionResult.PASS) { + newResult = previousResult; + break; + } + + } + + } + + if (newResult.shouldSwingHand()) { + player.swingHand(hand); + } + + return ActionResultUtil.shouldOverride(original, newResult) + ? newResult + : original; + } } diff --git a/src/main/java/io/github/apace100/apoli/power/ActionOnBlockUsePower.java b/src/main/java/io/github/apace100/apoli/power/ActionOnBlockUsePower.java index a631cac8a..767370fbf 100644 --- a/src/main/java/io/github/apace100/apoli/power/ActionOnBlockUsePower.java +++ b/src/main/java/io/github/apace100/apoli/power/ActionOnBlockUsePower.java @@ -3,6 +3,8 @@ import io.github.apace100.apoli.Apoli; import io.github.apace100.apoli.data.ApoliDataTypes; import io.github.apace100.apoli.power.factory.PowerFactory; +import io.github.apace100.apoli.util.BlockUsagePhase; +import io.github.apace100.apoli.util.PriorityPhase; import io.github.apace100.calio.data.SerializableData; import io.github.apace100.calio.data.SerializableDataTypes; import net.minecraft.block.pattern.CachedBlockPosition; @@ -30,34 +32,42 @@ public class ActionOnBlockUsePower extends ActiveInteractionPower { private final Consumer> blockAction; private final Predicate blockCondition; + private final EnumSet directions; + private final EnumSet usePhases; - public ActionOnBlockUsePower(PowerType type, LivingEntity entity, EnumSet hands, ActionResult actionResult, Predicate> itemCondition, Consumer> heldItemAction, ItemStack itemResult, Consumer> resultItemAction, Consumer entityAction, Predicate blockCondition, EnumSet directions, Consumer> blockAction, int priority) { + public ActionOnBlockUsePower(PowerType type, LivingEntity entity, Consumer entityAction, Consumer> blockAction, Consumer> heldItemAction, Consumer> resultItemAction, Predicate blockCondition, Predicate> itemCondition, ItemStack itemResult, EnumSet directions, EnumSet hands, EnumSet usePhases, ActionResult actionResult, int priority) { super(type, entity, hands, actionResult, itemCondition, heldItemAction, itemResult, resultItemAction, priority); this.entityAction = entityAction; this.blockCondition = blockCondition; this.directions = directions; this.blockAction = blockAction; + this.usePhases = usePhases; } - public boolean shouldExecute(BlockHitResult hitResult, Hand hand, ItemStack heldStack) { + public boolean shouldExecute(BlockUsagePhase usePhase, PriorityPhase priorityPhase, BlockHitResult hitResult, Hand hand, ItemStack heldStack) { return super.shouldExecute(hand, heldStack) + && priorityPhase.test(this.getPriority()) + && usePhases.contains(usePhase) && directions.contains(hitResult.getSide()) && (blockCondition == null || blockCondition.test(new CachedBlockPosition(entity.getWorld(), hitResult.getBlockPos(), true))); } public ActionResult executeAction(BlockHitResult hitResult, Hand hand) { - if(blockAction != null) { - blockAction.accept(Triple.of(entity.getWorld(), hitResult.getBlockPos(), hitResult.getSide())); + if (blockAction != null) { + this.blockAction.accept(Triple.of(entity.getWorld(), hitResult.getBlockPos(), hitResult.getSide())); + } + + if (entityAction != null) { + this.entityAction.accept(entity); } - if(entityAction != null) { - entityAction.accept(entity); + if (entity instanceof PlayerEntity player) { + this.performActorItemStuff(this, player, hand); } - performActorItemStuff(this, (PlayerEntity) entity, hand); - return getActionResult(); + return this.getActionResult(); } @@ -65,31 +75,34 @@ public static PowerFactory createFactory() { return new PowerFactory<>( Apoli.identifier("action_on_block_use"), new SerializableData() - .add("block_condition", ApoliDataTypes.BLOCK_CONDITION, null) .add("entity_action", ApoliDataTypes.ENTITY_ACTION, null) .add("block_action", ApoliDataTypes.BLOCK_ACTION, null) - .add("directions", SerializableDataTypes.DIRECTION_SET, EnumSet.allOf(Direction.class)) - .add("item_condition", ApoliDataTypes.ITEM_CONDITION, null) - .add("hands", SerializableDataTypes.HAND_SET, EnumSet.allOf(Hand.class)) - .add("result_stack", SerializableDataTypes.ITEM_STACK, null) .add("held_item_action", ApoliDataTypes.ITEM_ACTION, null) .add("result_item_action", ApoliDataTypes.ITEM_ACTION, null) + .add("block_condition", ApoliDataTypes.BLOCK_CONDITION, null) + .add("item_condition", ApoliDataTypes.ITEM_CONDITION, null) + .add("result_stack", SerializableDataTypes.ITEM_STACK, null) + .add("directions", SerializableDataTypes.DIRECTION_SET, EnumSet.allOf(Direction.class)) + .add("hands", SerializableDataTypes.HAND_SET, EnumSet.allOf(Hand.class)) + .add("usage_phases", ApoliDataTypes.BLOCK_USAGE_PHASE_SET, EnumSet.allOf(BlockUsagePhase.class)) .add("action_result", SerializableDataTypes.ACTION_RESULT, ActionResult.SUCCESS) .add("priority", SerializableDataTypes.INT, 0), data -> (powerType, livingEntity) -> new ActionOnBlockUsePower( powerType, livingEntity, - data.get("hands"), - data.get("action_result"), - data.get("item_condition"), + data.get("entity_action"), + data.get("block_action"), data.get("held_item_action"), - data.get("result_stack"), data.get("result_item_action"), - data.get("entity_action"), data.get("block_condition"), + data.get("item_condition"), + data.get("result_stack"), data.get("directions"), - data.get("block_action"), - data.get("priority")) + data.get("hands"), + data.get("usage_phases"), + data.get("action_result"), + data.get("priority") + ) ).allowCondition(); } } diff --git a/src/main/java/io/github/apace100/apoli/power/PreventBlockUsePower.java b/src/main/java/io/github/apace100/apoli/power/PreventBlockUsePower.java index 2291b3984..e3e1a9f41 100644 --- a/src/main/java/io/github/apace100/apoli/power/PreventBlockUsePower.java +++ b/src/main/java/io/github/apace100/apoli/power/PreventBlockUsePower.java @@ -3,36 +3,112 @@ import io.github.apace100.apoli.Apoli; import io.github.apace100.apoli.data.ApoliDataTypes; import io.github.apace100.apoli.power.factory.PowerFactory; +import io.github.apace100.apoli.util.BlockUsagePhase; import io.github.apace100.calio.data.SerializableData; +import io.github.apace100.calio.data.SerializableDataTypes; import net.minecraft.block.pattern.CachedBlockPosition; +import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.StackReference; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.Pair; +import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.WorldView; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import org.apache.commons.lang3.tuple.Triple; +import java.util.EnumSet; +import java.util.function.Consumer; import java.util.function.Predicate; -public class PreventBlockUsePower extends Power { +public class PreventBlockUsePower extends ActiveInteractionPower { private final Predicate blockCondition; - public PreventBlockUsePower(PowerType type, LivingEntity entity, Predicate blockCondition) { - super(type, entity); + private final Consumer entityAction; + private final Consumer> blockAction; + + private final EnumSet directions; + private final EnumSet usePhases; + + public PreventBlockUsePower(PowerType type, LivingEntity entity, Consumer entityAction, Consumer> blockAction, Consumer> resultItemAction, Consumer> heldItemAction, Predicate blockCondition, Predicate> itemCondition, ItemStack resultStack, EnumSet directions, EnumSet hands, EnumSet usePhases, int priority) { + super(type, entity, hands, ActionResult.FAIL, itemCondition, heldItemAction, resultStack, resultItemAction, priority); this.blockCondition = blockCondition; + this.entityAction = entityAction; + this.blockAction = blockAction; + this.directions = directions; + this.usePhases = usePhases; + } + + public void executeActions(BlockHitResult hitResult, Hand hand) { + + if (blockAction != null) { + this.blockAction.accept(Triple.of(entity.getWorld(), hitResult.getBlockPos(), hitResult.getSide())); + } + + if (entityAction != null) { + this.entityAction.accept(entity); + } + + if (entity instanceof PlayerEntity player) { + this.performActorItemStuff(this, player, hand); + } + + } + + public boolean doesPrevent(BlockUsagePhase usePhase, BlockHitResult hitResult, ItemStack heldStack, Hand hand) { + return super.shouldExecute(hand, heldStack) + && usePhases.contains(usePhase) + && directions.contains(hitResult.getSide()) + && (blockCondition == null || blockCondition.test(new CachedBlockPosition(entity.getWorld(), hitResult.getBlockPos(), true))); } - public boolean doesPrevent(WorldView world, BlockPos pos) { - return blockCondition == null || blockCondition.test(new CachedBlockPosition(world, pos, true)); + public static boolean doesPrevent(Entity holder, BlockUsagePhase usePhase, BlockHitResult hitResult, ItemStack heldStack, Hand hand) { + + CallInstance aipci = new CallInstance<>(); + aipci.add(holder, PreventBlockUsePower.class, p -> p.doesPrevent(usePhase, hitResult, heldStack, hand)); + + for (int i = aipci.getMaxPriority(); i >= aipci.getMinPriority(); i--) { + aipci.forEach(i, p -> ((PreventBlockUsePower) p).executeActions(hitResult, hand)); + } + + return !aipci.isEmpty(); + } public static PowerFactory createFactory() { return new PowerFactory<>( Apoli.identifier("prevent_block_use"), new SerializableData() - .add("block_condition", ApoliDataTypes.BLOCK_CONDITION, null), + .add("entity_action", ApoliDataTypes.ENTITY_ACTION, null) + .add("block_action", ApoliDataTypes.BLOCK_ACTION, null) + .add("result_item_action", ApoliDataTypes.ITEM_ACTION, null) + .add("held_item_action", ApoliDataTypes.ITEM_ACTION, null) + .add("block_condition", ApoliDataTypes.BLOCK_CONDITION, null) + .add("item_condition", ApoliDataTypes.ITEM_CONDITION, null) + .add("result_stack", SerializableDataTypes.ITEM_STACK, null) + .add("directions", SerializableDataTypes.DIRECTION_SET, EnumSet.allOf(Direction.class)) + .add("hands", SerializableDataTypes.HAND_SET, EnumSet.allOf(Hand.class)) + .add("usage_phases", ApoliDataTypes.BLOCK_USAGE_PHASE_SET, EnumSet.allOf(BlockUsagePhase.class)) + .add("priority", SerializableDataTypes.INT, 0), data -> (powerType, livingEntity) -> new PreventBlockUsePower( powerType, livingEntity, - data.get("block_condition") + data.get("entity_action"), + data.get("block_action"), + data.get("result_item_action"), + data.get("held_item_action"), + data.get("block_condition"), + data.get("item_condition"), + data.get("result_stack"), + data.get("directions"), + data.get("hands"), + data.get("usage_phases"), + data.get("priority") ) ).allowCondition(); } diff --git a/src/main/java/io/github/apace100/apoli/power/Prioritized.java b/src/main/java/io/github/apace100/apoli/power/Prioritized.java index d1e015f32..0c5880423 100644 --- a/src/main/java/io/github/apace100/apoli/power/Prioritized.java +++ b/src/main/java/io/github/apace100/apoli/power/Prioritized.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.stream.Stream; public interface Prioritized> { @@ -71,6 +70,10 @@ public void add(T t) { } + public boolean isEmpty() { + return buckets.isEmpty(); + } + } } diff --git a/src/main/java/io/github/apace100/apoli/util/ActionResultUtil.java b/src/main/java/io/github/apace100/apoli/util/ActionResultUtil.java new file mode 100644 index 000000000..b26192ecc --- /dev/null +++ b/src/main/java/io/github/apace100/apoli/util/ActionResultUtil.java @@ -0,0 +1,12 @@ +package io.github.apace100.apoli.util; + +import net.minecraft.util.ActionResult; + +public class ActionResultUtil { + + public static boolean shouldOverride(ActionResult oldResult, ActionResult newResult) { + return (newResult.isAccepted() && !oldResult.isAccepted()) + || (newResult.shouldSwingHand() && !oldResult.shouldSwingHand()); + } + +} diff --git a/src/main/java/io/github/apace100/apoli/util/BlockUsagePhase.java b/src/main/java/io/github/apace100/apoli/util/BlockUsagePhase.java new file mode 100644 index 000000000..3e9816f06 --- /dev/null +++ b/src/main/java/io/github/apace100/apoli/util/BlockUsagePhase.java @@ -0,0 +1,6 @@ +package io.github.apace100.apoli.util; + +public enum BlockUsagePhase { + BLOCK, + ITEM +} diff --git a/src/main/java/io/github/apace100/apoli/util/PriorityPhase.java b/src/main/java/io/github/apace100/apoli/util/PriorityPhase.java new file mode 100644 index 000000000..449ab79c7 --- /dev/null +++ b/src/main/java/io/github/apace100/apoli/util/PriorityPhase.java @@ -0,0 +1,25 @@ +package io.github.apace100.apoli.util; + +import java.util.function.IntPredicate; + +public enum PriorityPhase implements IntPredicate { + + BEFORE { + + @Override + public boolean test(int value) { + return value >= 0; + } + + }, + + AFTER { + + @Override + public boolean test(int value) { + return value < 0; + } + + } + +}