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>
<groupId>net.knarcraft</groupId>
<artifactId>permissionsigns</artifactId>
<artifactId>PermissionSigns</artifactId>
<version>1.0-SNAPSHOT</version>
<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.SignCreationRequest;
import net.knarcraft.permissionsigns.formatting.Translator;
import net.knarcraft.permissionsigns.listener.SignListener;
import net.knarcraft.permissionsigns.manager.EconomyManager;
import net.knarcraft.permissionsigns.manager.PermissionManager;
import net.knarcraft.permissionsigns.manager.SignManager;
import net.knarcraft.permissionsigns.thread.SignCreationRequestTimeoutThread;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission;
@ -19,6 +21,7 @@ import org.bukkit.plugin.ServicesManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import java.io.IOException;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
@ -94,7 +97,9 @@ public final class PermissionSigns extends JavaPlugin {
Stream<SignCreationRequest> matchingRequests = signCreationRequests.stream().filter(
(item) -> item.getPlayer().getUniqueId().equals(uuid));
List<SignCreationRequest> requestList = matchingRequests.toList();
signCreationRequests.remove(requestList.get(0));
if (requestList.size() > 0) {
signCreationRequests.remove(requestList.get(0));
}
}
@Override
@ -153,11 +158,13 @@ public final class PermissionSigns extends JavaPlugin {
} else {
throw new IllegalStateException("[PermissionSigns] Error: Vault could not be loaded");
}
getServer().getPluginManager().registerEvents(new SignListener(), this);
Translator.loadLanguages("en");
registerCommands();
BukkitScheduler scheduler = Bukkit.getScheduler();
scheduler.runTaskTimer(this, new SignCreationRequestTimeoutThread(signCreationRequests), 0L, 100L);
SignManager.loadSigns();
}
/**
@ -173,7 +180,11 @@ public final class PermissionSigns extends JavaPlugin {
@Override
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) {
// /ps create <name> <permission,permission> <cost> <duration> to create a new permission-sign
//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)) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.COMMAND_PLAYER_ONLY));
return false;
@ -31,12 +32,14 @@ public class CreateCommand implements CommandExecutor {
}
if (args.length < 2) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.MISSING_CREATION_INFO));
return false;
sender.sendMessage(usage);
return true;
}
PermissionSign newSign = parseSign(sender, args);
if (newSign == null) {
return false;
sender.sendMessage(usage);
return true;
}
PermissionSigns.addSignCreationRequest((Player) sender, newSign);
@ -54,19 +57,23 @@ public class CreateCommand implements CommandExecutor {
private PermissionSign parseSign(@NotNull CommandSender sender, @NotNull String[] args) {
String name = args[0];
String[] permissions = args[1].split(",");
double cost;
int duration;
try {
cost = Double.parseDouble(args[2]);
} catch (NumberFormatException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.COST_INVALID_NUMBER));
return null;
double cost = 0;
int duration = 0;
if (args.length > 2) {
try {
cost = Double.parseDouble(args[2]);
} catch (NumberFormatException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.COST_INVALID_NUMBER));
return null;
}
}
try {
duration = Integer.parseInt(args[3]);
} catch (NumberFormatException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.DURATION_INVALID_NUMBER));
return null;
if (args.length > 3) {
try {
duration = Integer.parseInt(args[3]);
} catch (NumberFormatException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.DURATION_INVALID_NUMBER));
return null;
}
}
return new PermissionSign(name, List.of(permissions), duration, cost);

View File

@ -1,20 +1,159 @@
package net.knarcraft.permissionsigns.command;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The tab completer for the create command
*/
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
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
return null;
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@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);
} else {
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 placeholder <p>The placeholder to replace</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) {
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 placeholders <p>The placeholders to replace</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) {
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>
*/
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>
*/
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) {
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)) {
permissionsBuilder.append(permissionNode);
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);
String timeUnit = Translator.getTranslatedMessage(TranslatableMessage.SIGN_TIME_UNIT);
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;
}
/**
* 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
*
* @param player <p>The player to grant the permission to</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);
}
@ -54,9 +39,11 @@ public class PermissionManager {
*
* @param player <p>The player to give the permission to</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);
temporaryPermissions.add(new TemporaryPermission(player, permissionNode, duration));
//TODO: Create and store a temporary permission
// Check all stored temporary permissions on startup:
// * Remove expired temporary permissions

View File

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

View File

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