441 lines
19 KiB
Java
441 lines
19 KiB
Java
package games.dmg.spigotbase;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Optional;
|
|
import java.util.TreeSet;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.ChatColor;
|
|
import org.bukkit.command.Command;
|
|
import org.bukkit.command.CommandExecutor;
|
|
import org.bukkit.command.CommandSender;
|
|
import org.bukkit.command.TabCompleter;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.plugin.java.JavaPlugin;
|
|
|
|
final class BaseAdminCommand implements CommandExecutor, TabCompleter {
|
|
private static final List<String> SUBCOMMANDS = List.of(
|
|
"status", "setlevel", "setprogress", "setsetting", "clearcooldown", "reset", "config"
|
|
);
|
|
private static final List<String> PATHS = List.of(
|
|
"base", "size", "flight", "warmup", "cooldown"
|
|
);
|
|
private static final List<String> COUNTERS = List.of(
|
|
"grass_dirt", "stone", "deepslate", "obsidian", "placements", "total_placements",
|
|
"base_breaks"
|
|
);
|
|
private static final List<String> SETTINGS = List.of("spawnable");
|
|
private static final List<String> ENABLE_MODES = List.of("enable", "disable");
|
|
private static final List<String> COOLDOWN_TYPES = List.of("personal", "visitor", "all");
|
|
private static final List<String> RESET_PATHS = List.of(
|
|
"size", "flight", "warmup", "cooldown", "all"
|
|
);
|
|
private static final List<String> LEVELS_THREE = List.of("0", "1", "2", "3");
|
|
private static final List<String> LEVELS_FOUR = List.of("0", "1", "2", "3", "4");
|
|
private final JavaPlugin plugin;
|
|
private final BaseStateManager stateManager;
|
|
private final AdminProgressionService progressionService;
|
|
private final PluginSettingsProvider settingsProvider;
|
|
|
|
BaseAdminCommand(
|
|
JavaPlugin plugin,
|
|
BaseStateManager stateManager,
|
|
AdminProgressionService progressionService,
|
|
PluginSettingsProvider settingsProvider
|
|
) {
|
|
this.plugin = plugin;
|
|
this.stateManager = stateManager;
|
|
this.progressionService = progressionService;
|
|
this.settingsProvider = settingsProvider;
|
|
}
|
|
|
|
@Override
|
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] arguments) {
|
|
if (!sender.hasPermission("spigotbase.admin")) {
|
|
sender.sendMessage(ChatColor.RED + "You do not have permission to administer Spigot Base.");
|
|
return true;
|
|
}
|
|
if (arguments.length > 0 && arguments[0].equalsIgnoreCase("config")) {
|
|
return updateConfig(sender, arguments);
|
|
}
|
|
if (arguments.length < 2) {
|
|
sendUsage(sender);
|
|
return true;
|
|
}
|
|
Optional<PlayerState> target = resolve(arguments[1]);
|
|
if (target.isEmpty()) {
|
|
sender.sendMessage(ChatColor.RED + "That player is not online or previously known.");
|
|
return true;
|
|
}
|
|
return switch (arguments[0].toLowerCase(Locale.ROOT)) {
|
|
case "status" -> showProgress(sender, target.orElseThrow(), arguments);
|
|
case "setlevel" -> setLevel(sender, target.orElseThrow(), arguments);
|
|
case "setprogress" -> setProgress(sender, target.orElseThrow(), arguments);
|
|
case "setsetting" -> setSetting(sender, target.orElseThrow(), arguments);
|
|
case "clearcooldown" -> clearCooldown(sender, target.orElseThrow(), arguments);
|
|
case "reset" -> reset(sender, target.orElseThrow(), arguments);
|
|
default -> {
|
|
sendUsage(sender);
|
|
yield true;
|
|
}
|
|
};
|
|
}
|
|
|
|
private boolean showProgress(
|
|
CommandSender sender,
|
|
PlayerState player,
|
|
String[] arguments
|
|
) {
|
|
if (arguments.length != 2) {
|
|
sender.sendMessage(ChatColor.RED + "Usage: /baseadmin status <player>");
|
|
return true;
|
|
}
|
|
sender.sendMessage(ChatColor.GOLD + "=== " + player.latestName() + " Base Progress ===");
|
|
sender.sendMessage(ChatColor.YELLOW + "Levels: base=" + player.baseLevel()
|
|
+ " size=" + player.sizeLevel() + " flight=" + player.flightLevel()
|
|
+ " warmup=" + player.warmupLevel() + " cooldown=" + player.cooldownLevel());
|
|
sender.sendMessage(ChatColor.GRAY + "Grass/dirt=" + player.grassAndDirtBroken()
|
|
+ " stone=" + player.stoneBroken() + " deepslate=" + player.deepslateBroken()
|
|
+ " obsidian=" + player.obsidianBroken());
|
|
int spawnableThreshold = settingsProvider.current()
|
|
.spawnableOverlayUnlockPlacements();
|
|
sender.sendMessage(ChatColor.GRAY + "In-base placements=" + player.blocksPlacedInBase()
|
|
+ " total placements=" + player.totalBlocksPlaced()
|
|
+ " breaks=" + player.blocksBrokenInBase());
|
|
sender.sendMessage(ChatColor.GRAY + "Spawnable overlay="
|
|
+ (player.totalBlocksPlaced() >= spawnableThreshold ? "unlocked" : "locked")
|
|
+ " (" + player.totalBlocksPlaced() + "/" + spawnableThreshold + ")");
|
|
sender.sendMessage(ChatColor.GRAY + "Toggles: navigation=" + player.navigationEnabled()
|
|
+ " flight=" + player.flightEnabled() + " border=" + player.borderEnabled()
|
|
+ " spawnable=" + player.spawnableOverlayEnabled() + " bossbar=" + player.bossBarEnabled() + " visitors=" + player.visitorsEnabled());
|
|
sender.sendMessage(ChatColor.GRAY + "Base: " + player.base()
|
|
.map(base -> base.worldName() + " " + base.x() + "," + base.y() + "," + base.z())
|
|
.orElse("not set"));
|
|
sender.sendMessage(ChatColor.GRAY + "Visitor cooldowns=" + player.visitorCooldownUntil().size());
|
|
return true;
|
|
}
|
|
|
|
private boolean setLevel(
|
|
CommandSender sender,
|
|
PlayerState target,
|
|
String[] arguments
|
|
) {
|
|
if (arguments.length != 4) {
|
|
sender.sendMessage(ChatColor.RED + "Usage: /baseadmin setlevel <player> <path> <level>");
|
|
return true;
|
|
}
|
|
final ProgressionPath path;
|
|
try {
|
|
path = ProgressionPath.valueOf(arguments[2].toUpperCase(Locale.ROOT));
|
|
} catch (IllegalArgumentException exception) {
|
|
sender.sendMessage(ChatColor.RED
|
|
+ "Usage: /baseadmin setlevel <player> "
|
|
+ "<base|size|flight|warmup|cooldown> <level>");
|
|
return true;
|
|
}
|
|
try {
|
|
int level = Integer.parseInt(arguments[3]);
|
|
PlayerState updated = stateManager.update(
|
|
target.playerId(),
|
|
target.latestName(),
|
|
current -> progressionService.setLevel(current, path, level)
|
|
);
|
|
stateManager.saveIfDirty();
|
|
sender.sendMessage(ChatColor.GREEN + "Set " + updated.latestName() + "'s "
|
|
+ path.name().toLowerCase(Locale.ROOT) + " level to " + level + ".");
|
|
} catch (NumberFormatException exception) {
|
|
sender.sendMessage(ChatColor.RED + "The level must be an integer.");
|
|
} catch (IllegalArgumentException exception) {
|
|
sender.sendMessage(ChatColor.RED + exception.getMessage());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private boolean setProgress(
|
|
CommandSender sender,
|
|
PlayerState target,
|
|
String[] arguments
|
|
) {
|
|
if (arguments.length != 4) {
|
|
sender.sendMessage(ChatColor.RED
|
|
+ "Usage: /baseadmin setprogress <player> <counter> <amount>");
|
|
return true;
|
|
}
|
|
final ProgressCounter counter;
|
|
try {
|
|
counter = ProgressCounter.valueOf(
|
|
arguments[2].toUpperCase(Locale.ROOT).replace('-', '_')
|
|
);
|
|
} catch (IllegalArgumentException exception) {
|
|
sender.sendMessage(ChatColor.RED
|
|
+ "Usage: /baseadmin setprogress <player> "
|
|
+ "<grass_dirt|stone|deepslate|obsidian|placements|total_placements|base_breaks> "
|
|
+ "<amount>");
|
|
return true;
|
|
}
|
|
try {
|
|
long amount = Long.parseLong(arguments[3]);
|
|
PlayerState updated = stateManager.update(
|
|
target.playerId(),
|
|
target.latestName(),
|
|
current -> progressionService.setProgress(current, counter, amount)
|
|
);
|
|
stateManager.saveIfDirty();
|
|
sender.sendMessage(ChatColor.GREEN + "Set " + updated.latestName() + "'s "
|
|
+ counter.name().toLowerCase(Locale.ROOT) + " progress to " + amount + ".");
|
|
} catch (NumberFormatException exception) {
|
|
sender.sendMessage(ChatColor.RED + "The progress amount must be an integer.");
|
|
} catch (IllegalArgumentException exception) {
|
|
sender.sendMessage(ChatColor.RED + exception.getMessage());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private boolean setSetting(
|
|
CommandSender sender,
|
|
PlayerState target,
|
|
String[] arguments
|
|
) {
|
|
if (arguments.length != 4
|
|
|| !arguments[2].equalsIgnoreCase("spawnable")
|
|
|| !ENABLE_MODES.contains(arguments[3].toLowerCase(Locale.ROOT))) {
|
|
sender.sendMessage(ChatColor.RED + "Usage: /baseadmin setsetting <player> "
|
|
+ "spawnable <enable|disable>");
|
|
return true;
|
|
}
|
|
boolean enabled = arguments[3].equalsIgnoreCase("enable");
|
|
if (enabled && target.totalBlocksPlaced()
|
|
< settingsProvider.current().spawnableOverlayUnlockPlacements()) {
|
|
sender.sendMessage(ChatColor.RED + "That player has not unlocked the spawnable overlay.");
|
|
return true;
|
|
}
|
|
if (enabled && (target.baseLevel() < 1 || target.base().isEmpty())) {
|
|
sender.sendMessage(ChatColor.RED + "That player does not have an established base.");
|
|
return true;
|
|
}
|
|
PlayerState updated = stateManager.update(
|
|
target.playerId(),
|
|
target.latestName(),
|
|
current -> current.withSpawnableOverlayEnabled(enabled)
|
|
);
|
|
stateManager.saveIfDirty();
|
|
sender.sendMessage(ChatColor.GREEN + "Set " + updated.latestName()
|
|
+ "'s spawnable overlay to " + (enabled ? "enabled" : "disabled") + ".");
|
|
return true;
|
|
}
|
|
|
|
private boolean clearCooldown(
|
|
CommandSender sender,
|
|
PlayerState target,
|
|
String[] arguments
|
|
) {
|
|
if (arguments.length < 2 || arguments.length > 3) {
|
|
sender.sendMessage(ChatColor.RED
|
|
+ "Usage: /baseadmin clearcooldown <player> [personal|visitor|all]");
|
|
return true;
|
|
}
|
|
String selection = arguments.length == 3
|
|
? arguments[2].toLowerCase(Locale.ROOT)
|
|
: "all";
|
|
if (!Arrays.asList("personal", "visitor", "all").contains(selection)) {
|
|
sender.sendMessage(ChatColor.RED
|
|
+ "Usage: /baseadmin clearcooldown <player> [personal|visitor|all]");
|
|
return true;
|
|
}
|
|
stateManager.update(target.playerId(), target.latestName(), current -> {
|
|
PlayerState updated = current;
|
|
if (selection.equals("personal") || selection.equals("all")) {
|
|
updated = updated.withoutPersonalCooldown();
|
|
}
|
|
if (selection.equals("visitor") || selection.equals("all")) {
|
|
updated = updated.withoutVisitorCooldowns();
|
|
}
|
|
return updated;
|
|
});
|
|
stateManager.saveIfDirty();
|
|
sender.sendMessage(ChatColor.GREEN + "Cleared " + selection + " cooldowns for "
|
|
+ target.latestName() + ".");
|
|
return true;
|
|
}
|
|
|
|
private boolean reset(CommandSender sender, PlayerState target, String[] arguments) {
|
|
if (arguments.length < 3) {
|
|
sender.sendMessage(ChatColor.RED + "Usage: /baseadmin reset <player> <path|all> [confirm]");
|
|
return true;
|
|
}
|
|
if (arguments[2].equalsIgnoreCase("all")) {
|
|
if (arguments.length != 4 || !arguments[3].equalsIgnoreCase("confirm")) {
|
|
sender.sendMessage(ChatColor.RED + "Repeat with: /baseadmin reset "
|
|
+ target.latestName() + " all confirm");
|
|
return true;
|
|
}
|
|
stateManager.update(
|
|
target.playerId(),
|
|
target.latestName(),
|
|
ignored -> PlayerState.newPlayer(target.playerId(), target.latestName())
|
|
);
|
|
stateManager.saveIfDirty();
|
|
sender.sendMessage(ChatColor.GREEN + "Reset all progression for " + target.latestName() + ".");
|
|
return true;
|
|
}
|
|
if (arguments.length != 3) {
|
|
sender.sendMessage(ChatColor.RED
|
|
+ "Usage: /baseadmin reset <player> <size|flight|warmup|cooldown|all> [confirm]");
|
|
return true;
|
|
}
|
|
if (arguments[2].equalsIgnoreCase("base")) {
|
|
sender.sendMessage(ChatColor.RED
|
|
+ "Resetting base removes all progression. Use: /baseadmin reset "
|
|
+ target.latestName() + " all confirm");
|
|
return true;
|
|
}
|
|
final ProgressionPath path;
|
|
try {
|
|
path = ProgressionPath.valueOf(arguments[2].toUpperCase(Locale.ROOT));
|
|
} catch (IllegalArgumentException exception) {
|
|
sender.sendMessage(ChatColor.RED
|
|
+ "Usage: /baseadmin reset <player> <size|flight|warmup|cooldown|all> [confirm]");
|
|
return true;
|
|
}
|
|
try {
|
|
stateManager.update(
|
|
target.playerId(),
|
|
target.latestName(),
|
|
current -> progressionService.resetPath(current, path)
|
|
);
|
|
stateManager.saveIfDirty();
|
|
sender.sendMessage(ChatColor.GREEN + "Reset " + path.name().toLowerCase(Locale.ROOT)
|
|
+ " progression for " + target.latestName() + ".");
|
|
} catch (IllegalArgumentException exception) {
|
|
sender.sendMessage(ChatColor.RED + exception.getMessage());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private boolean updateConfig(CommandSender sender, String[] arguments) {
|
|
if (arguments.length != 3) {
|
|
sender.sendMessage(ChatColor.RED + "Usage: /baseadmin config <key> <integer>");
|
|
return true;
|
|
}
|
|
String key = arguments[1];
|
|
Object previous = plugin.getConfig().get(key);
|
|
if (!(previous instanceof Number)) {
|
|
sender.sendMessage(ChatColor.RED + "Unknown numeric setting: " + key);
|
|
return true;
|
|
}
|
|
try {
|
|
long value = Long.parseLong(arguments[2]);
|
|
java.util.Map<String, Object> proposed =
|
|
new java.util.HashMap<>(plugin.getConfig().getValues(false));
|
|
proposed.put(key, value);
|
|
PluginSettings updated = PluginSettingsValidator.validateMaterials(
|
|
PluginSettings.from(proposed)
|
|
);
|
|
plugin.getConfig().set(key, value);
|
|
try {
|
|
plugin.saveConfig();
|
|
} catch (RuntimeException exception) {
|
|
plugin.getConfig().set(key, previous);
|
|
throw exception;
|
|
}
|
|
settingsProvider.update(updated);
|
|
sender.sendMessage(ChatColor.GREEN + "Updated " + key + " to " + value
|
|
+ "; the change is active immediately.");
|
|
} catch (NumberFormatException exception) {
|
|
sender.sendMessage(ChatColor.RED + "The setting value must be an integer.");
|
|
} catch (IllegalArgumentException exception) {
|
|
sender.sendMessage(ChatColor.RED + exception.getMessage());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public List<String> onTabComplete(
|
|
CommandSender sender,
|
|
Command command,
|
|
String alias,
|
|
String[] arguments
|
|
) {
|
|
if (!sender.hasPermission("spigotbase.admin")) {
|
|
return List.of();
|
|
}
|
|
if (arguments.length == 1) {
|
|
String prefix = arguments[0].toLowerCase(Locale.ROOT);
|
|
return SUBCOMMANDS.stream()
|
|
.filter(subcommand -> subcommand.startsWith(prefix))
|
|
.toList();
|
|
}
|
|
if (arguments.length == 2 && arguments[0].equalsIgnoreCase("config")) {
|
|
String prefix = arguments[1].toLowerCase(Locale.ROOT);
|
|
return plugin.getConfig().getValues(false).entrySet().stream()
|
|
.filter(entry -> entry.getValue() instanceof Number)
|
|
.map(java.util.Map.Entry::getKey)
|
|
.filter(key -> key.toLowerCase(Locale.ROOT).startsWith(prefix))
|
|
.sorted(String.CASE_INSENSITIVE_ORDER)
|
|
.toList();
|
|
}
|
|
if (arguments.length == 2) {
|
|
String prefix = arguments[1].toLowerCase(Locale.ROOT);
|
|
TreeSet<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
|
stateManager.knownPlayers().values().stream()
|
|
.map(PlayerState::latestName)
|
|
.forEach(names::add);
|
|
if (plugin.getServer() != null) {
|
|
plugin.getServer().getOnlinePlayers().stream()
|
|
.map(Player::getName)
|
|
.forEach(names::add);
|
|
}
|
|
return names.stream()
|
|
.filter(name -> name.toLowerCase(Locale.ROOT).startsWith(prefix))
|
|
.toList();
|
|
}
|
|
if (arguments.length == 3) {
|
|
List<String> options = switch (arguments[0].toLowerCase(Locale.ROOT)) {
|
|
case "setlevel" -> PATHS;
|
|
case "setprogress" -> COUNTERS;
|
|
case "setsetting" -> SETTINGS;
|
|
case "clearcooldown" -> COOLDOWN_TYPES;
|
|
case "reset" -> RESET_PATHS;
|
|
default -> List.of();
|
|
};
|
|
return complete(options, arguments[2]);
|
|
}
|
|
if (arguments.length == 4 && arguments[0].equalsIgnoreCase("setsetting")) {
|
|
return complete(ENABLE_MODES, arguments[3]);
|
|
}
|
|
if (arguments.length == 4 && arguments[0].equalsIgnoreCase("setlevel")) {
|
|
List<String> levels = switch (arguments[2].toLowerCase(Locale.ROOT)) {
|
|
case "base", "cooldown" -> LEVELS_FOUR;
|
|
case "size", "flight", "warmup" -> LEVELS_THREE;
|
|
default -> List.of();
|
|
};
|
|
return complete(levels, arguments[3]);
|
|
}
|
|
if (arguments.length == 4 && arguments[0].equalsIgnoreCase("reset")
|
|
&& arguments[2].equalsIgnoreCase("all")) {
|
|
return complete(List.of("confirm"), arguments[3]);
|
|
}
|
|
return List.of();
|
|
}
|
|
|
|
private static List<String> complete(List<String> options, String input) {
|
|
String prefix = input.toLowerCase(Locale.ROOT);
|
|
return options.stream().filter(option -> option.startsWith(prefix)).toList();
|
|
}
|
|
|
|
private Optional<PlayerState> resolve(String name) {
|
|
Player online = Bukkit.getPlayerExact(name);
|
|
if (online != null) {
|
|
return Optional.of(stateManager.player(online.getUniqueId(), online.getName()));
|
|
}
|
|
return stateManager.findByName(name);
|
|
}
|
|
|
|
private static void sendUsage(CommandSender sender) {
|
|
sender.sendMessage(ChatColor.YELLOW + "Usage: /baseadmin "
|
|
+ "<status|setlevel|setprogress|setsetting|clearcooldown|reset> <player> ...");
|
|
sender.sendMessage(ChatColor.YELLOW + " /baseadmin config <key> <integer>");
|
|
}
|
|
}
|