Initial Development

- Add Stop Complete
This commit is contained in:
BigPappa committed 2026-10-11 09:03:11 -05:00
commit 3566555893
31 files changed
+1018

No files matched your search

@@ -0,0 +1,72 @@
package com.rsscripting.rstaxi;
import com.rsscripting.rstaxi.commands.CabCommand;
import com.rsscripting.rstaxi.language.LanguageManager;
import com.rsscripting.rstaxi.database.DatabaseManager;
import org.bukkit.plugin.java.JavaPlugin;
public final class RSTaxi extends JavaPlugin {
private LanguageManager languageManager;
private DatabaseManager databaseManager;
@Override
public void onEnable() {
saveDefaultConfig();
if (!new java.io.File(getDataFolder(), "language/en_us.yml").exists()) {
saveResource("language/en_us.yml", false);
}
languageManager = new LanguageManager(this);
languageManager.load();
databaseManager = new DatabaseManager(this);
try {
databaseManager.connect();
databaseManager.initialize();
} catch (java.sql.SQLException e) {
getLogger().severe("Failed to initialize SQLite database!");
getLogger().severe(e.getMessage());
getServer().getPluginManager().disablePlugin(this);
return;
}
if (getCommand("cab") == null) {
getLogger().severe("Command 'cab' is missing from plugin.yml!");
getServer().getPluginManager().disablePlugin(this);
return;
}
getCommand("cab").setExecutor(new CabCommand(this));
getLogger().info("RS-Taxi has been enabled!");
}
@Override
public void onDisable() {
if (databaseManager != null) {
databaseManager.close();
}
getLogger().info("RS-Taxi has been disabled!");
}
public LanguageManager getLanguageManager() {
return languageManager;
}
public DatabaseManager getDatabaseManager() {
return databaseManager;
}
}
@@ -0,0 +1,211 @@
package com.rsscripting.rstaxi.commands;
import com.rsscripting.rstaxi.RSTaxi;
import com.rsscripting.rstaxi.platform.PlatformValidator;
import com.rsscripting.rstaxi.stops.StopManager;
import java.sql.SQLException;
import java.util.Map;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public final class CabCommand implements CommandExecutor {
private final RSTaxi plugin;
private final PlatformValidator platformValidator;
private final StopManager stopManager;
public CabCommand(RSTaxi plugin) {
this.plugin = plugin;
this.platformValidator = new PlatformValidator(plugin);
this.stopManager = new StopManager(plugin);
}
@Override
public boolean onCommand(
CommandSender sender,
Command command,
String label,
String[] args
) {
if (args.length == 0) {
return handleTravel(sender, args);
}
switch (args[0].toLowerCase()) {
case "add" -> {
return handleAdd(sender, args);
}
case "update" -> {
return handleUpdate(sender, args);
}
case "edit" -> {
return handleEdit(sender, args);
}
case "remove" -> {
return handleRemove(sender, args);
}
case "list" -> {
return handleList(sender);
}
default -> {
return handleTravel(sender, args);
}
}
}
private boolean handleTravel(
CommandSender sender,
String[] args
) {
if (!(sender instanceof Player player)) {
sender.sendMessage(
plugin.getLanguageManager()
.getMessage("general.player-only")
);
return true;
}
if (!player.hasPermission("rs-cab.use")) {
player.sendMessage(
plugin.getLanguageManager()
.getMessage("general.no-permission")
);
return true;
}
// Travel behavior will be implemented in Phase 2.
return true;
}
private boolean handleAdd(
CommandSender sender,
String[] args
) {
if (!sender.hasPermission("rs-cab.add")) {
sender.sendMessage(plugin.getLanguageManager()
.getMessage("general.no-permission"));
return true;
}
if (!(sender instanceof Player player)) {
sender.sendMessage(plugin.getLanguageManager()
.getMessage("general.player-only"));
return true;
}
String arguments = String.join(" ", args)
.substring("add".length()).trim();
String name;
String icon;
if (arguments.startsWith("'")) {
int closingQuote = arguments.indexOf('\'', 1);
if (closingQuote < 2) {
sender.sendMessage(plugin.getLanguageManager()
.getMessage("general.invalid-usage"));
return true;
}
name = arguments.substring(1, closingQuote);
icon = arguments.substring(closingQuote + 1).trim();
} else {
String[] parts = arguments.split("\\s+");
if (parts.length != 2) {
sender.sendMessage(plugin.getLanguageManager()
.getMessage("general.invalid-usage"));
return true;
}
name = parts[0];
icon = parts[1];
}
if (name.isBlank() || icon.isBlank()
|| icon.contains(" ")) {
sender.sendMessage(plugin.getLanguageManager()
.getMessage("general.invalid-usage"));
return true;
}
try {
if (stopManager.exists(name)) {
sender.sendMessage(plugin.getLanguageManager()
.getMessage("stop.already-exists",
Map.of("stop", name)));
return true;
}
if (!platformValidator.isValid(player)) {
sender.sendMessage(plugin.getLanguageManager()
.getMessage("platform.invalid"));
return true;
}
if (stopManager.add(name, icon, player.getLocation())) {
sender.sendMessage(plugin.getLanguageManager()
.getMessage("stop.added",
Map.of("stop", name)));
} else {
sender.sendMessage(plugin.getLanguageManager()
.getMessage("stop.already-exists",
Map.of("stop", name)));
}
} catch (SQLException | IllegalArgumentException e) {
plugin.getLogger().severe(
"Failed to create taxi stop: " + e.getMessage()
);
sender.sendMessage(plugin.getLanguageManager()
.getMessage("stop.creation-error"));
}
return true;
}
private boolean handleUpdate(
CommandSender sender,
String[] args
) {
return checkPermission(sender, "rs-cab.edit");
}
private boolean handleEdit(
CommandSender sender,
String[] args
) {
return checkPermission(sender, "rs-cab.edit");
}
private boolean handleRemove(
CommandSender sender,
String[] args
) {
return checkPermission(sender, "rs-cab.delete");
}
private boolean handleList(CommandSender sender) {
return checkPermission(sender, "rs-cab.use");
}
private boolean checkPermission(
CommandSender sender,
String permission
) {
if (!sender.hasPermission(permission)) {
sender.sendMessage(
plugin.getLanguageManager()
.getMessage("general.no-permission")
);
}
return true;
}
}
@@ -0,0 +1,83 @@
package com.rsscripting.rstaxi.database;
import com.rsscripting.rstaxi.RSTaxi;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public final class DatabaseManager {
private final RSTaxi plugin;
private Connection connection;
public DatabaseManager(RSTaxi plugin) {
this.plugin = plugin;
}
public void connect() throws SQLException {
File folder = plugin.getDataFolder();
if (!folder.exists() && !folder.mkdirs()) {
throw new SQLException(
"Could not create plugin data folder."
);
}
File databaseFile = new File(folder, "taxi.db");
String url = "jdbc:sqlite:" + databaseFile.getAbsolutePath();
connection = DriverManager.getConnection(url);
plugin.getLogger().info("SQLite database connected.");
}
public void initialize() throws SQLException {
if (connection == null || connection.isClosed()) {
throw new SQLException("Database is not connected.");
}
String sql = """
CREATE TABLE IF NOT EXISTS stops (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL COLLATE NOCASE UNIQUE,
icon TEXT NOT NULL,
world TEXT NOT NULL,
x REAL NOT NULL,
y REAL NOT NULL,
z REAL NOT NULL,
yaw REAL NOT NULL,
pitch REAL NOT NULL
);
""";
try (Statement statement = connection.createStatement()) {
statement.execute(sql);
}
plugin.getLogger().info("SQLite database initialized.");
}
public Connection getConnection() {
return connection;
}
public void close() {
if (connection == null) {
return;
}
try {
connection.close();
plugin.getLogger().info("SQLite database connection closed.");
} catch (SQLException e) {
plugin.getLogger().severe(
"Failed to close SQLite database: "
+ e.getMessage()
);
}
}
}
@@ -0,0 +1,110 @@
package com.rsscripting.rstaxi.language;
import com.rsscripting.rstaxi.RSTaxi;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.util.Map;
public final class LanguageManager {
private final RSTaxi plugin;
private YamlConfiguration language;
private YamlConfiguration english;
public LanguageManager(RSTaxi plugin) {
this.plugin = plugin;
}
public void load() {
File languageFolder =
new File(plugin.getDataFolder(), "language");
if (!languageFolder.exists() && !languageFolder.mkdirs()) {
plugin.getLogger().severe(
"Could not create the language folder."
);
}
File englishFile =
new File(languageFolder, "en_us.yml");
if (!englishFile.exists()) {
plugin.saveResource("language/en_us.yml", false);
}
english = YamlConfiguration.loadConfiguration(englishFile);
String selected = plugin.getConfig()
.getString("language", "en_us")
.trim();
if (!selected.matches("[a-zA-Z0-9_-]+")) {
plugin.getLogger().warning(
"Invalid language setting. Using en_us."
);
selected = "en_us";
}
File selectedFile =
new File(languageFolder, selected + ".yml");
if (!selectedFile.isFile()) {
plugin.getLogger().warning(
"Language file '" + selected
+ ".yml' was not found. Using en_us."
);
language = english;
return;
}
language = YamlConfiguration.loadConfiguration(selectedFile);
plugin.getLogger().info(
"Loaded language: " + selected
);
}
public String getMessage(String path) {
return getMessage(path, Map.of());
}
public String getMessage(
String path,
Map<String, String> placeholders
) {
String message = language == null
? null
: language.getString(path);
if (message == null && english != null) {
message = english.getString(path);
}
if (message == null) {
plugin.getLogger().warning(
"Missing language message: " + path
);
message = "&cMissing message: " + path;
}
String prefix = language == null
? ""
: language.getString("prefix", "");
if (!path.equals("prefix") && !prefix.isEmpty()) {
message = prefix + message;
}
for (Map.Entry<String, String> entry
: placeholders.entrySet()) {
message = message.replace(
"%" + entry.getKey() + "%",
entry.getValue()
);
}
return ChatColor.translateAlternateColorCodes('&', message);
}
}
@@ -0,0 +1,79 @@
package com.rsscripting.rstaxi.platform;
import com.rsscripting.rstaxi.RSTaxi;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import java.util.Map;
public final class PlatformValidator {
private final RSTaxi plugin;
private static final Map<String, int[]> OFFSETS = Map.of(
"north", new int[]{0, -1},
"northeast", new int[]{1, -1},
"east", new int[]{1, 0},
"southeast", new int[]{1, 1},
"south", new int[]{0, 1},
"southwest", new int[]{-1, 1},
"west", new int[]{-1, 0},
"northwest", new int[]{-1, -1}
);
public PlatformValidator(RSTaxi plugin) {
this.plugin = plugin;
}
public boolean isValid(Player player) {
if (!plugin.getConfig().getBoolean("platform.enabled", true)) {
return true;
}
Block center = player.getLocation()
.subtract(0, 1, 0)
.getBlock();
Material target = getConfiguredMaterial(
"platform.target-block"
);
if (center.getType() != target) {
return false;
}
for (Map.Entry<String, int[]> entry : OFFSETS.entrySet()) {
String direction = entry.getKey();
int[] offset = entry.getValue();
Material required = getConfiguredMaterial(
"platform.layout." + direction
);
Block surrounding = center.getRelative(
offset[0], 0, offset[1]
);
if (surrounding.getType() != required) {
return false;
}
}
return true;
}
private Material getConfiguredMaterial(String path) {
String name = plugin.getConfig().getString(path, "");
Material material = Material.matchMaterial(name);
if (material == null || !material.isBlock()) {
throw new IllegalArgumentException(
"Invalid platform material at " + path + ": " + name
);
}
return material;
}
}
@@ -0,0 +1,67 @@
package com.rsscripting.rstaxi.stops;
import com.rsscripting.rstaxi.RSTaxi;
import org.bukkit.Location;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public final class StopManager {
private final RSTaxi plugin;
public StopManager(RSTaxi plugin) {
this.plugin = plugin;
}
public boolean exists(String name) throws SQLException {
String sql = "SELECT 1 FROM stops WHERE name = ?";
try (PreparedStatement statement = connection()
.prepareStatement(sql)) {
statement.setString(1, name);
try (ResultSet results = statement.executeQuery()) {
return results.next();
}
}
}
public boolean add(
String name,
String icon,
Location location
) throws SQLException {
String sql = """
INSERT OR IGNORE INTO stops
(name, icon, world, x, y, z, yaw, pitch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""";
try (PreparedStatement statement = connection()
.prepareStatement(sql)) {
statement.setString(1, name);
statement.setString(2, icon);
statement.setString(
3, location.getWorld().getName()
);
statement.setDouble(4, location.getX());
statement.setDouble(5, location.getY());
statement.setDouble(6, location.getZ());
statement.setFloat(7, location.getYaw());
statement.setFloat(8, location.getPitch());
return statement.executeUpdate() == 1;
}
}
private Connection connection() {
return plugin.getDatabaseManager().getConnection();
}
}
+35
View File
@@ -0,0 +1,35 @@
# RS-Taxi Configuration
# Language file for your server.
# If plugin cannot find needed message in selected file,
# it will default to english for that message.
# Example: en_us, de_de, fr_fr
language: "en_us"
# Platform Settings.
platform:
# Determines wether platforms are required before adding a destination
# true: A platform is required to be built following the "layout" defined in config.
# false: A platform is not required.
enabled: true
# The center block of the platform if required.
target-block: "TARGET"
# Protect the platform blocks from destruction.
# true: platforms cannot be broken.
# false: platforms can be broken.
protect-platforms: true
#Layout requirement for add a destination if enabled is set to true.
layout:
north: "OAK_PLANKS"
northeast: "OAK_PLANKS"
east: "OAK_PLANKS"
southeast: "OAK_PLANKS"
south: "OAK_PLANKS"
southwest: "OAK_PLANKS"
west: "OAK_PLANKS"
northwest: "OAK_PLANKS"
+40
View File
@@ -0,0 +1,40 @@
prefix: "&8[&6RS-Taxi&8] "
general:
no-permission: "&cYou don't have permission to do that."
player-only: "&cThis command can only be used by a player."
player-not-found: "&cPlayer not found."
invalid-usage: "&cInvalid command usage."
unknown-stop: "&cThe stop &e%stop% &cdoes not exist."
no-stops: "&eNo taxi stops have been created yet."
database:
connection-success: "&aDatabase connected successfully."
connection-error: "&cCould not connect to the database."
stop:
added: "&aStop &e%stop% &ahas been created."
updated: "&aStop &e%stop% &ahas been moved to your current location."
edited: "&aStop &e%stop% &ahas been updated."
deleted: "&aStop &e%stop% &ahas been removed."
already-exists: "&cA stop named &e%stop% &calready exists."
invalid-icon: "&cThe icon &e%icon% &cis invalid."
creation-error: "&cUnable to create the taxi stop. Check the server logs."
teleport:
success: "&aYou have arrived at &e%stop%&a."
confirmation:
update: "&eAre you sure you want to move stop &6%stop%&e? Confirm the command to proceed."
delete: "&eAre you sure you want to delete stop &6%stop%&e? Confirm the command to proceed."
expired: "&cThat confirmation has expired."
list:
header: "&6Available Taxi Stops:"
entry: "&e%icon% &f%stop%"
platform:
invalid: "&cYou must stand on a valid taxi platform to create a stop."
+29
View File
@@ -0,0 +1,29 @@
name: RS-Taxi
version: '${project.version}'
main: com.rsscripting.rstaxi.RSTaxi
api-version: '26.1.2'
description: Travel between named taxi stops.
author: RSScripting
commands:
cab:
description: Travel between registered taxi stops.
usage: /cab <stop|add|update|edit|remove|list>
permissions:
rs-cab.use:
description: Allows players to travel to taxi stops.
default: true
rs-cab.add:
description: Allows players to create taxi stops.
default: op
rs-cab.edit:
description: Allows players to update and edit taxi stops.
default: op
rs-cab.delete:
description: Allows players to delete taxi stops.
default: op