onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command,
- @NotNull String s, @NotNull String[] strings) {
+ @NotNull String s, @NotNull String[] arguments) {
+ if (plugins == null) {
+ loadAvailablePermissions();
+ }
+
+ if (arguments.length == 1) {
+ return List.of("simple", "advanced");
+ } else if (arguments.length == 2) {
+ if (arguments[0].equalsIgnoreCase("simple")) {
+ return List.of("");
+ } else {
+ return List.of("add", "set", "clear");
+ }
+ } else if (arguments.length == 3) {
+ if (arguments[0].equalsIgnoreCase("simple")) {
+ return List.of("");
+ } else {
+ return List.of("economy", "item", "exp", "permission");
+ }
+ } else if (arguments.length == 4) {
+ CostType costType = CostType.parse(arguments[2]);
+ if (costType == null) {
+ return null;
+ }
+ return switch (costType) {
+ case PERMISSION -> tabCompletePermission(arguments[3]);
+ case ECONOMY -> List.of("0.5", "1", "10");
+ case EXP -> List.of("1, 5, 10");
+ case ITEM -> List.of();
+ };
+ }
return List.of();
}
+ /**
+ * Modifies the cost of an action
+ *
+ * @param arguments The arguments given by a player
+ * @param oldCost The previous cost of the action
+ * @param player The player attempting to alter the action cost
+ * @return The modified action cost, or null if something went wrong
+ */
+ private ActionCost modifyActionCost(@NotNull String[] arguments, @NotNull ActionCost oldCost, @NotNull Player player) {
+ CostType costType;
+ if (arguments.length > 2) {
+ costType = CostType.parse(arguments[2]);
+ } else {
+ costType = null;
+ }
+
+ switch (arguments[1].toLowerCase()) {
+ case "add":
+ if (costType == null) {
+ player.sendMessage("Invalid cost type specified!");
+ return null;
+ }
+ return parseCost(costType, Arrays.copyOfRange(arguments, 2, arguments.length), oldCost, player);
+ case "clear":
+ // Clears one or all fields of a cost
+ if (costType == null) {
+ return new ActionCost(0, 0, null, Set.of());
+ } else {
+ return switch (costType) {
+ case EXP -> oldCost.changeExpCost(0);
+ case ITEM -> oldCost.changeItemCost(null);
+ case ECONOMY -> oldCost.changeMonetaryCost(0);
+ case PERMISSION -> oldCost.changePermissionCost(Set.of());
+ };
+ }
+ case "set":
+ if (costType == null) {
+ player.sendMessage("Invalid cost type specified!");
+ return null;
+ }
+ return parseCost(costType, Arrays.copyOfRange(arguments, 2, arguments.length),
+ new ActionCost(0, 0, null, Set.of()), player);
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Parses an advanced action cost
+ *
+ * @param costType The type of cost to parse
+ * @param arguments The arguments given by the player
+ * @param oldCost The old cost to modify
+ * @param player The player changing the value
+ * @return The new action cost, or null if the process failed.
+ */
+ @Nullable
+ private ActionCost parseCost(@NotNull CostType costType, @NotNull String[] arguments, @NotNull ActionCost oldCost,
+ @NotNull Player player) {
+ switch (costType) {
+ case ECONOMY:
+ double economyCost = Double.parseDouble(arguments[0]);
+ if (economyCost < 0) {
+ player.sendMessage("Cost cannot be negative!");
+ return null;
+ }
+ return oldCost.changeMonetaryCost(economyCost);
+ case ITEM:
+ ItemStack itemCost = player.getInventory().getItemInMainHand();
+ if (itemCost.getType().isAir()) {
+ player.sendMessage("You have no item in your main hand");
+ return null;
+ }
+ return oldCost.changeItemCost(itemCost);
+ case PERMISSION:
+ Set permissionSet = new HashSet<>(Arrays.asList(arguments[0].split(",")));
+ return oldCost.changePermissionCost(permissionSet);
+ case EXP:
+ int expCost = Integer.parseInt(arguments[0]);
+ if (expCost < 0) {
+ player.sendMessage("Exp cost cannot be negative!");
+ return null;
+ }
+ return oldCost.changeExpCost(expCost);
+ default:
+ return null;
+ }
+ }
+
/**
* Parses a simple double cost
*
@@ -83,4 +211,103 @@ public class CostCommand implements TabExecutor {
}
}
+ /**
+ * Gets the tab complete value for the permission typed
+ *
+ * @param typedNode The full permission node typed by the player
+ * @return All known valid auto-complete options
+ */
+ private @NotNull List tabCompletePermission(@NotNull String typedNode) {
+ StringBuilder permissionListString = new StringBuilder();
+ if (typedNode.contains(",")) {
+ String[] permissionList = typedNode.split(",");
+ for (int i = 0; i < permissionList.length - 1; i++) {
+ permissionListString.append(permissionList[i]);
+ permissionListString.append(",");
+ }
+ typedNode = permissionList[permissionList.length - 1];
+ }
+ String permissionPrefix = permissionListString.toString();
+ List output;
+ if (typedNode.contains(".")) {
+ List matchingPermissions = permissions.get(typedNode.substring(0, typedNode.lastIndexOf(".")));
+ if (matchingPermissions == null) {
+ if (permissionPrefix.isEmpty()) {
+ output = new ArrayList<>();
+ } else {
+ List onlyPrefix = new ArrayList<>();
+ onlyPrefix.add(permissionPrefix);
+ output = onlyPrefix;
+ }
+ } else {
+ //Filter by the typed text
+ output = filterMatching(matchingPermissions, typedNode);
+ }
+ } else {
+ output = plugins;
+ }
+
+ //Add previous permissions in the comma-separated lists as a prefix
+ if (permissionPrefix.isEmpty()) {
+ return output;
+ } else {
+ List prefixed = new ArrayList<>(output.size());
+ for (String matchingCompletion : output) {
+ prefixed.add(permissionPrefix + matchingCompletion);
+ }
+ return prefixed;
+ }
+ }
+
+ /**
+ * Find completable strings which match the text typed by the command's sender
+ *
+ * @param values The values to filter
+ * @param typedText The text the player has started typing
+ * @return The given string values which start with the player's typed text
+ */
+ private @NotNull List filterMatching(@NotNull List values, @NotNull String typedText) {
+ List configValues = new ArrayList<>();
+ for (String value : values) {
+ if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
+ configValues.add(value);
+ }
+ }
+ return configValues;
+ }
+
+ /**
+ * Loads all permissions available from bukkit plugins
+ */
+ private static void loadAvailablePermissions() {
+ plugins = new ArrayList<>();
+ permissions = new HashMap<>();
+
+ for (Permission permission : Bukkit.getPluginManager().getPermissions()) {
+ loadPermission(permission.getName());
+ }
+ }
+
+ /**
+ * Loads a given permission into the proper lists and maps
+ *
+ * @param permissionName The permission to load
+ */
+ private static void loadPermission(@NotNull String permissionName) {
+ String[] permissionParts = permissionName.split("\\.");
+ if (permissionParts.length == 1 && !plugins.contains(permissionParts[0])) {
+ plugins.add(permissionParts[0]);
+ } else if (permissionParts.length > 1) {
+ StringJoiner pathJoiner = new StringJoiner(".");
+ for (int j = 0; j < permissionParts.length - 1; j++) {
+ pathJoiner.add(permissionParts[j]);
+ }
+ String path = pathJoiner.toString();
+ List permissionList = permissions.computeIfAbsent(path, k -> new ArrayList<>());
+ permissionList.add(permissionName);
+
+ loadPermission(path);
+ }
+ }
+
}
diff --git a/src/main/java/net/knarcraft/blacksmith/container/ActionCost.java b/src/main/java/net/knarcraft/blacksmith/container/ActionCost.java
index d5131da..49bc2a7 100644
--- a/src/main/java/net/knarcraft/blacksmith/container/ActionCost.java
+++ b/src/main/java/net/knarcraft/blacksmith/container/ActionCost.java
@@ -28,6 +28,46 @@ import java.util.Set;
public record ActionCost(double monetaryCost, int expCost, @Nullable ItemStack itemCost,
@NotNull Set requiredPermissions) implements ConfigurationSerializable {
+ /**
+ * Changes the monetary cost of this action
+ *
+ * @param monetaryCost The new monetary cost
+ * @return The resulting action cost
+ */
+ public ActionCost changeMonetaryCost(double monetaryCost) {
+ return new ActionCost(monetaryCost, this.expCost, this.itemCost, this.requiredPermissions);
+ }
+
+ /**
+ * Changes the experience cost of this action
+ *
+ * @param expCost The new experience cost
+ * @return The resulting action cost
+ */
+ public ActionCost changeExpCost(int expCost) {
+ return new ActionCost(this.monetaryCost, expCost, this.itemCost, this.requiredPermissions);
+ }
+
+ /**
+ * Changes the item cost of this action
+ *
+ * @param itemCost The new item cost
+ * @return The resulting action cost
+ */
+ public ActionCost changeItemCost(@Nullable ItemStack itemCost) {
+ return new ActionCost(this.monetaryCost, this.expCost, itemCost, this.requiredPermissions);
+ }
+
+ /**
+ * Changes the permission cost of this action
+ *
+ * @param requiredPermissions The new permission cost
+ * @return The resulting action cost
+ */
+ public ActionCost changePermissionCost(@NotNull Set requiredPermissions) {
+ return new ActionCost(this.monetaryCost, this.expCost, this.itemCost, requiredPermissions);
+ }
+
@NotNull
public String displayCost() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/net/knarcraft/blacksmith/property/CostType.java b/src/main/java/net/knarcraft/blacksmith/property/CostType.java
new file mode 100644
index 0000000..017cc57
--- /dev/null
+++ b/src/main/java/net/knarcraft/blacksmith/property/CostType.java
@@ -0,0 +1,49 @@
+package net.knarcraft.blacksmith.property;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * The type of costs applicable to an advanced cost configuration
+ */
+public enum CostType {
+
+ /**
+ * A monetary cost from an economy plugin
+ */
+ ECONOMY,
+
+ /**
+ * A permission requirement, rather than a cost
+ */
+ PERMISSION,
+
+ /**
+ * In-game experience levels
+ */
+ EXP,
+
+ /**
+ * A specific item is consumed
+ */
+ ITEM,
+ ;
+
+ /**
+ * Parses a text string denoting a cost type
+ *
+ * @param input The input to parse
+ * @return The parsed cost type, or null if not matched
+ */
+ @Nullable
+ public static CostType parse(@NotNull String input) {
+ String normalized = input.trim().toUpperCase();
+ for (CostType costType : CostType.values()) {
+ if (normalized.equals(costType.name())) {
+ return costType;
+ }
+ }
+ return null;
+ }
+
+}