CC-Tweaked/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java

759 lines
28 KiB
Java

/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.turtle.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleCommand;
import dan200.computercraft.api.turtle.TurtleCommandResult;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.event.TurtleActionEvent;
import dan200.computercraft.api.turtle.event.TurtleInspectItemEvent;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.asm.TaskCallback;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.peripheral.generic.data.ItemData;
import dan200.computercraft.shared.turtle.core.*;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* The turtle API allows you to control your turtle.
*
* @cc.module turtle
*/
public class TurtleAPI implements ILuaAPI
{
private final IAPIEnvironment environment;
private final ITurtleAccess turtle;
public TurtleAPI( IAPIEnvironment environment, ITurtleAccess turtle )
{
this.environment = environment;
this.turtle = turtle;
}
@Override
public String[] getNames()
{
return new String[] { "turtle" };
}
private MethodResult trackCommand( ITurtleCommand command )
{
environment.addTrackingChange( TrackingField.TURTLE_OPS );
return turtle.executeCommand( command );
}
/**
* Move the turtle forward one block.
*
* @return The turtle command result.
* @cc.treturn boolean Whether the turtle could successfully move.
* @cc.treturn string|nil The reason the turtle could not move.
*/
@LuaFunction
public final MethodResult forward()
{
return trackCommand( new TurtleMoveCommand( MoveDirection.FORWARD ) );
}
/**
* Move the turtle backwards one block.
*
* @return The turtle command result.
* @cc.treturn boolean Whether the turtle could successfully move.
* @cc.treturn string|nil The reason the turtle could not move.
*/
@LuaFunction
public final MethodResult back()
{
return trackCommand( new TurtleMoveCommand( MoveDirection.BACK ) );
}
/**
* Move the turtle up one block.
*
* @return The turtle command result.
* @cc.treturn boolean Whether the turtle could successfully move.
* @cc.treturn string|nil The reason the turtle could not move.
*/
@LuaFunction
public final MethodResult up()
{
return trackCommand( new TurtleMoveCommand( MoveDirection.UP ) );
}
/**
* Move the turtle down one block.
*
* @return The turtle command result.
* @cc.treturn boolean Whether the turtle could successfully move.
* @cc.treturn string|nil The reason the turtle could not move.
*/
@LuaFunction
public final MethodResult down()
{
return trackCommand( new TurtleMoveCommand( MoveDirection.DOWN ) );
}
/**
* Rotate the turtle 90 degress to the left.
*
* @return The turtle command result.
* @cc.treturn boolean Whether the turtle could successfully turn.
* @cc.treturn string|nil The reason the turtle could not turn.
*/
@LuaFunction
public final MethodResult turnLeft()
{
return trackCommand( new TurtleTurnCommand( TurnDirection.LEFT ) );
}
/**
* Rotate the turtle 90 degress to the right.
*
* @return The turtle command result.
* @cc.treturn boolean Whether the turtle could successfully turn.
* @cc.treturn string|nil The reason the turtle could not turn.
*/
@LuaFunction
public final MethodResult turnRight()
{
return trackCommand( new TurtleTurnCommand( TurnDirection.RIGHT ) );
}
/**
* Attempt to break the block in front of the turtle.
*
* This requires a turtle tool capable of breaking the block. Diamond pickaxes
* (mining turtles) can break any vanilla block, but other tools (such as axes)
* are more limited.
*
* @param side The specific tool to use. Should be "left" or "right".
* @return The turtle command result.
* @cc.treturn boolean Whether a block was broken.
* @cc.treturn string|nil The reason no block was broken.
*/
@LuaFunction
public final MethodResult dig( Optional<TurtleSide> side )
{
environment.addTrackingChange( TrackingField.TURTLE_OPS );
return trackCommand( TurtleToolCommand.dig( InteractDirection.FORWARD, side.orElse( null ) ) );
}
/**
* Attempt to break the block above the turtle. See {@link #dig} for full details.
*
* @param side The specific tool to use.
* @return The turtle command result.
* @cc.treturn boolean Whether a block was broken.
* @cc.treturn string|nil The reason no block was broken.
*/
@LuaFunction
public final MethodResult digUp( Optional<TurtleSide> side )
{
environment.addTrackingChange( TrackingField.TURTLE_OPS );
return trackCommand( TurtleToolCommand.dig( InteractDirection.UP, side.orElse( null ) ) );
}
/**
* Attempt to break the block below the turtle. See {@link #dig} for full details.
*
* @param side The specific tool to use.
* @return The turtle command result.
* @cc.treturn boolean Whether a block was broken.
* @cc.treturn string|nil The reason no block was broken.
*/
@LuaFunction
public final MethodResult digDown( Optional<TurtleSide> side )
{
environment.addTrackingChange( TrackingField.TURTLE_OPS );
return trackCommand( TurtleToolCommand.dig( InteractDirection.DOWN, side.orElse( null ) ) );
}
/**
* Place a block or item into the world in front of the turtle.
*
* "Placing" an item allows it to interact with blocks and entities in front of the turtle. For instance, buckets
* can pick up and place down fluids, and wheat can be used to breed cows. However, you cannot use {@link #place} to
* perform arbitrary block interactions, such as clicking buttons or flipping levers.
*
* @param args Arguments to place.
* @return The turtle command result.
* @cc.tparam [opt] string text When placing a sign, set its contents to this text.
* @cc.treturn boolean Whether the block could be placed.
* @cc.treturn string|nil The reason the block was not placed.
*/
@LuaFunction
public final MethodResult place( IArguments args )
{
return trackCommand( new TurtlePlaceCommand( InteractDirection.FORWARD, args.getAll() ) );
}
/**
* Place a block or item into the world above the turtle.
*
* @param args Arguments to place.
* @return The turtle command result.
* @cc.tparam [opt] string text When placing a sign, set its contents to this text.
* @cc.treturn boolean Whether the block could be placed.
* @cc.treturn string|nil The reason the block was not placed.
* @see #place For more information about placing items.
*/
@LuaFunction
public final MethodResult placeUp( IArguments args )
{
return trackCommand( new TurtlePlaceCommand( InteractDirection.UP, args.getAll() ) );
}
/**
* Place a block or item into the world below the turtle.
*
* @param args Arguments to place.
* @return The turtle command result.
* @cc.tparam [opt] string text When placing a sign, set its contents to this text.
* @cc.treturn boolean Whether the block could be placed.
* @cc.treturn string|nil The reason the block was not placed.
* @see #place For more information about placing items.
*/
@LuaFunction
public final MethodResult placeDown( IArguments args )
{
return trackCommand( new TurtlePlaceCommand( InteractDirection.DOWN, args.getAll() ) );
}
/**
* Drop the currently selected stack into the inventory in front of the turtle, or as an item into the world if
* there is no inventory.
*
* @param count The number of items to drop. If not given, the entire stack will be dropped.
* @return The turtle command result.
* @throws LuaException If dropping an invalid number of items.
* @cc.treturn boolean Whether items were dropped.
* @cc.treturn string|nil The reason the no items were dropped.
* @see #select
*/
@LuaFunction
public final MethodResult drop( Optional<Integer> count ) throws LuaException
{
return trackCommand( new TurtleDropCommand( InteractDirection.FORWARD, checkCount( count ) ) );
}
/**
* Drop the currently selected stack into the inventory above the turtle, or as an item into the world if there is
* no inventory.
*
* @param count The number of items to drop. If not given, the entire stack will be dropped.
* @return The turtle command result.
* @throws LuaException If dropping an invalid number of items.
* @cc.treturn boolean Whether items were dropped.
* @cc.treturn string|nil The reason the no items were dropped.
* @see #select
*/
@LuaFunction
public final MethodResult dropUp( Optional<Integer> count ) throws LuaException
{
return trackCommand( new TurtleDropCommand( InteractDirection.UP, checkCount( count ) ) );
}
/**
* Drop the currently selected stack into the inventory in front of the turtle, or as an item into the world if
* there is no inventory.
*
* @param count The number of items to drop. If not given, the entire stack will be dropped.
* @return The turtle command result.
* @throws LuaException If dropping an invalid number of items.
* @cc.treturn boolean Whether items were dropped.
* @cc.treturn string|nil The reason the no items were dropped.
* @see #select
*/
@LuaFunction
public final MethodResult dropDown( Optional<Integer> count ) throws LuaException
{
return trackCommand( new TurtleDropCommand( InteractDirection.DOWN, checkCount( count ) ) );
}
/**
* Change the currently selected slot.
*
* The selected slot is determines what slot actions like {@link #drop} or {@link #getItemCount} act on.
*
* @param slot The slot to select.
* @return The turtle command result.
* @throws LuaException If the slot is out of range.
* @cc.treturn true When the slot has been selected.
* @see #getSelectedSlot
*/
@LuaFunction
public final MethodResult select( int slot ) throws LuaException
{
int actualSlot = checkSlot( slot );
return turtle.executeCommand( turtle -> {
turtle.setSelectedSlot( actualSlot );
return TurtleCommandResult.success();
} );
}
/**
* Get the number of items in the given slot.
*
* @param slot The slot we wish to check. Defaults to the {@link #select selected slot}.
* @return The number of items in this slot.
* @throws LuaException If the slot is out of range.
*/
@LuaFunction
public final int getItemCount( Optional<Integer> slot ) throws LuaException
{
int actualSlot = checkSlot( slot ).orElse( turtle.getSelectedSlot() );
return turtle.getInventory().getItem( actualSlot ).getCount();
}
/**
* Get the remaining number of items which may be stored in this stack.
*
* For instance, if a slot contains 13 blocks of dirt, it has room for another 51.
*
* @param slot The slot we wish to check. Defaults to the {@link #select selected slot}.
* @return The space left in in this slot.
* @throws LuaException If the slot is out of range.
*/
@LuaFunction
public final int getItemSpace( Optional<Integer> slot ) throws LuaException
{
int actualSlot = checkSlot( slot ).orElse( turtle.getSelectedSlot() );
ItemStack stack = turtle.getInventory().getItem( actualSlot );
return stack.isEmpty() ? 64 : Math.min( stack.getMaxStackSize(), 64 ) - stack.getCount();
}
/**
* Check if there is a solid block in front of the turtle. In this case, solid refers to any non-air or liquid
* block.
*
* @return The turtle command result.
* @cc.treturn boolean If there is a solid block in front.
*/
@LuaFunction
public final MethodResult detect()
{
return trackCommand( new TurtleDetectCommand( InteractDirection.FORWARD ) );
}
/**
* Check if there is a solid block above the turtle. In this case, solid refers to any non-air or liquid block.
*
* @return The turtle command result.
* @cc.treturn boolean If there is a solid block in front.
*/
@LuaFunction
public final MethodResult detectUp()
{
return trackCommand( new TurtleDetectCommand( InteractDirection.UP ) );
}
/**
* Check if there is a solid block below the turtle. In this case, solid refers to any non-air or liquid block.
*
* @return The turtle command result.
* @cc.treturn boolean If there is a solid block in front.
*/
@LuaFunction
public final MethodResult detectDown()
{
return trackCommand( new TurtleDetectCommand( InteractDirection.DOWN ) );
}
/**
* Check if the block in front of the turtle is equal to the item in the currently selected slot.
*
* @return If the block and item are equal.
* @cc.treturn boolean If the block and item are equal.
*/
@LuaFunction
public final MethodResult compare()
{
return trackCommand( new TurtleCompareCommand( InteractDirection.FORWARD ) );
}
/**
* Check if the block above the turtle is equal to the item in the currently selected slot.
*
* @return If the block and item are equal.
* @cc.treturn boolean If the block and item are equal.
*/
@LuaFunction
public final MethodResult compareUp()
{
return trackCommand( new TurtleCompareCommand( InteractDirection.UP ) );
}
/**
* Check if the block below the turtle is equal to the item in the currently selected slot.
*
* @return If the block and item are equal.
* @cc.treturn boolean If the block and item are equal.
*/
@LuaFunction
public final MethodResult compareDown()
{
return trackCommand( new TurtleCompareCommand( InteractDirection.DOWN ) );
}
/**
* Attack the entity in front of the turtle.
*
* @param side The specific tool to use.
* @return The turtle command result.
* @cc.treturn boolean Whether an entity was attacked.
* @cc.treturn string|nil The reason nothing was attacked.
*/
@LuaFunction
public final MethodResult attack( Optional<TurtleSide> side )
{
return trackCommand( TurtleToolCommand.attack( InteractDirection.FORWARD, side.orElse( null ) ) );
}
/**
* Attack the entity above the turtle.
*
* @param side The specific tool to use.
* @return The turtle command result.
* @cc.treturn boolean Whether an entity was attacked.
* @cc.treturn string|nil The reason nothing was attacked.
*/
@LuaFunction
public final MethodResult attackUp( Optional<TurtleSide> side )
{
return trackCommand( TurtleToolCommand.attack( InteractDirection.UP, side.orElse( null ) ) );
}
/**
* Attack the entity below the turtle.
*
* @param side The specific tool to use.
* @return The turtle command result.
* @cc.treturn boolean Whether an entity was attacked.
* @cc.treturn string|nil The reason nothing was attacked.
*/
@LuaFunction
public final MethodResult attackDown( Optional<TurtleSide> side )
{
return trackCommand( TurtleToolCommand.attack( InteractDirection.DOWN, side.orElse( null ) ) );
}
/**
* Suck an item from the inventory in front of the turtle, or from an item floating in the world.
*
* This will pull items into the first acceptable slot, starting at the {@link #select currently selected} one.
*
* @param count The number of items to suck. If not given, up to a stack of items will be picked up.
* @return The turtle command result.
* @throws LuaException If given an invalid number of items.
* @cc.treturn boolean Whether items were picked up.
* @cc.treturn string|nil The reason the no items were picked up.
*/
@LuaFunction
public final MethodResult suck( Optional<Integer> count ) throws LuaException
{
return trackCommand( new TurtleSuckCommand( InteractDirection.FORWARD, checkCount( count ) ) );
}
/**
* Suck an item from the inventory above the turtle, or from an item floating in the world.
*
* @param count The number of items to suck. If not given, up to a stack of items will be picked up.
* @return The turtle command result.
* @throws LuaException If given an invalid number of items.
* @cc.treturn boolean Whether items were picked up.
* @cc.treturn string|nil The reason the no items were picked up.
*/
@LuaFunction
public final MethodResult suckUp( Optional<Integer> count ) throws LuaException
{
return trackCommand( new TurtleSuckCommand( InteractDirection.UP, checkCount( count ) ) );
}
/**
* Suck an item from the inventory below the turtle, or from an item floating in the world.
*
* @param count The number of items to suck. If not given, up to a stack of items will be picked up.
* @return The turtle command result.
* @throws LuaException If given an invalid number of items.
* @cc.treturn boolean Whether items were picked up.
* @cc.treturn string|nil The reason the no items were picked up.
*/
@LuaFunction
public final MethodResult suckDown( Optional<Integer> count ) throws LuaException
{
return trackCommand( new TurtleSuckCommand( InteractDirection.DOWN, checkCount( count ) ) );
}
/**
* Get the maximum amount of fuel this turtle currently holds.
*
* @return The fuel level, or "unlimited".
* @cc.treturn[1] number The current amount of fuel a turtle this turtle has.
* @cc.treturn[2] "unlimited" If turtles do not consume fuel when moving.
* @see #getFuelLimit()
* @see #refuel(Optional)
*/
@LuaFunction
public final Object getFuelLevel()
{
return turtle.isFuelNeeded() ? turtle.getFuelLevel() : "unlimited";
}
/**
* Refuel this turtle.
*
* While most actions a turtle can perform (such as digging or placing blocks), moving consumes fuel from the
* turtle's internal buffer. If a turtle has no fuel, it will not move.
*
* {@link #refuel} refuels the turtle, consuming fuel items (such as coal or lava buckets) from the currently
* selected slot and converting them into energy. This finishes once the turtle is fully refuelled or all items have
* been consumed.
*
* @param countA The maximum number of items to consume. One can pass `0` to check if an item is combustable or not.
* @return If this turtle could be refuelled.
* @throws LuaException If the refuel count is out of range.
* @cc.treturn[1] true If the turtle was refuelled.
* @cc.treturn[2] false If the turtle was not refuelled.
* @cc.treturn[2] string The reason the turtle was not refuelled (
* @cc.usage Refuel a turtle from the currently selected slot.
* <pre>{@code
* local level = turtle.getFuelLevel()
* if new_level == "unlimited" then error("Turtle does not need fuel", 0) end
*
* local ok, err = turtle.refuel()
* if ok then
* local new_level = turtle.getFuelLevel()
* print(("Refuelled %d, current level is %d"):format(new_level - level, new_level))
* else
* printError(err)
* end}</pre>
* @cc.usage Check if the current item is a valid fuel source.
* <pre>{@code
* local is_fuel, reason = turtle.refuel(0)
* if not is_fuel then printError(reason) end
* }</pre>
* @see #getFuelLevel()
* @see #getFuelLimit()
*/
@LuaFunction
public final MethodResult refuel( Optional<Integer> countA ) throws LuaException
{
int count = countA.orElse( Integer.MAX_VALUE );
if( count < 0 ) throw new LuaException( "Refuel count " + count + " out of range" );
return trackCommand( new TurtleRefuelCommand( count ) );
}
/**
* Compare the item in the currently selected slot to the item in another slot.
*
* @param slot The slot to compare to.
* @return If the items are the same.
* @throws LuaException If the slot is out of range.
* @cc.treturn boolean If the two items are equal.
*/
@LuaFunction
public final MethodResult compareTo( int slot ) throws LuaException
{
return trackCommand( new TurtleCompareToCommand( checkSlot( slot ) ) );
}
/**
* Move an item from the selected slot to another one.
*
* @param slotArg The slot to move this item to.
* @param countArg The maximum number of items to move.
* @return If the item was moved or not.
* @throws LuaException If the slot is out of range.
* @throws LuaException If the number of items is out of range.
* @cc.treturn boolean If some items were successfully moved.
*/
@LuaFunction
public final MethodResult transferTo( int slotArg, Optional<Integer> countArg ) throws LuaException
{
int slot = checkSlot( slotArg );
int count = checkCount( countArg );
return trackCommand( new TurtleTransferToCommand( slot, count ) );
}
/**
* Get the currently selected slot.
*
* @return The current slot.
* @see #select
*/
@LuaFunction
public final int getSelectedSlot()
{
return turtle.getSelectedSlot() + 1;
}
/**
* Get the maximum amount of fuel this turtle can hold.
*
* By default, normal turtles have a limit of 20,000 and advanced turtles of 100,000.
*
* @return The limit, or "unlimited".
* @cc.treturn[1] number The maximum amount of fuel a turtle can hold.
* @cc.treturn[2] "unlimited" If turtles do not consume fuel when moving.
* @see #getFuelLevel()
* @see #refuel(Optional)
*/
@LuaFunction
public final Object getFuelLimit()
{
return turtle.isFuelNeeded() ? turtle.getFuelLimit() : "unlimited";
}
/**
* Equip (or unequip) an item on the left side of this turtle.
*
* This finds the item in the currently selected slot and attempts to equip it to the left side of the turtle. The
* previous upgrade is removed and placed into the turtle's inventory. If there is no item in the slot, the previous
* upgrade is removed, but no new one is equipped.
*
* @return Whether an item was equiped or not.
* @cc.treturn[1] true If the item was equipped.
* @cc.treturn[2] false If we could not equip the item.
* @cc.treturn[2] string The reason equipping this item failed.
* @see #equipRight()
*/
@LuaFunction
public final MethodResult equipLeft()
{
return trackCommand( new TurtleEquipCommand( TurtleSide.LEFT ) );
}
/**
* Equip (or unequip) an item on the right side of this turtle.
*
* This finds the item in the currently selected slot and attempts to equip it to the right side of the turtle. The
* previous upgrade is removed and placed into the turtle's inventory. If there is no item in the slot, the previous
* upgrade is removed, but no new one is equipped.
*
* @return Whether an item was equiped or not.
* @cc.treturn[1] true If the item was equipped.
* @cc.treturn[2] false If we could not equip the item.
* @cc.treturn[2] string The reason equipping this item failed.
* @see #equipRight()
*/
@LuaFunction
public final MethodResult equipRight()
{
return trackCommand( new TurtleEquipCommand( TurtleSide.RIGHT ) );
}
/**
* Get information about the block in front of the turtle.
*
* @return The turtle command result.
* @cc.treturn boolean Whether there is a block in front of the turtle.
* @cc.treturn table|string Information about the block in front, or a message explaining that there is no block.
*/
@LuaFunction
public final MethodResult inspect()
{
return trackCommand( new TurtleInspectCommand( InteractDirection.FORWARD ) );
}
/**
* Get information about the block above the turtle.
*
* @return The turtle command result.
* @cc.treturn boolean Whether there is a block above the turtle.
* @cc.treturn table|string Information about the above below, or a message explaining that there is no block.
*/
@LuaFunction
public final MethodResult inspectUp()
{
return trackCommand( new TurtleInspectCommand( InteractDirection.UP ) );
}
/**
* Get information about the block below the turtle.
*
* @return The turtle command result.
* @cc.treturn boolean Whether there is a block below the turtle.
* @cc.treturn table|string Information about the block below, or a message explaining that there is no block.
*/
@LuaFunction
public final MethodResult inspectDown()
{
return trackCommand( new TurtleInspectCommand( InteractDirection.DOWN ) );
}
/**
* Get detailed information about the items in the given slot.
*
* @param context The Lua context
* @param slot The slot to get information about. Defaults to the {@link #select selected slot}.
* @param detailed Whether to include "detailed" information. When {@code true} the method will contain much
* more information about the item at the cost of taking longer to run.
* @return The command result.
* @throws LuaException If the slot is out of range.
* @cc.treturn nil|table Information about the given slot, or {@code nil} if it is empty.
* @cc.usage Print the current slot, assuming it contains 13 dirt.
*
* <pre>{@code
* print(textutils.serialize(turtle.getItemDetail()))
* -- => {
* -- name = "minecraft:dirt",
* -- count = 13,
* -- }
* }</pre>
*/
@LuaFunction
public final MethodResult getItemDetail( ILuaContext context, Optional<Integer> slot, Optional<Boolean> detailed ) throws LuaException
{
int actualSlot = checkSlot( slot ).orElse( turtle.getSelectedSlot() );
return detailed.orElse( false )
? TaskCallback.make( context, () -> getItemDetail( actualSlot, true ) )
: MethodResult.of( getItemDetail( actualSlot, false ) );
}
private Object[] getItemDetail( int slot, boolean detailed )
{
ItemStack stack = turtle.getInventory().getItem( slot );
if( stack.isEmpty() ) return new Object[] { null };
Map<String, Object> table = detailed
? ItemData.fill( new HashMap<>(), stack )
: ItemData.fillBasicSafe( new HashMap<>(), stack );
TurtleActionEvent event = new TurtleInspectItemEvent( turtle, stack, table, detailed );
if( MinecraftForge.EVENT_BUS.post( event ) ) return new Object[] { false, event.getFailureMessage() };
return new Object[] { table };
}
private static int checkSlot( int slot ) throws LuaException
{
if( slot < 1 || slot > 16 ) throw new LuaException( "Slot number " + slot + " out of range" );
return slot - 1;
}
private static Optional<Integer> checkSlot( Optional<Integer> slot ) throws LuaException
{
return slot.isPresent() ? Optional.of( checkSlot( slot.get() ) ) : Optional.empty();
}
private static int checkCount( Optional<Integer> countArg ) throws LuaException
{
int count = countArg.orElse( 64 );
if( count < 0 || count > 64 ) throw new LuaException( "Item count " + count + " out of range" );
return count;
}
}