mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 13:42:59 +00:00 
			
		
		
		
	Allow placing items against some blocks
We define a tag which allows specifying which blocks can be used. Right now this is is just cauldrons and hives, as they have "placing into" semantics. Closes #1305. Many thanks to Lindsay-Needs-Sleep for their initial work on this! Fixes #1008. I believe also fixes #854.
This commit is contained in:
		| @@ -14,6 +14,8 @@ import net.minecraft.world.item.Item; | ||||
| 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.state.BlockState; | ||||
| import net.minecraft.world.phys.BlockHitResult; | ||||
| 
 | ||||
| /** | ||||
|  * Tags provided by ComputerCraft. | ||||
| @@ -65,6 +67,12 @@ public class ComputerCraftTags { | ||||
|          */ | ||||
|         public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable"); | ||||
| 
 | ||||
|         /** | ||||
|          * Block which can be {@linkplain BlockState#use(Level, Player, InteractionHand, BlockHitResult) used} when | ||||
|          * calling {@code turtle.place()}. | ||||
|          */ | ||||
|         public static final TagKey<Block> TURTLE_CAN_USE = make("turtle_can_use"); | ||||
| 
 | ||||
|         private static TagKey<Block> make(String name) { | ||||
|             return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name)); | ||||
|         } | ||||
|   | ||||
| @@ -57,6 +57,8 @@ class TagProvider { | ||||
| 
 | ||||
|         tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB); | ||||
| 
 | ||||
|         tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE).addTag(BlockTags.CAULDRONS).addTag(BlockTags.BEEHIVES); | ||||
| 
 | ||||
|         // Make all blocks aside from command computer mineable. | ||||
|         tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add( | ||||
|             ModRegistry.Blocks.COMPUTER_NORMAL.get(), | ||||
|   | ||||
| @@ -53,6 +53,7 @@ import java.util.List; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| import java.util.function.Predicate; | ||||
| 
 | ||||
| /** | ||||
|  * This extends {@linkplain dan200.computercraft.impl.PlatformHelper the API's loader abstraction layer}, adding | ||||
| @@ -385,13 +386,15 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper | ||||
|      * Place an item against a block. | ||||
|      * <p> | ||||
|      * Implementations should largely mirror {@link ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)} | ||||
|      * (including any loader-specific modifications), except they should skip the call to {@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)}. | ||||
|      * (including any loader-specific modifications), except the call to {@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)} | ||||
|      * should only be evaluated when {@code canUseBlock} evaluates to true. | ||||
|      * | ||||
|      * @param player      The player which is placing this item. | ||||
|      * @param stack       The item to place. | ||||
|      * @param hit         The collision with the block we're placing against. | ||||
|      * @param canUseBlock Test whether the block should be interacted with first. | ||||
|      * @return Whether any interaction occurred. | ||||
|      * @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult) | ||||
|      */ | ||||
|     InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit); | ||||
|     InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock); | ||||
| } | ||||
|   | ||||
| @@ -22,6 +22,7 @@ import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.item.*; | ||||
| import net.minecraft.world.item.context.BlockPlaceContext; | ||||
| import net.minecraft.world.item.context.UseOnContext; | ||||
| @@ -29,6 +30,7 @@ import net.minecraft.world.level.Level; | ||||
| import net.minecraft.world.level.block.Block; | ||||
| import net.minecraft.world.level.block.entity.BlockEntity; | ||||
| import net.minecraft.world.level.block.entity.SignBlockEntity; | ||||
| import net.minecraft.world.level.block.state.BlockState; | ||||
| import net.minecraft.world.phys.BlockHitResult; | ||||
| import net.minecraft.world.phys.EntityHitResult; | ||||
| import net.minecraft.world.phys.Vec3; | ||||
| @@ -153,7 +155,7 @@ public class TurtlePlaceCommand implements TurtleCommand { | ||||
| 
 | ||||
|     private static boolean deployOnBlock( | ||||
|         ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side, | ||||
|         @Nullable Object[] extraArguments, boolean allowReplace, @Nullable ErrorMessage outErrorMessage | ||||
|         @Nullable Object[] extraArguments, boolean adjacent, @Nullable ErrorMessage outErrorMessage | ||||
|     ) { | ||||
|         // Re-orient the fake player | ||||
|         var playerDir = side.getOpposite(); | ||||
| @@ -169,14 +171,14 @@ public class TurtlePlaceCommand implements TurtleCommand { | ||||
|         // Check if there's something suitable to place onto | ||||
|         var hit = new BlockHitResult(new Vec3(hitX, hitY, hitZ), side, position, false); | ||||
|         var context = new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit); | ||||
|         if (!canDeployOnBlock(new BlockPlaceContext(context), turtle, turtlePlayer, position, side, allowReplace, outErrorMessage)) { | ||||
|         if (!canDeployOnBlock(new BlockPlaceContext(context), turtle, turtlePlayer, position, side, adjacent, outErrorMessage)) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         var item = stack.getItem(); | ||||
|         var existingTile = turtle.getLevel().getBlockEntity(position); | ||||
| 
 | ||||
|         var placed = doDeployOnBlock(stack, turtlePlayer, hit).consumesAction(); | ||||
|         var placed = doDeployOnBlock(stack, turtlePlayer, hit, adjacent).consumesAction(); | ||||
| 
 | ||||
|         // Set text on signs | ||||
|         if (placed && item instanceof SignItem && extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String message) { | ||||
| @@ -198,18 +200,23 @@ public class TurtlePlaceCommand implements TurtleCommand { | ||||
|      * @param stack        The stack the player is using. | ||||
|      * @param turtlePlayer The player which represents the turtle | ||||
|      * @param hit          Where the block we're placing against was clicked. | ||||
|      * @param adjacent     If the block is directly adjacent to the turtle, and so can be interacted with via | ||||
|      *                     {@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)}. | ||||
|      * @return If this item was deployed. | ||||
|      */ | ||||
|     private static InteractionResult doDeployOnBlock( | ||||
|         ItemStack stack, TurtlePlayer turtlePlayer, BlockHitResult hit | ||||
|     ) { | ||||
|         var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit); | ||||
|     private static InteractionResult doDeployOnBlock(ItemStack stack, TurtlePlayer turtlePlayer, BlockHitResult hit, boolean adjacent) { | ||||
|         var result = PlatformHelper.get().useOn( | ||||
|             turtlePlayer.player(), stack, hit, | ||||
|             adjacent ? x -> x.is(ComputerCraftTags.Blocks.TURTLE_CAN_USE) : x -> false | ||||
|         ); | ||||
|         if (result != InteractionResult.PASS) return result; | ||||
| 
 | ||||
|         var level = turtlePlayer.player().level; | ||||
| 
 | ||||
|         // We special case some items which we allow to place "normally". Yes, this is very ugly. | ||||
|         var item = stack.getItem(); | ||||
|         if (item instanceof BucketItem || item instanceof PlaceOnWaterBlockItem || stack.is(ComputerCraftTags.Items.TURTLE_CAN_PLACE)) { | ||||
|             return turtlePlayer.player().gameMode.useItem(turtlePlayer.player(), turtlePlayer.player().level, stack, InteractionHand.MAIN_HAND); | ||||
|             return turtlePlayer.player().gameMode.useItem(turtlePlayer.player(), level, stack, InteractionHand.MAIN_HAND); | ||||
|         } | ||||
| 
 | ||||
|         return InteractionResult.PASS; | ||||
|   | ||||
| @@ -55,6 +55,7 @@ import java.util.List; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| import java.util.function.Predicate; | ||||
| 
 | ||||
| @AutoService({ PlatformHelper.class, dan200.computercraft.impl.PlatformHelper.class, ComputerCraftAPIService.class }) | ||||
| public class TestPlatformHelper extends AbstractComputerCraftAPI implements PlatformHelper { | ||||
| @@ -202,7 +203,7 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { | ||||
|     public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock) { | ||||
|         throw new UnsupportedOperationException("Cannot interact with the world inside tests"); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -185,7 +185,7 @@ class Turtle_Test { | ||||
|      * | ||||
|      * Currently not required as turtles can no longer right-click cauldrons. | ||||
|      */ | ||||
|     @GameTest(required = false) | ||||
|     @GameTest | ||||
|     fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence { | ||||
|         thenOnComputer { | ||||
|             val details = getTurtleItemDetail(1, true) | ||||
|   | ||||
| @@ -23,6 +23,7 @@ import net.minecraft.gametest.framework.GameTestSequence | ||||
| import org.apache.logging.log4j.LogManager | ||||
| import java.io.InputStream | ||||
| import java.util.* | ||||
| import java.util.concurrent.CancellationException | ||||
| import java.util.concurrent.ConcurrentLinkedDeque | ||||
| import java.util.concurrent.atomic.AtomicReference | ||||
| 
 | ||||
| @@ -46,7 +47,7 @@ object ManagedComputers : ILuaMachine.Factory { | ||||
|                 task() | ||||
|                 monitor.result.set(Result.success(Unit)) | ||||
|             } catch (e: Throwable) { | ||||
|                 if (e !is AssertionError) LOGGER.error("Computer $label failed", e) | ||||
|                 if (e !is AssertionError && e !is CancellationException) LOGGER.error("Computer $label failed", e) | ||||
|                 monitor.result.set(Result.failure(e)) | ||||
|                 throw e | ||||
|             } finally { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| { | ||||
|     DataVersion: 2730, | ||||
|     DataVersion: 3218, | ||||
|     size: [3, 3, 3], | ||||
|     data: [ | ||||
|         {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, | ||||
| @@ -15,7 +15,7 @@ | ||||
|         {pos: [0, 1, 1], state: "minecraft:air"}, | ||||
|         {pos: [0, 1, 2], state: "minecraft:air"}, | ||||
|         {pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:turtle_normal", tag: {Color: 13388876, ComputerId: 0, display: {Name: '{"text":"Clean turtle"}'}}}], Label: "turtle_test.cleaned_with_cauldrons", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}}, | ||||
|         {pos: [1, 1, 1], state: "minecraft:cauldron{level:3}"}, | ||||
|         {pos: [1, 1, 1], state: "minecraft:water_cauldron{level:3}"}, | ||||
|         {pos: [1, 1, 2], state: "minecraft:air"}, | ||||
|         {pos: [2, 1, 0], state: "minecraft:air"}, | ||||
|         {pos: [2, 1, 1], state: "minecraft:air"}, | ||||
| @@ -33,8 +33,8 @@ | ||||
|     entities: [], | ||||
|     palette: [ | ||||
|         "minecraft:polished_andesite", | ||||
|         "computercraft:turtle_normal{facing:south,waterlogged:false}", | ||||
|         "minecraft:air", | ||||
|         "minecraft:cauldron{level:3}" | ||||
|         "minecraft:water_cauldron{level:3}", | ||||
|         "computercraft:turtle_normal{facing:south,waterlogged:false}" | ||||
|     ] | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								projects/fabric/src/generated/resources/data/computercraft/tags/blocks/turtle_can_use.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								projects/fabric/src/generated/resources/data/computercraft/tags/blocks/turtle_can_use.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {"replace": false, "values": ["#minecraft:cauldrons", "#minecraft:beehives"]} | ||||
| @@ -77,10 +77,7 @@ import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| import java.util.function.Supplier; | ||||
| import java.util.function.*; | ||||
| 
 | ||||
| @AutoService(dan200.computercraft.impl.PlatformHelper.class) | ||||
| public class PlatformHelperImpl implements PlatformHelper { | ||||
| @@ -303,10 +300,16 @@ public class PlatformHelperImpl implements PlatformHelper { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { | ||||
|     public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock) { | ||||
|         var result = UseBlockCallback.EVENT.invoker().interact(player, player.level, InteractionHand.MAIN_HAND, hit); | ||||
|         if (result != InteractionResult.PASS) return result; | ||||
| 
 | ||||
|         var block = player.level.getBlockState(hit.getBlockPos()); | ||||
|         if (!block.isAir() && canUseBlock.test(block)) { | ||||
|             var useResult = block.use(player.level, player, InteractionHand.MAIN_HAND, hit); | ||||
|             if (useResult.consumesAction()) return useResult; | ||||
|         } | ||||
| 
 | ||||
|         return stack.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit)); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										1
									
								
								projects/forge/src/generated/resources/data/computercraft/tags/blocks/turtle_can_use.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								projects/forge/src/generated/resources/data/computercraft/tags/blocks/turtle_can_use.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {"values": ["#minecraft:cauldrons", "#minecraft:beehives"]} | ||||
| @@ -74,10 +74,7 @@ import net.minecraftforge.registries.RegistryObject; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.*; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| import java.util.function.Supplier; | ||||
| import java.util.function.*; | ||||
| 
 | ||||
| @AutoService(dan200.computercraft.impl.PlatformHelper.class) | ||||
| public class PlatformHelperImpl implements PlatformHelper { | ||||
| @@ -318,17 +315,25 @@ public class PlatformHelperImpl implements PlatformHelper { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { | ||||
|     public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock) { | ||||
|         var level = player.level; | ||||
|         var pos = hit.getBlockPos(); | ||||
|         var event = ForgeHooks.onRightClickBlock(player, InteractionHand.MAIN_HAND, pos, hit); | ||||
|         if (event.isCanceled()) return event.getCancellationResult(); | ||||
| 
 | ||||
|         var context = new UseOnContext(player, InteractionHand.MAIN_HAND, hit); | ||||
|         if (event.getUseItem() == Event.Result.DENY) return InteractionResult.PASS; | ||||
| 
 | ||||
|         if (event.getUseItem() != Event.Result.DENY) { | ||||
|             var result = stack.onItemUseFirst(context); | ||||
|         return result != InteractionResult.PASS ? result : stack.useOn(context); | ||||
|             if (result != InteractionResult.PASS) return result; | ||||
|         } | ||||
| 
 | ||||
|         var block = level.getBlockState(hit.getBlockPos()); | ||||
|         if (event.getUseBlock() != Event.Result.DENY && !block.isAir() && canUseBlock.test(block)) { | ||||
|             var useResult = block.use(level, player, InteractionHand.MAIN_HAND, hit); | ||||
|             if (useResult.consumesAction()) return useResult; | ||||
|         } | ||||
| 
 | ||||
|         return event.getUseItem() == Event.Result.DENY ? InteractionResult.PASS : stack.useOn(context); | ||||
|     } | ||||
| 
 | ||||
|     private record RegistryWrapperImpl<T>( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates