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!
This commit is contained in:
Jonathan Coates 2021-05-29 15:11:29 +01:00
parent 9f7cc00fcb
commit 9142ccfc93
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
11 changed files with 281 additions and 337 deletions

View File

@ -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).

View File

@ -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 ) )
{

View File

@ -50,7 +50,7 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle )
Map<String, Object> 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() );

View File

@ -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() )
{

View File

@ -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<Entity, Vec3d> 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<ItemStack> 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<ItemStack> 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<ItemStack> 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;
}
}

View File

@ -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

View File

@ -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 ) )
{

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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<ItemStack, ItemStack> turtleDropConsumer( TileEntity til
private static void stopConsuming( TileEntity tile, ITurtleAccess turtle )
{
Direction direction = tile.isRemoved() ? null : turtle.getDirection().getOpposite();
List<ItemStack> extra = DropConsumer.clear();
for( ItemStack remainder : extra )
{
WorldUtil.dropItemStack( remainder, turtle.getWorld(), turtle.getPosition(), direction );
}
DropConsumer.clearAndDrop( turtle.getWorld(), turtle.getPosition(), direction );
}
}

View File

@ -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<ItemStack> clear()
return remainingStacks;
}
public static void clearAndDrop( World world, BlockPos pos, Direction direction )
{
List<ItemStack> remainingDrops = clear();
for( ItemStack remaining : remainingDrops ) WorldUtil.dropItemStack( remaining, world, pos, direction );
}
private static void handleDrops( ItemStack stack )
{
ItemStack remaining = dropConsumer.apply( stack );