diff --git a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java index 75b6899f9..9365806b0 100644 --- a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java +++ b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java @@ -25,7 +25,6 @@ import dan200.computercraft.shared.common.ContainerHeldItem; import dan200.computercraft.shared.common.IColouredItem; import dan200.computercraft.shared.computer.inventory.ContainerComputer; import dan200.computercraft.shared.computer.inventory.ContainerViewComputer; -import dan200.computercraft.shared.network.container.ViewComputerContainerData; import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive; import dan200.computercraft.shared.peripheral.monitor.ClientMonitor; import dan200.computercraft.shared.peripheral.printer.ContainerPrinter; diff --git a/src/main/java/dan200/computercraft/mixin/MixinScreen.java b/src/main/java/dan200/computercraft/mixin/MixinScreen.java index 42d929714..9d07929b5 100644 --- a/src/main/java/dan200/computercraft/mixin/MixinScreen.java +++ b/src/main/java/dan200/computercraft/mixin/MixinScreen.java @@ -6,7 +6,7 @@ package dan200.computercraft.mixin; -import dan200.computercraft.shared.command.CommandCopy; +import dan200.computercraft.shared.command.ClientCommands; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -22,7 +22,7 @@ import net.fabricmc.api.Environment; public class MixinScreen { @Inject (method = "sendMessage(Ljava/lang/String;Z)V", at = @At ("HEAD"), cancellable = true) public void sendClientCommand(String message, boolean add, CallbackInfo info) { - if (CommandCopy.onClientSendMessage(message)) { + if (ClientCommands.onClientSendMessage(message)) { info.cancel(); } } diff --git a/src/main/java/dan200/computercraft/shared/command/ClientCommands.java b/src/main/java/dan200/computercraft/shared/command/ClientCommands.java new file mode 100644 index 000000000..6845f2815 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/command/ClientCommands.java @@ -0,0 +1,50 @@ +/* + * 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.command; + +import dan200.computercraft.shared.util.IDAssigner; +import net.minecraft.util.Util; + +import java.io.File; + +/** + * Basic client-side commands. + * + * Simply hooks into client chat messages and intercepts matching strings. + */ +public final class ClientCommands +{ + public static final String OPEN_COMPUTER = "/computercraft open-computer "; + + private ClientCommands() + { + } + + public static boolean onClientSendMessage( String message ) + { + // Emulate the command on the client side + if( message.startsWith( OPEN_COMPUTER ) ) + { + String idStr = message.substring( OPEN_COMPUTER.length() ).trim(); + int id; + try + { + id = Integer.parseInt( idStr ); + } + catch( NumberFormatException ignore ) + { + return true; + } + + File file = new File( IDAssigner.getDir(), "computer/" + id ); + if( !file.isDirectory() ) return true; + + Util.getOperatingSystem().open( file ); + return true; + } + return false; + } +} diff --git a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java index 1cf41e2c9..7e687bb9d 100644 --- a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java +++ b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java @@ -3,44 +3,8 @@ * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ - package dan200.computercraft.shared.command; -import static dan200.computercraft.shared.command.CommandUtils.isPlayer; -import static dan200.computercraft.shared.command.Exceptions.NOT_TRACKING_EXCEPTION; -import static dan200.computercraft.shared.command.Exceptions.NO_TIMINGS_EXCEPTION; -import static dan200.computercraft.shared.command.Exceptions.TP_NOT_PLAYER; -import static dan200.computercraft.shared.command.Exceptions.TP_NOT_THERE; -import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.getComputerArgument; -import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.oneComputer; -import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.getComputersArgument; -import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.manyComputers; -import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.unwrap; -import static dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType.trackingField; -import static dan200.computercraft.shared.command.builder.CommandBuilder.args; -import static dan200.computercraft.shared.command.builder.CommandBuilder.command; -import static dan200.computercraft.shared.command.builder.HelpingArgumentBuilder.choice; -import static dan200.computercraft.shared.command.text.ChatHelpers.bool; -import static dan200.computercraft.shared.command.text.ChatHelpers.header; -import static dan200.computercraft.shared.command.text.ChatHelpers.link; -import static dan200.computercraft.shared.command.text.ChatHelpers.position; -import static dan200.computercraft.shared.command.text.ChatHelpers.text; -import static dan200.computercraft.shared.command.text.ChatHelpers.translate; -import static net.minecraft.server.command.CommandManager.literal; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import javax.annotation.Nonnull; - import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -57,10 +21,12 @@ import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.inventory.ContainerViewComputer; import dan200.computercraft.shared.network.container.ViewComputerContainerData; - +import dan200.computercraft.shared.util.IDAssigner; +import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.network.PacketByteBuf; import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket; import net.minecraft.screen.NamedScreenHandlerFactory; import net.minecraft.screen.ScreenHandler; @@ -69,345 +35,388 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.LiteralText; import net.minecraft.text.MutableText; -import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -public final class CommandComputerCraft { - public static final UUID SYSTEM_UUID = new UUID(0, 0); +import javax.annotation.Nonnull; +import java.io.File; +import java.util.*; + +import static dan200.computercraft.shared.command.CommandUtils.isPlayer; +import static dan200.computercraft.shared.command.Exceptions.*; +import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.getComputerArgument; +import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.oneComputer; +import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.*; +import static dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType.trackingField; +import static dan200.computercraft.shared.command.builder.CommandBuilder.args; +import static dan200.computercraft.shared.command.builder.CommandBuilder.command; +import static dan200.computercraft.shared.command.builder.HelpingArgumentBuilder.choice; +import static dan200.computercraft.shared.command.text.ChatHelpers.*; +import static net.minecraft.server.command.CommandManager.literal; + +public final class CommandComputerCraft +{ + public static final UUID SYSTEM_UUID = new UUID( 0, 0 ); private static final int DUMP_LIST_ID = 5373952; private static final int DUMP_SINGLE_ID = 1844510720; private static final int TRACK_ID = 373882880; - private static final List DEFAULT_FIELDS = Arrays.asList(TrackingField.TASKS, - TrackingField.TOTAL_TIME, - TrackingField.AVERAGE_TIME, - TrackingField.MAX_TIME); - public CommandComputerCraft() { + private CommandComputerCraft() + { } - public static void register(CommandDispatcher dispatcher, boolean bool) { - dispatcher.register(choice("computercraft").then(literal("dump").requires(UserLevel.OWNER_OP) - .executes(context -> { - TableBuilder table = new TableBuilder(DUMP_LIST_ID, - "Computer", - "On", - "Position"); + public static void register( CommandDispatcher dispatcher, Boolean dedicated ) + { + dispatcher.register( choice( "computercraft" ) + .then( literal( "dump" ) + .requires( UserLevel.OWNER_OP ) + .executes( context -> { + TableBuilder table = new TableBuilder( DUMP_LIST_ID, "Computer", "On", "Position" ); - ServerCommandSource source = context.getSource(); - List computers = - new ArrayList<>(ComputerCraft.serverComputerRegistry.getComputers()); + ServerCommandSource source = context.getSource(); + List computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() ); - // Unless we're on a server, limit the number of rows we can send. - World world = source.getWorld(); - BlockPos pos = new BlockPos(source.getPosition()); + // Unless we're on a server, limit the number of rows we can send. + World world = source.getWorld(); + BlockPos pos = new BlockPos( source.getPosition() ); - computers.sort((a, b) -> { - if (a.getWorld() == b.getWorld() && a.getWorld() == world) { - return Double.compare(a.getPosition() - .getSquaredDistance(pos), - b.getPosition() - .getSquaredDistance(pos)); - } else if (a.getWorld() == world) { - return -1; - } else if (b.getWorld() == world) { - return 1; - } else { - return Integer.compare(a.getInstanceID(), b.getInstanceID()); - } - }); + computers.sort( ( a, b ) -> { + if( a.getWorld() == b.getWorld() && a.getWorld() == world ) + { + return Double.compare( a.getPosition().getSquaredDistance( pos ), b.getPosition().getSquaredDistance( 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.row(linkComputer(source, computer, computer.getID()), - bool(computer.isOn()), - linkPosition(source, computer)); - } + for( ServerComputer computer : computers ) + { + table.row( + linkComputer( source, computer, computer.getID() ), + bool( computer.isOn() ), + linkPosition( source, computer ) + ); + } - table.display(context.getSource()); - return computers.size(); - }) - .then(args().arg("computer", oneComputer()) - .executes(context -> { - ServerComputer computer = getComputerArgument(context, "computer"); + table.display( context.getSource() ); + return computers.size(); + } ) + .then( args() + .arg( "computer", oneComputer() ) + .executes( context -> { + ServerComputer computer = getComputerArgument( context, "computer" ); - TableBuilder table = new TableBuilder(DUMP_SINGLE_ID); - table.row(header("Instance"), - text(Integer.toString(computer.getInstanceID()))); - table.row(header("Id"), text(Integer.toString(computer.getID()))); - table.row(header("Label"), text(computer.getLabel())); - table.row(header("On"), bool(computer.isOn())); - table.row(header("Position"), - linkPosition(context.getSource(), computer)); - table.row(header("Family"), - text(computer.getFamily() - .toString())); + TableBuilder table = new TableBuilder( DUMP_SINGLE_ID ); + table.row( header( "Instance" ), text( Integer.toString( computer.getInstanceID() ) ) ); + table.row( header( "Id" ), text( Integer.toString( computer.getID() ) ) ); + table.row( header( "Label" ), text( computer.getLabel() ) ); + table.row( header( "On" ), bool( computer.isOn() ) ); + table.row( header( "Position" ), linkPosition( context.getSource(), computer ) ); + table.row( header( "Family" ), text( computer.getFamily().toString() ) ); - for (ComputerSide side : ComputerSide.values()) { - IPeripheral peripheral = computer.getPeripheral(side); - if (peripheral != null) { - table.row(header("Peripheral " + side.getName()), - text(peripheral.getType())); - } - } + for( ComputerSide side : ComputerSide.values() ) + { + IPeripheral peripheral = computer.getPeripheral( side ); + if( peripheral != null ) + { + table.row( header( "Peripheral " + side.getName() ), text( peripheral.getType() ) ); + } + } - table.display(context.getSource()); - return 1; - }))) + table.display( context.getSource() ); + return 1; + } ) ) ) - .then(command("shutdown").requires(UserLevel.OWNER_OP) - .argManyValue("computers", - manyComputers(), - s -> ComputerCraft.serverComputerRegistry.getComputers()) - .executes((context, computers) -> { - int shutdown = 0; - for (ServerComputer computer : unwrap(context.getSource(), computers)) { - if (computer.isOn()) { - shutdown++; - } - computer.shutdown(); - } - context.getSource() - .sendFeedback(translate("commands.computercraft.shutdown.done", - shutdown, - computers.size()), false); - return shutdown; - })) + .then( command( "shutdown" ) + .requires( UserLevel.OWNER_OP ) + .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) + .executes( ( context, computers ) -> { + int shutdown = 0; + for( ServerComputer computer : unwrap( context.getSource(), computers ) ) + { + if( computer.isOn() ) shutdown++; + computer.shutdown(); + } + context.getSource().sendFeedback( translate( "commands.computercraft.shutdown.done", shutdown, computers.size() ), false ); + return shutdown; + } ) ) - .then(command("turn-on").requires(UserLevel.OWNER_OP) - .argManyValue("computers", - manyComputers(), - s -> ComputerCraft.serverComputerRegistry.getComputers()) - .executes((context, computers) -> { - int on = 0; - for (ServerComputer computer : unwrap(context.getSource(), computers)) { - if (!computer.isOn()) { - on++; - } - computer.turnOn(); - } - context.getSource() - .sendFeedback(translate("commands.computercraft.turn_on.done", - on, - computers.size()), false); - return on; - })) + .then( command( "turn-on" ) + .requires( UserLevel.OWNER_OP ) + .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) + .executes( ( context, computers ) -> { + int on = 0; + for( ServerComputer computer : unwrap( context.getSource(), computers ) ) + { + if( !computer.isOn() ) on++; + computer.turnOn(); + } + context.getSource().sendFeedback( translate( "commands.computercraft.turn_on.done", on, computers.size() ), false ); + return on; + } ) ) - .then(command("tp").requires(UserLevel.OP) - .arg("computer", oneComputer()) - .executes(context -> { - ServerComputer computer = getComputerArgument(context, "computer"); - World world = computer.getWorld(); - BlockPos pos = computer.getPosition(); + .then( command( "tp" ) + .requires( UserLevel.OP ) + .arg( "computer", oneComputer() ) + .executes( context -> { + ServerComputer computer = getComputerArgument( context, "computer" ); + World world = computer.getWorld(); + BlockPos pos = computer.getPosition(); - if (world == null || pos == null) { - throw TP_NOT_THERE.create(); - } + if( world == null || pos == null ) throw TP_NOT_THERE.create(); - Entity entity = context.getSource() - .getEntityOrThrow(); - if (!(entity instanceof ServerPlayerEntity)) { - throw TP_NOT_PLAYER.create(); - } + Entity entity = context.getSource().getEntityOrThrow(); + if( !(entity instanceof ServerPlayerEntity) ) throw TP_NOT_PLAYER.create(); - ServerPlayerEntity player = (ServerPlayerEntity) entity; - if (player.getEntityWorld() == world) { - player.networkHandler.teleportRequest(pos.getX() + 0.5, - pos.getY(), - pos.getZ() + 0.5, - 0, - 0, - EnumSet.noneOf( - PlayerPositionLookS2CPacket.Flag.class)); - } else { - player.teleport((ServerWorld) world, - pos.getX() + 0.5, - pos.getY(), - pos.getZ() + 0.5, - 0, - 0); - } + ServerPlayerEntity player = (ServerPlayerEntity) entity; + if( player.getEntityWorld() == world ) + { + player.networkHandler.teleportRequest( + pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0, + EnumSet.noneOf( PlayerPositionLookS2CPacket.Flag.class ) + ); + } + else + { + player.teleport( (ServerWorld) world, + pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0 + ); + } - return 1; - })) + return 1; + } ) ) - .then(command("queue").requires(UserLevel.ANYONE) - .arg("computer", manyComputers()) - .argManyValue("args", StringArgumentType.string(), Collections.emptyList()) - .executes((ctx, args) -> { - Collection computers = getComputersArgument(ctx, "computer"); - Object[] rest = args.toArray(); + .then( command( "queue" ) + .requires( UserLevel.ANYONE ) + .arg( "computer", manyComputers() ) + .argManyValue( "args", StringArgumentType.string(), Collections.emptyList() ) + .executes( ( ctx, args ) -> { + Collection computers = getComputersArgument( ctx, "computer" ); + Object[] rest = args.toArray(); - int queued = 0; - for (ServerComputer computer : computers) { - if (computer.getFamily() == ComputerFamily.COMMAND && computer.isOn()) { - computer.queueEvent("computer_command", rest); - queued++; - } - } + int queued = 0; + for( ServerComputer computer : computers ) + { + if( computer.getFamily() == ComputerFamily.COMMAND && computer.isOn() ) + { + computer.queueEvent( "computer_command", rest ); + queued++; + } + } - return queued; - })) + return queued; + } ) ) - .then(command("view").requires(UserLevel.OP) - .arg("computer", oneComputer()) - .executes(context -> { - ServerPlayerEntity player = context.getSource() - .getPlayer(); - ServerComputer computer = getComputerArgument(context, "computer"); - new ViewComputerContainerData(computer).open(player, - new NamedScreenHandlerFactory() { - @Nonnull - @Override - public Text getDisplayName() { - return new TranslatableText( - "gui.computercraft.view_computer"); - } + .then( command( "view" ) + .requires( UserLevel.OP ) + .arg( "computer", oneComputer() ) + .executes( context -> { + ServerPlayerEntity player = context.getSource().getPlayer(); + ServerComputer computer = getComputerArgument( context, "computer" ); + ViewComputerContainerData container = new ViewComputerContainerData( computer ); + container.open( player, new ExtendedScreenHandlerFactory() + { + @Override + public void writeScreenOpeningData( ServerPlayerEntity player, PacketByteBuf buf ) + { + container.toBytes( buf ); + } - @Nonnull - @Override - public ScreenHandler createMenu(int id, @Nonnull PlayerInventory player, @Nonnull PlayerEntity entity) { - return new ContainerViewComputer( - id, - computer); - } - }); - return 1; - })) + @Nonnull + @Override + public MutableText getDisplayName() + { + return new TranslatableText( "gui.computercraft.view_computer" ); + } - .then(choice("track").then(command("start").requires(UserLevel.OWNER_OP) - .executes(context -> { - getTimingContext(context.getSource()).start(); + @Nonnull + @Override + public ScreenHandler createMenu( int id, @Nonnull PlayerInventory player, @Nonnull PlayerEntity entity ) + { + return new ContainerViewComputer( id, computer ); + } + } ); + return 1; + } ) ) - String stopCommand = "/computercraft track stop"; - context.getSource() - .sendFeedback(translate( - "commands.computercraft.track.start.stop", - link(text(stopCommand), - stopCommand, - translate( - "commands.computercraft.track.stop.action"))), - false); - return 1; - })) + .then( choice( "track" ) + .then( command( "start" ) + .requires( UserLevel.OWNER_OP ) + .executes( context -> { + getTimingContext( context.getSource() ).start(); - .then(command("stop").requires(UserLevel.OWNER_OP) - .executes(context -> { - TrackingContext timings = getTimingContext(context.getSource()); - if (!timings.stop()) { - throw NOT_TRACKING_EXCEPTION.create(); - } - displayTimings(context.getSource(), - timings.getImmutableTimings(), - TrackingField.AVERAGE_TIME, - DEFAULT_FIELDS); - return 1; - })) + String stopCommand = "/computercraft track stop"; + context.getSource().sendFeedback( translate( "commands.computercraft.track.start.stop", + link( text( stopCommand ), stopCommand, translate( "commands.computercraft.track.stop.action" ) ) ), false ); + return 1; + } ) ) - .then(command("dump").requires(UserLevel.OWNER_OP) - .argManyValue("fields", trackingField(), DEFAULT_FIELDS) - .executes((context, fields) -> { - TrackingField sort; - if (fields.size() == 1 && DEFAULT_FIELDS.contains(fields.get( - 0))) { - sort = fields.get(0); - fields = DEFAULT_FIELDS; - } else { - sort = fields.get(0); - } + .then( command( "stop" ) + .requires( UserLevel.OWNER_OP ) + .executes( context -> { + TrackingContext timings = getTimingContext( context.getSource() ); + if( !timings.stop() ) throw NOT_TRACKING_EXCEPTION.create(); + displayTimings( context.getSource(), timings.getImmutableTimings(), TrackingField.AVERAGE_TIME, DEFAULT_FIELDS ); + return 1; + } ) ) - return displayTimings(context.getSource(), sort, fields); - })))); + .then( command( "dump" ) + .requires( UserLevel.OWNER_OP ) + .argManyValue( "fields", trackingField(), DEFAULT_FIELDS ) + .executes( ( context, fields ) -> { + TrackingField sort; + if( fields.size() == 1 && DEFAULT_FIELDS.contains( fields.get( 0 ) ) ) + { + sort = fields.get( 0 ); + fields = DEFAULT_FIELDS; + } + else + { + sort = fields.get( 0 ); + } + + return displayTimings( context.getSource(), sort, fields ); + } ) ) ) + ); } - private static Text linkComputer(ServerCommandSource source, ServerComputer serverComputer, int computerId) { - MutableText out = new LiteralText(""); + private static MutableText linkComputer( ServerCommandSource source, ServerComputer serverComputer, int computerId ) + { + MutableText out = new LiteralText( "" ); // Append the computer instance - if (serverComputer == null) { - out.append(text("?")); - } else { - out.append(link(text(Integer.toString(serverComputer.getInstanceID())), - "/computercraft dump " + serverComputer.getInstanceID(), - translate("commands.computercraft.dump.action"))); + if( serverComputer == null ) + { + out.append( text( "?" ) ); + } + else + { + out.append( link( + text( Integer.toString( serverComputer.getInstanceID() ) ), + "/computercraft dump " + serverComputer.getInstanceID(), + translate( "commands.computercraft.dump.action" ) + ) ); } // And ID - out.append(" (id " + computerId + ")"); + out.append( " (id " + computerId + ")" ); // And, if we're a player, some useful links - if (serverComputer != null && UserLevel.OP.test(source) && isPlayer(source)) { - out.append(" ") - .append(link(text("\u261b"), "/computercraft tp " + serverComputer.getInstanceID(), translate("commands.computercraft.tp.action"))) - .append(" ") - .append(link(text("\u20e2"), "/computercraft view " + serverComputer.getInstanceID(), translate("commands.computercraft.view.action"))); + if( serverComputer != null && UserLevel.OP.test( source ) && isPlayer( source ) ) + { + out + .append( " " ) + .append( link( + text( "\u261b" ), + "/computercraft tp " + serverComputer.getInstanceID(), + translate( "commands.computercraft.tp.action" ) + ) ) + .append( " " ) + .append( link( + text( "\u20e2" ), + "/computercraft view " + serverComputer.getInstanceID(), + translate( "commands.computercraft.view.action" ) + ) ); + } + + if( UserLevel.OWNER.test( source ) && isPlayer( source ) ) + { + MutableText linkPath = linkStorage( computerId ); + if( linkPath != null ) out.append( " " ).append( linkPath ); } return out; } - private static Text linkPosition(ServerCommandSource context, ServerComputer computer) { - if (UserLevel.OP.test(context)) { - return link(position(computer.getPosition()), "/computercraft tp " + computer.getInstanceID(), translate("commands.computercraft.tp.action")); - } else { - return position(computer.getPosition()); + private static MutableText linkPosition( ServerCommandSource context, ServerComputer computer ) + { + if( UserLevel.OP.test( context ) ) + { + return link( + position( computer.getPosition() ), + "/computercraft tp " + computer.getInstanceID(), + translate( "commands.computercraft.tp.action" ) + ); } + else + { + return position( computer.getPosition() ); + } + } + + private static MutableText linkStorage( int id ) + { + File file = new File( IDAssigner.getDir(), "computer/" + id ); + if( !file.isDirectory() ) return null; + + return link( + text( "\u270E" ), + ClientCommands.OPEN_COMPUTER + id, + translate( "commands.computercraft.dump.open_path" ) + ); } @Nonnull - private static TrackingContext getTimingContext(ServerCommandSource source) { + private static TrackingContext getTimingContext( ServerCommandSource source ) + { Entity entity = source.getEntity(); - return entity instanceof PlayerEntity ? Tracking.getContext(entity.getUuid()) : Tracking.getContext(SYSTEM_UUID); + return entity instanceof PlayerEntity ? Tracking.getContext( entity.getUuid() ) : Tracking.getContext( SYSTEM_UUID ); } - private static int displayTimings(ServerCommandSource source, @Nonnull List timings, @Nonnull TrackingField sortField, @Nonnull List fields) throws CommandSyntaxException { - if (timings.isEmpty()) { - throw NO_TIMINGS_EXCEPTION.create(); - } + private static final List DEFAULT_FIELDS = Arrays.asList( TrackingField.TASKS, TrackingField.TOTAL_TIME, TrackingField.AVERAGE_TIME, TrackingField.MAX_TIME ); + + private static int displayTimings( ServerCommandSource source, TrackingField sortField, List fields ) throws CommandSyntaxException + { + return displayTimings( source, getTimingContext( source ).getTimings(), sortField, fields ); + } + + private static int displayTimings( ServerCommandSource source, @Nonnull List timings, @Nonnull TrackingField sortField, @Nonnull List fields ) throws CommandSyntaxException + { + if( timings.isEmpty() ) throw NO_TIMINGS_EXCEPTION.create(); Map lookup = new HashMap<>(); int maxId = 0, maxInstance = 0; - for (ServerComputer server : ComputerCraft.serverComputerRegistry.getComputers()) { - lookup.put(server.getComputer(), server); + for( ServerComputer server : ComputerCraft.serverComputerRegistry.getComputers() ) + { + lookup.put( server.getComputer(), server ); - if (server.getInstanceID() > maxInstance) { - maxInstance = server.getInstanceID(); - } - if (server.getID() > maxId) { - maxId = server.getID(); - } + if( server.getInstanceID() > maxInstance ) maxInstance = server.getInstanceID(); + if( server.getID() > maxId ) maxId = server.getID(); } - timings.sort(Comparator.comparing(x -> x.get(sortField)).reversed()); + timings.sort( Comparator.comparing( x -> x.get( sortField ) ).reversed() ); - Text[] headers = new Text[1 + fields.size()]; - headers[0] = translate("commands.computercraft.track.dump.computer"); - for (int i = 0; i < fields.size(); i++) { - headers[i + 1] = translate(fields.get(i) - .translationKey()); - } - TableBuilder table = new TableBuilder(TRACK_ID, headers); + MutableText[] headers = new MutableText[1 + fields.size()]; + headers[0] = translate( "commands.computercraft.track.dump.computer" ); + for( int i = 0; i < fields.size(); i++ ) headers[i + 1] = translate( fields.get( i ).translationKey() ); + TableBuilder table = new TableBuilder( TRACK_ID, headers ); - for (ComputerTracker entry : timings) { + for( ComputerTracker entry : timings ) + { Computer computer = entry.getComputer(); - ServerComputer serverComputer = computer == null ? null : lookup.get(computer); + ServerComputer serverComputer = computer == null ? null : lookup.get( computer ); - Text computerComponent = linkComputer(source, serverComputer, entry.getComputerId()); + MutableText computerComponent = linkComputer( source, serverComputer, entry.getComputerId() ); - Text[] row = new Text[1 + fields.size()]; + MutableText[] row = new MutableText[1 + fields.size()]; row[0] = computerComponent; - for (int i = 0; i < fields.size(); i++) { - row[i + 1] = text(entry.getFormatted(fields.get(i))); - } - table.row(row); + for( int i = 0; i < fields.size(); i++ ) row[i + 1] = text( entry.getFormatted( fields.get( i ) ) ); + table.row( row ); } - table.display(source); + table.display( source ); return timings.size(); } - - private static int displayTimings(ServerCommandSource source, TrackingField sortField, List fields) throws CommandSyntaxException { - return displayTimings(source, getTimingContext(source).getTimings(), sortField, fields); - } } diff --git a/src/main/java/dan200/computercraft/shared/command/CommandCopy.java b/src/main/java/dan200/computercraft/shared/command/CommandCopy.java deleted file mode 100644 index 9922674f0..000000000 --- a/src/main/java/dan200/computercraft/shared/command/CommandCopy.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.command; - -import static net.minecraft.server.command.CommandManager.argument; -import static net.minecraft.server.command.CommandManager.literal; - -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.arguments.StringArgumentType; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.text.ClickEvent; -import net.minecraft.text.HoverEvent; -import net.minecraft.text.LiteralText; -import net.minecraft.text.Style; -import net.minecraft.text.Text; -import net.minecraft.text.TranslatableText; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; - -@Environment (EnvType.CLIENT) -public final class CommandCopy { - private static final String PREFIX = "/computercraft copy "; - - private CommandCopy() { - } - - public static void register(CommandDispatcher registry) { - registry.register(literal("computercraft").then(literal("copy")) - .then(argument("message", StringArgumentType.greedyString())) - .executes(context -> { - MinecraftClient.getInstance().keyboard.setClipboard(context.getArgument("message", String.class)); - return 1; - })); - } - - public static boolean onClientSendMessage(String message) { - // Emulate the command on the client side - if (message.startsWith(PREFIX)) { - MinecraftClient.getInstance().keyboard.setClipboard(message.substring(PREFIX.length())); - return true; - } - return false; - } - - public static Text createCopyText(String text) { - return new LiteralText(text).fillStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, PREFIX + text)) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, - new TranslatableText("gui.computercraft.tooltip.copy")))); - } -} diff --git a/src/main/java/dan200/computercraft/shared/command/UserLevel.java b/src/main/java/dan200/computercraft/shared/command/UserLevel.java index b618d5ec4..d0a142cf0 100644 --- a/src/main/java/dan200/computercraft/shared/command/UserLevel.java +++ b/src/main/java/dan200/computercraft/shared/command/UserLevel.java @@ -3,20 +3,20 @@ * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ - package dan200.computercraft.shared.command; -import java.util.function.Predicate; - import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; +import java.util.function.Predicate; + /** * The level a user must be at in order to execute a command. */ -public enum UserLevel implements Predicate { +public enum UserLevel implements Predicate +{ /** * Only can be used by the owner of the server: namely the server console or the player in SSP. */ @@ -37,37 +37,37 @@ public enum UserLevel implements Predicate { */ ANYONE; - @Override - public boolean test(ServerCommandSource source) { - if (this == ANYONE) { - return true; + public int toLevel() + { + switch( this ) + { + case OWNER: + return 4; + case OP: + case OWNER_OP: + return 2; + case ANYONE: + default: + return 0; } + } - // We *always* allow level 0 stuff, even if the - MinecraftServer server = source.getMinecraftServer(); - Entity sender = source.getEntity(); + @Override + public boolean test( ServerCommandSource source ) + { + if( this == ANYONE ) return true; - if (server.isSinglePlayer() && sender instanceof PlayerEntity && ((PlayerEntity) sender).getGameProfile() - .getName() - .equalsIgnoreCase(server.getServerModName())) { - if (this == OWNER || this == OWNER_OP) { + if( this == OWNER || this == OWNER_OP ) + { + MinecraftServer server = source.getMinecraftServer(); + Entity sender = source.getEntity(); + if( server.isSinglePlayer() && sender instanceof PlayerEntity && + ((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerModName() ) ) + { return true; } } - return source.hasPermissionLevel(this.toLevel()); - } - - public int toLevel() { - switch (this) { - case OWNER: - return 4; - case OP: - case OWNER_OP: - return 2; - case ANYONE: - default: - return 0; - } + return source.hasPermissionLevel( toLevel() ); } } diff --git a/src/main/java/dan200/computercraft/shared/command/text/ChatHelpers.java b/src/main/java/dan200/computercraft/shared/command/text/ChatHelpers.java index 4e13111f5..56d4a7201 100644 --- a/src/main/java/dan200/computercraft/shared/command/text/ChatHelpers.java +++ b/src/main/java/dan200/computercraft/shared/command/text/ChatHelpers.java @@ -3,81 +3,101 @@ * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ - package dan200.computercraft.shared.command.text; -import net.minecraft.text.ClickEvent; -import net.minecraft.text.HoverEvent; -import net.minecraft.text.LiteralText; -import net.minecraft.text.MutableText; -import net.minecraft.text.Style; -import net.minecraft.text.Text; -import net.minecraft.text.TranslatableText; +import net.minecraft.text.*; import net.minecraft.util.Formatting; import net.minecraft.util.math.BlockPos; /** * Various helpers for building chat messages. */ -public final class ChatHelpers { +public final class ChatHelpers +{ private static final Formatting HEADER = Formatting.LIGHT_PURPLE; private ChatHelpers() {} - public static MutableText text(String text) { - return new LiteralText(text == null ? "" : text); + public static MutableText coloured( String text, Formatting colour ) + { + MutableText component = new LiteralText( text == null ? "" : text ); + component.setStyle( component.getStyle().withColor( colour ) ); + return component; } - public static MutableText list(Text... children) { - MutableText component = new LiteralText(""); - for (Text child : children) { - component.append(child); + public static T coloured( T component, Formatting colour ) + { + component.setStyle( component.getStyle().withColor( colour ) ); + return component; + } + + public static MutableText text( String text ) + { + return new LiteralText( text == null ? "" : text ); + } + + public static MutableText translate( String text ) + { + return new TranslatableText( text == null ? "" : text ); + } + + public static MutableText translate( String text, Object... args ) + { + return new TranslatableText( text == null ? "" : text, args ); + } + + public static MutableText list( MutableText... children ) + { + MutableText component = new LiteralText( "" ); + for( MutableText child : children ) + { + component.append( child ); } return component; } - public static MutableText position(BlockPos pos) { - if (pos == null) { - return translate("commands.computercraft.generic.no_position"); - } - return translate("commands.computercraft.generic.position", pos.getX(), pos.getY(), pos.getZ()); + public static MutableText position( BlockPos pos ) + { + if( pos == null ) return translate( "commands.computercraft.generic.no_position" ); + return translate( "commands.computercraft.generic.position", pos.getX(), pos.getY(), pos.getZ() ); } - public static MutableText translate(String text) { - return new TranslatableText(text == null ? "" : text); + public static MutableText bool( boolean value ) + { + return value + ? coloured( translate( "commands.computercraft.generic.yes" ), Formatting.GREEN ) + : coloured( translate( "commands.computercraft.generic.no" ), Formatting.RED ); } - public static MutableText translate(String text, Object... args) { - return new TranslatableText(text == null ? "" : text, args); + public static MutableText link( MutableText component, String command, MutableText toolTip ) + { + return link( component, new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ), toolTip ); } - public static MutableText bool(boolean value) { - return value ? coloured(translate("commands.computercraft.generic.yes"), - Formatting.GREEN) : coloured(translate("commands.computercraft.generic.no"), Formatting.RED); - } - - public static T coloured(T component, Formatting colour) { - component.formatted(colour); - return component; - } - - public static MutableText link(MutableText component, String command, Text toolTip) { + public static MutableText link( MutableText component, ClickEvent click, MutableText toolTip ) + { Style style = component.getStyle(); - if (style.getColor() == null) { - style = style.withColor(Formatting.YELLOW); - } - style = style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command)); - style = style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, toolTip)); + if( style.getColor() == null ) style = style.withColor( Formatting.YELLOW ); + style = style.withClickEvent( click ); + style = style.withHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, toolTip ) ); - return component.setStyle(style); + component.setStyle(style); + + return component; } - public static MutableText header(String text) { - return coloured(text, HEADER); + public static MutableText header( String text ) + { + return coloured( text, HEADER ); } - public static MutableText coloured(String text, Formatting colour) { - return new LiteralText(text == null ? "" : text).formatted(colour); + public static MutableText copy( String text ) + { + LiteralText name = new LiteralText( text ); + name.setStyle( name.getStyle() + .withClickEvent( new ClickEvent( ClickEvent.Action.COPY_TO_CLIPBOARD, text ) ) + .withHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TranslatableText( "gui.computercraft.tooltip.copy" ) ) ) ); + return name; } } diff --git a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputerBase.java b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputerBase.java index 771d1e668..ed33e7488 100644 --- a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputerBase.java +++ b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputerBase.java @@ -37,7 +37,7 @@ public class ContainerComputerBase extends ScreenHandler implements IContainerCo id, x -> true, getComputer(player, new ComputerContainerData(new PacketByteBuf(packetByteBuf.copy()))), - new ComputerContainerData(packetByteBuf).getFamily()); + new ComputerContainerData(new PacketByteBuf(packetByteBuf.copy())).getFamily()); } protected ContainerComputerBase(ScreenHandlerType type, int id, Predicate canUse, IComputer computer, diff --git a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java index 182e5d206..dee003e66 100644 --- a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java @@ -29,6 +29,13 @@ public class ContainerViewComputer extends ContainerComputerBase implements ICon this.width = this.height = 0; } + public ContainerViewComputer(int id, PlayerInventory player, PacketByteBuf packetByteBuf) { + super(ComputerCraftRegistry.ModContainers.VIEW_COMPUTER, id, player, packetByteBuf); + ViewComputerContainerData data = new ViewComputerContainerData(new PacketByteBuf(packetByteBuf.copy())); + this.width = data.getWidth(); + this.height = data.getHeight(); + } + private static boolean canInteractWith(@Nonnull ServerComputer computer, @Nonnull PlayerEntity player) { // If this computer no longer exists then discard it. if (ComputerCraft.serverComputerRegistry.get(computer.getInstanceID()) != computer) { @@ -39,13 +46,6 @@ public class ContainerViewComputer extends ContainerComputerBase implements ICon return computer.getFamily() != ComputerFamily.COMMAND || TileCommandComputer.isUsable(player); } - public ContainerViewComputer(int id, PlayerInventory player, PacketByteBuf packetByteBuf) { - super(ComputerCraftRegistry.ModContainers.VIEW_COMPUTER, id, player, packetByteBuf); - ViewComputerContainerData data = new ViewComputerContainerData((PacketByteBuf) packetByteBuf.copy()); - this.width = data.getWidth(); - this.height = data.getHeight(); - } - public int getWidth() { return this.width; } diff --git a/src/main/java/dan200/computercraft/shared/network/container/ViewComputerContainerData.java b/src/main/java/dan200/computercraft/shared/network/container/ViewComputerContainerData.java index 13f259f62..963dc9b74 100644 --- a/src/main/java/dan200/computercraft/shared/network/container/ViewComputerContainerData.java +++ b/src/main/java/dan200/computercraft/shared/network/container/ViewComputerContainerData.java @@ -37,8 +37,8 @@ public class ViewComputerContainerData extends ComputerContainerData { } public ViewComputerContainerData(PacketByteBuf packetByteBuf) { - super(packetByteBuf); - this.toBytes(packetByteBuf); + super(new PacketByteBuf(packetByteBuf.copy())); + this.fromBytes(packetByteBuf); } @Override diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java index 77e4f0799..e267c601d 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java @@ -19,7 +19,7 @@ import dan200.computercraft.api.network.wired.IWiredNode; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheralTile; import dan200.computercraft.shared.ComputerCraftRegistry; -import dan200.computercraft.shared.command.CommandCopy; +import dan200.computercraft.shared.command.text.ChatHelpers; import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.util.DirectionUtil; @@ -109,11 +109,12 @@ public class TileCable extends TileGeneric implements IPeripheralTile { String newName = this.m_peripheral.getConnectedName(); if (!Objects.equal(newName, oldName)) { if (oldName != null) { - player.sendMessage(new TranslatableText("chat.computercraft.wired_modem.peripheral_disconnected", CommandCopy.createCopyText(oldName)), - false); + player.sendMessage(new TranslatableText("chat.computercraft.wired_modem.peripheral_disconnected", + ChatHelpers.copy(oldName)), false); } if (newName != null) { - player.sendMessage(new TranslatableText("chat.computercraft.wired_modem.peripheral_connected", CommandCopy.createCopyText(newName)), false); + player.sendMessage(new TranslatableText("chat.computercraft.wired_modem.peripheral_connected", + ChatHelpers.copy(newName)), false); } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java index 616ecd2df..47502b2dc 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java @@ -27,7 +27,7 @@ import dan200.computercraft.api.network.wired.IWiredElement; import dan200.computercraft.api.network.wired.IWiredNode; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheralTile; -import dan200.computercraft.shared.command.CommandCopy; +import dan200.computercraft.shared.command.text.ChatHelpers; import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.util.DirectionUtil; @@ -260,7 +260,7 @@ public class TileWiredModemFull extends TileGeneric implements IPeripheralTile { if (i > 0) { base.append(", "); } - base.append(CommandCopy.createCopyText(names.get(i))); + base.append(ChatHelpers.copy(names.get(i))); } player.sendMessage(new TranslatableText(kind, base), false); diff --git a/src/main/resources/assets/computercraft/lang/en_us.json b/src/main/resources/assets/computercraft/lang/en_us.json index 5c1433570..f64be4083 100644 --- a/src/main/resources/assets/computercraft/lang/en_us.json +++ b/src/main/resources/assets/computercraft/lang/en_us.json @@ -49,6 +49,7 @@ "commands.computercraft.dump.synopsis": "Display the status of computers.", "commands.computercraft.dump.desc": "Display the status of all computers or specific information about one computer. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\").", "commands.computercraft.dump.action": "View more info about this computer", + "commands.computercraft.dump.open_path": "View this computer's files", "commands.computercraft.shutdown.synopsis": "Shutdown computers remotely.", "commands.computercraft.shutdown.desc": "Shutdown the listed computers or all if none are specified. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\").", "commands.computercraft.shutdown.done": "Shutdown %s/%s computers",