1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-17 02:40:06 +00:00
CC-Tweaked/projects/common/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java
Jonathan Coates b3738a7a63
Use permission APIs for the /computercraft command
- Add a generic PermissionRegistry interface. This behaves similarly to
   our ShaderMod interface, searching all providers until it finds a
   compatible one.

   We could just make this part of the platform code instead, but this
   allows us to support multiple systems on Fabric, where things are
   less standardised.

   This interface behaves like a registry, rather than a straight
   `getPermission(node, player)` method, as Forge requires us to list
   our nodes up-front.

 - Add Forge (using the built-in system) and Fabric (using
   fabric-permissions-api) implementations of the above interface.

 - Register permission nodes for our commands, and use those
   instead. This does mean that the permissions check for the root
   /computercraft command now requires enumerating all child
   commands (and so potential does 7 permission lookups), but hopefully
   this isn't too bad in practice.

 - Remove UserLevel.OWNER - we never used this anywhere, and I can't
   imagine we'll want to in the future.
2023-08-27 12:22:40 +01:00

364 lines
17 KiB
Java

// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
import dan200.computercraft.shared.computer.metrics.basic.Aggregate;
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
import dan200.computercraft.shared.computer.metrics.basic.BasicComputerMetricsObserver;
import dan200.computercraft.shared.computer.metrics.basic.ComputerMetrics;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.RelativeMovement;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
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.metric;
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.commands.Commands.literal;
public final class CommandComputerCraft {
public static final UUID SYSTEM_UUID = new UUID(0, 0);
public static final String OPEN_COMPUTER = "computercraft open-computer ";
private CommandComputerCraft() {
}
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(choice("computercraft")
.then(literal("dump")
.requires(ModRegistry.Permissions.PERMISSION_DUMP)
.executes(context -> {
var table = new TableBuilder("DumpAll", "Computer", "On", "Position");
var source = context.getSource();
List<ServerComputer> computers = new ArrayList<>(ServerContext.get(source.getServer()).registry().getComputers());
Level world = source.getLevel();
var pos = BlockPos.containing(source.getPosition());
computers.sort((a, b) -> {
if (a.getLevel() == b.getLevel() && a.getLevel() == world) {
return Double.compare(a.getPosition().distSqr(pos), b.getPosition().distSqr(pos));
} else if (a.getLevel() == world) {
return -1;
} else if (b.getLevel() == world) {
return 1;
} else {
return Integer.compare(a.getInstanceID(), b.getInstanceID());
}
});
for (var 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 -> {
var computer = getComputerArgument(context, "computer");
var table = new TableBuilder("Dump");
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 (var side : ComputerSide.values()) {
var peripheral = computer.getPeripheral(side);
if (peripheral != null) {
table.row(header("Peripheral " + side.getName()), text(peripheral.getType()));
}
}
table.display(context.getSource());
return 1;
})))
.then(command("shutdown")
.requires(ModRegistry.Permissions.PERMISSION_SHUTDOWN)
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
.executes((context, computerSelectors) -> {
var shutdown = 0;
var computers = unwrap(context.getSource(), computerSelectors);
for (var computer : computers) {
if (computer.isOn()) shutdown++;
computer.shutdown();
}
context.getSource().sendSuccess(Component.translatable("commands.computercraft.shutdown.done", shutdown, computers.size()), false);
return shutdown;
}))
.then(command("turn-on")
.requires(ModRegistry.Permissions.PERMISSION_TURN_ON)
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
.executes((context, computerSelectors) -> {
var on = 0;
var computers = unwrap(context.getSource(), computerSelectors);
for (var computer : computers) {
if (!computer.isOn()) on++;
computer.turnOn();
}
context.getSource().sendSuccess(Component.translatable("commands.computercraft.turn_on.done", on, computers.size()), false);
return on;
}))
.then(command("tp")
.requires(ModRegistry.Permissions.PERMISSION_TP)
.arg("computer", oneComputer())
.executes(context -> {
var computer = getComputerArgument(context, "computer");
var world = computer.getLevel();
var pos = computer.getPosition();
var entity = context.getSource().getEntityOrException();
if (!(entity instanceof ServerPlayer player)) throw TP_NOT_PLAYER.create();
if (player.getCommandSenderWorld() == world) {
player.connection.teleport(
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0,
EnumSet.noneOf(RelativeMovement.class)
);
} else {
player.teleportTo(world,
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0
);
}
return 1;
}))
.then(command("queue")
.requires(ModRegistry.Permissions.PERMISSION_QUEUE)
.arg(
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
.suggests((context, builder) -> Suggestions.empty())
)
.argManyValue("args", StringArgumentType.string(), Collections.emptyList())
.executes((ctx, args) -> {
var computers = getComputersArgument(ctx, "computer");
var rest = args.toArray();
var queued = 0;
for (var computer : computers) {
if (computer.getFamily() == ComputerFamily.COMMAND && computer.isOn()) {
computer.queueEvent("computer_command", rest);
queued++;
}
}
return queued;
}))
.then(command("view")
.requires(ModRegistry.Permissions.PERMISSION_VIEW)
.arg("computer", oneComputer())
.executes(context -> {
var player = context.getSource().getPlayerOrException();
var computer = getComputerArgument(context, "computer");
new ComputerContainerData(computer, ItemStack.EMPTY).open(player, new MenuProvider() {
@Override
public Component getDisplayName() {
return Component.translatable("gui.computercraft.view_computer");
}
@Override
public AbstractContainerMenu createMenu(int id, Inventory player, Player entity) {
return new ViewComputerMenu(id, player, computer);
}
});
return 1;
}))
.then(choice("track")
.requires(ModRegistry.Permissions.PERMISSION_TRACK)
.then(command("start")
.executes(context -> {
getMetricsInstance(context.getSource()).start();
var stopCommand = "/computercraft track stop";
context.getSource().sendSuccess(Component.translatable(
"commands.computercraft.track.start.stop",
link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action"))
), false);
return 1;
}))
.then(command("stop")
.executes(context -> {
var timings = getMetricsInstance(context.getSource());
if (!timings.stop()) throw NOT_TRACKING_EXCEPTION.create();
displayTimings(context.getSource(), timings.getSnapshot(), new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG), DEFAULT_FIELDS);
return 1;
}))
.then(command("dump")
.argManyValue("fields", metric(), DEFAULT_FIELDS)
.executes((context, fields) -> {
AggregatedMetric 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 Component linkComputer(CommandSourceStack source, @Nullable ServerComputer serverComputer, int computerId) {
var out = Component.literal("");
// Append the computer instance
if (serverComputer == null) {
out.append(text("?"));
} else {
out.append(link(
text(Integer.toString(serverComputer.getInstanceID())),
"/computercraft dump " + serverComputer.getInstanceID(),
Component.translatable("commands.computercraft.dump.action")
));
}
// And ID
out.append(" (id " + computerId + ")");
// And, if we're a player, some useful links
if (serverComputer != null && isPlayer(source)) {
if (ModRegistry.Permissions.PERMISSION_TP.test(source)) {
out.append(" ").append(link(
text("\u261b"),
"/computercraft tp " + serverComputer.getInstanceID(),
Component.translatable("commands.computercraft.tp.action")
));
}
if (ModRegistry.Permissions.PERMISSION_VIEW.test(source)) {
out.append(" ").append(link(
text("\u20e2"),
"/computercraft view " + serverComputer.getInstanceID(),
Component.translatable("commands.computercraft.view.action")
));
}
}
if (isPlayer(source) && UserLevel.isOwner(source)) {
var linkPath = linkStorage(source, computerId);
if (linkPath != null) out.append(" ").append(linkPath);
}
return out;
}
private static Component linkPosition(CommandSourceStack context, ServerComputer computer) {
if (ModRegistry.Permissions.PERMISSION_TP.test(context)) {
return link(
position(computer.getPosition()),
"/computercraft tp " + computer.getInstanceID(),
Component.translatable("commands.computercraft.tp.action")
);
} else {
return position(computer.getPosition());
}
}
private static @Nullable Component linkStorage(CommandSourceStack source, int id) {
var file = new File(ServerContext.get(source.getServer()).storageDir().toFile(), "computer/" + id);
if (!file.isDirectory()) return null;
return link(
text("\u270E"),
"/" + OPEN_COMPUTER + id,
Component.translatable("commands.computercraft.dump.open_path")
);
}
private static BasicComputerMetricsObserver getMetricsInstance(CommandSourceStack source) {
var entity = source.getEntity();
return ServerContext.get(source.getServer()).metrics().getMetricsInstance(entity instanceof Player ? entity.getUUID() : SYSTEM_UUID);
}
private static final List<AggregatedMetric> DEFAULT_FIELDS = Arrays.asList(
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.COUNT),
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.NONE),
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG)
);
private static int displayTimings(CommandSourceStack source, AggregatedMetric sortField, List<AggregatedMetric> fields) throws CommandSyntaxException {
return displayTimings(source, getMetricsInstance(source).getTimings(), sortField, fields);
}
private static int displayTimings(CommandSourceStack source, List<ComputerMetrics> timings, AggregatedMetric sortField, List<AggregatedMetric> fields) throws CommandSyntaxException {
if (timings.isEmpty()) throw NO_TIMINGS_EXCEPTION.create();
timings.sort(Comparator.<ComputerMetrics, Long>comparing(x -> x.get(sortField.metric(), sortField.aggregate())).reversed());
var headers = new Component[1 + fields.size()];
headers[0] = Component.translatable("commands.computercraft.track.dump.computer");
for (var i = 0; i < fields.size(); i++) headers[i + 1] = fields.get(i).displayName();
var table = new TableBuilder("Metrics", headers);
for (var entry : timings) {
var serverComputer = entry.computer();
var computerComponent = linkComputer(source, serverComputer, entry.computerId());
var row = new Component[1 + fields.size()];
row[0] = computerComponent;
for (var i = 0; i < fields.size(); i++) {
var metric = fields.get(i);
row[i + 1] = text(entry.getFormatted(metric.metric(), metric.aggregate()));
}
table.row(row);
}
table.display(source);
return timings.size();
}
}