diff --git a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java index 6c11ecd88..db9191d04 100644 --- a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java +++ b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java @@ -10,11 +10,13 @@ import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.ITextComponent; import net.minecraft.world.World; import javax.annotation.Nonnull; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -48,8 +50,35 @@ public final class CommandComputerCraft extends CommandDelegate { TextTable table = new TextTable( "Instance", "Id", "On", "Position" ); - int max = 50; - for( ServerComputer computer : ComputerCraft.serverComputerRegistry.getComputers() ) + List computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() ); + + // Unless we're on a server, limit the number of rows we can send. + if( !(context.getSender() instanceof MinecraftServer) ) + { + World world = context.getSender().getEntityWorld(); + BlockPos pos = context.getSender().getPosition(); + + computers.sort( ( a, b ) -> { + if( a.getWorld() == b.getWorld() && a.getWorld() == world ) + { + return Double.compare( a.getPosition().distanceSq( pos ), b.getPosition().distanceSq( pos ) ); + } + else if( a.getWorld() == world ) + { + return -1; + } + else if( b.getWorld() == world ) + { + return 1; + } + else + { + return Integer.compare( a.getInstanceID(), b.getInstanceID() ); + } + } ); + } + + for( ServerComputer computer : computers ) { table.addRow( linkComputer( computer ), @@ -57,8 +86,6 @@ public final class CommandComputerCraft extends CommandDelegate bool( computer.isOn() ), linkPosition( context, computer ) ); - - if( max-- < 0 ) break; } table.displayTo( context.getSender() ); @@ -80,7 +107,7 @@ public final class CommandComputerCraft extends CommandDelegate IPeripheral peripheral = computer.getPeripheral( i ); if( peripheral != null ) { - table.addRow( header( "Peripheral " + Computer.s_sideNames[ i ] ), text( peripheral.getType() ) ); + table.addRow( header( "Peripheral " + Computer.s_sideNames[i] ), text( peripheral.getType() ) ); } } @@ -198,32 +225,36 @@ public final class CommandComputerCraft extends CommandDelegate } } ); - root.register(new SubCommandBase( + root.register( new SubCommandBase( "view", "", "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 arguments) throws CommandException { - if (arguments.size() != 1) throw new CommandException(context.getFullUsage()); + public void execute( @Nonnull CommandContext context, @Nonnull List 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"); + if( !(sender instanceof EntityPlayerMP) ) + { + throw new CommandException( "Cannot open terminal for non-player" ); } - ServerComputer computer = ComputerSelector.getComputer(arguments.get(0)); + ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) ); ComputerCraft.openComputerGUI( (EntityPlayerMP) sender, computer ); } @Nonnull @Override - public List getCompletion(@Nonnull CommandContext context, @Nonnull List arguments) { + public List getCompletion( @Nonnull CommandContext context, @Nonnull List arguments ) + { return arguments.size() == 1 ? ComputerSelector.completeComputer( arguments.get( 0 ) ) : Collections.emptyList(); } - }); + } ); return root; diff --git a/src/main/java/dan200/computercraft/shared/command/ComputerSelector.java b/src/main/java/dan200/computercraft/shared/command/ComputerSelector.java index 388fd4413..ef231ea78 100644 --- a/src/main/java/dan200/computercraft/shared/command/ComputerSelector.java +++ b/src/main/java/dan200/computercraft/shared/command/ComputerSelector.java @@ -3,14 +3,59 @@ 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.ComputerFamily; import dan200.computercraft.shared.computer.core.ServerComputer; import net.minecraft.command.CommandException; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.function.Predicate; public final class ComputerSelector { + private static ServerComputer getComputer( Predicate predicate, String kind ) throws CommandException + { + // We copy it to prevent concurrent modifications. + List computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() ); + List candidates = Lists.newArrayList(); + for( ServerComputer searchComputer : computers ) + { + if( predicate.test( searchComputer ) ) candidates.add( searchComputer ); + } + + if( candidates.size() == 0 ) + { + throw new CommandException( "No such computer for " + kind ); + } + else if( candidates.size() == 1 ) + { + return candidates.get( 0 ); + } + else + { + StringBuilder builder = new StringBuilder( "Multiple computers with " ) + .append( kind ).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() ); + } + } + public static ServerComputer getComputer( String selector ) throws CommandException { if( selector.length() > 0 && selector.charAt( 0 ) == '#' ) @@ -27,49 +72,17 @@ public final class ComputerSelector throw new CommandException( "'" + selector + "' is not a valid number" ); } - // We copy it to prevent concurrent modifications. - List computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() ); - List 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() ); - } + return getComputer( x -> x.getID() == id, "id " + id ); + } + else if( selector.length() > 0 && selector.charAt( 0 ) == '@' ) + { + String label = selector.substring( 1 ); + return getComputer( x -> Objects.equals( label, x.getLabel() ), "label '" + label + "'" ); + } + else if( selector.length() > 0 && selector.charAt( 0 ) == '~' ) + { + String familyName = selector.substring( 1 ); + return getComputer( x -> x.getFamily().name().equalsIgnoreCase( familyName ), "family '" + familyName + "'" ); } else { @@ -97,7 +110,7 @@ public final class ComputerSelector public static List completeComputer( String selector ) { - Set options = Sets.newHashSet(); + TreeSet options = Sets.newTreeSet(); // We copy it to prevent concurrent modifications. List computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() ); @@ -112,6 +125,26 @@ public final class ComputerSelector if( id.startsWith( selector ) ) options.add( "#" + id ); } } + else if( selector.length() > 0 && selector.charAt( 0 ) == '@' ) + { + String label = selector.substring( 1 ); + for( ServerComputer computer : computers ) + { + String thisLabel = computer.getLabel(); + if( thisLabel != null && thisLabel.startsWith( label ) ) options.add( "@" + thisLabel ); + } + } + else if( selector.length() > 0 && selector.charAt( 0 ) == '~' ) + { + String familyName = selector.substring( 1 ).toLowerCase( Locale.ENGLISH ); + for( ComputerFamily family : ComputerFamily.values() ) + { + if( family.name().toLowerCase( Locale.ENGLISH ).startsWith( familyName ) ) + { + options.add( "~" + family.name() ); + } + } + } else { for( ServerComputer computer : computers ) @@ -121,6 +154,20 @@ public final class ComputerSelector } } - return Lists.newArrayList( options ); + if( options.size() > 100 ) + { + ArrayList result = Lists.newArrayListWithCapacity( 100 ); + for( String element : options ) + { + if( result.size() > 100 ) break; + result.add( element ); + } + + return result; + } + else + { + return Lists.newArrayList( options ); + } } } diff --git a/src/main/java/dan200/computercraft/shared/command/framework/TextTable.java b/src/main/java/dan200/computercraft/shared/command/framework/TextTable.java index 005b50388..b1603ea8d 100644 --- a/src/main/java/dan200/computercraft/shared/command/framework/TextTable.java +++ b/src/main/java/dan200/computercraft/shared/command/framework/TextTable.java @@ -18,7 +18,7 @@ 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[] { + 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, @@ -55,7 +55,7 @@ public class TextTable } else if( CHARACTERS.indexOf( character ) != -1 ) { - return CHAR_WIDTHS[ character ]; + return CHAR_WIDTHS[character]; } else { @@ -108,10 +108,10 @@ public class TextTable public TextTable( @Nonnull String... header ) { - this.header = new ITextComponent[ header.length ]; + this.header = new ITextComponent[header.length]; for( int i = 0; i < header.length; i++ ) { - this.header[ i ] = ChatHelpers.header( header[ i ] ); + this.header[i] = ChatHelpers.header( header[i] ); } this.columns = header.length; } @@ -136,27 +136,32 @@ public class TextTable final int maxWidth = getMaxWidth( sender ); - int[] minWidths = new int[ columns ]; - int[] maxWidths = new int[ columns ]; - int[] rowWidths = new int[ columns ]; + 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 ); + maxWidths[i] = minWidths[i] = getWidth( header[i], sender ); } } - for( ITextComponent[] row : rows ) + // Limit the number of rows to something sensible. + int limit = isPlayer( sender ) ? 30 : 100; + if( limit > rows.size() ) limit = rows.size(); + + for( int y = 0; y < limit; y++ ) { + ITextComponent[] row = rows.get( y ); for( int i = 0; i < row.length; i++ ) { - int width = getWidth( row[ i ], sender ); - rowWidths[ i ] += width; - if( width > maxWidths[ i ] ) + int width = getWidth( row[i], sender ); + rowWidths[i] += width; + if( width > maxWidths[i] ) { - maxWidths[ i ] = width; + maxWidths[i] = width; } } } @@ -164,7 +169,7 @@ public class TextTable // Calculate the average width for( int i = 0; i < columns; i++ ) { - rowWidths[ i ] = Math.max( rowWidths[ i ], rows.size() ); + rowWidths[i] = Math.max( rowWidths[i], rows.size() ); } int totalWidth = (columns - 1) * getWidth( SEPARATOR, sender ); @@ -179,7 +184,7 @@ public class TextTable for( int i = 0; i < columns; i++ ) { if( i != 0 ) out.appendSibling( SEPARATOR ); - appendFixed( out, sender, header[ i ], maxWidths[ i ] ); + appendFixed( out, sender, header[i], maxWidths[i] ); } out.appendSibling( LINE ); @@ -190,17 +195,23 @@ public class TextTable out.appendSibling( LINE ); } - for( int i = 0; i < rows.size(); i++ ) + for( int i = 0; i < limit; 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 ] ); + appendFixed( out, sender, row[j], maxWidths[j] ); } } + if( limit != rows.size() ) + { + out.appendSibling( LINE ); + out.appendSibling( coloured( (rows.size() - limit) + " additional rows...", TextFormatting.AQUA ) ); + } + sender.sendMessage( out ); }