From 9142ccfc932c7f9aef91e51b1b1763f302f9d2c1 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sat, 29 May 2021 15:11:29 +0100 Subject: [PATCH] Rewrite turtle placing logic - Simplify how the turtle's inventory is processed. We now copy all items into the player inventory, attempt to place, and then copy the items back. This also fixes the problem where turtle.place() wouldn't (always) update the item which was placed. I'm also hoping this is more "correct" in how we process drops from entities and whatnot. Though I've not had any reports of issues, so it's probably fine. - Replace the "error message" string array with an actual object. It'd be nicer all these functions returned a TurtleCommandResult, but merging error messages from that is a little harder. Fun facts: the test suite was actually helpful here, and caught the fact that hoeing was broken! --- build.gradle | 2 +- .../shared/turtle/core/TurtleDropCommand.java | 2 +- .../turtle/core/TurtleInspectCommand.java | 2 +- .../shared/turtle/core/TurtleMoveCommand.java | 2 +- .../turtle/core/TurtlePlaceCommand.java | 448 +++++++----------- .../shared/turtle/core/TurtlePlayer.java | 131 +++-- .../shared/turtle/core/TurtleSuckCommand.java | 2 +- .../shared/turtle/upgrades/TurtleHoe.java | 4 +- .../shared/turtle/upgrades/TurtleShovel.java | 4 +- .../shared/turtle/upgrades/TurtleTool.java | 14 +- .../shared/util/DropConsumer.java | 7 + 11 files changed, 281 insertions(+), 337 deletions(-) diff --git a/build.gradle b/build.gradle index 1d581291a..1f3d797d8 100644 --- a/build.gradle +++ b/build.gradle @@ -343,7 +343,7 @@ task setupServer(type: Copy) { tasks.register('testInGame', JavaExec.class).configure { it.group('test server') it.description("Runs tests on a temporary Minecraft server.") - it.dependsOn(setupServer, 'prepareRunTestServer') + it.dependsOn(setupServer, 'prepareRunTestServer', 'cleanTestInGame') // Copy from runTestServer. We do it in this slightly odd way as runTestServer // isn't created until the task is configured (which is no good for us). diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java index 3283108f1..afc510d67 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java @@ -62,7 +62,7 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) IItemHandler inventory = InventoryUtil.getInventory( world, newPosition, side ); // Fire the event, restoring the inventory and exiting if it is cancelled. - TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtlePlayer player = TurtlePlayer.getWithPosition( turtle, oldPosition, direction ); TurtleInventoryEvent.Drop event = new TurtleInventoryEvent.Drop( turtle, player, world, newPosition, inventory, stack ); if( MinecraftForge.EVENT_BUS.post( event ) ) { diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java index 16dbc3511..3468f1d91 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java @@ -50,7 +50,7 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) Map table = BlockData.fill( new HashMap<>(), state ); // Fire the event, exiting if it is cancelled - TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction ); TurtleBlockEvent.Inspect event = new TurtleBlockEvent.Inspect( turtle, turtlePlayer, world, newPosition, state, table ); if( MinecraftForge.EVENT_BUS.post( event ) ) return TurtleCommandResult.failure( event.getFailureMessage() ); diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java index d90fe2cd4..35bf90cb3 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java @@ -47,7 +47,7 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) BlockPos oldPosition = turtle.getPosition(); BlockPos newPosition = oldPosition.relative( direction ); - TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction ); TurtleCommandResult canEnterResult = canEnter( turtlePlayer, oldWorld, newPosition ); if( !canEnterResult.isSuccess() ) { diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java index f669b1a0f..075be9309 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java @@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.TurtleCommandResult; import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import dan200.computercraft.shared.TurtlePermissions; -import dan200.computercraft.shared.util.DirectionUtil; import dan200.computercraft.shared.util.DropConsumer; import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.WorldUtil; @@ -20,6 +19,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.item.*; +import net.minecraft.network.play.client.CUseEntityPacket; import net.minecraft.tileentity.SignTileEntity; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ActionResult; @@ -34,11 +34,13 @@ import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerInteractEvent; -import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.wrapper.InvWrapper; import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nonnull; -import java.util.List; + +import static net.minecraftforge.eventbus.api.Event.Result; public class TurtlePlaceCommand implements ITurtleCommand { @@ -57,10 +59,7 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) { // Get thing to place ItemStack stack = turtle.getInventory().getItem( turtle.getSelectedSlot() ); - if( stack.isEmpty() ) - { - return TurtleCommandResult.failure( "No items to place" ); - } + if( stack.isEmpty() ) return TurtleCommandResult.failure( "No items to place" ); // Remember old block Direction direction = this.direction.toWorldDir( turtle ); @@ -68,144 +67,63 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) // Create a fake player, and orient it appropriately BlockPos playerPosition = turtle.getPosition().relative( direction ); - TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction ); + TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, playerPosition, direction ); TurtleBlockEvent.Place place = new TurtleBlockEvent.Place( turtle, turtlePlayer, turtle.getWorld(), coordinates, stack ); - if( MinecraftForge.EVENT_BUS.post( place ) ) - { - return TurtleCommandResult.failure( place.getFailureMessage() ); - } + if( MinecraftForge.EVENT_BUS.post( place ) ) return TurtleCommandResult.failure( place.getFailureMessage() ); // Do the deploying - String[] errorMessage = new String[1]; - ItemStack remainder = deploy( stack, turtle, turtlePlayer, direction, extraArguments, errorMessage ); - if( remainder != stack ) + turtlePlayer.loadInventory( turtle ); + ErrorMessage message = new ErrorMessage(); + boolean result = deploy( stack, turtle, turtlePlayer, direction, extraArguments, message ); + turtlePlayer.unloadInventory( turtle ); + if( result ) { - // Put the remaining items back - turtle.getInventory().setItem( turtle.getSelectedSlot(), remainder ); - turtle.getInventory().setChanged(); - // Animate and return success turtle.playAnimation( TurtleAnimation.WAIT ); return TurtleCommandResult.success(); } + else if( message.message != null ) + { + return TurtleCommandResult.failure( message.message ); + } else { - if( errorMessage[0] != null ) - { - return TurtleCommandResult.failure( errorMessage[0] ); - } - else if( stack.getItem() instanceof BlockItem ) - { - return TurtleCommandResult.failure( "Cannot place block here" ); - } - else - { - return TurtleCommandResult.failure( "Cannot place item here" ); - } + return TurtleCommandResult.failure( stack.getItem() instanceof BlockItem ? "Cannot place block here" : "Cannot place item here" ); } } - public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, Direction direction, Object[] extraArguments, String[] outErrorMessage ) + public static boolean deployCopiedItem( @Nonnull ItemStack stack, ITurtleAccess turtle, Direction direction, Object[] extraArguments, ErrorMessage outErrorMessage ) { // Create a fake player, and orient it appropriately BlockPos playerPosition = turtle.getPosition().relative( direction ); - TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction ); - - return deploy( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage ); + TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, playerPosition, direction ); + turtlePlayer.loadInventory( stack ); + boolean result = deploy( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage ); + turtlePlayer.inventory.clearContent(); + return result; } - public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, String[] outErrorMessage ) + private static boolean deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, ErrorMessage outErrorMessage ) { // Deploy on an entity - ItemStack remainder = deployOnEntity( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage ); - if( remainder != stack ) - { - return remainder; - } + if( deployOnEntity( stack, turtle, turtlePlayer ) ) return true; - // Deploy on the block immediately in front BlockPos position = turtle.getPosition(); BlockPos newPosition = position.relative( direction ); - remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition, direction.getOpposite(), extraArguments, true, outErrorMessage ); - if( remainder != stack ) - { - return remainder; - } - // Deploy on the block one block away - remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.relative( direction ), direction.getOpposite(), extraArguments, false, outErrorMessage ); - if( remainder != stack ) - { - return remainder; - } - - if( direction.getAxis() != Direction.Axis.Y ) - { + // Try to deploy against a block. Tries the following options: + // Deploy on the block immediately in front + return deployOnBlock( stack, turtle, turtlePlayer, newPosition, direction.getOpposite(), extraArguments, true, outErrorMessage ) + // Deploy on the block one block away + || deployOnBlock( stack, turtle, turtlePlayer, newPosition.relative( direction ), direction.getOpposite(), extraArguments, false, outErrorMessage ) // Deploy down on the block in front - remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.below(), Direction.UP, extraArguments, false, outErrorMessage ); - if( remainder != stack ) - { - return remainder; - } - } - - // Deploy back onto the turtle - remainder = deployOnBlock( stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage ); - if( remainder != stack ) - { - return remainder; - } - - // If nothing worked, return the original stack unchanged - return stack; + || (direction.getAxis() != Direction.Axis.Y && deployOnBlock( stack, turtle, turtlePlayer, newPosition.below(), Direction.UP, extraArguments, false, outErrorMessage )) + // Deploy back onto the turtle + || deployOnBlock( stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage ); } - public static TurtlePlayer createPlayer( ITurtleAccess turtle, BlockPos position, Direction direction ) - { - TurtlePlayer turtlePlayer = TurtlePlayer.get( turtle ); - orientPlayer( turtle, turtlePlayer, position, direction ); - return turtlePlayer; - } - - private static void orientPlayer( ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction direction ) - { - double posX = position.getX() + 0.5; - double posY = position.getY() + 0.5; - double posZ = position.getZ() + 0.5; - - // Stop intersection with the turtle itself - if( turtle.getPosition().equals( position ) ) - { - posX += 0.48 * direction.getStepX(); - posY += 0.48 * direction.getStepY(); - posZ += 0.48 * direction.getStepZ(); - } - - if( direction.getAxis() != Direction.Axis.Y ) - { - turtlePlayer.yRot = direction.toYRot(); - turtlePlayer.xRot = 0.0f; - } - else - { - turtlePlayer.yRot = turtle.getDirection().toYRot(); - turtlePlayer.xRot = DirectionUtil.toPitchAngle( direction ); - } - - turtlePlayer.setPosRaw( posX, posY, posZ ); - turtlePlayer.xo = posX; - turtlePlayer.yo = posY; - turtlePlayer.zo = posZ; - turtlePlayer.xRotO = turtlePlayer.xRot; - turtlePlayer.yRotO = turtlePlayer.yRot; - - turtlePlayer.yHeadRot = turtlePlayer.yRot; - turtlePlayer.yHeadRotO = turtlePlayer.yHeadRot; - } - - @Nonnull - private static ItemStack deployOnEntity( @Nonnull ItemStack stack, final ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, String[] outErrorMessage ) + private static boolean deployOnEntity( @Nonnull ItemStack stack, final ITurtleAccess turtle, TurtlePlayer turtlePlayer ) { // See if there is an entity present final World world = turtle.getWorld(); @@ -213,81 +131,57 @@ private static ItemStack deployOnEntity( @Nonnull ItemStack stack, final ITurtle Vec3d turtlePos = turtlePlayer.position(); Vec3d rayDir = turtlePlayer.getViewVector( 1.0f ); Pair hit = WorldUtil.rayTraceEntities( world, turtlePos, rayDir, 1.5 ); - if( hit == null ) - { - return stack; - } - - // Load up the turtle's inventory - ItemStack stackCopy = stack.copy(); - turtlePlayer.loadInventory( stackCopy ); + if( hit == null ) return false; // Start claiming entity drops Entity hitEntity = hit.getKey(); Vec3d hitPos = hit.getValue(); - DropConsumer.set( - hitEntity, - drop -> InventoryUtil.storeItems( drop, turtle.getItemHandler(), turtle.getSelectedSlot() ) - ); - // Place on the entity - boolean placed = false; - ActionResultType cancelResult = ForgeHooks.onInteractEntityAt( turtlePlayer, hitEntity, hitPos, Hand.MAIN_HAND ); - if( cancelResult == null ) - { - cancelResult = hitEntity.interactAt( turtlePlayer, hitPos, Hand.MAIN_HAND ); - } + IItemHandler itemHandler = new InvWrapper( turtlePlayer.inventory ); + DropConsumer.set( hitEntity, drop -> InventoryUtil.storeItems( drop, itemHandler, 1 ) ); - if( cancelResult == ActionResultType.SUCCESS ) - { - placed = true; - } - else - { - // See EntityPlayer.interactOn - cancelResult = ForgeHooks.onInteractEntity( turtlePlayer, hitEntity, Hand.MAIN_HAND ); - if( cancelResult == ActionResultType.SUCCESS ) - { - placed = true; - } - else if( cancelResult == null ) - { - if( hitEntity.interact( turtlePlayer, Hand.MAIN_HAND ) ) - { - placed = true; - } - else if( hitEntity instanceof LivingEntity ) - { - placed = stackCopy.interactEnemy( turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND ); - if( placed ) turtlePlayer.loadInventory( stackCopy ); - } - } - } + boolean placed = doDeployOnEntity( stack, turtlePlayer, hitEntity, hitPos ); - // Stop claiming drops - List remainingDrops = DropConsumer.clear(); - for( ItemStack remaining : remainingDrops ) - { - WorldUtil.dropItemStack( remaining, world, position, turtle.getDirection().getOpposite() ); - } - - // Put everything we collected into the turtles inventory, then return - ItemStack remainder = turtlePlayer.unloadInventory( turtle ); - if( !placed && ItemStack.matches( stack, remainder ) ) - { - return stack; - } - else if( !remainder.isEmpty() ) - { - return remainder; - } - else - { - return ItemStack.EMPTY; - } + DropConsumer.clearAndDrop( world, position, turtle.getDirection().getOpposite() ); + return placed; } - private static boolean canDeployOnBlock( @Nonnull BlockItemUseContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position, Direction side, boolean allowReplaceable, String[] outErrorMessage ) + /** + * Place a block onto an entity. For instance, feeding cows. + * + * @param stack The stack we're placing. + * @param turtlePlayer The player of the turtle we're placing. + * @param hitEntity The entity we're interacting with. + * @param hitPos The position our ray trace hit the entity. + * @return If this item was deployed. + * @see net.minecraft.network.play.ServerPlayNetHandler#handleInteract(CUseEntityPacket) + * @see net.minecraft.entity.player.PlayerEntity#interactOn(Entity, Hand) + */ + private static boolean doDeployOnEntity( @Nonnull ItemStack stack, TurtlePlayer turtlePlayer, @Nonnull Entity hitEntity, @Nonnull Vec3d hitPos ) + { + // Placing "onto" a block follows two flows. First we try to interactAt. If that doesn't succeed, then we try to + // call the normal interact path. Cancelling an interactAt *does not* cancel a normal interact path. + + ActionResultType interactAt = ForgeHooks.onInteractEntityAt( turtlePlayer, hitEntity, hitPos, Hand.MAIN_HAND ); + if( interactAt == null ) interactAt = hitEntity.interactAt( turtlePlayer, hitPos, Hand.MAIN_HAND ); + if( interactAt.consumesAction() ) return true; + + ActionResultType interact = ForgeHooks.onInteractEntity( turtlePlayer, hitEntity, Hand.MAIN_HAND ); + if( interact != null ) return interact.consumesAction(); + + if( hitEntity.interact( turtlePlayer, Hand.MAIN_HAND ) ) return true; + if( hitEntity instanceof LivingEntity ) + { + return stack.interactEnemy( turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND ); + } + + return false; + } + + private static boolean canDeployOnBlock( + @Nonnull BlockItemUseContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position, + Direction side, boolean allowReplaceable, ErrorMessage outErrorMessage + ) { World world = turtle.getWorld(); if( !World.isInWorldBounds( position ) || world.isEmptyBlock( position ) || @@ -309,7 +203,7 @@ private static boolean canDeployOnBlock( @Nonnull BlockItemUseContext context, I : TurtlePermissions.isBlockEditable( world, position.relative( side ), player ); if( !editable ) { - if( outErrorMessage != null ) outErrorMessage[0] = "Cannot place in protected area"; + if( outErrorMessage != null ) outErrorMessage.message = "Cannot place in protected area"; return false; } } @@ -317,131 +211,123 @@ private static boolean canDeployOnBlock( @Nonnull BlockItemUseContext context, I return true; } - @Nonnull - private static ItemStack deployOnBlock( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side, Object[] extraArguments, boolean allowReplace, String[] outErrorMessage ) + private static boolean deployOnBlock( + @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side, + Object[] extraArguments, boolean allowReplace, ErrorMessage outErrorMessage + ) { // Re-orient the fake player Direction playerDir = side.getOpposite(); BlockPos playerPosition = position.relative( side ); - orientPlayer( turtle, turtlePlayer, playerPosition, playerDir ); - - ItemStack stackCopy = stack.copy(); - turtlePlayer.loadInventory( stackCopy ); + turtlePlayer.setPosition( turtle, playerPosition, playerDir ); // Calculate where the turtle would hit the block float hitX = 0.5f + side.getStepX() * 0.5f; float hitY = 0.5f + side.getStepY() * 0.5f; float hitZ = 0.5f + side.getStepZ() * 0.5f; - if( Math.abs( hitY - 0.5f ) < 0.01f ) - { - hitY = 0.45f; - } + if( Math.abs( hitY - 0.5f ) < 0.01f ) hitY = 0.45f; // Check if there's something suitable to place onto BlockRayTraceResult hit = new BlockRayTraceResult( new Vec3d( hitX, hitY, hitZ ), side, position, false ); ItemUseContext context = new ItemUseContext( turtlePlayer, Hand.MAIN_HAND, hit ); if( !canDeployOnBlock( new BlockItemUseContext( context ), turtle, turtlePlayer, position, side, allowReplace, outErrorMessage ) ) { - return stack; + return false; } - // Load up the turtle's inventory Item item = stack.getItem(); - - // Do the deploying (put everything in the players inventory) - boolean placed = false; TileEntity existingTile = turtle.getWorld().getBlockEntity( position ); - // See PlayerInteractionManager.processRightClickBlock - // TODO: ^ Check we're still consistent. - PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, Hand.MAIN_HAND, position, side ); - if( !event.isCanceled() ) - { - if( item.onItemUseFirst( stack, context ) == ActionResultType.SUCCESS ) - { - placed = true; - turtlePlayer.loadInventory( stackCopy ); - } - else if( event.getUseItem() != Event.Result.DENY && - stackCopy.useOn( context ) == ActionResultType.SUCCESS ) - { - placed = true; - turtlePlayer.loadInventory( stackCopy ); - } - } - - if( !placed && (item instanceof BucketItem || item instanceof BoatItem || item instanceof LilyPadItem || item instanceof GlassBottleItem) ) - { - ActionResultType actionResult = ForgeHooks.onItemRightClick( turtlePlayer, Hand.MAIN_HAND ); - if( actionResult == ActionResultType.SUCCESS ) - { - placed = true; - } - else if( actionResult == null ) - { - ActionResult result = stackCopy.use( turtle.getWorld(), turtlePlayer, Hand.MAIN_HAND ); - if( result.getResult() == ActionResultType.SUCCESS && !ItemStack.matches( stack, result.getObject() ) ) - { - placed = true; - turtlePlayer.loadInventory( result.getObject() ); - } - } - } + boolean placed = doDeployOnBlock( stack, turtlePlayer, position, side, context ).consumesAction(); // Set text on signs - if( placed && item instanceof SignItem ) + if( placed && item instanceof SignItem && extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String ) { - if( extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String ) + World world = turtle.getWorld(); + TileEntity tile = world.getBlockEntity( position ); + if( tile == null || tile == existingTile ) { - World world = turtle.getWorld(); - TileEntity tile = world.getBlockEntity( position ); - if( tile == null || tile == existingTile ) - { - tile = world.getBlockEntity( position.relative( side ) ); - } - if( tile instanceof SignTileEntity ) - { - SignTileEntity signTile = (SignTileEntity) tile; - String s = (String) extraArguments[0]; - String[] split = s.split( "\n" ); - int firstLine = split.length <= 2 ? 1 : 0; - for( int i = 0; i < signTile.messages.length; i++ ) - { - if( i >= firstLine && i < firstLine + split.length ) - { - if( split[i - firstLine].length() > 15 ) - { - signTile.messages[i] = new StringTextComponent( split[i - firstLine].substring( 0, 15 ) ); - } - else - { - signTile.messages[i] = new StringTextComponent( split[i - firstLine] ); - } - } - else - { - signTile.messages[i] = new StringTextComponent( "" ); - } - } - signTile.setChanged(); - world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), 3 ); - } + tile = world.getBlockEntity( position.relative( side ) ); + } + + if( tile instanceof SignTileEntity ) setSignText( world, tile, (String) extraArguments[0] ); + } + + return placed; + } + + /** + * Attempt to place an item into the world. Returns true/false if an item was placed. + * + * @param stack The stack the player is using. + * @param turtlePlayer The player which represents the turtle + * @param position The block we're deploying against's position. + * @param side The side of the block we're deploying against. + * @param context The context of this place action. + * @return If this item was deployed. + * @see net.minecraft.server.management.PlayerInteractionManager#useItemOn For the original implementation. + */ + private static ActionResultType doDeployOnBlock( + @Nonnull ItemStack stack, TurtlePlayer turtlePlayer, BlockPos position, Direction side, ItemUseContext context + ) + { + PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, Hand.MAIN_HAND, position, side ); + if( event.isCanceled() ) return event.getCancellationResult(); + + if( event.getUseItem() != Result.DENY ) + { + ActionResultType result = stack.onItemUseFirst( context ); + if( result != ActionResultType.PASS ) return result; + } + + if( event.getUseItem() != Result.DENY ) + { + ActionResultType result = stack.useOn( context ); + if( result != ActionResultType.PASS ) return result; + } + + Item item = stack.getItem(); + if( item instanceof BucketItem || item instanceof BoatItem || item instanceof LilyPadItem || item instanceof GlassBottleItem ) + { + ActionResultType actionResult = ForgeHooks.onItemRightClick( turtlePlayer, Hand.MAIN_HAND ); + if( actionResult != null && actionResult != ActionResultType.PASS ) return actionResult; + + ActionResult result = stack.use( context.getLevel(), turtlePlayer, Hand.MAIN_HAND ); + if( result.getResult().consumesAction() && !ItemStack.matches( stack, result.getObject() ) ) + { + turtlePlayer.setItemInHand( Hand.MAIN_HAND, result.getObject() ); + return result.getResult(); } } - // Put everything we collected into the turtles inventory, then return - ItemStack remainder = turtlePlayer.unloadInventory( turtle ); - if( !placed && ItemStack.matches( stack, remainder ) ) + return ActionResultType.PASS; + } + + private static void setSignText( World world, TileEntity tile, String message ) + { + SignTileEntity signTile = (SignTileEntity) tile; + String[] split = message.split( "\n" ); + int firstLine = split.length <= 2 ? 1 : 0; + for( int i = 0; i < signTile.messages.length; i++ ) { - return stack; - } - else if( !remainder.isEmpty() ) - { - return remainder; - } - else - { - return ItemStack.EMPTY; + if( i >= firstLine && i < firstLine + split.length ) + { + String line = split[i - firstLine]; + signTile.messages[i] = line.length() > 15 + ? new StringTextComponent( line.substring( 0, 15 ) ) + : new StringTextComponent( line ); + } + else + { + signTile.messages[i] = new StringTextComponent( "" ); + } } + signTile.setChanged(); + world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), 3 ); + } + + private static class ErrorMessage + { + String message; } } diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java index a7a167048..e456648f1 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java @@ -9,6 +9,7 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.shared.Registry; +import dan200.computercraft.shared.util.DirectionUtil; import dan200.computercraft.shared.util.FakeNetHandler; import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.WorldUtil; @@ -73,23 +74,6 @@ private static GameProfile getProfile( @Nullable GameProfile profile ) return profile != null && profile.isComplete() ? profile : DEFAULT_PROFILE; } - private void setState( ITurtleAccess turtle ) - { - if( containerMenu != inventoryMenu ) - { - ComputerCraft.log.warn( "Turtle has open container ({})", containerMenu ); - doCloseContainer(); - } - - BlockPos position = turtle.getPosition(); - setPosRaw( position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5 ); - - yRot = turtle.getDirection().toYRot(); - xRot = 0.0f; - - inventory.clearContent(); - } - public static TurtlePlayer get( ITurtleAccess access ) { if( !(access instanceof TurtleBrain) ) return create( access ); @@ -109,37 +93,114 @@ public static TurtlePlayer get( ITurtleAccess access ) return player; } - public void loadInventory( @Nonnull ItemStack currentStack ) + public static TurtlePlayer getWithPosition( ITurtleAccess turtle, BlockPos position, Direction direction ) { - // Load up the fake inventory - inventory.selected = 0; - inventory.setItem( 0, currentStack ); + TurtlePlayer turtlePlayer = get( turtle ); + turtlePlayer.setPosition( turtle, position, direction ); + return turtlePlayer; } - public ItemStack unloadInventory( ITurtleAccess turtle ) + private void setState( ITurtleAccess turtle ) { - // Get the item we placed with - ItemStack results = inventory.getItem( 0 ); - inventory.setItem( 0, ItemStack.EMPTY ); + if( containerMenu != inventoryMenu ) + { + ComputerCraft.log.warn( "Turtle has open container ({})", containerMenu ); + doCloseContainer(); + } + + BlockPos position = turtle.getPosition(); + setPosRaw( position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5 ); + + yRot = turtle.getDirection().toYRot(); + xRot = 0.0f; + + inventory.clearContent(); + } + + public void setPosition( ITurtleAccess turtle, BlockPos position, Direction direction ) + { + double posX = position.getX() + 0.5; + double posY = position.getY() + 0.5; + double posZ = position.getZ() + 0.5; + + // Stop intersection with the turtle itself + if( turtle.getPosition().equals( position ) ) + { + posX += 0.48 * direction.getStepX(); + posY += 0.48 * direction.getStepY(); + posZ += 0.48 * direction.getStepZ(); + } + + if( direction.getAxis() != Direction.Axis.Y ) + { + yRot = direction.toYRot(); + xRot = 0.0f; + } + else + { + yRot = turtle.getDirection().toYRot(); + xRot = DirectionUtil.toPitchAngle( direction ); + } + + setPosRaw( posX, posY, posZ ); + xo = posX; + yo = posY; + zo = posZ; + xRotO = xRot; + yRotO = yRot; + + yHeadRot = yRot; + yHeadRotO = yHeadRot; + } + + public void loadInventory( @Nonnull ItemStack stack ) + { + inventory.clearContent(); + inventory.selected = 0; + inventory.setItem( 0, stack ); + } + + public void loadInventory( @Nonnull ITurtleAccess turtle ) + { + inventory.clearContent(); + + int currentSlot = turtle.getSelectedSlot(); + int slots = turtle.getItemHandler().getSlots(); + + // Load up the fake inventory + inventory.selected = 0; + for( int i = 0; i < slots; i++ ) + { + inventory.setItem( i, turtle.getItemHandler().getStackInSlot( (currentSlot + i) % slots ) ); + } + } + + public void unloadInventory( ITurtleAccess turtle ) + { + int currentSlot = turtle.getSelectedSlot(); + int slots = turtle.getItemHandler().getSlots(); + + // Load up the fake inventory + inventory.selected = 0; + for( int i = 0; i < slots; i++ ) + { + turtle.getItemHandler().setStackInSlot( (currentSlot + i) % slots, inventory.getItem( i ) ); + } // Store (or drop) anything else we found BlockPos dropPosition = turtle.getPosition(); Direction dropDirection = turtle.getDirection().getOpposite(); - for( int i = 0; i < inventory.getContainerSize(); i++ ) + int totalSize = inventory.getContainerSize(); + for( int i = slots; i < totalSize; i++ ) { - ItemStack stack = inventory.getItem( i ); - if( !stack.isEmpty() ) + ItemStack remainder = InventoryUtil.storeItems( inventory.getItem( i ), turtle.getItemHandler(), turtle.getSelectedSlot() ); + if( !remainder.isEmpty() ) { - ItemStack remainder = InventoryUtil.storeItems( stack, turtle.getItemHandler(), turtle.getSelectedSlot() ); - if( !remainder.isEmpty() ) - { - WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection ); - } - inventory.setItem( i, ItemStack.EMPTY ); + WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection ); } } + inventory.setChanged(); - return results; } @Nonnull diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java index 1dc5bb475..7783258e0 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java @@ -58,7 +58,7 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) IItemHandler inventory = InventoryUtil.getInventory( world, blockPosition, side ); // Fire the event, exiting if it is cancelled. - TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, turtlePosition, direction ); + TurtlePlayer player = TurtlePlayer.getWithPosition( turtle, turtlePosition, direction ); TurtleInventoryEvent.Suck event = new TurtleInventoryEvent.Suck( turtle, player, world, blockPosition, inventory ); if( MinecraftForge.EVENT_BUS.post( event ) ) { diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java index 044416527..9360aacbd 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java @@ -59,9 +59,7 @@ public TurtleCommandResult useTool( @Nonnull ITurtleAccess turtle, @Nonnull Turt { if( verb == TurtleVerb.DIG ) { - ItemStack hoe = item.copy(); - ItemStack remainder = TurtlePlaceCommand.deploy( hoe, turtle, direction, null, null ); - if( remainder != hoe ) + if( TurtlePlaceCommand.deployCopiedItem( item.copy(), turtle, direction, null, null ) ) { return TurtleCommandResult.success(); } diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java index 3981ad12d..b7e5513e8 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java @@ -63,9 +63,7 @@ public TurtleCommandResult useTool( @Nonnull ITurtleAccess turtle, @Nonnull Turt { if( verb == TurtleVerb.DIG ) { - ItemStack shovel = item.copy(); - ItemStack remainder = TurtlePlaceCommand.deploy( shovel, turtle, direction, null, null ); - if( remainder != shovel ) + if( TurtlePlaceCommand.deployCopiedItem( item.copy(), turtle, direction, null, null ) ) { return TurtleCommandResult.success(); } diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java index 3f6a02ebe..62b6bab06 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import dan200.computercraft.shared.TurtlePermissions; import dan200.computercraft.shared.turtle.core.TurtleBrain; -import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand; import dan200.computercraft.shared.turtle.core.TurtlePlayer; import dan200.computercraft.shared.util.DropConsumer; import dan200.computercraft.shared.util.InventoryUtil; @@ -45,7 +44,6 @@ import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nonnull; -import java.util.List; import java.util.function.Function; public class TurtleTool extends AbstractTurtleUpgrade @@ -140,7 +138,7 @@ private TurtleCommandResult attack( ITurtleAccess turtle, Direction direction, T TileEntity turtleTile = turtle instanceof TurtleBrain ? ((TurtleBrain) turtle).getOwner() : world.getBlockEntity( position ); if( turtleTile == null ) return TurtleCommandResult.failure( "Turtle has vanished from existence." ); - final TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, position, direction ); + final TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, position, direction ); // See if there is an entity present Vec3d turtlePos = turtlePlayer.position(); @@ -204,7 +202,7 @@ private TurtleCommandResult attack( ITurtleAccess turtle, Direction direction, T // Put everything we collected into the turtles inventory, then return if( attacked ) { - turtlePlayer.unloadInventory( turtle ); + turtlePlayer.inventory.clearContent(); return TurtleCommandResult.success(); } } @@ -229,7 +227,7 @@ private TurtleCommandResult dig( ITurtleAccess turtle, Direction direction, Turt BlockState state = world.getBlockState( blockPosition ); IFluidState fluidState = world.getFluidState( blockPosition ); - TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, turtlePosition, direction ); + TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, turtlePosition, direction ); turtlePlayer.loadInventory( item.copy() ); if( ComputerCraft.turtlesObeyBlockProtection ) @@ -293,10 +291,6 @@ private static Function turtleDropConsumer( TileEntity til private static void stopConsuming( TileEntity tile, ITurtleAccess turtle ) { Direction direction = tile.isRemoved() ? null : turtle.getDirection().getOpposite(); - List extra = DropConsumer.clear(); - for( ItemStack remainder : extra ) - { - WorldUtil.dropItemStack( remainder, turtle.getWorld(), turtle.getPosition(), direction ); - } + DropConsumer.clearAndDrop( turtle.getWorld(), turtle.getPosition(), direction ); } } diff --git a/src/main/java/dan200/computercraft/shared/util/DropConsumer.java b/src/main/java/dan200/computercraft/shared/util/DropConsumer.java index 7aeaee014..d6e6232c2 100644 --- a/src/main/java/dan200/computercraft/shared/util/DropConsumer.java +++ b/src/main/java/dan200/computercraft/shared/util/DropConsumer.java @@ -9,6 +9,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.item.ItemEntity; import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -66,6 +67,12 @@ public static List clear() return remainingStacks; } + public static void clearAndDrop( World world, BlockPos pos, Direction direction ) + { + List remainingDrops = clear(); + for( ItemStack remaining : remainingDrops ) WorldUtil.dropItemStack( remaining, world, pos, direction ); + } + private static void handleDrops( ItemStack stack ) { ItemStack remaining = dropConsumer.apply( stack );