diff --git a/src/main/java/net/modfest/fireblanket/Fireblanket.java b/src/main/java/net/modfest/fireblanket/Fireblanket.java index 1885d81..1c4290e 100644 --- a/src/main/java/net/modfest/fireblanket/Fireblanket.java +++ b/src/main/java/net/modfest/fireblanket/Fireblanket.java @@ -7,6 +7,7 @@ import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; +import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; diff --git a/src/main/java/net/modfest/fireblanket/FireblanketConstants.java b/src/main/java/net/modfest/fireblanket/FireblanketConstants.java new file mode 100644 index 0000000..dd1a109 --- /dev/null +++ b/src/main/java/net/modfest/fireblanket/FireblanketConstants.java @@ -0,0 +1,26 @@ +package net.modfest.fireblanket; + +import net.minecraft.block.Block; +import net.minecraft.entity.EntityType; +import net.minecraft.item.Item; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; + +public class FireblanketConstants { + public static final String MOD_ID = "fireblanket"; + + public static final TagKey BLOCK_INTERACTION_RESTRICTED = tag(RegistryKeys.BLOCK, "block_interaction_restricted"); + public static final TagKey ITEM_INTERACTION_RESTRICTED = tag(RegistryKeys.ITEM, "item_interaction_restricted"); + public static final TagKey> ENTITY_INTERACTION_RESTRICTED = tag(RegistryKeys.ENTITY_TYPE, "entity_interaction_restricted"); + + public static final TagKey> ENTITY_ATTACK_RESTRICTED = tag(RegistryKeys.ENTITY_TYPE, "entity_attack_restricted"); + + public static Identifier id(String id) { return Identifier.of(MOD_ID, id); } + + private static TagKey tag(RegistryKey> reg, String id) { + return TagKey.of(reg, id(id)); + } +} diff --git a/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinEggItem.java b/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinEggItem.java deleted file mode 100644 index b172f51..0000000 --- a/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinEggItem.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.modfest.fireblanket.mixin.adventure_fix; - -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.EggItem; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Hand; -import net.minecraft.util.TypedActionResult; -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; - -@Mixin(EggItem.class) -public class MixinEggItem { - @Inject(method = "use", at = @At("HEAD"), cancellable = true) - private void fireblanket$spamPrevention(World world, PlayerEntity user, Hand hand, CallbackInfoReturnable> cir) { - if (!user.getAbilities().allowModifyWorld) { - cir.setReturnValue(TypedActionResult.pass(user.getStackInHand(hand))); - } - } -} diff --git a/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinFlowerPotBlock.java b/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinFlowerPotBlock.java deleted file mode 100644 index 4866e07..0000000 --- a/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinFlowerPotBlock.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.modfest.fireblanket.mixin.adventure_fix; - -import net.minecraft.block.BlockState; -import net.minecraft.block.FlowerPotBlock; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.util.ActionResult; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -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; - -@Mixin(FlowerPotBlock.class) -public class MixinFlowerPotBlock { - - @Inject(at = @At("HEAD"), method = "onUse", cancellable = true) - public void fireblanket$noStealPotsInAdventure(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit, - CallbackInfoReturnable ci) { - if (!player.canModifyBlocks()) { - ci.setReturnValue(ActionResult.FAIL); - } - } - -} diff --git a/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinItemStack.java b/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinItemStack.java new file mode 100644 index 0000000..85c491b --- /dev/null +++ b/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinItemStack.java @@ -0,0 +1,43 @@ +package net.modfest.fireblanket.mixin.adventure_fix; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.world.World; +import net.modfest.fireblanket.FireblanketConstants; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ItemStack.class) +public abstract class MixinItemStack { + @Shadow public abstract boolean isIn(TagKey tag); + + // corner case: item is allowed, but block interact is not allowed. + // preventing use on blocks here might prevent non-interacting items + // like rulers from simply using the blockpos. + @Inject(method = "useOnBlock", at = @At("HEAD"), cancellable = true) + private void fireblanket$filterItemUseOnBlockByTag(ItemUsageContext context, CallbackInfoReturnable ci) { + PlayerEntity player = context.getPlayer(); + if (player == null) return; + if (!player.getAbilities().allowModifyWorld && this.isIn(FireblanketConstants.ITEM_INTERACTION_RESTRICTED)) { + ci.setReturnValue(ActionResult.FAIL); + ci.cancel(); + } + } + + @Inject(method = "use", at = @At("HEAD"), cancellable = true) + private void fireblanket$filterItemUseByTag(World world, PlayerEntity user, Hand hand, CallbackInfoReturnable> ci) { + if (!user.getAbilities().allowModifyWorld && this.isIn(FireblanketConstants.ITEM_INTERACTION_RESTRICTED)) { + ci.setReturnValue(TypedActionResult.fail(user.getStackInHand(hand))); + ci.cancel(); + } + } +} diff --git a/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinPlayerInteractEntityC2SPacketHandler.java b/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinPlayerInteractEntityC2SPacketHandler.java new file mode 100644 index 0000000..ae99c98 --- /dev/null +++ b/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinPlayerInteractEntityC2SPacketHandler.java @@ -0,0 +1,55 @@ +package net.modfest.fireblanket.mixin.adventure_fix; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.util.Hand; +import net.modfest.fireblanket.FireblanketConstants; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Vanilla uses an anonymous class to process entity interaction serverside, so there's not + * really a better place to do this. Entity#interactAt is only called here, + * so we might as well filter both. + * + * While the client has an easy way to do the check in ClientPlayerInteractionManager, + * the ServerPlayerInteractionManager does not. /shrug + */ +@Mixin(targets = "net.minecraft.server.network.ServerPlayNetworkHandler$1") +public class MixinPlayerInteractEntityC2SPacketHandler { + @Shadow @Final private ServerPlayNetworkHandler field_28963; // outer this + @Shadow @Final private Entity field_28962; // target entity captured variable + + @Inject( + method = "Lnet/minecraft/server/network/ServerPlayNetworkHandler$1;processInteract(Lnet/minecraft/util/Hand;Lnet/minecraft/server/network/ServerPlayNetworkHandler$Interaction;)V", + at = @At("HEAD"), + cancellable = true + ) + private void fireblanket$filterEntityInteractByTag(Hand hand, ServerPlayNetworkHandler.Interaction action, CallbackInfo ci) { + PlayerEntity player = field_28963.player; + ItemStack stack = player.getStackInHand(hand); + if (!player.getAbilities().allowModifyWorld && ( + stack.isIn(FireblanketConstants.ITEM_INTERACTION_RESTRICTED) || + field_28962.getType().isIn(FireblanketConstants.ENTITY_INTERACTION_RESTRICTED))) { + ci.cancel(); + } + } + + @Inject( + method = "Lnet/minecraft/server/network/ServerPlayNetworkHandler$1;attack()V", + at = @At("HEAD"), + cancellable = true + ) + private void fireblanket$filterAttackEntityByTag(CallbackInfo ci) { + PlayerEntity player = field_28963.player; + if (!player.getAbilities().allowModifyWorld && field_28962.getType().isIn(FireblanketConstants.ENTITY_ATTACK_RESTRICTED)) { + ci.cancel(); + } + } +} diff --git a/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinServerPlayerInteractionManager.java b/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinServerPlayerInteractionManager.java new file mode 100644 index 0000000..ad9e2df --- /dev/null +++ b/src/main/java/net/modfest/fireblanket/mixin/adventure_fix/MixinServerPlayerInteractionManager.java @@ -0,0 +1,46 @@ +package net.modfest.fireblanket.mixin.adventure_fix; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.network.ServerPlayerInteractionManager; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.ItemActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.world.World; +import net.modfest.fireblanket.FireblanketConstants; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(ServerPlayerInteractionManager.class) +public class MixinServerPlayerInteractionManager { + @WrapOperation( + method = "interactBlock", + at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;onUseWithItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/Hand;Lnet/minecraft/util/hit/BlockHitResult;)Lnet/minecraft/util/ItemActionResult;") + ) + private ItemActionResult fireblanket$filterItemBlockInteractByTag(BlockState blockState, ItemStack stack, World world, PlayerEntity player, Hand hand, BlockHitResult hitResult, Operation op) { + if (!player.getAbilities().allowModifyWorld) { + if (stack.isIn(FireblanketConstants.ITEM_INTERACTION_RESTRICTED)) { + return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; + } + if (blockState.isIn(FireblanketConstants.BLOCK_INTERACTION_RESTRICTED)) { + return ItemActionResult.FAIL; + } + } + return op.call(blockState, stack, world, player, hand, hitResult); + } + + @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/hit/BlockHitResult;)Lnet/minecraft/util/ActionResult;") + ) + private ActionResult fireblanket$filterBlockInteractByTag(BlockState blockState, World world, PlayerEntity player, BlockHitResult hitResult, Operation op) { + if (!player.getAbilities().allowModifyWorld && blockState.isIn(FireblanketConstants.BLOCK_INTERACTION_RESTRICTED)) { + return ActionResult.FAIL; + } + return op.call(blockState, world, player, hitResult); + } +} diff --git a/src/main/java/net/modfest/fireblanket/mixin/client/adventure_fix/MixinClientPlayerInteractionManager.java b/src/main/java/net/modfest/fireblanket/mixin/client/adventure_fix/MixinClientPlayerInteractionManager.java new file mode 100644 index 0000000..12aa279 --- /dev/null +++ b/src/main/java/net/modfest/fireblanket/mixin/client/adventure_fix/MixinClientPlayerInteractionManager.java @@ -0,0 +1,89 @@ +package net.modfest.fireblanket.mixin.client.adventure_fix; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.block.BlockState; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.ItemActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.world.World; +import net.modfest.fireblanket.FireblanketConstants; +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.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ClientPlayerInteractionManager.class) +public class MixinClientPlayerInteractionManager { + @Inject( + method = "interactEntity", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;syncSelectedSlot()V", shift = At.Shift.AFTER), + cancellable = true) + private void fireblanket$filterEntityInteractByTag(PlayerEntity player, Entity entity, Hand hand, CallbackInfoReturnable ci) { + if (!player.getAbilities().allowModifyWorld && ( + entity.getType().isIn(FireblanketConstants.ENTITY_INTERACTION_RESTRICTED) || + player.getStackInHand(hand).isIn(FireblanketConstants.ITEM_INTERACTION_RESTRICTED))) { + ci.setReturnValue(ActionResult.FAIL); + ci.cancel(); + } + } + + @Inject( + method = "interactEntityAtLocation", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;syncSelectedSlot()V", shift = At.Shift.AFTER), + cancellable = true + ) + private void fireblanket$filterEntityInteractAtLocationByTag(PlayerEntity player, Entity entity, EntityHitResult hitResult, Hand hand, CallbackInfoReturnable ci) { + if (!player.getAbilities().allowModifyWorld && ( + entity.getType().isIn(FireblanketConstants.ENTITY_INTERACTION_RESTRICTED) || + player.getStackInHand(hand).isIn(FireblanketConstants.ITEM_INTERACTION_RESTRICTED))) { + ci.setReturnValue(ActionResult.FAIL); + ci.cancel(); + } + } + + @WrapOperation( + method = "interactBlockInternal", + at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;onUseWithItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/Hand;Lnet/minecraft/util/hit/BlockHitResult;)Lnet/minecraft/util/ItemActionResult;") + ) + private ItemActionResult fireblanket$filterItemBlockInteractByTag(BlockState blockState, ItemStack stack, World world, PlayerEntity player, Hand hand, BlockHitResult hitResult, Operation op) { + if (!player.getAbilities().allowModifyWorld) { + if (stack.isIn(FireblanketConstants.ITEM_INTERACTION_RESTRICTED)) { + return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; + } + if (blockState.isIn(FireblanketConstants.BLOCK_INTERACTION_RESTRICTED)) { + return ItemActionResult.FAIL; + } + } + return op.call(blockState, stack, world, player, hand, hitResult); + } + + @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/hit/BlockHitResult;)Lnet/minecraft/util/ActionResult;") + ) + private ActionResult fireblanket$filterBlockInteractByTag(BlockState blockState, World world, PlayerEntity player, BlockHitResult hitResult, Operation op) { + if (!player.getAbilities().allowModifyWorld && blockState.isIn(FireblanketConstants.BLOCK_INTERACTION_RESTRICTED)) { + return ActionResult.FAIL; + } + return op.call(blockState, world, player, hitResult); + } + + @Inject( + method = "attackEntity", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;syncSelectedSlot()V", shift = At.Shift.AFTER), + cancellable = true + ) + private void fireblanket$filterAttackEntityByTag(PlayerEntity player, Entity target, CallbackInfo ci) { + if (!player.getAbilities().allowModifyWorld && target.getType().isIn(FireblanketConstants.ENTITY_ATTACK_RESTRICTED)) { + ci.cancel(); + } + } +} diff --git a/src/main/resources/data/fireblanket/tags/block/block_interaction_restricted.json b/src/main/resources/data/fireblanket/tags/block/block_interaction_restricted.json new file mode 100644 index 0000000..6e82d59 --- /dev/null +++ b/src/main/resources/data/fireblanket/tags/block/block_interaction_restricted.json @@ -0,0 +1,17 @@ +{ + "values": [ + "#minecraft:trapdoors", + "#minecraft:shulker_boxes", + "#minecraft:fence_gates", + "minecraft:chest", + "minecraft:trapped_chest", + "minecraft:chiseled_bookshelf", + "minecraft:repeater", + "minecraft:comparator", + "minecraft:daylight_detector", + "minecraft:dispenser", + "minecraft:dropper", + "minecraft:hopper", + "minecraft:flower_pot" + ] +} diff --git a/src/main/resources/data/fireblanket/tags/entity_type/entity_attack_restricted.json b/src/main/resources/data/fireblanket/tags/entity_type/entity_attack_restricted.json new file mode 100644 index 0000000..35f6921 --- /dev/null +++ b/src/main/resources/data/fireblanket/tags/entity_type/entity_attack_restricted.json @@ -0,0 +1,7 @@ +{ + "values": [ + "minecraft:item_frame", + "minecraft:painting", + "minecraft:sheep" + ] +} diff --git a/src/main/resources/data/fireblanket/tags/entity_type/entity_interaction_restricted.json b/src/main/resources/data/fireblanket/tags/entity_type/entity_interaction_restricted.json new file mode 100644 index 0000000..57c164a --- /dev/null +++ b/src/main/resources/data/fireblanket/tags/entity_type/entity_interaction_restricted.json @@ -0,0 +1,6 @@ +{ + "values": [ + "minecraft:item_frame", + "minecraft:leash_knot" + ] +} diff --git a/src/main/resources/data/fireblanket/tags/item/item_interaction_restricted.json b/src/main/resources/data/fireblanket/tags/item/item_interaction_restricted.json new file mode 100644 index 0000000..20c9ff4 --- /dev/null +++ b/src/main/resources/data/fireblanket/tags/item/item_interaction_restricted.json @@ -0,0 +1,6 @@ +{ + "values": [ + "minecraft:shears", + "minecraft:egg" + ] +} diff --git a/src/main/resources/fireblanket.accesswidener b/src/main/resources/fireblanket.accesswidener index a1dd7a1..828339b 100644 --- a/src/main/resources/fireblanket.accesswidener +++ b/src/main/resources/fireblanket.accesswidener @@ -3,6 +3,7 @@ accessWidener v2 named accessible class net/minecraft/client/render/chunk/ChunkBuilder$BuiltChunk$RebuildTask accessible class net/minecraft/server/world/ServerChunkManager$MainThreadExecutor +accessible class net/minecraft/server/network/ServerPlayNetworkHandler$Interaction accessible method net/minecraft/server/world/ServerChunkLoadingManager entryIterator ()Ljava/lang/Iterable; accessible method net/minecraft/world/storage/ChunkCompressionFormat (ILjava/lang/String;Lnet/minecraft/world/storage/ChunkCompressionFormat$Wrapper;Lnet/minecraft/world/storage/ChunkCompressionFormat$Wrapper;)V diff --git a/src/main/resources/fireblanket.mixins.json b/src/main/resources/fireblanket.mixins.json index 94bb12a..d029124 100644 --- a/src/main/resources/fireblanket.mixins.json +++ b/src/main/resources/fireblanket.mixins.json @@ -9,8 +9,9 @@ "accessor.EntityTypeAccessor", "accessor.ServerChunkManagerAccessor", "accessor.ServerLoginNetworkHandlerAccessor", - "adventure_fix.MixinEggItem", - "adventure_fix.MixinFlowerPotBlock", + "adventure_fix.MixinItemStack", + "adventure_fix.MixinPlayerInteractEntityC2SPacketHandler", + "adventure_fix.MixinServerPlayerInteractionManager", "ai.MixinTemptGoal", "be_sync.MixinBlockEntity", "be_sync.MixinChunkHolder", @@ -57,6 +58,7 @@ "client.MixinClientPlayNetworkHandler", "client.MixinRenderSystem", "client.MixinSignBlockEntityRenderer", + "client.adventure_fix.MixinClientPlayerInteractionManager", "client.be_masking.MixinBlockEntityRenderDispatcher", "client.be_masking.MixinRebuildTask", "client.be_masking.sodium.MixinChunkRenderRebuildTask",