diff --git a/src/main/java/net/minecraftforge/server/command/ChunkGenWorker.java b/src/main/java/net/minecraftforge/server/command/ChunkGenWorker.java index 54ce383017..715179d8c7 100644 --- a/src/main/java/net/minecraftforge/server/command/ChunkGenWorker.java +++ b/src/main/java/net/minecraftforge/server/command/ChunkGenWorker.java @@ -24,11 +24,11 @@ import java.util.Queue; import net.minecraft.command.ICommandSender; import net.minecraft.server.management.PlayerChunkMapEntry; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextComponentBase; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; import net.minecraftforge.common.DimensionManager; -import net.minecraftforge.common.ForgeChunkManager; import net.minecraftforge.common.ForgeModContainer; import net.minecraftforge.common.WorldWorkerManager.IWorker; @@ -80,11 +80,17 @@ public class ChunkGenWorker implements IWorker return ret; } + @Deprecated // TODO remove in 1.13 public TextComponentTranslation getStartMessage() { return new TextComponentTranslation("commands.forge.gen.start", total, start.getX(), start.getZ(), dim); } + public TextComponentBase getStartMessage(ICommandSender sender) + { + return TextComponentHelper.createComponentTranslation(sender, "commands.forge.gen.start", total, start.getX(), start.getZ(), dim); + } + @Override public boolean hasWork() { @@ -105,7 +111,7 @@ public class ChunkGenWorker implements IWorker world = DimensionManager.getWorld(dim); if (world == null) { - listener.sendMessage(new TextComponentTranslation("commands.forge.gen.dim_fail", dim)); + listener.sendMessage(TextComponentHelper.createComponentTranslation(listener, "commands.forge.gen.dim_fail", dim)); queue.clear(); return; } @@ -119,7 +125,7 @@ public class ChunkGenWorker implements IWorker if (++lastNotification >= notificationFrequency) { - listener.sendMessage(new TextComponentTranslation("commands.forge.gen.progress", total - queue.size(), total)); + listener.sendMessage(TextComponentHelper.createComponentTranslation(listener, "commands.forge.gen.progress", total - queue.size(), total)); lastNotification = 0; } @@ -153,7 +159,7 @@ public class ChunkGenWorker implements IWorker if (queue.size() == 0) { - listener.sendMessage(new TextComponentTranslation("commands.forge.gen.complete", genned, total, dim)); + listener.sendMessage(TextComponentHelper.createComponentTranslation(listener, "commands.forge.gen.complete", genned, total, dim)); ForgeModContainer.dimensionUnloadQueueDelay = oldUnloadDelay; } } diff --git a/src/main/java/net/minecraftforge/server/command/CommandEntity.java b/src/main/java/net/minecraftforge/server/command/CommandEntity.java new file mode 100644 index 0000000000..17fbd85900 --- /dev/null +++ b/src/main/java/net/minecraftforge/server/command/CommandEntity.java @@ -0,0 +1,192 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package net.minecraftforge.server.command; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.Maps; +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.command.WrongUsageException; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.DimensionManager; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; + +class CommandEntity extends CommandTreeBase +{ + public CommandEntity() + { + addSubcommand(new EntityListCommand()); + addSubcommand(new CommandTreeHelp(this)); + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.entity.usage"; + } + + @Override + public int getRequiredPermissionLevel() + { + return 2; + } + + @Override + public String getName() + { + return "entity"; + } + + private static class EntityListCommand extends CommandBase + { + @Override + public String getName() + { + return "list"; + } + + @Override + public int getRequiredPermissionLevel() + { + return 2; + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.entity.list.usage"; + } + + @Override + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { + String filter = "*"; + if (args.length > 0) + { + filter = args[0]; + } + final String cleanFilter = filter.replace("?", ".?").replace("*", ".*?"); + Set names = EntityList.getEntityNameList().stream().filter(n -> n.toString().matches(cleanFilter)).collect(Collectors.toSet()); + + if (names.isEmpty()) + throw new WrongUsageException("commands.forge.entity.list.invalid"); + + int dim = args.length > 1 ? parseInt(args[1]) : sender.getEntityWorld().provider.getDimension(); + + WorldServer world = DimensionManager.getWorld(dim); + if (world == null) + throw new WrongUsageException("commands.forge.entity.list.invalidworld", dim); + + Map>> list = Maps.newHashMap(); + List entities = world.loadedEntityList; + entities.forEach(e -> { + ResourceLocation key = EntityList.getKey(e); + + MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); + ChunkPos chunk = new ChunkPos(e.getPosition()); + info.left++; + info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); + }); + + if (names.size() == 1) + { + ResourceLocation name = names.iterator().next(); + Pair> info = list.get(name); + if (info == null) + throw new WrongUsageException("commands.forge.entity.list.none"); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.entity.list.single.header", name, info.getLeft())); + List> toSort = new ArrayList<>(); + toSort.addAll(info.getRight().entrySet()); + toSort.sort((a, b) -> { + if (Objects.equals(a.getValue(), b.getValue())) + { + return a.getKey().toString().compareTo(b.getKey().toString()); + } + else + { + return b.getValue() - a.getValue(); + } + }); + long limit = 10; + for (Map.Entry e : toSort) + { + if (limit-- == 0) break; + sender.sendMessage(new TextComponentString(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z)); + } + } + else + { + + List> info = new ArrayList<>(); + list.forEach((key, value) -> { + if (names.contains(key)) + { + Pair of = Pair.of(key, value.left); + info.add(of); + } + }); + info.sort((a, b) -> { + if (Objects.equals(a.getRight(), b.getRight())) + { + return a.getKey().toString().compareTo(b.getKey().toString()); + } + else + { + return b.getRight() - a.getRight(); + } + }); + + if (info.size() == 0) + throw new WrongUsageException("commands.forge.entity.list.none"); + + int count = info.stream().mapToInt(Pair::getRight).sum(); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.entity.list.multiple.header", count)); + info.forEach(e -> sender.sendMessage(new TextComponentString(" " + e.getValue() + ": " + e.getKey()))); + } + } + + @Override + public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos) + { + if (args.length == 1) + { + String[] entityNames = EntityList.getEntityNameList().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new); + return getListOfStringsMatchingLastWord(args, entityNames); + } + return Collections.emptyList(); + } + } +} diff --git a/src/main/java/net/minecraftforge/server/command/CommandGenerate.java b/src/main/java/net/minecraftforge/server/command/CommandGenerate.java new file mode 100644 index 0000000000..c471398efa --- /dev/null +++ b/src/main/java/net/minecraftforge/server/command/CommandGenerate.java @@ -0,0 +1,90 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package net.minecraftforge.server.command; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.command.WrongUsageException; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.WorldWorkerManager; + +class CommandGenerate extends CommandBase +{ + @Override + public String getName() + { + return "generate"; + } + + @Override + public List getAliases() + { + return Collections.singletonList("gen"); + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.gen.usage"; + } + + @Override + public int getRequiredPermissionLevel() + { + return 4; + } + + @Override + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { + // x y z chunkCount [dim] [interval] + if (args.length < 4) + { + throw new WrongUsageException("commands.forge.gen.usage"); + } + + BlockPos blockpos = parseBlockPos(sender, args, 1, false); + int count = parseInt(args[3], 10); + int dim = args.length >= 5 ? parseInt(args[4]) : sender.getEntityWorld().provider.getDimension(); + int interval = args.length >= 6 ? parseInt(args[5]) : -1; + BlockPos chunkpos = new BlockPos(blockpos.getX() >> 4, 0, blockpos.getZ() >> 4); + + ChunkGenWorker worker = new ChunkGenWorker(sender, chunkpos, count, dim, interval); + sender.sendMessage(worker.getStartMessage(sender)); + WorldWorkerManager.addWorker(worker); + } + + @Override + public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos) + { + if (args.length < 4) + { + return getTabCompletionCoordinate(args, 0, targetPos); + } + // Chunk Count? No completion + // Dimension, Add support for names? Get list of ids? Meh + return Collections.emptyList(); + } +} diff --git a/src/main/java/net/minecraftforge/server/command/CommandTps.java b/src/main/java/net/minecraftforge/server/command/CommandTps.java new file mode 100644 index 0000000000..04f7012080 --- /dev/null +++ b/src/main/java/net/minecraftforge/server/command/CommandTps.java @@ -0,0 +1,111 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package net.minecraftforge.server.command; + +import java.text.DecimalFormat; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.DimensionType; +import net.minecraftforge.common.DimensionManager; + +class CommandTps extends CommandBase +{ + private static final DecimalFormat TIME_FORMATTER = new DecimalFormat("########0.000"); + + @Override + public String getName() + { + return "tps"; + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.tps.usage"; + } + + @Override + public int getRequiredPermissionLevel() + { + return 0; + } + + @Override + public boolean checkPermission(MinecraftServer server, ICommandSender sender) + { + return true; + } + + @Override + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { + int dim = 0; + boolean summary = true; + if (args.length > 0) + { + dim = parseInt(args[0]); + summary = false; + } + + if (summary) + { + for (Integer dimId : DimensionManager.getIDs()) + { + double worldTickTime = mean(server.worldTickTimes.get(dimId)) * 1.0E-6D; + double worldTPS = Math.min(1000.0/worldTickTime, 20); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tps.summary", getDimensionPrefix(dimId), TIME_FORMATTER.format(worldTickTime), TIME_FORMATTER.format(worldTPS))); + } + double meanTickTime = mean(server.tickTimeArray) * 1.0E-6D; + double meanTPS = Math.min(1000.0/meanTickTime, 20); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tps.summary","Overall", TIME_FORMATTER.format(meanTickTime), TIME_FORMATTER.format(meanTPS))); + } + else + { + double worldTickTime = mean(server.worldTickTimes.get(dim)) * 1.0E-6D; + double worldTPS = Math.min(1000.0/worldTickTime, 20); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tps.summary", getDimensionPrefix(dim), TIME_FORMATTER.format(worldTickTime), TIME_FORMATTER.format(worldTPS))); + } + } + + private static String getDimensionPrefix(int dimId) + { + DimensionType providerType = DimensionManager.getProviderType(dimId); + if (providerType == null) + { + return String.format("Dim %2d", dimId); + } + else + { + return String.format("Dim %2d (%s)", dimId, providerType.getName()); + } + } + + private static long mean(long[] values) + { + long sum = 0L; + for (long v : values) + { + sum += v; + } + return sum / values.length; + } +} diff --git a/src/main/java/net/minecraftforge/server/command/CommandTrack.java b/src/main/java/net/minecraftforge/server/command/CommandTrack.java new file mode 100644 index 0000000000..fd3e4f89d3 --- /dev/null +++ b/src/main/java/net/minecraftforge/server/command/CommandTrack.java @@ -0,0 +1,84 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package net.minecraftforge.server.command; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.command.WrongUsageException; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.server.ForgeTimeTracker; + +class CommandTrack extends CommandBase +{ + @Override + public String getName() + { + return "track"; + } + + @Override + public int getRequiredPermissionLevel() + { + return 2; + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.usage.tracking"; + } + + @Override + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { + if (args.length != 2) + { + throw new WrongUsageException("commands.forge.usage.tracking"); + } + String type = args[0]; + int duration = parseInt(args[1], 1, 60); + + if ("te".equals(type)) + { + ForgeTimeTracker.tileEntityTrackingDuration = duration; + ForgeTimeTracker.tileEntityTracking = true; + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.te.enabled", duration)); + } + else + { + throw new WrongUsageException("commands.forge.usage.tracking"); + } + } + + @Override + public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos) + { + if (args.length == 1) + { + return Collections.singletonList("te"); + } + return Collections.emptyList(); + } +} diff --git a/src/main/java/net/minecraftforge/server/command/CommandTreeBase.java b/src/main/java/net/minecraftforge/server/command/CommandTreeBase.java index 683fcf728e..898a2d6bb2 100644 --- a/src/main/java/net/minecraftforge/server/command/CommandTreeBase.java +++ b/src/main/java/net/minecraftforge/server/command/CommandTreeBase.java @@ -25,9 +25,7 @@ import net.minecraft.command.ICommand; import net.minecraft.command.ICommandSender; import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.text.TextComponentString; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; @@ -44,16 +42,32 @@ import java.util.Map; */ public abstract class CommandTreeBase extends CommandBase { - private final Map commandMap = new HashMap(); + private final Map commandMap = new HashMap<>(); + private final Map commandAliasMap = new HashMap<>(); - public void addSubcommand(ICommand c) + public void addSubcommand(ICommand command) { - commandMap.put(c.getName(), c); + commandMap.put(command.getName(), command); + for (String alias : command.getAliases()) + { + commandAliasMap.put(alias, command); + } } public Collection getSubCommands() { - return getCommandMap().values(); + return commandMap.values(); + } + + @Nullable + public ICommand getSubCommand(String command) + { + ICommand cmd = commandMap.get(command); + if (cmd != null) + { + return cmd; + } + return commandAliasMap.get(command); } public Map getCommandMap() @@ -63,12 +77,12 @@ public abstract class CommandTreeBase extends CommandBase public List getSortedCommandList() { - List list = new ArrayList(getSubCommands()); + List list = new ArrayList<>(getSubCommands()); Collections.sort(list); return list; } - private static String[] shiftArgs(String[] s) + private static String[] shiftArgs(@Nullable String[] s) { if(s == null || s.length == 0) { @@ -81,14 +95,13 @@ public abstract class CommandTreeBase extends CommandBase } @Override - @Nonnull - public List getTabCompletions(@Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args, @Nullable BlockPos pos) + public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos pos) { if(args.length == 1) { - List keys = new ArrayList(); + List keys = new ArrayList<>(); - for(ICommand c : getCommandMap().values()) + for (ICommand c : getSubCommands()) { if(c.checkPermission(server, sender)) { @@ -96,11 +109,11 @@ public abstract class CommandTreeBase extends CommandBase } } - Collections.sort(keys, null); + keys.sort(null); return getListOfStringsMatchingLastWord(args, keys); } - ICommand cmd = getCommandMap().get(args[0]); + ICommand cmd = getSubCommand(args[0]); if(cmd != null) { @@ -111,12 +124,12 @@ public abstract class CommandTreeBase extends CommandBase } @Override - public boolean isUsernameIndex(@Nonnull String[] args, int index) + public boolean isUsernameIndex(String[] args, int index) { - if(index > 0 && args.length > 1) + if (index > 0 && args.length > 1) { - ICommand cmd = getCommandMap().get(args[0]); - if(cmd != null) + ICommand cmd = getSubCommand(args[0]); + if (cmd != null) { return cmd.isUsernameIndex(shiftArgs(args), index - 1); } @@ -126,19 +139,21 @@ public abstract class CommandTreeBase extends CommandBase } @Override - public void execute(@Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args) throws CommandException + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException { - if(args.length < 1) + if (args.length < 1) { - sender.sendMessage(new TextComponentString(CommandBase.joinNiceStringFromCollection(commandMap.keySet()))); + String subCommandsString = getAvailableSubCommandsString(server, sender); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.tree_base.available_subcommands", subCommandsString)); } else { - ICommand cmd = getCommandMap().get(args[0]); + ICommand cmd = getSubCommand(args[0]); if(cmd == null) { - throw new CommandException("commands.tree_base.invalid_cmd", args[0]); + String subCommandsString = getAvailableSubCommandsString(server, sender); + throw new CommandException("commands.tree_base.invalid_cmd.list_subcommands", args[0], subCommandsString); } else if(!cmd.checkPermission(server, sender)) { @@ -150,4 +165,17 @@ public abstract class CommandTreeBase extends CommandBase } } } + + private String getAvailableSubCommandsString(MinecraftServer server, ICommandSender sender) + { + Collection availableCommands = new ArrayList<>(); + for (ICommand command : getSubCommands()) + { + if (command.checkPermission(server, sender)) + { + availableCommands.add(command.getName()); + } + } + return CommandBase.joinNiceStringFromCollection(availableCommands); + } } diff --git a/src/main/java/net/minecraftforge/server/command/CommandTreeHelp.java b/src/main/java/net/minecraftforge/server/command/CommandTreeHelp.java new file mode 100644 index 0000000000..e951de765a --- /dev/null +++ b/src/main/java/net/minecraftforge/server/command/CommandTreeHelp.java @@ -0,0 +1,121 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package net.minecraftforge.server.command; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandSender; +import net.minecraft.server.MinecraftServer; + +/** + * Add help for parent and all its children. + * Must be added to parent after all other commands. + */ +public class CommandTreeHelp extends CommandTreeBase +{ + private final ICommand parent; + + public CommandTreeHelp(CommandTreeBase parent) + { + this.parent = parent; + for (ICommand command : parent.getSubCommands()) + { + addSubcommand(new HelpSubCommand(this, command)); + } + } + + @Override + public int getRequiredPermissionLevel() + { + return 0; + } + + @Override + public String getName() + { + return "help"; + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.usage.help"; + } + + @Override + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { + if (args.length == 0) + { + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, parent.getUsage(sender))); + for (ICommand subCommand : getSubCommands()) + { + if (subCommand instanceof HelpSubCommand && subCommand.checkPermission(server, sender)) + { + subCommand.execute(server, sender, args); + } + } + return; + } + super.execute(server, sender, args); + } + + public static class HelpSubCommand extends CommandBase + { + private final CommandTreeHelp parent; + private final ICommand command; + + public HelpSubCommand(CommandTreeHelp parent, ICommand command) + { + this.parent = parent; + this.command = command; + } + + @Override + public int getRequiredPermissionLevel() + { + return 0; + } + + @Override + public String getName() + { + return command.getName(); + } + + @Override + public String getUsage(ICommandSender sender) + { + return command.getUsage(sender); + } + + @Override + public boolean checkPermission(MinecraftServer server, ICommandSender sender) + { + return command.checkPermission(server, sender); + } + + @Override + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, command.getUsage(sender))); + } + } +} diff --git a/src/main/java/net/minecraftforge/server/command/ForgeCommand.java b/src/main/java/net/minecraftforge/server/command/ForgeCommand.java index b03b66c3e2..7473e72cb5 100644 --- a/src/main/java/net/minecraftforge/server/command/ForgeCommand.java +++ b/src/main/java/net/minecraftforge/server/command/ForgeCommand.java @@ -19,41 +19,21 @@ package net.minecraftforge.server.command; -import java.text.DecimalFormat; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; +import net.minecraft.command.ICommand; -import javax.annotation.Nullable; - -import org.apache.commons.lang3.tuple.MutablePair; -import org.apache.commons.lang3.tuple.Pair; - -import com.google.common.collect.Maps; - -import net.minecraft.command.CommandBase; -import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; -import net.minecraft.command.WrongUsageException; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityList; import net.minecraft.server.MinecraftServer; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.ChunkPos; -import net.minecraft.util.text.TextComponentString; -import net.minecraft.util.text.TextComponentTranslation; -import net.minecraft.world.WorldServer; -import net.minecraftforge.common.DimensionManager; -import net.minecraftforge.common.WorldWorkerManager; -import net.minecraftforge.server.ForgeTimeTracker; -public class ForgeCommand extends CommandBase { - - private static final DecimalFormat timeFormatter = new DecimalFormat("########0.000"); +public class ForgeCommand extends CommandTreeBase +{ + public ForgeCommand() + { + super.addSubcommand(new CommandTps()); + super.addSubcommand(new CommandTrack()); + super.addSubcommand(new CommandGenerate()); + super.addSubcommand(new CommandEntity()); + super.addSubcommand(new CommandTreeHelp(this)); + } @Override public String getName() @@ -62,239 +42,26 @@ public class ForgeCommand extends CommandBase { } @Override - public String getUsage(ICommandSender icommandsender) + public void addSubcommand(ICommand command) { - return "commands.forge.usage"; + throw new UnsupportedOperationException("Don't add sub-commands to /forge, create your own command."); } @Override public int getRequiredPermissionLevel() { - return 2; - } - @Override - public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException - { - if (args.length == 0) - { - throw new WrongUsageException("commands.forge.usage"); - } - else if ("help".equals(args[0])) - { - throw new WrongUsageException("commands.forge.usage"); - } - else if ("tps".equals(args[0])) - { - displayTPS(server, sender, args); - } - /*else if ("tpslog".equals(args[0])) - { - doTPSLog(server, sender, args); - } - */ - else if ("track".equals(args[0])) - { - handleTracking(server, sender, args); - } - else if ("gen".equals(args[0])) - { - handleGen(server, sender, args); - } - else if ("entity".equals(args[0])) - { - handleEntity(server, sender, args); - } - else - { - throw new WrongUsageException("commands.forge.usage"); - } + return 0; } @Override - public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos) + public boolean checkPermission(MinecraftServer server, ICommandSender sender) { - if (args.length <= 1) - return getListOfStringsMatchingLastWord(args, "help", "tps", "track", "gen", "entity"); - - switch (args[0].toLowerCase(Locale.ENGLISH)) - { - case "gen": - if (args.length > 1 && args.length <= 4) - return getTabCompletionCoordinate(args, 1, targetPos); - if (args.length == 5) //Chunk Count? No completion - return Collections.emptyList(); - if (args.length == 6) // Dimension, Add support for names? Get list of ids? Meh - return Collections.emptyList(); - break; - case "entity": - if (args.length == 2) - return getListOfStringsMatchingLastWord(args, "help", "list"); - if (args.length == 3) - return getListOfStringsMatchingLastWord(args,EntityList.getEntityNameList().stream().map(e -> e.toString()).sorted().toArray(String[]::new)); - break; - } - return Collections.emptyList(); + return true; } - private void handleTracking(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + @Override + public String getUsage(ICommandSender icommandsender) { - if (args.length != 3) - { - throw new WrongUsageException("commands.forge.usage.tracking"); - } - String type = args[1]; - int duration = parseInt(args[2], 1, 60); - - if ("te".equals(type)) - { - doTurnOnTileEntityTracking(server, sender, duration); - } - else - { - throw new WrongUsageException("commands.forge.usage.tracking"); - } - } - - private void doTurnOnTileEntityTracking(MinecraftServer server, ICommandSender sender, int duration) - { - ForgeTimeTracker.tileEntityTrackingDuration = duration; - ForgeTimeTracker.tileEntityTracking = true; - sender.sendMessage(new TextComponentTranslation("commands.forge.tracking.te.enabled", duration)); - } - - private void doTPSLog(MinecraftServer server, ICommandSender sender, String[] args) - { - - } - - private void displayTPS(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException - { - int dim = 0; - boolean summary = true; - if (args.length > 1) - { - dim = parseInt(args[1]); - summary = false; - } - if (summary) - { - for (Integer dimId : DimensionManager.getIDs()) - { - double worldTickTime = ForgeCommand.mean(server.worldTickTimes.get(dimId)) * 1.0E-6D; - double worldTPS = Math.min(1000.0/worldTickTime, 20); - sender.sendMessage(new TextComponentTranslation("commands.forge.tps.summary",String.format("Dim %d", dimId), timeFormatter.format(worldTickTime), timeFormatter.format(worldTPS))); - } - double meanTickTime = ForgeCommand.mean(server.tickTimeArray) * 1.0E-6D; - double meanTPS = Math.min(1000.0/meanTickTime, 20); - sender.sendMessage(new TextComponentTranslation("commands.forge.tps.summary","Overall", timeFormatter.format(meanTickTime), timeFormatter.format(meanTPS))); - } - else - { - double worldTickTime = ForgeCommand.mean(server.worldTickTimes.get(dim)) * 1.0E-6D; - double worldTPS = Math.min(1000.0/worldTickTime, 20); - sender.sendMessage(new TextComponentTranslation("commands.forge.tps.summary",String.format("Dim %d", dim), timeFormatter.format(worldTickTime), timeFormatter.format(worldTPS))); - } - } - - private void handleGen(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException - { - // gen x y z chunkCount [dim] [shape?] - if (args.length < 5) - throw new WrongUsageException("commands.forge.gen.usage"); - - BlockPos blockpos = parseBlockPos(sender, args, 1, false); - int count = parseInt(args[4], 10); - int dim = args.length >= 6 ? parseInt(args[5]) : sender.getEntityWorld().provider.getDimension(); - int interval = args.length >= 7 ? parseInt(args[6]) : -1; - BlockPos chunkpos = new BlockPos(blockpos.getX() >> 4, 0, blockpos.getZ() >> 4); - - ChunkGenWorker worker = new ChunkGenWorker(sender, chunkpos, count, dim, interval); - sender.sendMessage(worker.getStartMessage()); - WorldWorkerManager.addWorker(worker); - } - - private static long mean(long[] values) - { - long sum = 0l; - for (long v : values) - { - sum+=v; - } - - return sum / values.length; - } - - private void handleEntity(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException - { - if (args.length < 2 || args[1].toLowerCase(Locale.ENGLISH).equals("help")) - throw new WrongUsageException("commands.forge.entity.usage"); - - switch (args[1].toLowerCase(Locale.ENGLISH)) - { - case "list": - String filter = "*"; - if (args.length > 2) - { - if (args[2].toLowerCase(Locale.ENGLISH).equals("help")) - throw new WrongUsageException("commands.forge.entity.list.usage"); - filter = args[2]; - } - final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); - Set names = EntityList.getEntityNameList().stream().filter(n -> n.toString().matches(cleanfilter)).collect(Collectors.toSet()); - - if (names.isEmpty()) - throw new WrongUsageException("commands.forge.entity.list.invalid"); - - int dim = args.length > 3 ? parseInt(args[3]) : sender.getEntityWorld().provider.getDimension(); - - Map>> list = Maps.newHashMap(); - WorldServer world = DimensionManager.getWorld(dim); - if (world == null) - throw new WrongUsageException("commands.forge.entity.list.invalidworld", dim); - - List entities = world.getLoadedEntityList(); - entities.forEach(e -> { - ResourceLocation key = EntityList.getKey(e); - - MutablePair> info = list.get(key); - if (info == null) - { - info = MutablePair.of(0, Maps.newHashMap()); - list.put(key, info); - } - ChunkPos chunk = new ChunkPos(e.getPosition()); - info.left++; - info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); - }); - - if (names.size() == 1) - { - ResourceLocation name = names.iterator().next(); - Pair> info = list.get(name); - if (info == null) - throw new WrongUsageException("commands.forge.entity.list.none"); - sender.sendMessage(new TextComponentTranslation("commands.forge.entity.list.single.header", name, info.getLeft())); - info.getRight().entrySet().stream() - .sorted((a,b) -> a.getValue() != b.getValue() ? b.getValue() - a.getValue() : a.getKey().toString().compareTo(b.getKey().toString())) - .limit(10).forEach(e -> sender.sendMessage(new TextComponentString(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z))); - } - else - { - - List> info = list.entrySet().stream() - .filter(e -> names.contains(e.getKey())) - .map(e -> Pair.of(e.getKey(), e.getValue().left)) - .sorted((a, b) -> a.getRight() != b.getRight() ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString())) - .collect(Collectors.toList()); - - if (info == null || info.size() == 0) - throw new WrongUsageException("commands.forge.entity.list.none"); - - int count = info.stream().mapToInt(a -> a.getRight()).sum(); - sender.sendMessage(new TextComponentTranslation("commands.forge.entity.list.multiple.header", count)); - info.forEach(e -> sender.sendMessage(new TextComponentString(" " + e.getValue() + ": " + e.getKey()))); - } - break; - } + return "commands.forge.usage"; } } diff --git a/src/main/java/net/minecraftforge/server/command/TextComponentHelper.java b/src/main/java/net/minecraftforge/server/command/TextComponentHelper.java new file mode 100644 index 0000000000..8e86b8b790 --- /dev/null +++ b/src/main/java/net/minecraftforge/server/command/TextComponentHelper.java @@ -0,0 +1,57 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package net.minecraftforge.server.command; + +import io.netty.channel.Channel; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.text.TextComponentBase; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.util.text.translation.I18n; +import net.minecraftforge.fml.common.network.NetworkRegistry; + +public class TextComponentHelper +{ + private TextComponentHelper() {} + + /** + * Detects when sending to a vanilla client and falls back to sending english, + * since they don't have the lang data necessary to translate on the client. + */ + public static TextComponentBase createComponentTranslation(ICommandSender sender, final String translation, final Object... args) + { + if (isVanillaClient(sender)) + { + return new TextComponentString(I18n.translateToLocalFormatted(translation, args)); + } + return new TextComponentTranslation(translation, args); + } + + private static boolean isVanillaClient(ICommandSender sender) + { + if (sender instanceof EntityPlayerMP) + { + EntityPlayerMP playerMP = (EntityPlayerMP) sender; + Channel channel = playerMP.connection.netManager.channel(); + return !channel.attr(NetworkRegistry.FML_MARKER).get(); + } + return false; + } +} diff --git a/src/main/java/net/minecraftforge/server/command/package-info.java b/src/main/java/net/minecraftforge/server/command/package-info.java new file mode 100644 index 0000000000..7ad578a5ef --- /dev/null +++ b/src/main/java/net/minecraftforge/server/command/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package net.minecraftforge.server.command; + +import javax.annotation.ParametersAreNonnullByDefault; + +import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/resources/assets/forge/lang/en_US.lang b/src/main/resources/assets/forge/lang/en_US.lang index 4d20cb91e3..7e6a0c1181 100644 --- a/src/main/resources/assets/forge/lang/en_US.lang +++ b/src/main/resources/assets/forge/lang/en_US.lang @@ -1,12 +1,14 @@ -commands.forge.usage=Use /forge . Subcommands are tps, track, gen -commands.forge.usage.tracking=Use /forge track . Valid types are te (Tile Entities). Duration is < 60. +commands.forge.usage=Use /forge +commands.forge.usage.tracking=Use /forge track . Valid types are "te" (Tile Entities). Duration in seconds must be <= 60. +commands.forge.usage.help=Use help [command] to view usages for that command. commands.forge.tps.summary=%s : Mean tick time: %d ms. Mean TPS: %d +commands.forge.tps.usage=Use /forge tps [dimension] commands.forge.gen.usage=Use /forge gen [dimension] [interval] commands.forge.gen.dim_fail=Failed to load world for dimension %d, Task terminated. commands.forge.gen.progress=Generation Progress: %d/%d commands.forge.gen.complete=Finished generating %d new chunks (out of %d) for dimension %d. commands.forge.gen.start=Starting to generate %d chunks in a spiral around %d, %d in dimension %d. -commands.forge.entity.usage=Use /forge entity [list] help for more infomration on a specific command. +commands.forge.entity.usage=Use /forge entity help for more information on entity subcommands. commands.forge.entity.list.usage=Use /forge entity list [filter] [dim] to get entity info that matches the optional filter. commands.forge.entity.list.invalid=Invalid filter, does not match any entities. Use /forge entity list for a proper list commands.forge.entity.list.invalidworld=Could not load world for dimension %d. Please select a valid dimension. @@ -16,6 +18,8 @@ commands.forge.entity.list.multiple.header=Total: %d commands.forge.tracking.te.enabled=Tile Entity tracking enabled for %d seconds. commands.tree_base.invalid_cmd=Invalid subcommand '%s'! +commands.tree_base.invalid_cmd.list_subcommands=Invalid subcommand '%s'! Available subcommands: %s +commands.tree_base.available_subcommands=Available SubCommands: %s forge.texture.preload.warning=Warning: Texture %s not preloaded, will cause render glitches! forge.client.shutdown.internal=Shutting down internal server... forge.update.newversion=New Forge version available: %s