diff --git a/src/main/java/com/rsscripting/rstaxi/RSTaxi.java b/src/main/java/com/rsscripting/rstaxi/RSTaxi.java index 6314cb6..8e233a9 100644 --- a/src/main/java/com/rsscripting/rstaxi/RSTaxi.java +++ b/src/main/java/com/rsscripting/rstaxi/RSTaxi.java @@ -2,6 +2,7 @@ package com.rsscripting.rstaxi; import com.rsscripting.rstaxi.commands.CabCommand; +import com.rsscripting.rstaxi.commands.CabTabCompleter; import com.rsscripting.rstaxi.language.LanguageManager; import com.rsscripting.rstaxi.database.DatabaseManager; import org.bukkit.plugin.java.JavaPlugin; @@ -42,6 +43,7 @@ public final class RSTaxi extends JavaPlugin { } getCommand("cab").setExecutor(new CabCommand(this)); + getCommand("cab").setTabCompleter(new CabTabCompleter(this)); getLogger().info("RS-Taxi has been enabled!"); } @@ -62,8 +64,6 @@ public final class RSTaxi extends JavaPlugin { return languageManager; } - - public DatabaseManager getDatabaseManager() { return databaseManager; } diff --git a/src/main/java/com/rsscripting/rstaxi/commands/CabCommand.java b/src/main/java/com/rsscripting/rstaxi/commands/CabCommand.java index eeee74d..328ea49 100644 --- a/src/main/java/com/rsscripting/rstaxi/commands/CabCommand.java +++ b/src/main/java/com/rsscripting/rstaxi/commands/CabCommand.java @@ -16,11 +16,14 @@ public final class CabCommand implements CommandExecutor { private final RSTaxi plugin; private final PlatformValidator platformValidator; private final StopManager stopManager; + private final CabSearchCommand searchCommand; + public CabCommand(RSTaxi plugin) { this.plugin = plugin; this.platformValidator = new PlatformValidator(plugin); this.stopManager = new StopManager(plugin); + this.searchCommand = new CabSearchCommand(plugin); } @Override @@ -50,6 +53,9 @@ public final class CabCommand implements CommandExecutor { case "list" -> { return handleList(sender); } + case "search" -> { + return searchCommand.execute(sender, args); + } default -> { return handleTravel(sender, args); } diff --git a/src/main/java/com/rsscripting/rstaxi/commands/CabSearchCommand.java b/src/main/java/com/rsscripting/rstaxi/commands/CabSearchCommand.java new file mode 100644 index 0000000..91224eb --- /dev/null +++ b/src/main/java/com/rsscripting/rstaxi/commands/CabSearchCommand.java @@ -0,0 +1,111 @@ + +package com.rsscripting.rstaxi.commands; + +import com.rsscripting.rstaxi.RSTaxi; +import com.rsscripting.rstaxi.stops.StopManager; +import java.sql.SQLException; +import java.util.Map; +import java.util.Locale; +import org.bukkit.command.CommandSender; + +public final class CabSearchCommand { + + private final RSTaxi plugin; + private final StopManager stopManager; + + public CabSearchCommand(RSTaxi plugin) { + this.plugin = plugin; + this.stopManager = new StopManager(plugin); + } + + public boolean execute(CommandSender sender, String[] args) { + if (!sender.hasPermission("rs-cab.use")) { + sender.sendMessage( + plugin.getLanguageManager() + .getMessage("general.no-permission") + ); + return true; + } + + if (args.length < 2) { + sendUsage(sender); + return true; + } + + String query = String.join(" ", args) + .substring(args[0].length() + 1) + .trim(); + + if (query.isBlank()) { + sendUsage(sender); + return true; + } + + String lowerQuery = query.toLowerCase(Locale.ROOT); + boolean iconSearch = lowerQuery.startsWith("icon:"); + boolean textSearch = lowerQuery.startsWith("text:"); + + if (!iconSearch && !textSearch) { + sendUsage(sender); + return true; + } + + String value = query.substring(query.indexOf(':') + 1).trim(); + + if (value.isBlank()) { + sendUsage(sender); + return true; + } + + try { + var stops = iconSearch + ? stopManager.searchByIcon(value) + : stopManager.searchByName(value); + + if (stops.isEmpty()) { + sender.sendMessage( + plugin.getLanguageManager() + .getMessage("search.no-results") + ); + return true; + } + + sender.sendMessage( + plugin.getLanguageManager() + .getMessage("search.header") + ); + + for (StopManager.StopEntry stop : stops) { + sender.sendMessage( + plugin.getLanguageManager().getMessage( + "list.entry", + Map.of( + "icon", stop.icon(), + "stop", stop.name() + ) + ) + ); + } + } catch (SQLException e) { + plugin.getLogger().log( + java.util.logging.Level.SEVERE, + "Failed to search taxi destinations.", + e + ); + + sender.sendMessage( + plugin.getLanguageManager() + .getMessage("search.error") + ); + } + + return true; + } + + private void sendUsage(CommandSender sender) { + sender.sendMessage( + plugin.getLanguageManager() + .getMessage("general.invalid-usage") + ); + } +} diff --git a/src/main/java/com/rsscripting/rstaxi/commands/CabTabCompleter.java b/src/main/java/com/rsscripting/rstaxi/commands/CabTabCompleter.java new file mode 100644 index 0000000..d789be8 --- /dev/null +++ b/src/main/java/com/rsscripting/rstaxi/commands/CabTabCompleter.java @@ -0,0 +1,137 @@ + +package com.rsscripting.rstaxi.commands; + +import com.rsscripting.rstaxi.RSTaxi; +import com.rsscripting.rstaxi.stops.StopManager; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; + +public final class CabTabCompleter implements TabCompleter { + + private final RSTaxi plugin; + private final StopManager stopManager; + + public CabTabCompleter(RSTaxi plugin) { + this.plugin = plugin; + this.stopManager = new StopManager(plugin); + } + + @Override + public List onTabComplete( + CommandSender sender, + Command command, + String alias, + String[] args + ) { + List suggestions = new ArrayList<>(); + + if (args.length == 1) { + String input = args[0].toLowerCase(Locale.ROOT); + + // Management commands first, in the requested order. + if (sender.hasPermission("rs-cab.add") + && "add".startsWith(input)) { + suggestions.add("add"); + } + + if (sender.hasPermission("rs-cab.use") + && "search".startsWith(input)) { + suggestions.add("search"); + } + + if (sender.hasPermission("rs-cab.delete") + && "remove".startsWith(input)) { + suggestions.add("remove"); + } + + // Destinations follow the management commands. + if (sender.hasPermission("rs-cab.use")) { + try { + List stops = + stopManager.getAllStops(); + + stops.sort(Comparator.comparing( + StopManager.StopEntry::name, + String.CASE_INSENSITIVE_ORDER + )); + + for (StopManager.StopEntry stop : stops) { + String name = stop.name(); + + if (name.toLowerCase(Locale.ROOT) + .startsWith(input) + && !isReservedCommand(name)) { + suggestions.add(name); + } + } + } catch (SQLException e) { + plugin.getLogger().log( + Level.WARNING, + "Failed to load taxi destinations for tab completion.", + e + ); + } + } + + return suggestions; + } + + // Preserve search prefix and icon suggestions. + if (args[0].equalsIgnoreCase("search") + && sender.hasPermission("rs-cab.use")) { + + String input = String.join(" ", args) + .substring(args[0].length() + 1); + + String lowerInput = input.toLowerCase(Locale.ROOT); + + if (args.length == 2 + && !lowerInput.startsWith("icon:")) { + if ("text:".startsWith(lowerInput)) { + suggestions.add("text:"); + } + + if ("icon:".startsWith(lowerInput)) { + suggestions.add("icon:"); + } + + return suggestions; + } + + if (lowerInput.startsWith("icon:")) { + String iconPrefix = input.substring("icon:".length()) + .toLowerCase(Locale.ROOT); + + try { + for (String icon : stopManager.getUsedIcons()) { + if (icon.toLowerCase(Locale.ROOT) + .startsWith(iconPrefix)) { + suggestions.add("icon:" + icon); + } + } + } catch (SQLException e) { + plugin.getLogger().log( + Level.WARNING, + "Failed to load taxi icons for tab completion.", + e + ); + } + } + } + return suggestions; + } + + private boolean isReservedCommand(String name) { + return name.equalsIgnoreCase("add") + || name.equalsIgnoreCase("remove") + || name.equalsIgnoreCase("search") + || name.equalsIgnoreCase("list"); + } +} diff --git a/src/main/java/com/rsscripting/rstaxi/stops/StopManager.java b/src/main/java/com/rsscripting/rstaxi/stops/StopManager.java index 9012c55..5b46520 100644 --- a/src/main/java/com/rsscripting/rstaxi/stops/StopManager.java +++ b/src/main/java/com/rsscripting/rstaxi/stops/StopManager.java @@ -90,6 +90,94 @@ public final class StopManager { return stops; } + + public List searchByName(String name) + throws SQLException { + + String sql = """ + SELECT name, icon + FROM stops + WHERE name LIKE ? ESCAPE '\\' + ORDER BY name COLLATE NOCASE + """; + + List stops = new ArrayList<>(); + + String escapedName = name + .replace("\\", "\\\\") + .replace("%", "\\%") + .replace("_", "\\_"); + + try (PreparedStatement statement = connection() + .prepareStatement(sql)) { + + statement.setString(1, "%" + escapedName + "%"); + + try (ResultSet results = statement.executeQuery()) { + while (results.next()) { + stops.add(new StopEntry( + results.getString("name"), + results.getString("icon") + )); + } + } + } + + return stops; + } + + public List searchByIcon(String icon) + throws SQLException { + + String sql = """ + SELECT name, icon + FROM stops + WHERE icon = ? + ORDER BY name COLLATE NOCASE + """; + + List stops = new ArrayList<>(); + + try (PreparedStatement statement = connection() + .prepareStatement(sql)) { + + statement.setString(1, icon); + + try (ResultSet results = statement.executeQuery()) { + while (results.next()) { + stops.add(new StopEntry( + results.getString("name"), + results.getString("icon") + )); + } + } + } + + return stops; + } + + + public List getUsedIcons() throws SQLException { + String sql = """ + SELECT DISTINCT icon + FROM stops + ORDER BY icon COLLATE NOCASE + """; + + List icons = new ArrayList<>(); + + try (PreparedStatement statement = connection() + .prepareStatement(sql); + ResultSet results = statement.executeQuery()) { + + while (results.next()) { + icons.add(results.getString("icon")); + } + } + + return icons; + } + private Connection connection() { return plugin.getDatabaseManager().getConnection(); } diff --git a/src/main/resources/language/en_us.yml b/src/main/resources/language/en_us.yml index 1e52f05..4311325 100644 --- a/src/main/resources/language/en_us.yml +++ b/src/main/resources/language/en_us.yml @@ -38,3 +38,7 @@ list: platform: invalid: "&cYou must stand on a valid taxi platform to create a destination." +search: + header: "&6Search Results:" + no-results: "&eNo taxi stops matched your search." + error: "&cUnable to search taxi destinations. Check the server logs." \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6080bb8..1e4bc62 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -9,7 +9,6 @@ author: RSScripting commands: cab: description: Travel between registered taxi stops. - usage: /cab permissions: rs-cab.use: diff --git a/target/RS-Taxi-0.1.0.jar b/target/RS-Taxi-0.1.0.jar index 5860160..3d1cf48 100644 Binary files a/target/RS-Taxi-0.1.0.jar and b/target/RS-Taxi-0.1.0.jar differ diff --git a/target/classes/com/rsscripting/rstaxi/RSTaxi.class b/target/classes/com/rsscripting/rstaxi/RSTaxi.class index dbe8eff..8331df7 100644 Binary files a/target/classes/com/rsscripting/rstaxi/RSTaxi.class and b/target/classes/com/rsscripting/rstaxi/RSTaxi.class differ diff --git a/target/classes/com/rsscripting/rstaxi/commands/CabCommand.class b/target/classes/com/rsscripting/rstaxi/commands/CabCommand.class index c7bb954..eac9cc7 100644 Binary files a/target/classes/com/rsscripting/rstaxi/commands/CabCommand.class and b/target/classes/com/rsscripting/rstaxi/commands/CabCommand.class differ diff --git a/target/classes/com/rsscripting/rstaxi/stops/StopManager.class b/target/classes/com/rsscripting/rstaxi/stops/StopManager.class index 37e1d5a..a42af0e 100644 Binary files a/target/classes/com/rsscripting/rstaxi/stops/StopManager.class and b/target/classes/com/rsscripting/rstaxi/stops/StopManager.class differ diff --git a/target/classes/language/en_us.yml b/target/classes/language/en_us.yml index 1e52f05..4311325 100644 --- a/target/classes/language/en_us.yml +++ b/target/classes/language/en_us.yml @@ -38,3 +38,7 @@ list: platform: invalid: "&cYou must stand on a valid taxi platform to create a destination." +search: + header: "&6Search Results:" + no-results: "&eNo taxi stops matched your search." + error: "&cUnable to search taxi destinations. Check the server logs." \ No newline at end of file diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml index fda3015..4827e07 100644 --- a/target/classes/plugin.yml +++ b/target/classes/plugin.yml @@ -9,7 +9,6 @@ author: RSScripting commands: cab: description: Travel between registered taxi stops. - usage: /cab permissions: rs-cab.use: diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 09b45d3..6a42d04 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,7 +1,9 @@ com\rsscripting\rstaxi\stops\StopManager.class com\rsscripting\rstaxi\platform\PlatformValidator.class com\rsscripting\rstaxi\RSTaxi.class +com\rsscripting\rstaxi\commands\CabTabCompleter.class com\rsscripting\rstaxi\stops\StopManager$StopEntry.class com\rsscripting\rstaxi\commands\CabCommand.class com\rsscripting\rstaxi\database\DatabaseManager.class +com\rsscripting\rstaxi\commands\CabSearchCommand.class com\rsscripting\rstaxi\language\LanguageManager.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 5d26fe8..03d27db 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,4 +1,6 @@ C:\Users\russe\IdeaProjects\RS-Taxi\src\main\java\com\rsscripting\rstaxi\commands\CabCommand.java +C:\Users\russe\IdeaProjects\RS-Taxi\src\main\java\com\rsscripting\rstaxi\commands\CabSearchCommand.java +C:\Users\russe\IdeaProjects\RS-Taxi\src\main\java\com\rsscripting\rstaxi\commands\CabTabCompleter.java C:\Users\russe\IdeaProjects\RS-Taxi\src\main\java\com\rsscripting\rstaxi\database\DatabaseManager.java C:\Users\russe\IdeaProjects\RS-Taxi\src\main\java\com\rsscripting\rstaxi\language\LanguageManager.java C:\Users\russe\IdeaProjects\RS-Taxi\src\main\java\com\rsscripting\rstaxi\platform\PlatformValidator.java