1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-14 12:10:30 +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:
Jonathan Coates 2023-03-04 18:17:23 +00:00
parent 566315947b
commit 118d04f018
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
12 changed files with 65 additions and 33 deletions

View File

@ -14,6 +14,8 @@ import net.minecraft.world.item.Item;
import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block; 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. * Tags provided by ComputerCraft.
@ -65,6 +67,12 @@ public class ComputerCraftTags {
*/ */
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable"); 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) { private static TagKey<Block> make(String name) {
return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name)); return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
} }

View File

@ -57,6 +57,8 @@ class TagProvider {
tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB); 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. // Make all blocks aside from command computer mineable.
tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add( tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(
ModRegistry.Blocks.COMPUTER_NORMAL.get(), ModRegistry.Blocks.COMPUTER_NORMAL.get(),

View File

@ -53,6 +53,7 @@ import java.util.List;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
/** /**
* This extends {@linkplain dan200.computercraft.impl.PlatformHelper the API's loader abstraction layer}, adding * 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. * Place an item against a block.
* <p> * <p>
* Implementations should largely mirror {@link ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)} * 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 player The player which is placing this item.
* @param stack The item to place. * @param stack The item to place.
* @param hit The collision with the block we're placing against. * @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. * @return Whether any interaction occurred.
* @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult) * @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);
} }

View File

@ -22,6 +22,7 @@ import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.*; import net.minecraft.world.item.*;
import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.UseOnContext; 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.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.SignBlockEntity; 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.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@ -153,7 +155,7 @@ public class TurtlePlaceCommand implements TurtleCommand {
private static boolean deployOnBlock( private static boolean deployOnBlock(
ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side, 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 // Re-orient the fake player
var playerDir = side.getOpposite(); var playerDir = side.getOpposite();
@ -169,14 +171,14 @@ public class TurtlePlaceCommand implements TurtleCommand {
// Check if there's something suitable to place onto // Check if there's something suitable to place onto
var hit = new BlockHitResult(new Vec3(hitX, hitY, hitZ), side, position, false); var hit = new BlockHitResult(new Vec3(hitX, hitY, hitZ), side, position, false);
var context = new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit); 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; return false;
} }
var item = stack.getItem(); var item = stack.getItem();
var existingTile = turtle.getLevel().getBlockEntity(position); 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 // Set text on signs
if (placed && item instanceof SignItem && extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String message) { 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 stack The stack the player is using.
* @param turtlePlayer The player which represents the turtle * @param turtlePlayer The player which represents the turtle
* @param hit Where the block we're placing against was clicked. * @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. * @return If this item was deployed.
*/ */
private static InteractionResult doDeployOnBlock( private static InteractionResult doDeployOnBlock(ItemStack stack, TurtlePlayer turtlePlayer, BlockHitResult hit, boolean adjacent) {
ItemStack stack, TurtlePlayer turtlePlayer, BlockHitResult hit var result = PlatformHelper.get().useOn(
) { turtlePlayer.player(), stack, hit,
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; 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. // We special case some items which we allow to place "normally". Yes, this is very ugly.
var item = stack.getItem(); var item = stack.getItem();
if (item instanceof BucketItem || item instanceof PlaceOnWaterBlockItem || stack.is(ComputerCraftTags.Items.TURTLE_CAN_PLACE)) { 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; return InteractionResult.PASS;

View File

@ -55,6 +55,7 @@ import java.util.List;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
@AutoService({ PlatformHelper.class, dan200.computercraft.impl.PlatformHelper.class, ComputerCraftAPIService.class }) @AutoService({ PlatformHelper.class, dan200.computercraft.impl.PlatformHelper.class, ComputerCraftAPIService.class })
public class TestPlatformHelper extends AbstractComputerCraftAPI implements PlatformHelper { public class TestPlatformHelper extends AbstractComputerCraftAPI implements PlatformHelper {
@ -202,7 +203,7 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat
} }
@Override @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"); throw new UnsupportedOperationException("Cannot interact with the world inside tests");
} }

View File

@ -185,7 +185,7 @@ class Turtle_Test {
* *
* Currently not required as turtles can no longer right-click cauldrons. * Currently not required as turtles can no longer right-click cauldrons.
*/ */
@GameTest(required = false) @GameTest
fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence { fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence {
thenOnComputer { thenOnComputer {
val details = getTurtleItemDetail(1, true) val details = getTurtleItemDetail(1, true)

View File

@ -23,6 +23,7 @@ import net.minecraft.gametest.framework.GameTestSequence
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import java.io.InputStream import java.io.InputStream
import java.util.* import java.util.*
import java.util.concurrent.CancellationException
import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
@ -46,7 +47,7 @@ object ManagedComputers : ILuaMachine.Factory {
task() task()
monitor.result.set(Result.success(Unit)) monitor.result.set(Result.success(Unit))
} catch (e: Throwable) { } 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)) monitor.result.set(Result.failure(e))
throw e throw e
} finally { } finally {

View File

@ -1,5 +1,5 @@
{ {
DataVersion: 2730, DataVersion: 3218,
size: [3, 3, 3], size: [3, 3, 3],
data: [ data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"}, {pos: [0, 0, 0], state: "minecraft:polished_andesite"},
@ -15,7 +15,7 @@
{pos: [0, 1, 1], state: "minecraft:air"}, {pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], 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, 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: [1, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"}, {pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"}, {pos: [2, 1, 1], state: "minecraft:air"},
@ -33,8 +33,8 @@
entities: [], entities: [],
palette: [ palette: [
"minecraft:polished_andesite", "minecraft:polished_andesite",
"computercraft:turtle_normal{facing:south,waterlogged:false}",
"minecraft:air", "minecraft:air",
"minecraft:cauldron{level:3}" "minecraft:water_cauldron{level:3}",
"computercraft:turtle_normal{facing:south,waterlogged:false}"
] ]
} }

View File

@ -0,0 +1 @@
{"replace": false, "values": ["#minecraft:cauldrons", "#minecraft:beehives"]}

View File

@ -77,10 +77,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.function.BiFunction; import java.util.function.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@AutoService(dan200.computercraft.impl.PlatformHelper.class) @AutoService(dan200.computercraft.impl.PlatformHelper.class)
public class PlatformHelperImpl implements PlatformHelper { public class PlatformHelperImpl implements PlatformHelper {
@ -303,10 +300,16 @@ public class PlatformHelperImpl implements PlatformHelper {
} }
@Override @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); var result = UseBlockCallback.EVENT.invoker().interact(player, player.level, InteractionHand.MAIN_HAND, hit);
if (result != InteractionResult.PASS) return result; 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)); return stack.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit));
} }

View File

@ -0,0 +1 @@
{"values": ["#minecraft:cauldrons", "#minecraft:beehives"]}

View File

@ -74,10 +74,7 @@ import net.minecraftforge.registries.RegistryObject;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import java.util.function.BiFunction; import java.util.function.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@AutoService(dan200.computercraft.impl.PlatformHelper.class) @AutoService(dan200.computercraft.impl.PlatformHelper.class)
public class PlatformHelperImpl implements PlatformHelper { public class PlatformHelperImpl implements PlatformHelper {
@ -318,17 +315,25 @@ public class PlatformHelperImpl implements PlatformHelper {
} }
@Override @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 level = player.level;
var pos = hit.getBlockPos(); var pos = hit.getBlockPos();
var event = ForgeHooks.onRightClickBlock(player, InteractionHand.MAIN_HAND, pos, hit); var event = ForgeHooks.onRightClickBlock(player, InteractionHand.MAIN_HAND, pos, hit);
if (event.isCanceled()) return event.getCancellationResult(); if (event.isCanceled()) return event.getCancellationResult();
var context = new UseOnContext(player, InteractionHand.MAIN_HAND, hit); 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);
if (result != InteractionResult.PASS) return result;
}
var result = stack.onItemUseFirst(context); var block = level.getBlockState(hit.getBlockPos());
return result != InteractionResult.PASS ? result : stack.useOn(context); 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>( private record RegistryWrapperImpl<T>(