CC-Tweaked/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java

266 lines
9.7 KiB
Java

/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.apis;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.peripheral.generic.data.BlockData;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.block.BlockState;
import net.minecraft.command.CommandSource;
import net.minecraft.command.Commands;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import java.util.*;
/**
* @cc.module commands
*/
public class CommandAPI implements ILuaAPI
{
private final TileCommandComputer computer;
public CommandAPI( TileCommandComputer computer )
{
this.computer = computer;
}
@Override
public String[] getNames()
{
return new String[] { "commands" };
}
private static Object createOutput( String output )
{
return new Object[] { output };
}
private Object[] doCommand( String command )
{
MinecraftServer server = computer.getWorld().getServer();
if( server == null || !server.isCommandBlockEnabled() )
{
return new Object[] { false, createOutput( "Command blocks disabled by server" ) };
}
Commands commandManager = server.getCommandManager();
TileCommandComputer.CommandReceiver receiver = computer.getReceiver();
try
{
receiver.clearOutput();
int result = commandManager.handleCommand( computer.getSource(), command );
return new Object[] { result > 0, receiver.copyOutput(), result };
}
catch( Throwable t )
{
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error running command.", t );
return new Object[] { false, createOutput( "Java Exception Thrown: " + t ) };
}
}
private static Map<?, ?> getBlockInfo( World world, BlockPos pos )
{
// Get the details of the block
BlockState state = world.getBlockState( pos );
Map<String, Object> table = BlockData.fill( new HashMap<>(), state );
TileEntity tile = world.getTileEntity( pos );
if( tile != null ) table.put( "nbt", NBTUtil.toLua( tile.write( new CompoundNBT() ) ) );
return table;
}
/**
* Execute a specific command.
*
* @param command The command to execute.
* @return See {@code cc.treturn}.
* @cc.treturn boolean Whether the command executed successfully.
* @cc.treturn { string... } The output of this command, as a list of lines.
* @cc.treturn number|nil The number of "affected" objects, or `nil` if the command failed. The definition of this
* varies from command to command.
* @cc.usage Set the block above the command computer to stone.
* <pre>
* commands.exec("setblock ~ ~1 ~ minecraft:stone")
* </pre>
*/
@LuaFunction( mainThread = true )
public final Object[] exec( String command )
{
return doCommand( command );
}
/**
* Asynchronously execute a command.
*
* Unlike {@link #exec}, this will immediately return, instead of waiting for the
* command to execute. This allows you to run multiple commands at the same
* time.
*
* When this command has finished executing, it will queue a `task_complete`
* event containing the result of executing this command (what {@link #exec} would
* return).
*
* @param context The context this command executes under.
* @param command The command to execute.
* @return The "task id". When this command has been executed, it will queue a `task_complete` event with a matching id.
* @throws LuaException (hidden) If the task cannot be created.
* @cc.tparam string command The command to execute.
* @cc.usage Asynchronously sets the block above the computer to stone.
* <pre>
* commands.execAsync("~ ~1 ~ minecraft:stone")
* </pre>
* @cc.see parallel One may also use the parallel API to run multiple commands at once.
*/
@LuaFunction
public final long execAsync( ILuaContext context, String command ) throws LuaException
{
return context.issueMainThreadTask( () -> doCommand( command ) );
}
/**
* List all available commands which the computer has permission to execute.
*
* @param args Arguments to this function.
* @return A list of all available commands
* @throws LuaException (hidden) On non-string arguments.
* @cc.tparam string ... The sub-command to complete.
*/
@LuaFunction( mainThread = true )
public final List<String> list( IArguments args ) throws LuaException
{
MinecraftServer server = computer.getWorld().getServer();
if( server == null ) return Collections.emptyList();
CommandNode<CommandSource> node = server.getCommandManager().getDispatcher().getRoot();
for( int j = 0; j < args.count(); j++ )
{
String name = args.getString( j );
node = node.getChild( name );
if( !(node instanceof LiteralCommandNode) ) return Collections.emptyList();
}
List<String> result = new ArrayList<>();
for( CommandNode<?> child : node.getChildren() )
{
if( child instanceof LiteralCommandNode<?> ) result.add( child.getName() );
}
return result;
}
/**
* Get the position of the current command computer.
*
* @return The block's position.
* @cc.treturn number This computer's x position.
* @cc.treturn number This computer's y position.
* @cc.treturn number This computer's z position.
* @cc.see gps.locate To get the position of a non-command computer.
*/
@LuaFunction
public final Object[] getBlockPosition()
{
// This is probably safe to do on the Lua thread. Probably.
BlockPos pos = computer.getPos();
return new Object[] { pos.getX(), pos.getY(), pos.getZ() };
}
/**
* Get information about a range of blocks.
*
* This returns the same information as @{getBlockInfo}, just for multiple
* blocks at once.
*
* Blocks are traversed by ascending y level, followed by z and x - the returned
* table may be indexed using `x + z*width + y*depth*depth`.
*
* @param minX The start x coordinate of the range to query.
* @param minY The start y coordinate of the range to query.
* @param minZ The start z coordinate of the range to query.
* @param maxX The end x coordinate of the range to query.
* @param maxY The end y coordinate of the range to query.
* @param maxZ The end z coordinate of the range to query.
* @return A list of information about each block.
* @throws LuaException If the coordinates are not within the world.
* @throws LuaException If trying to get information about more than 4096 blocks.
*/
@LuaFunction( mainThread = true )
public final List<Map<?, ?>> getBlockInfos( int minX, int minY, int minZ, int maxX, int maxY, int maxZ ) throws LuaException
{
// Get the details of the block
World world = computer.getWorld();
BlockPos min = new BlockPos(
Math.min( minX, maxX ),
Math.min( minY, maxY ),
Math.min( minZ, maxZ )
);
BlockPos max = new BlockPos(
Math.max( minX, maxX ),
Math.max( minY, maxY ),
Math.max( minZ, maxZ )
);
if( !World.isValid( min ) || !World.isValid( max ) )
{
throw new LuaException( "Co-ordinates out of range" );
}
int blocks = (max.getX() - min.getX() + 1) * (max.getY() - min.getY() + 1) * (max.getZ() - min.getZ() + 1);
if( blocks > 4096 ) throw new LuaException( "Too many blocks" );
List<Map<?, ?>> results = new ArrayList<>( blocks );
for( int y = min.getY(); y <= max.getY(); y++ )
{
for( int z = min.getZ(); z <= max.getZ(); z++ )
{
for( int x = min.getX(); x <= max.getX(); x++ )
{
BlockPos pos = new BlockPos( x, y, z );
results.add( getBlockInfo( world, pos ) );
}
}
}
return results;
}
/**
* Get some basic information about a block.
*
* The returned table contains the current name, metadata and block state (as
* with @{turtle.inspect}). If there is a tile entity for that block, its NBT
* will also be returned.
*
* @param x The x position of the block to query.
* @param y The y position of the block to query.
* @param z The z position of the block to query.
* @return The given block's information.
* @throws LuaException If the coordinates are not within the world, or are not currently loaded.
*/
@LuaFunction( mainThread = true )
public final Map<?, ?> getBlockInfo( int x, int y, int z ) throws LuaException
{
// Get the details of the block
World world = computer.getWorld();
BlockPos position = new BlockPos( x, y, z );
if( World.isValid( position ) )
{
return getBlockInfo( world, position );
}
else
{
throw new LuaException( "Co-ordinates out of range" );
}
}
}