aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/commands/ForceStart.java1
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/commands/ForceStop.java1
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/commands/Join.java4
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/commands/Tumble.java17
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/config/ArenaManager.java32
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/config/ConfigManager.java12
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/config/LanguageManager.java21
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/game/EventListener.java8
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/game/Game.java137
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/plugin/CustomConfig.java8
-rw-r--r--src/main/java/com/MylesAndMore/Tumble/plugin/SubCommand.java8
11 files changed, 187 insertions, 62 deletions
diff --git a/src/main/java/com/MylesAndMore/Tumble/commands/ForceStart.java b/src/main/java/com/MylesAndMore/Tumble/commands/ForceStart.java
index 2d39c83..bf130ea 100644
--- a/src/main/java/com/MylesAndMore/Tumble/commands/ForceStart.java
+++ b/src/main/java/com/MylesAndMore/Tumble/commands/ForceStart.java
@@ -32,6 +32,7 @@ public class ForceStart implements SubCommand, CommandExecutor, TabCompleter {
Game game;
if (args.length < 1 || args[0] == null) {
+ // no arena passed in, try to infer from game player is in
game = arenaManager.findGamePlayerIsIn((Player)sender);
if (game == null) {
sender.sendMessage(languageManager.fromKey("missing-arena-parameter"));
diff --git a/src/main/java/com/MylesAndMore/Tumble/commands/ForceStop.java b/src/main/java/com/MylesAndMore/Tumble/commands/ForceStop.java
index 24f430c..8ab48c2 100644
--- a/src/main/java/com/MylesAndMore/Tumble/commands/ForceStop.java
+++ b/src/main/java/com/MylesAndMore/Tumble/commands/ForceStop.java
@@ -32,6 +32,7 @@ public class ForceStop implements SubCommand, CommandExecutor, TabCompleter {
Game game;
if (args.length < 1 || args[0] == null) {
+ // no arena passed in, try to infer from game player is in
game = arenaManager.findGamePlayerIsIn((Player)sender);
if (game == null) {
sender.sendMessage(languageManager.fromKey("missing-arena-parameter"));
diff --git a/src/main/java/com/MylesAndMore/Tumble/commands/Join.java b/src/main/java/com/MylesAndMore/Tumble/commands/Join.java
index 1dbbad6..3177f3e 100644
--- a/src/main/java/com/MylesAndMore/Tumble/commands/Join.java
+++ b/src/main/java/com/MylesAndMore/Tumble/commands/Join.java
@@ -59,6 +59,7 @@ public class Join implements SubCommand, CommandExecutor, TabCompleter {
Game game;
if (args.length < 2 || args[1] == null) {
+ // try to infer game type from game taking place in the arena
if (arena.game == null) {
sender.sendMessage(languageManager.fromKey("specify-game-type"));
return false;
@@ -80,10 +81,12 @@ public class Join implements SubCommand, CommandExecutor, TabCompleter {
}
if (arena.game == null) {
+ // no game is taking place in this arena, start one
game = arena.game = new Game(arena, type);
}
else
{
+ // a game is taking place in this arena, check that it is the right type
if (arena.game.type == type) {
game = arena.game;
}
@@ -96,6 +99,7 @@ public class Join implements SubCommand, CommandExecutor, TabCompleter {
}
}
+ // check to make sure the arena has a game spawn
if (game.arena.gameSpawn == null) {
if (p.isOp()) {
sender.sendMessage(languageManager.fromKey("arena-not-ready-op"));
diff --git a/src/main/java/com/MylesAndMore/Tumble/commands/Tumble.java b/src/main/java/com/MylesAndMore/Tumble/commands/Tumble.java
index 2cf5b90..67213ee 100644
--- a/src/main/java/com/MylesAndMore/Tumble/commands/Tumble.java
+++ b/src/main/java/com/MylesAndMore/Tumble/commands/Tumble.java
@@ -42,6 +42,7 @@ public class Tumble implements CommandExecutor, TabCompleter {
return false;
}
+ // pass command action through to subCommand
subCmd.onCommand(sender, command, args[0], removeFirst(args));
return true;
}
@@ -49,6 +50,7 @@ public class Tumble implements CommandExecutor, TabCompleter {
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (args.length == 1) {
+ // show only subCommands the user has permission for
ArrayList<String> PermittedSubCmds = new ArrayList<>();
for (SubCommand subCmd: subCommands.values()) {
if (sender.hasPermission(subCmd.getPermission())) {
@@ -63,6 +65,7 @@ public class Tumble implements CommandExecutor, TabCompleter {
return Collections.emptyList();
}
+ // pass tab complete through to subCommand
if (subCommands.get(args[0]) instanceof TabCompleter tcmp) {
return tcmp.onTabComplete(sender, command, args[0], removeFirst(args));
}
@@ -74,13 +77,23 @@ public class Tumble implements CommandExecutor, TabCompleter {
return Collections.emptyList();
}
+ /**
+ * Create a copy of an array with the first element removed
+ * @param arr the source array
+ * @return the source without the first element
+ */
private String[] removeFirst(String[] arr) {
ArrayList<String> tmp = new ArrayList<>(List.of(arr));
tmp.remove(0);
return tmp.toArray(new String[0]);
}
- private static Map.Entry<String, SubCommand> CmdNameAsKey(SubCommand s) {
- return Map.entry(s.getCommandName(),s);
+ /**
+ * Creates a map entry with the name of the subCommand as the key and the subCommand itself as the value
+ * @param cmd The subCommand to use
+ * @return A map entry from the subCommand
+ */
+ private static Map.Entry<String, SubCommand> CmdNameAsKey(SubCommand cmd) {
+ return Map.entry(cmd.getCommandName(),cmd);
}
}
diff --git a/src/main/java/com/MylesAndMore/Tumble/config/ArenaManager.java b/src/main/java/com/MylesAndMore/Tumble/config/ArenaManager.java
index aa29cf1..b4364d3 100644
--- a/src/main/java/com/MylesAndMore/Tumble/config/ArenaManager.java
+++ b/src/main/java/com/MylesAndMore/Tumble/config/ArenaManager.java
@@ -18,6 +18,9 @@ import java.util.Objects;
import static com.MylesAndMore.Tumble.Main.plugin;
+/**
+ * Manages arenas.yml and stores list of arenas
+ */
public class ArenaManager {
public HashMap<String, Arena> arenas;
@@ -25,11 +28,17 @@ public class ArenaManager {
private final CustomConfig arenasYml = new CustomConfig("arenas.yml");
private final FileConfiguration config = arenasYml.getConfig();
+ /**
+ * Create an ArenaManager
+ */
public ArenaManager() {
arenasYml.saveDefaultConfig();
readConfig();
}
+ /**
+ * Read arenas from arenas.ynl and populate this.arenas
+ */
public void readConfig() {
// arenas
@@ -78,6 +87,9 @@ public class ArenaManager {
}
}
+ /**
+ * Write arenas from this.arenas to arenas.yml
+ */
public void WriteConfig() {
config.set("arenas", null); // clear everything
@@ -118,16 +130,16 @@ public class ArenaManager {
}
/**
- * tries to convert a config section in the following format to a world
+ * Tries to convert a config section in the following format to a world
* section:
* x:
* y:
* z:
* world:
- * @param section the section in the yaml with x, y, z, and world as its children
- * @return result of either:
- * success = true and a world
- * success = false and an error string
+ * @param section The section in the yaml with x, y, z, and world as its children
+ * @return Result of either:
+ * Result#success = true and Result#value OR
+ * Result#success = false and Result#error
*/
private Result<Location> readWorld(@Nullable ConfigurationSection section) {
@@ -155,6 +167,16 @@ public class ArenaManager {
return new Result<>(new Location(world,x,y,z));
}
+ /**
+ * Write a location into the config using the following format:
+ * section:
+ * x:
+ * y:
+ * z:
+ * world:
+ * @param path The path of the section to write
+ * @param location The location to write
+ */
private void WriteWorld(String path, @NotNull Location location) {
ConfigurationSection section = config.getConfigurationSection(path);
diff --git a/src/main/java/com/MylesAndMore/Tumble/config/ConfigManager.java b/src/main/java/com/MylesAndMore/Tumble/config/ConfigManager.java
index 3b56f7f..13cc779 100644
--- a/src/main/java/com/MylesAndMore/Tumble/config/ConfigManager.java
+++ b/src/main/java/com/MylesAndMore/Tumble/config/ConfigManager.java
@@ -7,6 +7,9 @@ import java.util.Objects;
import static com.MylesAndMore.Tumble.Main.plugin;
+/**
+ * Manages config.yml and stores its options
+ */
public class ConfigManager {
public boolean HideLeaveJoin;
public int waitDuration;
@@ -15,12 +18,18 @@ public class ConfigManager {
private final Configuration config = configYml.getConfig();
private final Configuration defaultConfig = Objects.requireNonNull(config.getDefaults());
+ /**
+ * Create a config manager
+ */
public ConfigManager() {
configYml.saveDefaultConfig();
validate();
readConfig();
}
+ /**
+ * Check keys of config.yml against the defaults
+ */
public void validate() {
boolean invalid = false;
for (String key : defaultConfig.getKeys(true)) {
@@ -34,6 +43,9 @@ public class ConfigManager {
}
}
+ /**
+ * Reads options in from config.yml
+ */
public void readConfig() {
HideLeaveJoin = config.getBoolean("hide-join-leave-messages", false);
waitDuration = config.getInt("wait-duration", 15);
diff --git a/src/main/java/com/MylesAndMore/Tumble/config/LanguageManager.java b/src/main/java/com/MylesAndMore/Tumble/config/LanguageManager.java
index 917ce78..7c82664 100644
--- a/src/main/java/com/MylesAndMore/Tumble/config/LanguageManager.java
+++ b/src/main/java/com/MylesAndMore/Tumble/config/LanguageManager.java
@@ -8,16 +8,25 @@ import java.util.Objects;
import static com.MylesAndMore.Tumble.Main.plugin;
+/**
+ * Manages language.yml and allows retrieval of keys
+ */
public class LanguageManager {
private final CustomConfig languageYml = new CustomConfig("language.yml");
private final Configuration config = languageYml.getConfig();
private final Configuration defaultConfig = Objects.requireNonNull(config.getDefaults());
+ /**
+ * Creates a new LanguageManager
+ */
public LanguageManager() {
languageYml.saveDefaultConfig();
validate();
}
+ /**
+ * Check keys of language.yml against the defaults
+ */
public void validate() {
boolean invalid = false;
for (String key : defaultConfig.getKeys(true)) {
@@ -31,10 +40,22 @@ public class LanguageManager {
}
}
+ /**
+ * Gets a key from language.yml and prepends the prefix.
+ * If it is not present, a default value will be returned
+ * @param key The key representing the message
+ * @return The message from the key
+ */
public String fromKey(String key) {
return fromKeyNoPrefix("prefix") + fromKeyNoPrefix(key);
}
+ /**
+ * Gets a key from language.yml.
+ * If it is not present, a default value will be returned
+ * @param key The key representing the message
+ * @return The message from the key
+ */
public String fromKeyNoPrefix(String key) {
String val = config.getString(key);
diff --git a/src/main/java/com/MylesAndMore/Tumble/game/EventListener.java b/src/main/java/com/MylesAndMore/Tumble/game/EventListener.java
index 7abf774..1bc31b3 100644
--- a/src/main/java/com/MylesAndMore/Tumble/game/EventListener.java
+++ b/src/main/java/com/MylesAndMore/Tumble/game/EventListener.java
@@ -24,12 +24,16 @@ import static com.MylesAndMore.Tumble.Main.configManager;
import static com.MylesAndMore.Tumble.Main.plugin;
/**
- * Tumble event listener for all plugin and game-related events.
+ * An event listener for a game of tumble.
*/
public class EventListener implements Listener {
-
World gameWorld;
Game game;
+
+ /**
+ * Create a new EventListener
+ * @param game The game that the EventListener belongs to.
+ */
public EventListener(Game game) {
this.game = game;
this.gameWorld = game.arena.gameSpawn.getWorld();
diff --git a/src/main/java/com/MylesAndMore/Tumble/game/Game.java b/src/main/java/com/MylesAndMore/Tumble/game/Game.java
index 20d8869..169ce33 100644
--- a/src/main/java/com/MylesAndMore/Tumble/game/Game.java
+++ b/src/main/java/com/MylesAndMore/Tumble/game/Game.java
@@ -35,15 +35,41 @@ public class Game {
private List<Player> playersAlive;
private EventListener eventListener;
+ /**
+ * Create a new Game
+ * @param arena The arena the game is taking place in
+ * @param type The game type
+ */
public Game(@NotNull Arena arena, @NotNull GameType type) {
this.arena = arena;
this.type = type;
this.gameSpawn = arena.gameSpawn;
+ }
+
+ /**
+ * Adds a player to the wait area. Called from /tmbl join
+ * Precondition: the game is in state WAITING
+ * @param p Player to add
+ */
+ public void addPlayer(Player p) {
+ gamePlayers.add(p);
+ if (arena.waitArea != null) {
+ inventories.put(p,p.getInventory().getContents());
+ p.teleport(arena.waitArea);
+ p.getInventory().clear();
+ }
+ if (gamePlayers.size() >= 2 && gameState == GameState.WAITING) {
+ autoStart();
+ }
+ else {
+ displayActionbar(Collections.singletonList(p), languageManager.fromKeyNoPrefix("waiting-for-players"));
+ }
}
/**
- * Creates a new Game
+ * Starts the game
+ * Called from /tmbl forceStart or after the wait counter finishes
*/
public void gameStart() {
@@ -52,12 +78,15 @@ public class Game {
return;
}
+ // cancel wait timer
Bukkit.getServer().getScheduler().cancelTask(autoStartID);
autoStartID = -1;
+ // register event listener
eventListener = new EventListener(this);
Bukkit.getServer().getPluginManager().registerEvents(eventListener, plugin);
+ // save inventories (if not already done)
for (Player p : gamePlayers) {
if (!inventories.containsKey(p)) {
inventories.put(p, p.getInventory().getContents());
@@ -68,20 +97,26 @@ public class Game {
}
/**
- * Starts a new round
+ * Starts a round
*/
private void roundStart() {
gameState = GameState.STARTING;
playersAlive = new ArrayList<>(gamePlayers);
+
scatterPlayers(gamePlayers);
// Put all players in spectator to prevent them from getting kicked for flying
setGamemode(gamePlayers, GameMode.SPECTATOR);
- Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> setGamemode(gamePlayers, GameMode.SPECTATOR), 10);
+ // do it again in case they were not in the world yet
+ Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
+ setGamemode(gamePlayers, GameMode.SPECTATOR);
+ }, 10);
+
clearInventories(gamePlayers);
clearArena();
prepareGameType(type);
+
+ // Begin the countdown sequence
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
- // Begin the countdown sequence
countdown(() -> {
setGamemode(gamePlayers, GameMode.SURVIVAL);
gameState = GameState.RUNNING;
@@ -91,42 +126,45 @@ public class Game {
/**
* Type specific setup: Generating layers and giving items
- * @param type can be either "shovels", "snowballs", or "mixed"
+ * @param type game type,
*/
private void prepareGameType(GameType type) {
- roundType = type; // note: may need deepcopy this for it to work properly
- if (roundType.equals(GameType.MIXED)) {
- // Randomly select either shovels or snowballs and re-run the method
- Random random = new Random();
- switch (random.nextInt(2)) {
- case 0 -> roundType = GameType.SHOVELS;
- case 1 -> roundType = GameType.SNOWBALLS;
- }
- }
-
- switch (roundType) {
+ roundType = type;
+ switch (type) {
case SHOVELS -> {
Generator.generateLayersShovels(gameSpawn.clone());
+
ItemStack shovel = new ItemStack(Material.IRON_SHOVEL);
shovel.addEnchantment(Enchantment.SILK_TOUCH, 1);
giveItems(gamePlayers, shovel);
- // Schedule a process to give snowballs after 2m30s (so people can't island, the OG game had this); add 160t because of the countdown
+
+ // Schedule a process to give snowballs after 2m30s (so people can't island, the OG game had this);
+ // add 160t because of the countdown
gameID = Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
clearInventories(gamePlayers);
giveItems(gamePlayers, new ItemStack(Material.SNOWBALL));
displayActionbar(gamePlayers, languageManager.fromKeyNoPrefix("showdown"));
playSound(gamePlayers, Sound.ENTITY_ELDER_GUARDIAN_CURSE, SoundCategory.HOSTILE, 1, 1);
+
// End the round in another 2m30s
gameID = Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, this::roundEnd, 3000);
}, 3160);
}
case SNOWBALLS -> {
Generator.generateLayersSnowballs(gameSpawn.clone());
+
giveItems(gamePlayers, new ItemStack(Material.SNOWBALL));
// End the round in 5m
gameID = Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, this::roundEnd, 6160);
}
+ case MIXED -> {
+ Random random = new Random();
+ switch (random.nextInt(2)) {
+ case 0 -> prepareGameType(GameType.SHOVELS);
+ case 1 -> prepareGameType(GameType.SNOWBALLS);
+ }
+ }
}
}
@@ -138,8 +176,9 @@ public class Game {
gameState = GameState.ENDING;
Bukkit.getServer().getScheduler().cancelTask(gameID);
gameID = -1;
- // Clear old layers (as a fill command, this would be /fill ~-20 ~-20 ~-20 ~20 ~ ~20 relative to spawn)
+
playSound(gamePlayers, Sound.BLOCK_NOTE_BLOCK_PLING, SoundCategory.BLOCKS, 5, 0);
+
// Check if there was a definite winner or not
if (!playersAlive.isEmpty()) {
Player winner = playersAlive.get(0);
@@ -148,11 +187,11 @@ public class Game {
gameWins.put(winner, 0);
}
gameWins.put(winner, gameWins.get(winner)+1);
+
if (gameWins.get(winner) == 3) {
gameEnd();
}
- // If that player doesn't have three wins, nobody else does, so we need another round
- else {
+ else { // If that player doesn't have three wins, nobody else does, so we need another round
displayTitles(gamePlayers, languageManager.fromKeyNoPrefix("round-over"), languageManager.fromKeyNoPrefix("round-winner").replace("%winner%", winner.getDisplayName()), 5, 60, 5);
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, this::roundStart, 100);
}
@@ -173,33 +212,35 @@ public class Game {
setGamemode(gamePlayers, GameMode.SPECTATOR);
clearInventories(gamePlayers);
+ // display winner
Player winner = getPlayerWithMostWins(gameWins);
if (winner != null) {
displayTitles(gamePlayers, languageManager.fromKeyNoPrefix("game-over"), languageManager.fromKeyNoPrefix("game-winner").replace("%winner%",winner.getDisplayName()), 5, 60, 5);
}
+
displayActionbar(gamePlayers, languageManager.fromKeyNoPrefix("lobby-in-10"));
// Wait 10s (200t), then
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
-
clearArena();
+ // teleport player back and restore inventory
for (Player p : gamePlayers) {
- // Restore inventories
- if (inventories.containsKey(p)) {
- p.getInventory().setContents(inventories.get(p));
- }
-
if (p == winner && arena.winnerLobby != null) {
p.teleport(arena.winnerLobby);
}
else {
p.teleport(Objects.requireNonNull(arena.lobby));
}
+
+ if (inventories.containsKey(p)) {
+ p.getInventory().setContents(inventories.get(p));
+ }
}
}, 200);
}
+
Bukkit.getServer().getScheduler().cancelTask(gameID);
gameID = -1;
Bukkit.getServer().getScheduler().cancelTask(autoStartID);
@@ -208,8 +249,13 @@ public class Game {
arena.game = null;
}
+ /**
+ * Stops the game, usually while it is still going
+ * called if too many players leave, or from /tmbl forceStop
+ */
public void stopGame() {
gamePlayers.forEach(this::removePlayer);
+
Bukkit.getServer().getScheduler().cancelTask(gameID);
gameID = -1;
Bukkit.getServer().getScheduler().cancelTask(autoStartID);
@@ -224,48 +270,37 @@ public class Game {
* @param p Player to remove
*/
public void removePlayer(Player p) {
+ gamePlayers.remove(p);
+ // check if the game has not started yet
if (gameState == GameState.WAITING) {
- gamePlayers.remove(p);
+
+ // inform player that there are no longer enough players to start
if (gamePlayers.size() < 2) {
displayActionbar(gamePlayers, languageManager.fromKeyNoPrefix("waiting-for-players"));
}
+ // teleport player back and restore inventory
if (arena.waitArea != null) {
+ p.getInventory().clear();
p.teleport(arena.lobby);
+ if (inventories.containsKey(p)) {
+ p.getInventory().setContents(inventories.get(p));
+ }
}
}
else {
- gamePlayers.remove(p);
+ // stop the game if there are not enough players
if (gamePlayers.size() < 2) {
- gameEnd();
+ stopGame();
}
+
+ // teleport player back and restore inventory
p.getInventory().clear();
+ p.teleport(arena.lobby);
if (inventories.containsKey(p)) {
p.getInventory().setContents(inventories.get(p));
}
- p.teleport(arena.lobby);
- }
- }
-
- /**
- * Adds a player to the wait area. Called from /tumble-join
- * Precondition: the game is in state WAITING
- * @param p Player to add
- */
- public void addPlayer(Player p) {
- gamePlayers.add(p);
- // save inventory
- if (arena.waitArea != null) {
- inventories.put(p,p.getInventory().getContents());
- p.teleport(arena.waitArea);
- p.getInventory().clear();
- }
- if (gamePlayers.size() >= 2 && gameState == GameState.WAITING) {
- autoStart();
- }
- else {
- displayActionbar(Collections.singletonList(p), languageManager.fromKeyNoPrefix("waiting-for-players"));
}
}
diff --git a/src/main/java/com/MylesAndMore/Tumble/plugin/CustomConfig.java b/src/main/java/com/MylesAndMore/Tumble/plugin/CustomConfig.java
index b77a59d..8ef4638 100644
--- a/src/main/java/com/MylesAndMore/Tumble/plugin/CustomConfig.java
+++ b/src/main/java/com/MylesAndMore/Tumble/plugin/CustomConfig.java
@@ -12,11 +12,19 @@ import java.util.logging.Level;
import static com.MylesAndMore.Tumble.Main.plugin;
+/**
+ * Allows additional configs to be created with the same saving methods as the default config
+ * Most code is copied from {@link org.bukkit.plugin.java.JavaPlugin}
+ */
public class CustomConfig {
private FileConfiguration newConfig = null;
private final File configFile;
private final String fileName;
+ /**
+ * Create a new CustomConfig
+ * @param fileName Name of the YAML file to create
+ */
public CustomConfig(String fileName) {
this.fileName = fileName;
this.configFile = new File(plugin.getDataFolder(), fileName);
diff --git a/src/main/java/com/MylesAndMore/Tumble/plugin/SubCommand.java b/src/main/java/com/MylesAndMore/Tumble/plugin/SubCommand.java
index 2158584..cc09527 100644
--- a/src/main/java/com/MylesAndMore/Tumble/plugin/SubCommand.java
+++ b/src/main/java/com/MylesAndMore/Tumble/plugin/SubCommand.java
@@ -2,7 +2,11 @@ package com.MylesAndMore.Tumble.plugin;
import org.bukkit.command.CommandExecutor;
+/**
+ * Requires that subCommands have a commandName and permission getter.
+ * This allows the permission and commandName to be checked from the base command.
+ */
public interface SubCommand extends CommandExecutor {
- public String getCommandName();
- public String getPermission();
+ String getCommandName();
+ String getPermission();
}