Adds tab completion for the creation command, and fixes several bugs

This commit is contained in:
Kristian Knarvik 2022-01-14 15:19:27 +01:00
parent 5c2831bbb7
commit 252d3ed88a
10 changed files with 202 additions and 50 deletions

View File

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>net.knarcraft</groupId> <groupId>net.knarcraft</groupId>
<artifactId>permissionsigns</artifactId> <artifactId>PermissionSigns</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@ -5,8 +5,10 @@ import net.knarcraft.permissionsigns.command.PermissionSignsTabCompleter;
import net.knarcraft.permissionsigns.container.PermissionSign; import net.knarcraft.permissionsigns.container.PermissionSign;
import net.knarcraft.permissionsigns.container.SignCreationRequest; import net.knarcraft.permissionsigns.container.SignCreationRequest;
import net.knarcraft.permissionsigns.formatting.Translator; import net.knarcraft.permissionsigns.formatting.Translator;
import net.knarcraft.permissionsigns.listener.SignListener;
import net.knarcraft.permissionsigns.manager.EconomyManager; import net.knarcraft.permissionsigns.manager.EconomyManager;
import net.knarcraft.permissionsigns.manager.PermissionManager; import net.knarcraft.permissionsigns.manager.PermissionManager;
import net.knarcraft.permissionsigns.manager.SignManager;
import net.knarcraft.permissionsigns.thread.SignCreationRequestTimeoutThread; import net.knarcraft.permissionsigns.thread.SignCreationRequestTimeoutThread;
import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission; import net.milkbowl.vault.permission.Permission;
@ -19,6 +21,7 @@ import org.bukkit.plugin.ServicesManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
@ -94,8 +97,10 @@ public final class PermissionSigns extends JavaPlugin {
Stream<SignCreationRequest> matchingRequests = signCreationRequests.stream().filter( Stream<SignCreationRequest> matchingRequests = signCreationRequests.stream().filter(
(item) -> item.getPlayer().getUniqueId().equals(uuid)); (item) -> item.getPlayer().getUniqueId().equals(uuid));
List<SignCreationRequest> requestList = matchingRequests.toList(); List<SignCreationRequest> requestList = matchingRequests.toList();
if (requestList.size() > 0) {
signCreationRequests.remove(requestList.get(0)); signCreationRequests.remove(requestList.get(0));
} }
}
@Override @Override
public void onEnable() { public void onEnable() {
@ -153,11 +158,13 @@ public final class PermissionSigns extends JavaPlugin {
} else { } else {
throw new IllegalStateException("[PermissionSigns] Error: Vault could not be loaded"); throw new IllegalStateException("[PermissionSigns] Error: Vault could not be loaded");
} }
getServer().getPluginManager().registerEvents(new SignListener(), this);
Translator.loadLanguages("en"); Translator.loadLanguages("en");
registerCommands(); registerCommands();
BukkitScheduler scheduler = Bukkit.getScheduler(); BukkitScheduler scheduler = Bukkit.getScheduler();
scheduler.runTaskTimer(this, new SignCreationRequestTimeoutThread(signCreationRequests), 0L, 100L); scheduler.runTaskTimer(this, new SignCreationRequestTimeoutThread(signCreationRequests), 0L, 100L);
SignManager.loadSigns();
} }
/** /**
@ -173,7 +180,11 @@ public final class PermissionSigns extends JavaPlugin {
@Override @Override
public void onDisable() { public void onDisable() {
// Plugin shutdown logic try {
SignManager.saveSigns();
} catch (IOException e) {
e.printStackTrace();
}
} }
} }

View File

@ -21,6 +21,7 @@ public class CreateCommand implements CommandExecutor {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
// /ps create <name> <permission,permission> <cost> <duration> to create a new permission-sign // /ps create <name> <permission,permission> <cost> <duration> to create a new permission-sign
//Name and permission(s) required, but duration and cost optional //Name and permission(s) required, but duration and cost optional
String usage = "/ps create <name> <permission,permission> [cost] [duration] - Used for creating a new permission sign";
if (!(sender instanceof Player)) { if (!(sender instanceof Player)) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.COMMAND_PLAYER_ONLY)); sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.COMMAND_PLAYER_ONLY));
return false; return false;
@ -31,12 +32,14 @@ public class CreateCommand implements CommandExecutor {
} }
if (args.length < 2) { if (args.length < 2) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.MISSING_CREATION_INFO)); sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.MISSING_CREATION_INFO));
return false; sender.sendMessage(usage);
return true;
} }
PermissionSign newSign = parseSign(sender, args); PermissionSign newSign = parseSign(sender, args);
if (newSign == null) { if (newSign == null) {
return false; sender.sendMessage(usage);
return true;
} }
PermissionSigns.addSignCreationRequest((Player) sender, newSign); PermissionSigns.addSignCreationRequest((Player) sender, newSign);
@ -54,20 +57,24 @@ public class CreateCommand implements CommandExecutor {
private PermissionSign parseSign(@NotNull CommandSender sender, @NotNull String[] args) { private PermissionSign parseSign(@NotNull CommandSender sender, @NotNull String[] args) {
String name = args[0]; String name = args[0];
String[] permissions = args[1].split(","); String[] permissions = args[1].split(",");
double cost; double cost = 0;
int duration; int duration = 0;
if (args.length > 2) {
try { try {
cost = Double.parseDouble(args[2]); cost = Double.parseDouble(args[2]);
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.COST_INVALID_NUMBER)); sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.COST_INVALID_NUMBER));
return null; return null;
} }
}
if (args.length > 3) {
try { try {
duration = Integer.parseInt(args[3]); duration = Integer.parseInt(args[3]);
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.DURATION_INVALID_NUMBER)); sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.DURATION_INVALID_NUMBER));
return null; return null;
} }
}
return new PermissionSign(name, List.of(permissions), duration, cost); return new PermissionSign(name, List.of(permissions), duration, cost);
} }

View File

@ -1,20 +1,159 @@
package net.knarcraft.permissionsigns.command; package net.knarcraft.permissionsigns.command;
import org.bukkit.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter; import org.bukkit.command.TabCompleter;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* The tab completer for the create command * The tab completer for the create command
*/ */
public class CreateTabCompleter implements TabCompleter { public class CreateTabCompleter implements TabCompleter {
private static List<String> plugins;
private static Map<String, List<String>> permissions;
private static List<String> numbers;
private static List<String> empty;
private static List<String> name;
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
return null; @NotNull String[] args) {
//First, display all plugins, filtering on written text
//Second, if a dot has been input, go to the correct branch,
//Might want to save the plugin list, but generate permission lists on the fly as needed
if (plugins == null) {
loadAvailablePermissions();
numbers = new ArrayList<>();
numbers.add(String.valueOf(0));
empty = new ArrayList<>();
name = new ArrayList<>();
name.add("<name>");
}
if (args.length > 4) {
return empty;
} else if (args.length > 2) {
return numbers;
} else if (args.length > 1) {
return tabCompletePermission(args[1]);
} else {
return name;
}
}
/**
* Gets the tab complete value for the permission typed
*
* @param typedNode <p>The full permission node typed by the player</p>
* @return <p>All known valid auto-complete options</p>
*/
private List<String> tabCompletePermission(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<String> output;
if (typedNode.contains(".")) {
List<String> matchingPermissions = permissions.get(typedNode.substring(0, typedNode.lastIndexOf(".")));
if (matchingPermissions == null) {
if (permissionPrefix.isEmpty()) {
output = new ArrayList<>();
} else {
List<String> 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<String> 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 <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values which start with the player's typed text</p>
*/
private List<String> filterMatching(List<String> values, String typedText) {
List<String> 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 (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
for (Permission permission : plugin.getDescription().getPermissions()) {
loadPermission(permission);
}
}
}
/**
* Loads a given permission into the proper lists and maps
*
* @param permission <p>The permission to load</p>
*/
private static void loadPermission(Permission permission) {
String permissionName = permission.getName();
String[] permissionParts = permissionName.split("\\.");
for (int i = 0; i < permissionParts.length; i++) {
if (i == 0) {
plugins.add(permissionParts[0]);
} else {
StringBuilder pathBuilder = new StringBuilder();
for (int j = 0; j < i; j++) {
pathBuilder.append(".");
pathBuilder.append(permissionParts[j]);
}
String path = pathBuilder.substring(1);
List<String> permissionList = permissions.get(path);
if (permissionList == null) {
permissionList = new ArrayList<>();
permissions.put(pathBuilder.substring(1), permissionList);
}
permissionList.add(permissionName);
}
}
} }
} }

View File

@ -165,7 +165,7 @@ public class PermissionSign {
return Translator.getTranslatedMessage(TranslatableMessage.SIGN_COST_FREE); return Translator.getTranslatedMessage(TranslatableMessage.SIGN_COST_FREE);
} else { } else {
String currency = EconomyManager.getCurrency(cost != 1); String currency = EconomyManager.getCurrency(cost != 1);
return String.format("%f%s", cost, currency); return String.format("%.2f%s", cost, currency);
} }
} }

View File

@ -13,10 +13,10 @@ public class StringFormatter {
* @param input <p>The input string to replace in</p> * @param input <p>The input string to replace in</p>
* @param placeholder <p>The placeholder to replace</p> * @param placeholder <p>The placeholder to replace</p>
* @param replacement <p>The replacement value</p> * @param replacement <p>The replacement value</p>
* @return <p>The input string with all placeholder instances replaced</p> * @return <p>The input string with the placeholder replaced</p>
*/ */
public static String replacePlaceholder(String input, String placeholder, String replacement) { public static String replacePlaceholder(String input, String placeholder, String replacement) {
return input.replaceAll(placeholder, replacement); return input.replace(placeholder, replacement);
} }
/** /**
@ -25,7 +25,7 @@ public class StringFormatter {
* @param input <p>The input string to replace in</p> * @param input <p>The input string to replace in</p>
* @param placeholders <p>The placeholders to replace</p> * @param placeholders <p>The placeholders to replace</p>
* @param replacements <p>The replacement values</p> * @param replacements <p>The replacement values</p>
* @return <p>The input string with all placeholder instances replaced</p> * @return <p>The input string with placeholders replaced</p>
*/ */
public static String replacePlaceholders(String input, String[] placeholders, String[] replacements) { public static String replacePlaceholders(String input, String[] placeholders, String[] replacements) {
for (int i = 0; i < Math.min(placeholders.length, replacements.length); i++) { for (int i = 0; i < Math.min(placeholders.length, replacements.length); i++) {
@ -61,7 +61,7 @@ public class StringFormatter {
* @return <p>The formatted message</p> * @return <p>The formatted message</p>
*/ */
public static String formatInfoMessage(String message) { public static String formatInfoMessage(String message) {
return ChatColor.DARK_RED + formatMessage(message); return ChatColor.DARK_GREEN + formatMessage(message);
} }
/** /**
@ -71,7 +71,7 @@ public class StringFormatter {
* @return <p>The formatted message</p> * @return <p>The formatted message</p>
*/ */
public static String formatErrorMessage(String message) { public static String formatErrorMessage(String message) {
return ChatColor.DARK_GREEN + formatMessage(message); return ChatColor.DARK_RED + formatMessage(message);
} }
/** /**
@ -82,7 +82,8 @@ public class StringFormatter {
*/ */
private static String formatMessage(String message) { private static String formatMessage(String message) {
return ChatColor.translateAlternateColorCodes('&', return ChatColor.translateAlternateColorCodes('&',
Translator.getTranslatedMessage(TranslatableMessage.PREFIX)) + ChatColor.RESET + message; Translator.getTranslatedMessage(TranslatableMessage.PREFIX)) + " " + ChatColor.RESET +
ChatColor.translateAlternateColorCodes('&', message);
} }
} }

View File

@ -135,7 +135,11 @@ public class SignListener implements Listener {
if (!player.hasPermission(permissionNode)) { if (!player.hasPermission(permissionNode)) {
permissionsBuilder.append(permissionNode); permissionsBuilder.append(permissionNode);
permissionsBuilder.append(", "); permissionsBuilder.append(", ");
PermissionManager.addPermission(player, permissionNode, permissionSign.getDuration() == 0); if (permissionSign.getDuration() == 0) {
PermissionManager.addPermission(player, permissionNode);
} else {
PermissionManager.addTemporaryPermission(player, permissionNode, permissionSign.getDuration());
}
} }
} }
@ -144,7 +148,7 @@ public class SignListener implements Listener {
grantedPermissions = grantedPermissions.substring(0, grantedPermissions.length() - 2); grantedPermissions = grantedPermissions.substring(0, grantedPermissions.length() - 2);
String timeUnit = Translator.getTranslatedMessage(TranslatableMessage.SIGN_TIME_UNIT); String timeUnit = Translator.getTranslatedMessage(TranslatableMessage.SIGN_TIME_UNIT);
player.sendMessage(StringFormatter.replacePlaceholders(successMessage, new String[]{"{permissions}", "{time}"}, player.sendMessage(StringFormatter.replacePlaceholders(successMessage, new String[]{"{permissions}", "{time}"},
new String[]{grantedPermissions, permissionSign.getDuration() + timeUnit})); new String[]{grantedPermissions, permissionSign.getDuration() + " " + timeUnit}));
} }
/** /**

View File

@ -24,28 +24,13 @@ public class PermissionManager {
PermissionManager.permission = permission; PermissionManager.permission = permission;
} }
/**
* Grants a permission to the given player
*
* @param player <p>The player to grant the permission to</p>
* @param permissionNode <p>The permission node to grant the player</p>
* @param permanent <p>Whether to permanently grant the permission node</p>
*/
public static void addPermission(Player player, String permissionNode, boolean permanent) {
if (permanent) {
addPermission(player, permissionNode);
} else {
addTemporaryPermission(player, permissionNode);
}
}
/** /**
* Grants a permanent permission to a player * Grants a permanent permission to a player
* *
* @param player <p>The player to grant the permission to</p> * @param player <p>The player to grant the permission to</p>
* @param permissionNode <p>The permission node to grant to the player</p> * @param permissionNode <p>The permission node to grant to the player</p>
*/ */
private static void addPermission(Player player, String permissionNode) { public static void addPermission(Player player, String permissionNode) {
permission.playerAdd(player, permissionNode); permission.playerAdd(player, permissionNode);
} }
@ -54,9 +39,11 @@ public class PermissionManager {
* *
* @param player <p>The player to give the permission to</p> * @param player <p>The player to give the permission to</p>
* @param permissionNode <p>The temporary permission to grant</p> * @param permissionNode <p>The temporary permission to grant</p>
* @param duration <p>The duration for which the player should keep the given permission</p>
*/ */
private static void addTemporaryPermission(OfflinePlayer player, String permissionNode) { public static void addTemporaryPermission(OfflinePlayer player, String permissionNode, int duration) {
permission.playerAddTransient(player, permissionNode); permission.playerAddTransient(player, permissionNode);
temporaryPermissions.add(new TemporaryPermission(player, permissionNode, duration));
//TODO: Create and store a temporary permission //TODO: Create and store a temporary permission
// Check all stored temporary permissions on startup: // Check all stored temporary permissions on startup:
// * Remove expired temporary permissions // * Remove expired temporary permissions

View File

@ -15,6 +15,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
/** /**
@ -81,10 +82,10 @@ public class SignManager {
*/ */
public static void loadSigns() { public static void loadSigns() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile); YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.getConfigurationSection("signs"); ConfigurationSection signSection = configuration.getConfigurationSection("signs");
managedSigns = new HashMap<>(); managedSigns = new HashMap<>();
if (signSection == null) { if (signSection == null) {
PermissionSigns.getInstance().getLogger().log(Level.WARNING, "Signs section not found in signs.yml");
return; return;
} }
for (String key : signSection.getKeys(false)) { for (String key : signSection.getKeys(false)) {
@ -95,6 +96,7 @@ public class SignManager {
e.getMessage()); e.getMessage());
} }
} }
PermissionSigns.getInstance().getLogger().log(Level.INFO, "Permission signs finished loading");
//TODO: Might want to re-draw signs here in case any signs have changed //TODO: Might want to re-draw signs here in case any signs have changed
} }
@ -120,8 +122,9 @@ public class SignManager {
String[] locationParts = key.split(","); String[] locationParts = key.split(",");
Location signLocation; Location signLocation;
try { try {
signLocation = new Location(Bukkit.getWorld(locationParts[0]), Double.parseDouble(locationParts[1]), signLocation = new Location(Bukkit.getWorld(UUID.fromString(locationParts[0])),
Double.parseDouble(locationParts[2]), Double.parseDouble(locationParts[3])); Double.parseDouble(locationParts[1]), Double.parseDouble(locationParts[2]),
Double.parseDouble(locationParts[3]));
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
throw new InvalidConfigurationException("Invalid sign coordinates"); throw new InvalidConfigurationException("Invalid sign coordinates");
} }

View File

@ -1,6 +1,6 @@
name: PermissionSigns name: PermissionSigns
version: '${project.version}' version: 1.0
main: net.knarcraft.permissionsigns.Permissionsigns main: net.knarcraft.permissionsigns.PermissionSigns
api-version: 1.18 api-version: 1.18
prefix: PermissionSigns prefix: PermissionSigns
depend: [ Vault ] depend: [ Vault ]