Skip to content

Commit

Permalink
Rethink PlatformHelper.useOn
Browse files Browse the repository at this point in the history
useOn is now only responsible for firing the actual mod loader events,
and just returns the result of firing that event. The actual calling of
Block.use/Item.useOn now live in TurtlePlaceCommand.

This isn't especially useful for 1.20.1, but is more relevant on 1.21.1
when we look at #2011, as the shared code is much larger.
  • Loading branch information
SquidDev committed Jan 12, 2025
1 parent 5465770 commit 9a914e7
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
Expand All @@ -53,7 +54,6 @@
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
Expand Down Expand Up @@ -375,20 +375,40 @@ default double getReachDistance(Player player) {
boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos);

/**
* Place an item against a block.
* The result of attempting to use an item on a block.
*/
sealed interface UseOnResult {
/**
* This interaction was intercepted by an event, and handled.
*
* @param result The result of using an item on a block.
*/
record Handled(InteractionResult result) implements UseOnResult {
}

/**
* This result was not handled, and should be handled by the caller.
*
* @param block Whether the block may be used ({@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)}).
* @param item Whether the item may be used on the block ({@link ItemStack#useOn(UseOnContext)}).
* @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)
*/
record Continue(boolean block, boolean item) implements UseOnResult {
}
}

/**
* Run mod-loader specific code before placing an item against a block.
* <p>
* Implementations should largely mirror {@link ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, 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.
* This should dispatch any mod-loader specific events that are fired when clicking a block. It does necessarily
* handle the actual clicking of the block — see {@link UseOnResult.Handled} and {@link UseOnResult.Continue}.
*
* @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.
* @return Whether any interaction occurred.
* @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)
*/
InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock);
UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit);

/**
* Whether {@link net.minecraft.network.chat.ClickEvent.Action#RUN_COMMAND} can be used to run client commands.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/**
* An object that holds a pocket computer item.
*/
public sealed interface PocketHolder permits PocketHolder.EntityHolder {
public sealed interface PocketHolder {
/**
* The level this holder is in.
*
Expand Down Expand Up @@ -54,7 +54,7 @@ public sealed interface PocketHolder permits PocketHolder.EntityHolder {
/**
* An {@link Entity} holding a pocket computer.
*/
sealed interface EntityHolder extends PocketHolder permits PocketHolder.PlayerHolder, PocketHolder.ItemEntityHolder {
sealed interface EntityHolder extends PocketHolder {
/**
* Get the entity holding this pocket computer.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,22 @@ private static boolean deployOnBlock(
* @return If this item was deployed.
*/
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 result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit);
if (result instanceof PlatformHelper.UseOnResult.Handled handled) {
if (handled.result() != InteractionResult.PASS) return handled.result();
} else {
var canUse = (PlatformHelper.UseOnResult.Continue) result;

var player = turtlePlayer.player();
var block = player.level().getBlockState(hit.getBlockPos());
if (adjacent && canUse.block() && block.is(ComputerCraftTags.Blocks.TURTLE_CAN_USE)) {
var useResult = block.use(player.level(), player, InteractionHand.MAIN_HAND, hit);
if (useResult.consumesAction()) return useResult;
}

var useOnResult = stack.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit));
if (useOnResult != InteractionResult.PASS) return useOnResult;
}

var level = turtlePlayer.player().level();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
Expand Down Expand Up @@ -343,8 +344,12 @@ private boolean useTool(ServerLevel level, ITurtleAccess turtle, TurtlePlayer tu
}

var hit = TurtlePlaceCommand.getHitResult(position, direction.getOpposite());
var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit, x -> false);
return result.consumesAction();
var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit);
if (result instanceof PlatformHelper.UseOnResult.Handled handled) {
return handled.result().consumesAction();
} else {
return ((PlatformHelper.UseOnResult.Continue) result).item() && item.useOn(new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit)).consumesAction();
}
}

private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
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 {
Expand Down Expand Up @@ -223,7 +222,7 @@ public boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPo
}

@Override
public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock) {
public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) {
throw new UnsupportedOperationException("Cannot interact with the world inside tests");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,22 @@ class Turtle_Test {
}
}

/**
* Checks that turtles cannot place items into non-adjacent blocks.
*
* See [ComputerCraftTags.Blocks.TURTLE_CAN_USE].
*/
@GameTest
fun Place_into_composter_non_adjacent(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
turtle.place(ObjectArguments()).await()
.assertArrayEquals(false, "Cannot place item here", message = "Failed to place item")
}
thenExecute {
helper.assertBlockIs(BlockPos(2, 2, 3)) { it.block == Blocks.COMPOSTER && it.getValue(ComposterBlock.LEVEL) == 0 }
}
}

/**
* Checks that turtles can place bottles into beehives.
*
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.*;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.Level;
Expand All @@ -84,7 +83,10 @@
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

@AutoService(dan200.computercraft.impl.PlatformHelper.class)
public class PlatformHelperImpl implements PlatformHelper {
Expand Down Expand Up @@ -309,17 +311,10 @@ public boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPo
}

@Override
public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock) {
public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) {
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));
if (result != InteractionResult.PASS) return new UseOnResult.Handled(result);
return new UseOnResult.Continue(true, true);
}

private record RegistryWrapperImpl<T>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@

import javax.annotation.Nullable;
import java.util.*;
import java.util.function.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

@AutoService(dan200.computercraft.impl.PlatformHelper.class)
public class PlatformHelperImpl implements PlatformHelper {
Expand Down Expand Up @@ -324,25 +327,18 @@ public boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPo
}

@Override
public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock) {
var level = player.level();
public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) {
var pos = hit.getBlockPos();
var event = ForgeHooks.onRightClickBlock(player, InteractionHand.MAIN_HAND, pos, hit);
if (event.isCanceled()) return event.getCancellationResult();
if (event.isCanceled()) return new UseOnResult.Handled(event.getCancellationResult());

var context = new UseOnContext(player, InteractionHand.MAIN_HAND, hit);
if (event.getUseItem() != Event.Result.DENY) {
var result = stack.onItemUseFirst(context);
if (result != InteractionResult.PASS) return result;
if (result != InteractionResult.PASS) return new UseOnResult.Handled(event.getCancellationResult());
}

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);
return new UseOnResult.Continue(event.getUseBlock() != Event.Result.DENY, event.getUseItem() != Event.Result.DENY);
}

@Override
Expand Down

0 comments on commit 9a914e7

Please sign in to comment.