diff --git a/ic_plugin_coordinates/.mvn/jvm.config b/ic_plugin_coordinates/.mvn/jvm.config
new file mode 100644
index 0000000..e69de29
diff --git a/ic_plugin_coordinates/.mvn/maven.config b/ic_plugin_coordinates/.mvn/maven.config
new file mode 100644
index 0000000..e69de29
diff --git a/ic_plugin_coordinates/pom.xml b/ic_plugin_coordinates/pom.xml
new file mode 100644
index 0000000..50b70a0
--- /dev/null
+++ b/ic_plugin_coordinates/pom.xml
@@ -0,0 +1,127 @@
+
+
+ 4.0.0
+
+ nl.interestingcorner.coordinates
+ ic_plugin_coordinates
+ 1.0-SNAPSHOT
+
+ ic_plugin_coordinates
+
+ https://interesting-corner.nl
+
+
+ UTF-8
+ 17
+
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+
+
+
+
+ org.junit
+ junit-bom
+ 5.11.0
+ pom
+ import
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ com.google.errorprone
+ error_prone_annotations
+ 2.28.0
+ provided
+
+
+ org.spigotmc
+ spigot-api
+ 1.21.8-R0.1-SNAPSHOT
+ provided
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.50.3.0
+
+
+
+
+ ${project.basedir}/src/main/java
+
+
+ ${project.basedir}/src/main/resources
+
+ plugin.yml
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.4.0
+
+
+
+ maven-resources-plugin
+ 3.3.1
+
+
+ maven-compiler-plugin
+ 3.13.0
+
+
+ maven-surefire-plugin
+ 3.3.0
+
+
+ maven-jar-plugin
+ 3.4.2
+
+
+ ${db.username}
+ ${db.password}
+
+
+
+
+ maven-install-plugin
+ 3.1.2
+
+
+ maven-deploy-plugin
+ 3.1.2
+
+
+
+ maven-site-plugin
+ 3.12.1
+
+
+ maven-project-info-reports-plugin
+ 3.6.1
+
+
+
+
+
diff --git a/ic_plugin_coordinates/settings.xml b/ic_plugin_coordinates/settings.xml
new file mode 100644
index 0000000..1e175f7
--- /dev/null
+++ b/ic_plugin_coordinates/settings.xml
@@ -0,0 +1,14 @@
+
+
+
+ default
+
+ spigot
+ Z7nhqy+spigot-minecraft
+ ic_minecraft
+ localhost
+
+
+
+
\ No newline at end of file
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/App.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/App.java
new file mode 100644
index 0000000..eb159e3
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/App.java
@@ -0,0 +1,50 @@
+package nl.interestingcorner.coordinates;
+
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.PluginCommand;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import nl.interestingcorner.coordinates.commands.CoordinatesCommand;
+import nl.interestingcorner.coordinates.db.DatabaseManager;
+import nl.interestingcorner.coordinates.gui.CoordinatesGUIListener;
+
+public class App extends JavaPlugin {
+
+ @Override
+ public void onEnable() {
+ getLogger().info("Eyooo we boutta get lit!");
+
+ if (!DatabaseManager.INSTANCE.initialize(this)) {
+ getLogger().severe("Could not initialize database. Exiting!");
+ setEnabled(false);
+ return;
+ }
+ getLogger().info("Successfully initialized database");
+
+ getServer().getPluginManager().registerEvents(new CoordinatesGUIListener(), this);
+ registerCommands();
+
+ }
+
+ @Override
+ public void onDisable() {
+ getLogger().info("Eyooo we outta here!");
+
+ if (!DatabaseManager.INSTANCE.close()) {
+ getLogger().severe("Error while trying to close db connection");
+ }
+ }
+
+ private void registerCommands() {
+ registerSimgleCommand("ic-coords", new CoordinatesCommand());
+ }
+
+ private void registerSimgleCommand(String name, CommandExecutor executor)
+ {
+ PluginCommand pc = this.getCommand(name);
+ if (pc != null)
+ {
+ pc.setExecutor(executor);
+ }
+ }
+}
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/AddCoordinateCommandHandler.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/AddCoordinateCommandHandler.java
new file mode 100644
index 0000000..7590177
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/AddCoordinateCommandHandler.java
@@ -0,0 +1,160 @@
+package nl.interestingcorner.coordinates.commands;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import net.md_5.bungee.api.ChatColor;
+import net.md_5.bungee.api.chat.ComponentStyle;
+import net.md_5.bungee.api.chat.TextComponent;
+import net.md_5.bungee.api.chat.TranslatableComponent;
+import nl.interestingcorner.coordinates.db.Coordinate;
+import nl.interestingcorner.coordinates.db.DatabaseManager;
+import nl.interestingcorner.coordinates.db.MinecraftColor;
+
+public class AddCoordinateCommandHandler implements CoordinatesCommandHandler {
+
+ /**
+ * Minimum arguments needed for adding a command: name, description, color
+ */
+ private final int MIN_ARGS = 4;
+
+ private CommandSender sender;
+
+ @Override
+ public boolean handleCommand(CommandSender sender, String[] args) {
+ if (args.length < MIN_ARGS) {
+ return false;
+ }
+
+ this.sender = sender;
+
+ List argsWithoutFirstCommand = removeFirstArg(args);
+ if (argsWithoutFirstCommand.isEmpty()) {
+ return false;
+ }
+
+ List parsedArgs = parseArgs(argsWithoutFirstCommand.toArray(String[]::new));
+ if (parsedArgs.isEmpty()) {
+ return false;
+ }
+
+ sender.sendMessage("Args after parsing:");
+ sender.sendMessage("0:" + parsedArgs.get(0));
+ sender.sendMessage("1:" + parsedArgs.get(1));
+ sender.sendMessage("2:" + parsedArgs.get(2));
+
+ String name = parsedArgs.get(0);
+ String description = parsedArgs.get(1);
+ String color = parsedArgs.get(2);
+
+ if (!MinecraftColor.isValidColor(color)) {
+ sendInvalidColorMessage(color);
+ return false;
+ }
+
+ if (sender instanceof Player player) {
+ Location playerLocation = player.getLocation();
+ if (playerLocation == null) {
+ return false;
+ }
+
+ World playerWorld = playerLocation.getWorld();
+ if (playerWorld == null) {
+ return false;
+ }
+
+ //TODO check if item doesnt already exist, coordinate with same values, create isEquals method for coordinate
+ return DatabaseManager.INSTANCE
+ .addCoordinate(name, // name
+ description, // description
+ new Coordinate.Position(playerLocation.getBlockX(), playerLocation.getBlockY(),
+ playerLocation.getBlockZ()), // position
+ playerWorld.getEnvironment().equals(World.Environment.NETHER), // nether
+ playerWorld.getName(), // world
+ MinecraftColor.fromString(color) // color
+ );
+ }
+
+ return true;
+ }
+
+ private List removeFirstArg(String[] args) {
+ List onlyArgs = new ArrayList<>();
+
+ sender.sendMessage("removeFirstArg: size of args is " + String.valueOf(args.length));
+ for (int i = 1; i < args.length; i++) {
+ onlyArgs.add(args[i]);
+ }
+
+ sender.sendMessage("removeFirstArg: size of onlyArgs is " + String.valueOf(onlyArgs.size()));
+ return onlyArgs;
+ }
+
+ private List parseArgs(String[] args) {
+ List res = new ArrayList<>();
+ StringBuilder currentArg = new StringBuilder();
+ boolean inQuotes = false;
+
+ for (String arg : args) {
+ if (inQuotes) {
+ if (arg.endsWith("\"")) {
+ // end of quoted entry
+ // add final arg to result
+ String replaced = arg.replace("\"", "");
+ currentArg.append(" ").append(replaced);
+ res.add(currentArg.toString());
+
+ // clear current arg
+ currentArg.setLength(0);
+ inQuotes = false;
+ } else {
+ currentArg.append(" ").append(arg);
+ }
+ } else {
+ if (arg.startsWith("\"")) {
+ String replaced = arg.replace("\"", "");
+
+ if (arg.endsWith("\"")) {
+ // if the current arg starts and ends with a ", just add the arg to the result
+ res.add(replaced);
+ } else {
+ // if the current arg doesn't end with a ", it is quoted and we need to handle
+ // the remainder
+ inQuotes = true;
+ currentArg.append(replaced);
+ }
+ } else {
+ res.add(arg);
+ }
+ }
+ }
+
+ return res;
+ }
+
+ private void sendInvalidColorMessage(String color)
+ {
+ TextComponent finalMessage = new TextComponent();
+ TextComponent colorMessage = new TextComponent("Color ");
+ colorMessage.setColor(ChatColor.RED);
+ finalMessage.addExtra(colorMessage);
+
+ TextComponent enteredColor = new TextComponent(color);
+ enteredColor.setColor(ChatColor.AQUA);
+ finalMessage.addExtra(enteredColor);
+
+ TextComponent endOfMessage = new TextComponent(" is not a valid color! Please use a regular minecraft color");
+ endOfMessage.setColor(ChatColor.RED);
+ finalMessage.addExtra(endOfMessage);
+ sender.spigot().sendMessage(finalMessage);
+ }
+
+}
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommand.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommand.java
new file mode 100644
index 0000000..dd17308
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommand.java
@@ -0,0 +1,30 @@
+package nl.interestingcorner.coordinates.commands;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+
+public class CoordinatesCommand implements CommandExecutor{
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ StringBuilder sb = new StringBuilder("Got command " + command.getName() + " label " + label + " and args ");
+ for (String string : args) {
+ sb.append(" ");
+ sb.append(string);
+ sb.append(" ");
+ }
+
+ sender.sendMessage("Args length: " + String.valueOf(args.length));
+ sender.sendMessage(sb.toString());
+
+ // strategy design pattern
+ return switch (args[0]) {
+ case "get" -> new GetCoordinatesCommandHandler().handleCommand(sender, args);
+ case "add" -> new AddCoordinateCommandHandler().handleCommand(sender, args);
+ default -> false;
+ };
+ }
+
+
+}
\ No newline at end of file
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommandHandler.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommandHandler.java
new file mode 100644
index 0000000..8e21f1d
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommandHandler.java
@@ -0,0 +1,7 @@
+package nl.interestingcorner.coordinates.commands;
+
+import org.bukkit.command.CommandSender;
+
+public interface CoordinatesCommandHandler {
+ boolean handleCommand(CommandSender sender, String[] args);
+}
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/GetCoordinatesCommandHandler.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/GetCoordinatesCommandHandler.java
new file mode 100644
index 0000000..607cab2
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/GetCoordinatesCommandHandler.java
@@ -0,0 +1,46 @@
+package nl.interestingcorner.coordinates.commands;
+
+import java.util.List;
+
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import nl.interestingcorner.coordinates.db.Coordinate;
+import nl.interestingcorner.coordinates.db.DatabaseManager;
+import nl.interestingcorner.coordinates.gui.CoordinatesGUI;
+
+public class GetCoordinatesCommandHandler implements CoordinatesCommandHandler {
+
+ @Override
+ public boolean handleCommand(CommandSender sender, String[] args) {
+ if (sender instanceof Player player) {
+ List coords;
+ if (args.length < 2) {
+ coords = DatabaseManager.INSTANCE.getAllCoordinates();
+ } else if (args[1].equalsIgnoreCase("world")) {
+ String world = player.getWorld().getName();
+ coords = DatabaseManager.INSTANCE.getAllCoordinates(world);
+ } else {
+ sender.sendMessage("Invalid argument: " + args[1]);
+ return false;
+ }
+
+ if (coords.isEmpty()) {
+ player.sendMessage("No coordinates found! Add some with the §3/ic-coords §5add §fcommand.");
+ return true;
+ }
+
+ StringBuilder res = new StringBuilder("Coordinates: ");
+
+ for (Coordinate coordinate : coords) {
+ res.append(coordinate.toString());
+ }
+
+ player.sendMessage(res.toString());
+
+ CoordinatesGUI.open(player, coords);
+ }
+ return true;
+ }
+
+}
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/Coordinate.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/Coordinate.java
new file mode 100644
index 0000000..fb72c83
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/Coordinate.java
@@ -0,0 +1,109 @@
+package nl.interestingcorner.coordinates.db;
+
+/**
+ * +-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+-----+---------+----------------+
+ * | Field | Type | Null | Key | Default | Extra |
+ * +-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+-----+---------+----------------+
+ * | id | int | NO | PRI | NULL | auto_increment |
+ * | name | varchar(255) | NO | | NULL | |
+ * | x | int | NO | | NULL | |
+ * | y | int | NO | | NULL | |
+ * | z | int | NO | | NULL | |
+ * | description | varchar(255) | NO | | NULL | |
+ * | color |
+ * enum('dark_red','red','black','blue','aqua','dark_aqua','green','gold','dark_purple','light_purple','yellow','dark_green','gray','dark_gray','white','dark_blue')
+ * | YES | | white | |
+ * | nether | tinyint(1) | YES | | 0 | |
+ * +-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+-----+---------+----------------+
+ */
+
+
+/**
+ * Class to represent a coordinate to teleport to
+ */
+public class Coordinate {
+ /**
+ * auto-generated ID
+ */
+ public int id;
+
+ /**
+ * name of the coordinate
+ */
+ public String name;
+
+ /**
+ * description of the coordinate
+ */
+ public String description;
+
+ /**
+ * position in x,y,z
+ */
+ public Position position;
+
+ /**
+ * If the coordinate is in the nether
+ */
+ public boolean nether;
+
+ /**
+ * The color to display the coordinate name in
+ */
+ public MinecraftColor color;
+
+ /**
+ * the world for this coordinate. This corresponds to the name of the world in
+ * Multiverse
+ */
+ public String world;
+
+ public Coordinate(int id, String name, String description, Position position, boolean nether, String world) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.position = position;
+ this.nether = nether;
+ this.world = world;
+ this.color = MinecraftColor.WHITE;
+ }
+
+ public Coordinate(int id, String name, String description, Position position, boolean nether, String world,
+ MinecraftColor color) {
+ this(id, name, description, position, nether, world);
+ this.color = color;
+ }
+
+ public record Position(int x, int y, int z) {
+ }
+
+ public static String createTableStatement = """
+ CREATE TABLE IF NOT EXISTS coordinates (
+ id INTEGER PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ description VARCHAR(255),
+ x INT NOT NULL,
+ y INT NOT NULL,
+ z INT NOT NULL,
+ nether BOOLEAN NOT NULL DEFAULT FALSE,
+ color VARCHAR(255),
+ world VARCHAR(255) DEFAULT "white"
+ );
+ """;
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("{ ");
+ sb.append("id: ").append(id)
+ .append(", name: '").append(name).append('\'')
+ .append(", description: '").append(description).append('\'')
+ .append(", position: { x: ").append(position.x())
+ .append(", y: ").append(position.y())
+ .append(", z: ").append(position.z()).append(" }")
+ .append(", nether: ").append(nether)
+ .append(", color: ").append(color != null ? color.toString() : "null")
+ .append(", world: '").append(world).append('\'')
+ .append(" }");
+ return sb.toString();
+ }
+}
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/DatabaseManager.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/DatabaseManager.java
new file mode 100644
index 0000000..d4784ce
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/DatabaseManager.java
@@ -0,0 +1,216 @@
+package nl.interestingcorner.coordinates.db;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.bukkit.plugin.java.JavaPlugin;
+
+public enum DatabaseManager {
+
+ INSTANCE;
+
+ private Connection connection;
+ private JavaPlugin app;
+
+ /**
+ * Initializes the database. Creates the file and sets up the db structure.
+ *
+ * @param app the app to use for logging
+ * @return true if the database was initialized successfully. False if not.
+ */
+ public boolean initialize(JavaPlugin app) {
+ this.app = app;
+
+ if (!setupDatabase()) {
+ return false;
+ }
+
+ if (!initializeTables()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Closes the connectino to the database
+ *
+ * @return true if the connection closed succesfully, false if not.
+ */
+ public boolean close() {
+ try {
+ if (this.connection != null && !this.connection.isClosed()) {
+ this.connection.close();
+ }
+ } catch (SQLException ex) {
+ this.app.getLogger().log(Level.SEVERE, "Error while closing the databse connection: {0}", ex.getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets all coordinates from the database
+ *
+ * @return a list with all coordinates from the database. Empty if an error
+ * occurred.
+ */
+ public List getAllCoordinates() {
+ List result = new ArrayList<>();
+ try {
+ PreparedStatement getAllCoordinatesStatement = this.connection
+ .prepareStatement("SELECT * FROM coordinates");
+
+ result = convertResultsToCoordinates(getAllCoordinatesStatement.executeQuery());
+
+ } catch (SQLException e) {
+ this.app.getLogger().log(Level.SEVERE, "Could not get coordinates! {0}", e.getMessage());
+ }
+ return result;
+ }
+
+ public List getAllCoordinates(String world) {
+ List result = new ArrayList<>();
+ try {
+ PreparedStatement getAllCoordinatesStatement = this.connection
+ .prepareStatement("SELECT * FROM coordinates WHERE world = ?");
+ getAllCoordinatesStatement.setString(1, world);
+
+ result = convertResultsToCoordinates(getAllCoordinatesStatement.executeQuery());
+
+ } catch (SQLException e) {
+ this.app.getLogger().log(Level.SEVERE, "Could not get coordinates for world {0}! {1}",
+ new Object[] { world, e.getMessage() });
+ }
+ return result;
+ }
+
+ public List convertResultsToCoordinates(ResultSet results) throws SQLException {
+ List coordinates = new ArrayList<>();
+ while (results.next()) {
+
+ MinecraftColor color = MinecraftColor.fromString(results.getString("color"));
+ if (color == null) {
+ color = MinecraftColor.WHITE;
+ }
+
+ Coordinate c = new Coordinate(
+ results.getInt("id"),
+ results.getString("name"),
+ results.getString("description"),
+ new Coordinate.Position(
+ results.getInt("x"),
+ results.getInt("y"),
+ results.getInt("z")),
+ results.getBoolean("nether"),
+ results.getString("world"),
+ color);
+
+ coordinates.add(c);
+ }
+
+ return coordinates;
+ }
+
+ /**
+ * Adds a new coordinate to the database with the given parameters.
+ *
+ * @param name the name of the coordinate
+ * @param description a short description of the coordinate
+ * @param position the position of the coordinate where the player will
+ * spwan when they teleport to the coordinate
+ * @param nether if the coordinate is in the nether
+ * @param world the Multiverse world the coordinate belongs to
+ * @param color the color to display for the coordinate name
+ * @return true if the command was added successfully, false otherwise
+ */
+ public boolean addCoordinate(String name, String description, Coordinate.Position position, boolean nether,
+ String world, MinecraftColor color) {
+ String sql = "INSERT INTO coordinates (name, description, x, y, z, nether, color, world) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
+ try {
+ PreparedStatement addCoordinateStatement = this.connection.prepareStatement(sql);
+ addCoordinateStatement.setString(1, name);
+ addCoordinateStatement.setString(2, description);
+ addCoordinateStatement.setInt(3, position.x());
+ addCoordinateStatement.setInt(4, position.y());
+ addCoordinateStatement.setInt(5, position.z());
+ addCoordinateStatement.setBoolean(6, nether);
+ addCoordinateStatement.setString(7, color.name());
+ addCoordinateStatement.setString(8, world);
+
+ addCoordinateStatement.executeUpdate();
+ } catch (SQLException e) {
+ this.app.getLogger().log(Level.SEVERE, "Error adding coordinate to database: {0}", e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sets up the connectino to the database. Creates a new .db file if it
+ * doesn't exist yet.
+ *
+ * @return true if the connection was set up successfully. False if not.
+ */
+ private boolean setupDatabase() {
+ if (!this.app.getDataFolder().exists()) {
+ if (!this.app.getDataFolder().mkdirs()) {
+ app.getLogger().severe("Could not create data folder");
+ return false;
+ }
+ }
+
+ File dbFile = new File(app.getDataFolder(), "database.db");
+ String url = "jdbc:sqlite:" + dbFile.getAbsolutePath();
+
+ try {
+ this.connection = DriverManager.getConnection(url);
+ } catch (SQLException e) {
+ this.app.getLogger().log(Level.SEVERE, "Could not connect to database file {0}: {1}",
+ new Object[] { dbFile, e.getMessage() });
+ }
+
+ this.app.getLogger().log(Level.INFO, "Connected to SQLite database: {0}", dbFile.getName());
+
+ return true;
+ }
+
+ /**
+ * initializes the tables for the database.
+ *
+ * @return true if the tables were initialized successfully. False if not.
+ */
+ private boolean initializeTables() {
+ try {
+ PreparedStatement createTableStatement = this.connection.prepareStatement(Coordinate.createTableStatement);
+ createTableStatement.executeUpdate();
+ this.app.getLogger().fine("Executed create table statement");
+
+ PreparedStatement getTablesStatement = this.connection.prepareStatement(
+ "SELECT name FROM sqlite_master WHERE type='table'");
+ ResultSet results = getTablesStatement.executeQuery();
+
+ while (results.next()) {
+ this.app.getLogger().log(Level.INFO, "Found table: {0}", results.getString("name"));
+ }
+ } catch (SQLException e) {
+ this.app.getLogger().log(Level.SEVERE, "Could not create table: {0}", e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/MinecraftColor.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/MinecraftColor.java
new file mode 100644
index 0000000..11fbd1f
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/MinecraftColor.java
@@ -0,0 +1,135 @@
+package nl.interestingcorner.coordinates.db;
+
+import java.util.HashMap;
+
+import org.bukkit.Material;
+
+public enum MinecraftColor {
+ DARK_RED("dark_red"),
+ RED("red"),
+ BLACK("black"),
+ BLUE("blue"),
+ AQUA("aqua"),
+ DARK_AQUA("dark_aqua"),
+ GREEN("green"),
+ GOLD("gold"),
+ DARK_PURPLE("dark_purple"),
+ LIGHT_PURPLE("light_purple"),
+ YELLOW("yellow"),
+ DARK_GREEN("dark_green"),
+ GRAY("gray"),
+ DARK_GRAY("dark_gray"),
+ WHITE("white"),
+ DARK_BLUE("dark_blue");
+
+ private final String name;
+
+ private static final HashMap materialsMap = new HashMap<>() {{
+ put(DARK_RED, Material.RED_WOOL);
+ put(RED, Material.RED_WOOL);
+ put(BLACK, Material.BLACK_WOOL);
+ put(BLUE, Material.BLUE_WOOL);
+ put(AQUA, Material.LIGHT_BLUE_WOOL);
+ put(DARK_AQUA, Material.CYAN_WOOL);
+ put(GREEN, Material.GREEN_WOOL);
+ put(GOLD, Material.YELLOW_WOOL);
+ put(DARK_PURPLE, Material.PURPLE_WOOL);
+ put(LIGHT_PURPLE, Material.MAGENTA_WOOL);
+ put(YELLOW, Material.YELLOW_WOOL);
+ put(DARK_GREEN, Material.LIME_WOOL);
+ put(GRAY, Material.LIGHT_GRAY_WOOL);
+ put(DARK_GRAY, Material.GRAY_WOOL);
+ put(WHITE, Material.WHITE_WOOL);
+ put(DARK_BLUE, Material.BLUE_WOOL);
+ }};
+
+ private static final HashMap colorCodesMap = new HashMap<>() {{
+ put(DARK_RED, "§4");
+ put(RED, "§c");
+ put(BLACK, "§0");
+ put(BLUE, "§9");
+ put(AQUA, "§b");
+ put(DARK_AQUA, "§3");
+ put(GREEN, "§a");
+ put(GOLD, "§6");
+ put(DARK_PURPLE, "§5");
+ put(LIGHT_PURPLE, "§d");
+ put(YELLOW, "§e");
+ put(DARK_GREEN, "§2");
+ put(GRAY, "§7");
+ put(DARK_GRAY, "§8");
+ put(WHITE, "§f");
+ put(DARK_BLUE, "§1");
+ }};
+
+ MinecraftColor(String name) {
+ this.name = name;
+ }
+
+ public boolean equalsName(String otherName) {
+ return name.equalsIgnoreCase(otherName);
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+
+ /**
+ * Finds a MinecraftColor from a string value
+ *
+ * @param name the name to find the MinecraftColor from
+ * @return the MinecraftColor if found, null if not found.
+ */
+ public static MinecraftColor fromString(String name) {
+ if (name == null) {
+ return WHITE;
+ }
+
+ for (MinecraftColor color : MinecraftColor.values()) {
+ if (color.equalsName(name)) {
+ return color;
+ }
+ }
+ return WHITE;
+ }
+
+ /**
+ * Checks if a given name is a valid color value.
+ * @param name the name to check
+ * @return true if it's a valid color value, false otherwise
+ */
+ public static boolean isValidColor(String name)
+ {
+ if (name == null)
+ {
+ return false;
+ }
+
+ for (MinecraftColor color : values()) {
+ if (color.equalsName(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Converts this MinecraftColor to a corresponding Material.
+ *
+ * @return the Material corresponding to this MinecraftColor
+ */
+ public Material toMaterial() {
+ return materialsMap.get(this);
+ }
+
+ /**
+ * Converts this MinecraftColor to a corresponding Minecraft color code.
+ * @return
+ */
+ public String toColorCode() {
+ return colorCodesMap.get(this);
+ }
+}
+
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUI.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUI.java
new file mode 100644
index 0000000..626afc5
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUI.java
@@ -0,0 +1,68 @@
+package nl.interestingcorner.coordinates.gui;
+
+import java.util.List;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import nl.interestingcorner.coordinates.db.Coordinate;
+import nl.interestingcorner.coordinates.db.MinecraftColor;
+
+public class CoordinatesGUI {
+ public static void open(Player player, List coordinates) {
+
+ int coordinatesAmount = coordinates.size();
+ int size;
+ if (coordinatesAmount <= 9) {
+ size = 9;
+ } else {
+ // TODO handle if size is more than 54 - 9, add pagination
+ /**
+ * if more than 45, add pages. Split by 45 items per page.
+ * use bottom row leftmost and rightmost item for navigation.
+ * use paper item for page number display in middle.
+ * use lore of navigation items clicked to handle showing next/previous page.
+ * use player.getOpeninventory() to get current inventory and update or close it.
+ */
+
+ // round up to nearest multiple of 9
+ size = ((coordinatesAmount / 9) + 1) * 9;
+ player.sendMessage("Size: " + size);
+ }
+
+ // Create an inventory with 9 slots and a title
+ Inventory gui = Bukkit.createInventory(null, size, "Coordinates Menu");
+
+ for (Coordinate coordinate : coordinates) {
+ ItemStack item = createCoordinateItem(coordinate);
+ gui.addItem(item);
+ }
+
+ // Open the GUI for the player
+ player.openInventory(gui);
+ }
+
+ private static ItemStack createCoordinateItem(Coordinate coordinate) {
+ ItemStack itemStack = new ItemStack(coordinate.color.toMaterial());
+ ItemMeta meta = itemStack.getItemMeta();
+ if (meta == null) {
+ return itemStack;
+ }
+ meta.setDisplayName(coordinate.color.toColorCode() + coordinate.name);
+ meta.setLore(List.of(
+ MinecraftColor.WHITE.toColorCode() + coordinate.description,
+ coordinate.nether ? MinecraftColor.RED.toColorCode() + "Nether Coordinate" : "Overworld Coordinate",
+ "X: " + coordinate.position.x(),
+ "Y: " + coordinate.position.y(),
+ "Z: " + coordinate.position.z(),
+ "World: " + coordinate.world
+ ));
+ itemStack.setItemMeta(meta);
+
+ return itemStack;
+ }
+}
diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUIListener.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUIListener.java
new file mode 100644
index 0000000..ef040b6
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUIListener.java
@@ -0,0 +1,33 @@
+package nl.interestingcorner.coordinates.gui;
+
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.entity.Player;
+import org.bukkit.ChatColor;
+
+public class CoordinatesGUIListener implements Listener{
+
+ @EventHandler
+ public void onInventoryClick(InventoryClickEvent event) {
+
+ // Check if this is our custom GUI
+ if (event.getView().getTitle().equals("Coordinates Menu")) {
+ event.setCancelled(true); // Prevent taking the item
+
+ if (event.getCurrentItem() == null) return;
+
+ Player player = (Player) event.getWhoClicked();
+
+ switch (event.getSlot()) {
+ case 0 -> {
+ player.sendMessage(ChatColor.GREEN + "You clicked Add Coordinate!");
+ player.closeInventory();
+ // Here you could open another GUI or run a command
+ }
+ // Add other cases for other slots if needed
+ }
+ }
+ }
+
+}
diff --git a/ic_plugin_coordinates/src/main/resources/plugin.yml b/ic_plugin_coordinates/src/main/resources/plugin.yml
new file mode 100644
index 0000000..a6cb5f3
--- /dev/null
+++ b/ic_plugin_coordinates/src/main/resources/plugin.yml
@@ -0,0 +1,9 @@
+main: nl.interestingcorner.coordinates.App
+name: IC-coords
+version: 0.1
+api-version: 1.21
+
+commands:
+ ic-coords:
+ description: Main command for the coordinates
+ usage: /ic-coords get | add
\ No newline at end of file
diff --git a/ic_plugin_coordinates/src/test/java/nl/interestingcorner/AppTest.java b/ic_plugin_coordinates/src/test/java/nl/interestingcorner/AppTest.java
new file mode 100644
index 0000000..6d3028e
--- /dev/null
+++ b/ic_plugin_coordinates/src/test/java/nl/interestingcorner/AppTest.java
@@ -0,0 +1,19 @@
+package nl.interestingcorner;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest {
+
+ /**
+ * Rigorous Test :-)
+ */
+ @Test
+ public void shouldAnswerWithTrue() {
+ assertTrue(true);
+ }
+}