mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-15 11:45:42 +00:00
Copy over CCTweaks's command system
This adds several commands which may be useful for server owners. It'd be nice to integrate this into ComputerCraft itself, but the associated command framework is quite large so we'd have to think about it.
This commit is contained in:
parent
04590befb3
commit
f3b11bc1c2
@ -25,6 +25,7 @@ import dan200.computercraft.core.filesystem.FileMount;
|
|||||||
import dan200.computercraft.core.filesystem.JarMount;
|
import dan200.computercraft.core.filesystem.JarMount;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
import dan200.computercraft.shared.command.CommandComputer;
|
import dan200.computercraft.shared.command.CommandComputer;
|
||||||
|
import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||||
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
|
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
|
||||||
import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
|
import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
|
||||||
import dan200.computercraft.shared.computer.blocks.BlockComputer;
|
import dan200.computercraft.shared.computer.blocks.BlockComputer;
|
||||||
@ -427,6 +428,7 @@ public class ComputerCraft
|
|||||||
public void onServerStarting( FMLServerStartingEvent event )
|
public void onServerStarting( FMLServerStartingEvent event )
|
||||||
{
|
{
|
||||||
event.registerServerCommand( new CommandComputer() );
|
event.registerServerCommand( new CommandComputer() );
|
||||||
|
event.registerServerCommand( new CommandComputerCraft() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mod.EventHandler
|
@Mod.EventHandler
|
||||||
|
@ -0,0 +1,256 @@
|
|||||||
|
package dan200.computercraft.shared.command;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import dan200.computercraft.core.computer.Computer;
|
||||||
|
import dan200.computercraft.shared.command.framework.*;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
import net.minecraft.command.CommandException;
|
||||||
|
import net.minecraft.command.ICommandSender;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.player.EntityPlayerMP;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.text.ITextComponent;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static dan200.computercraft.shared.command.framework.ChatHelpers.*;
|
||||||
|
|
||||||
|
public final class CommandComputerCraft extends CommandDelegate
|
||||||
|
{
|
||||||
|
public CommandComputerCraft()
|
||||||
|
{
|
||||||
|
super( create() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISubCommand create()
|
||||||
|
{
|
||||||
|
CommandRoot root = new CommandRoot(
|
||||||
|
"computercraft", "Various commands for controlling computers.",
|
||||||
|
"The /computercraft command provides various debugging and administrator tools for controlling and " +
|
||||||
|
"interacting with computers."
|
||||||
|
);
|
||||||
|
|
||||||
|
root.register( new SubCommandBase(
|
||||||
|
"dump", "[id]", "Display the status of computers.", UserLevel.OWNER_OP,
|
||||||
|
"Display the status of all computers or specific information about one computer. You can either specify the computer's instance " +
|
||||||
|
"id (e.g. 123) or computer id (e.g #123)."
|
||||||
|
)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||||
|
{
|
||||||
|
if( arguments.size() == 0 )
|
||||||
|
{
|
||||||
|
TextTable table = new TextTable( "Instance", "Id", "On", "Position" );
|
||||||
|
|
||||||
|
int max = 50;
|
||||||
|
for( ServerComputer computer : ComputerCraft.serverComputerRegistry.getComputers() )
|
||||||
|
{
|
||||||
|
table.addRow(
|
||||||
|
linkComputer( computer ),
|
||||||
|
text( Integer.toString( computer.getID() ) ),
|
||||||
|
bool( computer.isOn() ),
|
||||||
|
linkPosition( context, computer )
|
||||||
|
);
|
||||||
|
|
||||||
|
if( max-- < 0 ) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.displayTo( context.getSender() );
|
||||||
|
}
|
||||||
|
else if( arguments.size() == 1 )
|
||||||
|
{
|
||||||
|
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
|
||||||
|
|
||||||
|
TextTable table = new TextTable();
|
||||||
|
table.addRow( header( "Instance" ), text( Integer.toString( computer.getInstanceID() ) ) );
|
||||||
|
table.addRow( header( "Id" ), text( Integer.toString( computer.getID() ) ) );
|
||||||
|
table.addRow( header( "Label" ), text( computer.getLabel() ) );
|
||||||
|
table.addRow( header( "On" ), bool( computer.isOn() ) );
|
||||||
|
table.addRow( header( "Position" ), linkPosition( context, computer ) );
|
||||||
|
table.addRow( header( "Family" ), text( computer.getFamily().toString() ) );
|
||||||
|
|
||||||
|
for( int i = 0; i < 6; i++ )
|
||||||
|
{
|
||||||
|
IPeripheral peripheral = computer.getPeripheral( i );
|
||||||
|
if( peripheral != null )
|
||||||
|
{
|
||||||
|
table.addRow( header( "Peripheral " + Computer.s_sideNames[ i ] ), text( peripheral.getType() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.displayTo( context.getSender() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new CommandException( context.getFullUsage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||||
|
{
|
||||||
|
return arguments.size() == 1
|
||||||
|
? ComputerSelector.completeComputer( arguments.get( 0 ) )
|
||||||
|
: Collections.<String>emptyList();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
root.register( new SubCommandBase(
|
||||||
|
"shutdown", "[ids...]", "Shutdown computers remotely.", UserLevel.OWNER_OP,
|
||||||
|
"Shutdown the listed computers or all if none are specified. You can either specify the computer's instance " +
|
||||||
|
"id (e.g. 123) or computer id (e.g #123)."
|
||||||
|
)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||||
|
{
|
||||||
|
List<ServerComputer> computers = Lists.newArrayList();
|
||||||
|
if( arguments.size() > 0 )
|
||||||
|
{
|
||||||
|
for( String arg : arguments )
|
||||||
|
{
|
||||||
|
computers.add( ComputerSelector.getComputer( arg ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
computers.addAll( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||||
|
}
|
||||||
|
|
||||||
|
int shutdown = 0;
|
||||||
|
for( ServerComputer computer : computers )
|
||||||
|
{
|
||||||
|
if( computer.isOn() ) shutdown++;
|
||||||
|
computer.unload();
|
||||||
|
}
|
||||||
|
context.getSender().sendMessage( text( "Shutdown " + shutdown + " / " + computers.size() + " computers" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||||
|
{
|
||||||
|
return arguments.size() == 0
|
||||||
|
? Collections.<String>emptyList()
|
||||||
|
: ComputerSelector.completeComputer( arguments.get( arguments.size() - 1 ) );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
root.register( new SubCommandBase(
|
||||||
|
"tp", "<id>", "Teleport to a specific computer.", UserLevel.OP,
|
||||||
|
"Teleport to the location of a computer. You can either specify the computer's instance " +
|
||||||
|
"id (e.g. 123) or computer id (e.g #123)."
|
||||||
|
)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||||
|
{
|
||||||
|
if( arguments.size() != 1 ) throw new CommandException( context.getFullUsage() );
|
||||||
|
|
||||||
|
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
|
||||||
|
World world = computer.getWorld();
|
||||||
|
BlockPos pos = computer.getPosition();
|
||||||
|
|
||||||
|
if( world == null || pos == null ) throw new CommandException( "Cannot locate computer in world" );
|
||||||
|
|
||||||
|
ICommandSender sender = context.getSender();
|
||||||
|
if( !(sender instanceof Entity) ) throw new CommandException( "Sender is not an entity" );
|
||||||
|
|
||||||
|
if( sender instanceof EntityPlayerMP )
|
||||||
|
{
|
||||||
|
EntityPlayerMP entity = (EntityPlayerMP) sender;
|
||||||
|
if( entity.getEntityWorld() != world )
|
||||||
|
{
|
||||||
|
context.getServer().getPlayerList().changePlayerDimension( entity, world.provider.getDimension() );
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.setPositionAndUpdate( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5 );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Entity entity = (Entity) sender;
|
||||||
|
if( entity.getEntityWorld() != world )
|
||||||
|
{
|
||||||
|
entity.changeDimension( world.provider.getDimension() );
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.setLocationAndAngles(
|
||||||
|
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5,
|
||||||
|
entity.rotationYaw, entity.rotationPitch
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||||
|
{
|
||||||
|
return arguments.size() == 1
|
||||||
|
? ComputerSelector.completeComputer( arguments.get( 0 ) )
|
||||||
|
: Collections.<String>emptyList();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
root.register(new SubCommandBase(
|
||||||
|
"view", "<id>", "View the terminal of a computer.", UserLevel.OP,
|
||||||
|
"Open the terminal of a computer, allowing remote control of a computer. This does not provide access to " +
|
||||||
|
"turtle's inventories. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123)."
|
||||||
|
) {
|
||||||
|
@Override
|
||||||
|
public void execute(@Nonnull CommandContext context, @Nonnull List<String> arguments) throws CommandException {
|
||||||
|
if (arguments.size() != 1) throw new CommandException(context.getFullUsage());
|
||||||
|
|
||||||
|
ICommandSender sender = context.getSender();
|
||||||
|
if (!(sender instanceof EntityPlayerMP)) {
|
||||||
|
throw new CommandException("Cannot open terminal for non-player");
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerComputer computer = ComputerSelector.getComputer(arguments.get(0));
|
||||||
|
ComputerCraft.openComputerGUI( (EntityPlayerMP) sender, computer );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<String> getCompletion(@Nonnull CommandContext context, @Nonnull List<String> arguments) {
|
||||||
|
return arguments.size() == 1
|
||||||
|
? ComputerSelector.completeComputer( arguments.get( 0 ) )
|
||||||
|
: Collections.emptyList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ITextComponent linkComputer( ServerComputer computer )
|
||||||
|
{
|
||||||
|
return link(
|
||||||
|
text( Integer.toString( computer.getInstanceID() ) ),
|
||||||
|
"/computercraft dump " + computer.getInstanceID(),
|
||||||
|
"View more info about this computer"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ITextComponent linkPosition( CommandContext context, ServerComputer computer )
|
||||||
|
{
|
||||||
|
if( UserLevel.OP.canExecute( context ) )
|
||||||
|
{
|
||||||
|
return link(
|
||||||
|
position( computer.getPosition() ),
|
||||||
|
"/computercraft tp " + computer.getInstanceID(),
|
||||||
|
"Teleport to this computer"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return position( computer.getPosition() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
package dan200.computercraft.shared.command;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
import net.minecraft.command.CommandException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class ComputerSelector
|
||||||
|
{
|
||||||
|
public static ServerComputer getComputer( String selector ) throws CommandException
|
||||||
|
{
|
||||||
|
if( selector.length() > 0 && selector.charAt( 0 ) == '#' )
|
||||||
|
{
|
||||||
|
selector = selector.substring( 1 );
|
||||||
|
|
||||||
|
int id;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
id = Integer.parseInt( selector );
|
||||||
|
}
|
||||||
|
catch( NumberFormatException e )
|
||||||
|
{
|
||||||
|
throw new CommandException( "'" + selector + "' is not a valid number" );
|
||||||
|
}
|
||||||
|
|
||||||
|
// We copy it to prevent concurrent modifications.
|
||||||
|
List<ServerComputer> computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||||
|
List<ServerComputer> candidates = Lists.newArrayList();
|
||||||
|
for( ServerComputer searchComputer : computers )
|
||||||
|
{
|
||||||
|
if( searchComputer.getID() == id )
|
||||||
|
{
|
||||||
|
candidates.add( searchComputer );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( candidates.size() == 0 )
|
||||||
|
{
|
||||||
|
throw new CommandException( "No such computer for id " + id );
|
||||||
|
}
|
||||||
|
else if( candidates.size() == 1 )
|
||||||
|
{
|
||||||
|
return candidates.get( 0 );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StringBuilder builder = new StringBuilder( "Multiple computers with id " )
|
||||||
|
.append( id ).append( " (instances " );
|
||||||
|
|
||||||
|
boolean first = true;
|
||||||
|
for( ServerComputer computer : candidates )
|
||||||
|
{
|
||||||
|
if( first )
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.append( ", " );
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append( computer.getInstanceID() );
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append( ")" );
|
||||||
|
|
||||||
|
throw new CommandException( builder.toString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int instance;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
instance = Integer.parseInt( selector );
|
||||||
|
}
|
||||||
|
catch( NumberFormatException e )
|
||||||
|
{
|
||||||
|
throw new CommandException( "'" + selector + "' is not a valid number" );
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerComputer computer = ComputerCraft.serverComputerRegistry.get( instance );
|
||||||
|
if( computer == null )
|
||||||
|
{
|
||||||
|
throw new CommandException( "No such computer for instance id " + instance );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return computer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> completeComputer( String selector )
|
||||||
|
{
|
||||||
|
Set<String> options = Sets.newHashSet();
|
||||||
|
|
||||||
|
// We copy it to prevent concurrent modifications.
|
||||||
|
List<ServerComputer> computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||||
|
|
||||||
|
if( selector.length() > 0 && selector.charAt( 0 ) == '#' )
|
||||||
|
{
|
||||||
|
selector = selector.substring( 1 );
|
||||||
|
|
||||||
|
for( ServerComputer computer : computers )
|
||||||
|
{
|
||||||
|
String id = Integer.toString( computer.getID() );
|
||||||
|
if( id.startsWith( selector ) ) options.add( "#" + id );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for( ServerComputer computer : computers )
|
||||||
|
{
|
||||||
|
String id = Integer.toString( computer.getInstanceID() );
|
||||||
|
if( id.startsWith( selector ) ) options.add( id );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Lists.newArrayList( options );
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package dan200.computercraft.shared.command;
|
||||||
|
|
||||||
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
|
import dan200.computercraft.shared.computer.core.IComputer;
|
||||||
|
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
import net.minecraft.entity.player.EntityPlayer;
|
||||||
|
import net.minecraft.inventory.Container;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.util.text.TextComponentTranslation;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class ContainerViewComputer extends Container implements IContainerComputer
|
||||||
|
{
|
||||||
|
private final IComputer computer;
|
||||||
|
|
||||||
|
public ContainerViewComputer( IComputer computer )
|
||||||
|
{
|
||||||
|
this.computer = computer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IComputer getComputer()
|
||||||
|
{
|
||||||
|
return computer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canInteractWith( @Nonnull EntityPlayer player )
|
||||||
|
{
|
||||||
|
if( computer instanceof ServerComputer )
|
||||||
|
{
|
||||||
|
ServerComputer serverComputer = (ServerComputer) computer;
|
||||||
|
|
||||||
|
// If this computer no longer exists then discard it.
|
||||||
|
if( ComputerCraft.serverComputerRegistry.get( serverComputer.getInstanceID() ) != serverComputer )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're a command computer then ensure we're in creative
|
||||||
|
if( serverComputer.getFamily() == ComputerFamily.Command )
|
||||||
|
{
|
||||||
|
MinecraftServer server = player.getServer();
|
||||||
|
if( server == null || !server.isCommandBlockEnabled() )
|
||||||
|
{
|
||||||
|
player.sendMessage( new TextComponentTranslation( "advMode.notEnabled" ) );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if( !ComputerCraft.canPlayerUseCommands( player ) || !player.capabilities.isCreativeMode )
|
||||||
|
{
|
||||||
|
player.sendMessage( new TextComponentTranslation( "advMode.notAllowed" ) );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
package dan200.computercraft.shared.command.framework;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.text.ITextComponent;
|
||||||
|
import net.minecraft.util.text.Style;
|
||||||
|
import net.minecraft.util.text.TextComponentString;
|
||||||
|
import net.minecraft.util.text.TextFormatting;
|
||||||
|
import net.minecraft.util.text.event.ClickEvent;
|
||||||
|
import net.minecraft.util.text.event.HoverEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various helpers for building chat messages
|
||||||
|
*/
|
||||||
|
public final class ChatHelpers
|
||||||
|
{
|
||||||
|
private static final TextFormatting HEADER = TextFormatting.LIGHT_PURPLE;
|
||||||
|
private static final TextFormatting SYNOPSIS = TextFormatting.AQUA;
|
||||||
|
private static final TextFormatting NAME = TextFormatting.GREEN;
|
||||||
|
|
||||||
|
public static ITextComponent coloured( String text, TextFormatting colour )
|
||||||
|
{
|
||||||
|
ITextComponent component = new TextComponentString( text == null ? "" : text );
|
||||||
|
component.getStyle().setColor( colour );
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITextComponent text( String text )
|
||||||
|
{
|
||||||
|
return new TextComponentString( text == null ? "" : text );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITextComponent list( ITextComponent... children )
|
||||||
|
{
|
||||||
|
ITextComponent component = new TextComponentString( "" );
|
||||||
|
for( ITextComponent child : children )
|
||||||
|
{
|
||||||
|
component.appendSibling( child );
|
||||||
|
}
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITextComponent getHelp( CommandContext context, ISubCommand command, String prefix )
|
||||||
|
{
|
||||||
|
ITextComponent output = new TextComponentString( "" )
|
||||||
|
.appendSibling( coloured( "/" + prefix + " " + command.getUsage( context ), HEADER ) )
|
||||||
|
.appendText( " " )
|
||||||
|
.appendSibling( coloured( command.getSynopsis(), SYNOPSIS ) );
|
||||||
|
|
||||||
|
String desc = command.getDescription();
|
||||||
|
if( !Strings.isNullOrEmpty( desc ) ) output.appendText( "\n" + desc );
|
||||||
|
|
||||||
|
if( command instanceof CommandRoot )
|
||||||
|
{
|
||||||
|
for( ISubCommand subCommand : ((CommandRoot) command).getSubCommands().values() )
|
||||||
|
{
|
||||||
|
if( !subCommand.checkPermission( context ) ) continue;
|
||||||
|
|
||||||
|
output.appendText( "\n" );
|
||||||
|
|
||||||
|
ITextComponent component = coloured( subCommand.getName(), NAME );
|
||||||
|
component.getStyle().setClickEvent( new ClickEvent(
|
||||||
|
ClickEvent.Action.SUGGEST_COMMAND,
|
||||||
|
"/" + prefix + " " + subCommand.getName()
|
||||||
|
) );
|
||||||
|
output.appendSibling( component );
|
||||||
|
|
||||||
|
output.appendText( " - " + subCommand.getSynopsis() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITextComponent position( BlockPos pos )
|
||||||
|
{
|
||||||
|
if( pos == null ) return text( "<no pos>" );
|
||||||
|
return formatted( "%d, %d, %d", pos.getX(), pos.getY(), pos.getZ() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITextComponent bool( boolean value )
|
||||||
|
{
|
||||||
|
if( value )
|
||||||
|
{
|
||||||
|
ITextComponent component = new TextComponentString( "Y" );
|
||||||
|
component.getStyle().setColor( TextFormatting.GREEN );
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ITextComponent component = new TextComponentString( "N" );
|
||||||
|
component.getStyle().setColor( TextFormatting.RED );
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITextComponent formatted( String format, Object... args )
|
||||||
|
{
|
||||||
|
return new TextComponentString( String.format( format, args ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITextComponent link( ITextComponent component, String command, String toolTip )
|
||||||
|
{
|
||||||
|
Style style = component.getStyle();
|
||||||
|
|
||||||
|
if( style.getColor() == null ) style.setColor( TextFormatting.YELLOW );
|
||||||
|
style.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ) );
|
||||||
|
style.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TextComponentString( toolTip ) ) );
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITextComponent header( String text )
|
||||||
|
{
|
||||||
|
return coloured( text, HEADER );
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package dan200.computercraft.shared.command.framework;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import net.minecraft.command.ICommandSender;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the way a command was invoked, including the command sender, the current server and
|
||||||
|
* the "path" to this command.
|
||||||
|
*/
|
||||||
|
public final class CommandContext
|
||||||
|
{
|
||||||
|
private final MinecraftServer server;
|
||||||
|
private final ICommandSender sender;
|
||||||
|
private final List<ISubCommand> path;
|
||||||
|
|
||||||
|
public CommandContext( MinecraftServer server, ICommandSender sender, ISubCommand initial )
|
||||||
|
{
|
||||||
|
this.server = server;
|
||||||
|
this.sender = sender;
|
||||||
|
this.path = Collections.singletonList( initial );
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandContext( MinecraftServer server, ICommandSender sender, List<ISubCommand> path )
|
||||||
|
{
|
||||||
|
this.server = server;
|
||||||
|
this.sender = sender;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandContext enter( ISubCommand child )
|
||||||
|
{
|
||||||
|
List<ISubCommand> newPath = Lists.newArrayListWithExpectedSize( path.size() + 1 );
|
||||||
|
newPath.addAll( path );
|
||||||
|
newPath.add( child );
|
||||||
|
return new CommandContext( server, sender, newPath );
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandContext parent()
|
||||||
|
{
|
||||||
|
if( path.size() == 1 ) throw new IllegalStateException( "No parent command" );
|
||||||
|
return new CommandContext( server, sender, path.subList( 0, path.size() - 1 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullPath()
|
||||||
|
{
|
||||||
|
StringBuilder out = new StringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
for( ISubCommand command : path )
|
||||||
|
{
|
||||||
|
if( first )
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out.append( ' ' );
|
||||||
|
}
|
||||||
|
|
||||||
|
out.append( command.getName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullUsage()
|
||||||
|
{
|
||||||
|
return "/" + getFullPath() + " " + path.get( path.size() - 1 ).getUsage( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ISubCommand> getPath()
|
||||||
|
{
|
||||||
|
return Collections.unmodifiableList( path );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRootCommand()
|
||||||
|
{
|
||||||
|
return path.get( 0 ).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinecraftServer getServer()
|
||||||
|
{
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommandSender getSender()
|
||||||
|
{
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
package dan200.computercraft.shared.command.framework;
|
||||||
|
|
||||||
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import net.minecraft.command.CommandException;
|
||||||
|
import net.minecraft.command.ICommand;
|
||||||
|
import net.minecraft.command.ICommandSender;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link net.minecraft.command.ICommand} which delegates to a {@link ISubCommand}.
|
||||||
|
*/
|
||||||
|
public class CommandDelegate implements ICommand
|
||||||
|
{
|
||||||
|
private final ISubCommand command;
|
||||||
|
|
||||||
|
public CommandDelegate( ISubCommand command )
|
||||||
|
{
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return command.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getUsage( @Nonnull ICommandSender sender )
|
||||||
|
{
|
||||||
|
return "/" + command.getName() + " " + command.getUsage( new CommandContext( sender.getServer(), sender, command ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<String> getAliases()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args ) throws CommandException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
command.execute( new CommandContext( server, sender, command ), Arrays.asList( args ) );
|
||||||
|
}
|
||||||
|
catch( CommandException e )
|
||||||
|
{
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
catch( Throwable e )
|
||||||
|
{
|
||||||
|
ComputerCraft.log.error( "Unhandled exception in command", e );
|
||||||
|
throw new CommandException( "Unhandled exception: " + e.toString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<String> getTabCompletions( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args, @Nullable BlockPos pos )
|
||||||
|
{
|
||||||
|
return command.getCompletion( new CommandContext( server, sender, command ), Arrays.asList( args ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkPermission( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender )
|
||||||
|
{
|
||||||
|
return command.checkPermission( new CommandContext( server, sender, command ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUsernameIndex( @Nonnull String[] args, int index )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo( @Nonnull ICommand o )
|
||||||
|
{
|
||||||
|
return getName().compareTo( o.getName() );
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
package dan200.computercraft.shared.command.framework;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import net.minecraft.command.CommandBase;
|
||||||
|
import net.minecraft.command.CommandException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A command which delegates to a series of sub commands
|
||||||
|
*/
|
||||||
|
public class CommandRoot implements ISubCommand
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final String synopsis;
|
||||||
|
private final String description;
|
||||||
|
private final Map<String, ISubCommand> subCommands = Maps.newHashMap();
|
||||||
|
|
||||||
|
public CommandRoot( String name, String synopsis, String description )
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.synopsis = synopsis;
|
||||||
|
this.description = description;
|
||||||
|
|
||||||
|
register( new SubCommandHelp( this ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register( ISubCommand command )
|
||||||
|
{
|
||||||
|
subCommands.put( command.getName(), command );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getUsage( CommandContext context )
|
||||||
|
{
|
||||||
|
StringBuilder out = new StringBuilder( "<" );
|
||||||
|
boolean first = true;
|
||||||
|
for( ISubCommand command : subCommands.values() )
|
||||||
|
{
|
||||||
|
if( command.checkPermission( context ) )
|
||||||
|
{
|
||||||
|
if( first )
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out.append( "|" );
|
||||||
|
}
|
||||||
|
|
||||||
|
out.append( command.getName() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.append( ">" ).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getSynopsis()
|
||||||
|
{
|
||||||
|
return synopsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getDescription()
|
||||||
|
{
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkPermission( @Nonnull CommandContext context )
|
||||||
|
{
|
||||||
|
for( ISubCommand command : subCommands.values() )
|
||||||
|
{
|
||||||
|
if( command.checkPermission( context ) ) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ISubCommand> getSubCommands()
|
||||||
|
{
|
||||||
|
return Collections.unmodifiableMap( subCommands );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||||
|
{
|
||||||
|
if( arguments.size() == 0 )
|
||||||
|
{
|
||||||
|
context.getSender().sendMessage( ChatHelpers.getHelp( context, this, context.getFullPath() ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ISubCommand command = subCommands.get( arguments.get( 0 ) );
|
||||||
|
if( command == null || !command.checkPermission( context ) )
|
||||||
|
{
|
||||||
|
throw new CommandException( getName() + " " + getUsage( context ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
command.execute( context.enter( command ), arguments.subList( 1, arguments.size() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||||
|
{
|
||||||
|
if( arguments.size() == 0 )
|
||||||
|
{
|
||||||
|
return Lists.newArrayList( subCommands.keySet() );
|
||||||
|
}
|
||||||
|
else if( arguments.size() == 1 )
|
||||||
|
{
|
||||||
|
List<String> list = Lists.newArrayList();
|
||||||
|
String match = arguments.get( 0 );
|
||||||
|
|
||||||
|
for( ISubCommand command : subCommands.values() )
|
||||||
|
{
|
||||||
|
if( CommandBase.doesStringStartWith( match, command.getName() ) && command.checkPermission( context ) )
|
||||||
|
{
|
||||||
|
list.add( command.getName() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ISubCommand command = subCommands.get( arguments.get( 0 ) );
|
||||||
|
if( command == null || !command.checkPermission( context ) ) return Collections.emptyList();
|
||||||
|
|
||||||
|
return command.getCompletion( context, arguments.subList( 1, arguments.size() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package dan200.computercraft.shared.command.framework;
|
||||||
|
|
||||||
|
import net.minecraft.command.CommandException;
|
||||||
|
import net.minecraft.command.ICommand;
|
||||||
|
import net.minecraft.command.ICommandSender;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A slightly different implementation of {@link ICommand} which is delegated to.
|
||||||
|
*/
|
||||||
|
public interface ISubCommand
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the name of this command
|
||||||
|
*
|
||||||
|
* @return The name of this command
|
||||||
|
* @see ICommand#getName()
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the usage of this command
|
||||||
|
*
|
||||||
|
* @param context The context this command is executed in
|
||||||
|
* @return The usage of this command
|
||||||
|
* @see ICommand#getUsage(ICommandSender)
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
String getUsage( CommandContext context );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a short description of this command, including its usage.
|
||||||
|
*
|
||||||
|
* @return The command's synopsis
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
String getSynopsis();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the lengthy description of this command. This synopsis is prepended to this.
|
||||||
|
*
|
||||||
|
* @return The command's description
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
String getDescription();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a given command sender has permission to execute this command.
|
||||||
|
*
|
||||||
|
* @param context The current command context.
|
||||||
|
* @return Whether this command can be executed.
|
||||||
|
* @see ICommand#checkPermission(MinecraftServer, ICommandSender)
|
||||||
|
*/
|
||||||
|
boolean checkPermission( @Nonnull CommandContext context );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute this command
|
||||||
|
*
|
||||||
|
* @param context The current command context.
|
||||||
|
* @param arguments The arguments passed @throws CommandException When an error occurs
|
||||||
|
* @see ICommand#execute(MinecraftServer, ICommandSender, String[])
|
||||||
|
*/
|
||||||
|
void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of possible completions
|
||||||
|
*
|
||||||
|
* @param context The current command context.
|
||||||
|
* @param arguments The arguments passed. You should complete the last one.
|
||||||
|
* @return List of possible completions
|
||||||
|
* @see ICommand#getTabCompletions(MinecraftServer, ICommandSender, String[], BlockPos)
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments );
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package dan200.computercraft.shared.command.framework;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class SubCommandBase implements ISubCommand
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final String usage;
|
||||||
|
private final String synopsis;
|
||||||
|
private final String description;
|
||||||
|
private final UserLevel level;
|
||||||
|
|
||||||
|
public SubCommandBase( String name, String usage, String synopsis, UserLevel level, String description )
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.usage = usage;
|
||||||
|
this.synopsis = synopsis;
|
||||||
|
this.description = description;
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubCommandBase( String name, String synopsis, UserLevel level, String description )
|
||||||
|
{
|
||||||
|
this( name, "", synopsis, level, description );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getUsage( CommandContext context )
|
||||||
|
{
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getSynopsis()
|
||||||
|
{
|
||||||
|
return synopsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getDescription()
|
||||||
|
{
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkPermission( @Nonnull CommandContext context )
|
||||||
|
{
|
||||||
|
return level.canExecute( context );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
package dan200.computercraft.shared.command.framework;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import joptsimple.internal.Strings;
|
||||||
|
import net.minecraft.command.CommandBase;
|
||||||
|
import net.minecraft.command.CommandException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SubCommandHelp implements ISubCommand
|
||||||
|
{
|
||||||
|
private final CommandRoot branchCommand;
|
||||||
|
|
||||||
|
public SubCommandHelp( CommandRoot branchCommand )
|
||||||
|
{
|
||||||
|
this.branchCommand = branchCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return "help";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getUsage( CommandContext context )
|
||||||
|
{
|
||||||
|
return "[command]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getSynopsis()
|
||||||
|
{
|
||||||
|
return "Provide help for a specific command";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getDescription()
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkPermission( @Nonnull CommandContext context )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||||
|
{
|
||||||
|
ISubCommand command = branchCommand;
|
||||||
|
|
||||||
|
for( int i = 0; i < arguments.size(); i++ )
|
||||||
|
{
|
||||||
|
String commandName = arguments.get( i );
|
||||||
|
if( command instanceof CommandRoot )
|
||||||
|
{
|
||||||
|
command = ((CommandRoot) command).getSubCommands().get( commandName );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new CommandException( Strings.join( arguments.subList( 0, i ), " " ) + " has no sub-commands" );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( command == null )
|
||||||
|
{
|
||||||
|
throw new CommandException( "No such command " + Strings.join( arguments.subList( 0, i + 1 ), " " ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder prefix = new StringBuilder( context.parent().getFullPath() );
|
||||||
|
for( String argument : arguments )
|
||||||
|
{
|
||||||
|
prefix.append( ' ' ).append( argument );
|
||||||
|
}
|
||||||
|
context.getSender().sendMessage( ChatHelpers.getHelp( context, command, prefix.toString() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||||
|
{
|
||||||
|
CommandRoot command = branchCommand;
|
||||||
|
|
||||||
|
for( int i = 0; i < arguments.size() - 1; i++ )
|
||||||
|
{
|
||||||
|
String commandName = arguments.get( i );
|
||||||
|
ISubCommand subCommand = command.getSubCommands().get( commandName );
|
||||||
|
|
||||||
|
if( subCommand instanceof CommandRoot )
|
||||||
|
{
|
||||||
|
command = (CommandRoot) subCommand;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( arguments.size() == 0 )
|
||||||
|
{
|
||||||
|
return Lists.newArrayList( command.getSubCommands().keySet() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<String> list = Lists.newArrayList();
|
||||||
|
String match = arguments.get( arguments.size() - 1 );
|
||||||
|
|
||||||
|
for( String entry : command.getSubCommands().keySet() )
|
||||||
|
{
|
||||||
|
if( CommandBase.doesStringStartWith( match, entry ) )
|
||||||
|
{
|
||||||
|
list.add( entry );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,250 @@
|
|||||||
|
package dan200.computercraft.shared.command.framework;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import net.minecraft.command.ICommandSender;
|
||||||
|
import net.minecraft.entity.player.EntityPlayerMP;
|
||||||
|
import net.minecraft.util.text.ITextComponent;
|
||||||
|
import net.minecraft.util.text.TextComponentString;
|
||||||
|
import net.minecraft.util.text.TextFormatting;
|
||||||
|
import net.minecraftforge.common.util.FakePlayer;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
|
||||||
|
import static dan200.computercraft.shared.command.framework.ChatHelpers.text;
|
||||||
|
|
||||||
|
public class TextTable
|
||||||
|
{
|
||||||
|
private static final String CHARACTERS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
|
||||||
|
private static final int[] CHAR_WIDTHS = new int[] {
|
||||||
|
6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4,
|
||||||
|
4, 6, 7, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
|
||||||
|
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
|
||||||
|
3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6,
|
||||||
|
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6,
|
||||||
|
6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6,
|
||||||
|
8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6,
|
||||||
|
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
|
||||||
|
9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9,
|
||||||
|
8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7,
|
||||||
|
7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final ITextComponent SEPARATOR = coloured( " | ", TextFormatting.GRAY );
|
||||||
|
private static final ITextComponent LINE = text( "\n" );
|
||||||
|
|
||||||
|
private static int getWidth( char character, ICommandSender sender )
|
||||||
|
{
|
||||||
|
if( sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer) )
|
||||||
|
{
|
||||||
|
// Use font widths here.
|
||||||
|
if( character == 167 )
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if( character == 32 )
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
else if( CHARACTERS.indexOf( character ) != -1 )
|
||||||
|
{
|
||||||
|
return CHAR_WIDTHS[ character ];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Eh, close enough.
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getWidth( ITextComponent text, ICommandSender sender )
|
||||||
|
{
|
||||||
|
int sum = 0;
|
||||||
|
String chars = text.getUnformattedText();
|
||||||
|
for( int i = 0; i < chars.length(); i++ )
|
||||||
|
{
|
||||||
|
sum += getWidth( chars.charAt( i ), sender );
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPlayer( ICommandSender sender )
|
||||||
|
{
|
||||||
|
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getMaxWidth( ICommandSender sender )
|
||||||
|
{
|
||||||
|
return isPlayer( sender ) ? 320 : 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int columns = -1;
|
||||||
|
private final ITextComponent[] header;
|
||||||
|
private final List<ITextComponent[]> rows = Lists.newArrayList();
|
||||||
|
|
||||||
|
public TextTable( @Nonnull ITextComponent... header )
|
||||||
|
{
|
||||||
|
this.header = header;
|
||||||
|
this.columns = header.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextTable()
|
||||||
|
{
|
||||||
|
header = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextTable( @Nonnull String... header )
|
||||||
|
{
|
||||||
|
this.header = new ITextComponent[ header.length ];
|
||||||
|
for( int i = 0; i < header.length; i++ )
|
||||||
|
{
|
||||||
|
this.header[ i ] = ChatHelpers.header( header[ i ] );
|
||||||
|
}
|
||||||
|
this.columns = header.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addRow( @Nonnull ITextComponent... row )
|
||||||
|
{
|
||||||
|
if( columns == -1 )
|
||||||
|
{
|
||||||
|
columns = row.length;
|
||||||
|
}
|
||||||
|
else if( row.length != columns )
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException( "Row is the incorrect length" );
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.add( row );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void displayTo( ICommandSender sender )
|
||||||
|
{
|
||||||
|
if( columns <= 0 ) return;
|
||||||
|
|
||||||
|
final int maxWidth = getMaxWidth( sender );
|
||||||
|
|
||||||
|
int[] minWidths = new int[ columns ];
|
||||||
|
int[] maxWidths = new int[ columns ];
|
||||||
|
int[] rowWidths = new int[ columns ];
|
||||||
|
|
||||||
|
if( header != null )
|
||||||
|
{
|
||||||
|
for( int i = 0; i < columns; i++ )
|
||||||
|
{
|
||||||
|
maxWidths[ i ] = minWidths[ i ] = getWidth( header[ i ], sender );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for( ITextComponent[] row : rows )
|
||||||
|
{
|
||||||
|
for( int i = 0; i < row.length; i++ )
|
||||||
|
{
|
||||||
|
int width = getWidth( row[ i ], sender );
|
||||||
|
rowWidths[ i ] += width;
|
||||||
|
if( width > maxWidths[ i ] )
|
||||||
|
{
|
||||||
|
maxWidths[ i ] = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the average width
|
||||||
|
for( int i = 0; i < columns; i++ )
|
||||||
|
{
|
||||||
|
rowWidths[ i ] = Math.max( rowWidths[ i ], rows.size() );
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalWidth = (columns - 1) * getWidth( SEPARATOR, sender );
|
||||||
|
for( int x : maxWidths ) totalWidth += x;
|
||||||
|
|
||||||
|
// TODO: Limit the widths of some entries if totalWidth > maxWidth
|
||||||
|
|
||||||
|
ITextComponent out = new TextComponentString( "" );
|
||||||
|
|
||||||
|
if( header != null )
|
||||||
|
{
|
||||||
|
for( int i = 0; i < columns; i++ )
|
||||||
|
{
|
||||||
|
if( i != 0 ) out.appendSibling( SEPARATOR );
|
||||||
|
appendFixed( out, sender, header[ i ], maxWidths[ i ] );
|
||||||
|
}
|
||||||
|
out.appendSibling( LINE );
|
||||||
|
|
||||||
|
// Round the width up rather than down
|
||||||
|
int rowCharWidth = getWidth( '=', sender );
|
||||||
|
int rowWidth = totalWidth / rowCharWidth + (totalWidth % rowCharWidth == 0 ? 0 : 1);
|
||||||
|
out.appendSibling( coloured( StringUtils.repeat( '=', rowWidth ), TextFormatting.GRAY ) );
|
||||||
|
out.appendSibling( LINE );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( int i = 0; i < rows.size(); i++ )
|
||||||
|
{
|
||||||
|
ITextComponent[] row = rows.get( i );
|
||||||
|
if( i != 0 ) out.appendSibling( LINE );
|
||||||
|
for( int j = 0; j < columns; j++ )
|
||||||
|
{
|
||||||
|
if( j != 0 ) out.appendSibling( SEPARATOR );
|
||||||
|
appendFixed( out, sender, row[ j ], maxWidths[ j ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage( out );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendFixed( ITextComponent out, ICommandSender sender, ITextComponent entry, int maxWidth )
|
||||||
|
{
|
||||||
|
int length = getWidth( entry, sender );
|
||||||
|
int delta = length - maxWidth;
|
||||||
|
if( delta < 0 )
|
||||||
|
{
|
||||||
|
// Convert to overflow;
|
||||||
|
delta = -delta;
|
||||||
|
|
||||||
|
// We have to remove some padding as there is a padding added between formatted and unformatted text
|
||||||
|
if( !entry.getStyle().isEmpty() && isPlayer( sender ) ) delta -= 1;
|
||||||
|
|
||||||
|
out.appendSibling( entry );
|
||||||
|
|
||||||
|
int spaceWidth = getWidth( ' ', sender );
|
||||||
|
|
||||||
|
int spaces = delta / spaceWidth;
|
||||||
|
int missing = delta % spaceWidth;
|
||||||
|
spaces -= missing;
|
||||||
|
|
||||||
|
ITextComponent component = new TextComponentString( StringUtils.repeat( ' ', spaces < 0 ? 0 : spaces ) );
|
||||||
|
if( missing > 0 )
|
||||||
|
{
|
||||||
|
ITextComponent bold = new TextComponentString( StringUtils.repeat( ' ', missing ) );
|
||||||
|
bold.getStyle().setBold( true );
|
||||||
|
component.appendSibling( bold );
|
||||||
|
}
|
||||||
|
|
||||||
|
out.appendSibling( component );
|
||||||
|
}
|
||||||
|
else if( delta > 0 )
|
||||||
|
{
|
||||||
|
out.appendSibling( entry );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out.appendSibling( entry );
|
||||||
|
|
||||||
|
// We have to add some padding as we expect a padding between formatted and unformatted text
|
||||||
|
// and there won't be.
|
||||||
|
if( entry.getStyle().isEmpty() && isPlayer( sender ) ) out.appendText( " " );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package dan200.computercraft.shared.command.framework;
|
||||||
|
|
||||||
|
import net.minecraft.command.ICommandSender;
|
||||||
|
import net.minecraft.entity.player.EntityPlayerMP;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The level a user must be at in order to execute a command.
|
||||||
|
*/
|
||||||
|
public enum UserLevel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Only can be used by the owner of the server: namely the server console or the player in SSP.
|
||||||
|
*/
|
||||||
|
OWNER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can only be used by ops.
|
||||||
|
*/
|
||||||
|
OP,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used by any op, or the player in SSP.
|
||||||
|
*/
|
||||||
|
OWNER_OP,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used by anyone.
|
||||||
|
*/
|
||||||
|
ANYONE;
|
||||||
|
|
||||||
|
public int toLevel()
|
||||||
|
{
|
||||||
|
switch( this )
|
||||||
|
{
|
||||||
|
case OWNER:
|
||||||
|
return 4;
|
||||||
|
case OP:
|
||||||
|
case OWNER_OP:
|
||||||
|
return 2;
|
||||||
|
case ANYONE:
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canExecute( CommandContext context )
|
||||||
|
{
|
||||||
|
if( this == ANYONE ) return true;
|
||||||
|
|
||||||
|
// We *always* allow level 0 stuff, even if the
|
||||||
|
MinecraftServer server = context.getServer();
|
||||||
|
ICommandSender sender = context.getSender();
|
||||||
|
|
||||||
|
if( server.isSinglePlayer() && sender instanceof EntityPlayerMP &&
|
||||||
|
((EntityPlayerMP) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerOwner() ) )
|
||||||
|
{
|
||||||
|
if( this == OWNER || this == OWNER_OP ) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sender.canUseCommand( toLevel(), context.getRootCommand() );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user