diff --git a/src/main/java/net/minecraftforge/server/command/CommandTreeBase.java b/src/main/java/net/minecraftforge/server/command/CommandTreeBase.java
new file mode 100644
index 0000000000..3c7911ca08
--- /dev/null
+++ b/src/main/java/net/minecraftforge/server/command/CommandTreeBase.java
@@ -0,0 +1,138 @@
+/*
+ * 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;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.text.TextComponentString;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Base class for commands that has subcommands.
+ *
+ * E.g. /team settings set [value]
+ * settings is subcommand of team and set is subcommand of settings
+ */
+public abstract class CommandTreeBase extends CommandBase
+{
+ private final HashMap commandMap = new HashMap();
+
+ public void addSubcommand(ICommand c)
+ {
+ commandMap.put(c.getCommandName(), c);
+ }
+
+ public Collection getSubCommands()
+ {
+ return Collections.unmodifiableCollection(commandMap.values());
+ }
+
+ private static String[] shiftArgs(String[] s)
+ {
+ if(s == null || s.length == 0)
+ {
+ return new String[0];
+ }
+
+ String[] s1 = new String[s.length - 1];
+ System.arraycopy(s, 1, s1, 0, s1.length);
+ return s1;
+ }
+
+ @Override
+ public List getTabCompletionOptions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos pos)
+ {
+ if(args.length == 1)
+ {
+ List keys = new ArrayList();
+
+ for(ICommand c : commandMap.values())
+ {
+ if(c.checkPermission(server, sender))
+ {
+ keys.add(c.getCommandName());
+ }
+ }
+
+ Collections.sort(keys, null);
+ return getListOfStringsMatchingLastWord(args, keys);
+ }
+
+ ICommand cmd = commandMap.get(args[0]);
+
+ if(cmd != null)
+ {
+ return cmd.getTabCompletionOptions(server, sender, shiftArgs(args), pos);
+ }
+
+ return super.getTabCompletionOptions(server, sender, args, pos);
+ }
+
+ @Override
+ public boolean isUsernameIndex(String[] args, int index)
+ {
+ if(index > 0 && args.length > 1)
+ {
+ ICommand cmd = commandMap.get(args[0]);
+ if(cmd != null)
+ {
+ return cmd.isUsernameIndex(shiftArgs(args), index - 1);
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
+ {
+ if(args.length < 1)
+ {
+ sender.addChatMessage(new TextComponentString(CommandBase.joinNiceStringFromCollection(commandMap.keySet())));
+ }
+ else
+ {
+ ICommand cmd = commandMap.get(args[0]);
+
+ if(cmd == null)
+ {
+ throw new CommandException("commands.tree_base.invalid_cmd", args[0]);
+ }
+ else if(!cmd.checkPermission(server, sender))
+ {
+ throw new CommandException("commands.generic.permission");
+ }
+ else
+ {
+ cmd.execute(server, sender, shiftArgs(args));
+ }
+ }
+ }
+}
diff --git a/src/main/resources/assets/forge/lang/en_US.lang b/src/main/resources/assets/forge/lang/en_US.lang
index 55dd14dfc0..02c81a52e1 100644
--- a/src/main/resources/assets/forge/lang/en_US.lang
+++ b/src/main/resources/assets/forge/lang/en_US.lang
@@ -3,6 +3,7 @@ commands.forge.usage.tracking=Use /forge track . Valid types ar
commands.forge.tps.summary=%s : Mean tick time: %d ms. Mean TPS: %d
commands.forge.tracking.te.enabled=Tile Entity tracking enabled for %d seconds.
+commands.tree_base.invalid_cmd=Invalid subcommand '%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
diff --git a/src/test/java/net/minecraftforge/test/CommandTreeBaseTest.java b/src/test/java/net/minecraftforge/test/CommandTreeBaseTest.java
new file mode 100644
index 0000000000..c46c5cc415
--- /dev/null
+++ b/src/test/java/net/minecraftforge/test/CommandTreeBaseTest.java
@@ -0,0 +1,133 @@
+package net.minecraftforge.test;
+
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.CommandException;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.util.text.TextComponentString;
+import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
+import net.minecraftforge.server.command.CommandTreeBase;
+
+@Mod(modid = CommandTreeBaseTest.MOD_ID, name = "CommandTreeBaseTest", version = "1.0.0")
+public class CommandTreeBaseTest
+{
+ public static final String MOD_ID = "CommandTreeBaseTest";
+
+ @Mod.Instance(CommandTreeBaseTest.MOD_ID)
+ public static CommandTreeBaseTest inst;
+
+ @Mod.EventHandler
+ public void onServerStarted(FMLServerStartingEvent event)
+ {
+ event.registerServerCommand(new CommandTreeTest());
+ }
+
+ public static class CommandTreeTest extends CommandTreeBase
+ {
+ public CommandTreeTest()
+ {
+ addSubcommand(new CommandPing());
+ addSubcommand(new CommandValue());
+ }
+
+ @Override
+ public String getCommandName()
+ {
+ return "treecommand_test";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender)
+ {
+ return "commands.treecommand_test.usage";
+ }
+
+ public static class CommandPing extends CommandBase
+ {
+ @Override
+ public String getCommandName()
+ {
+ return "ping";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender)
+ {
+ return "commands.treecommand_test.ping.usage";
+ }
+
+ @Override
+ public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
+ {
+ sender.addChatMessage(new TextComponentString("Pong!"));
+ }
+ }
+
+ public static class CommandValue extends CommandTreeBase
+ {
+ private static int value = 0;
+
+ public CommandValue()
+ {
+ addSubcommand(new CommandSet());
+ addSubcommand(new CommandGet());
+ }
+
+ @Override
+ public String getCommandName()
+ {
+ return "value";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender)
+ {
+ return "commands.treecommand_test.value.usage";
+ }
+
+ public static class CommandSet extends CommandBase
+ {
+ @Override
+ public String getCommandName()
+ {
+ return "set";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender)
+ {
+ return "commands.treecommand_test.value.set.usage";
+ }
+
+ @Override
+ public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
+ {
+ value = CommandTreeTest.parseInt(args[0]);
+ sender.addChatMessage(new TextComponentString("Test value set to: " + value));
+ }
+ }
+
+ public static class CommandGet extends CommandBase
+ {
+ @Override
+ public String getCommandName()
+ {
+ return "get";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender)
+ {
+ return "commands.treecommand_test.value.get.usage";
+ }
+
+ @Override
+ public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
+ {
+ sender.addChatMessage(new TextComponentString("Test value: " + value));
+ }
+ }
+ }
+ }
+}