diff --git a/README.md b/README.md index 9ec7286..dd1d4d2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ The Core plugin for the Minecraft Minigame server, The Grubnest 2. Look at the To-do column and find a task that is assigned to you 3. Find an epic task. Epic tasks are tasks that have several subtasks that you must complete. Start here 4. Complete your task. For an epic task, start here but for each separate task issue -5. Create a new branch off of the `development`branch for your task. If it's a bug, the branch name should start with "hotfix". If it's an enhancement it should start with "feature". +5. Fork the development branch +5. Create a new branch off of the `development`branch you just cloned. If it's a bug, the branch name should start with "hotfix". If it's an enhancement it should start with "feature". 6. Make any changes you need to implement the issue 7. Create a Pull Request to merge your changes into the `development` branch. 8. Wait for your code to be approved. If you want to work on another issue in the meantime that requires work you did in this issue, when you make your new branch for the next task, make it off of the branch that is under review and not right off `development`. You can rebase the branch after it is merged. diff --git a/pom.xml b/pom.xml index fe9f9f1..1fba3a2 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,11 @@ provided remapped-mojang + + com.zaxxer + HikariCP + 5.0.1 + compile + - \ No newline at end of file diff --git a/src/main/java/com/grubnest/game/core/DatabaseHandler/ConnectionPoolManager.java b/src/main/java/com/grubnest/game/core/DatabaseHandler/ConnectionPoolManager.java new file mode 100644 index 0000000..cfe689a --- /dev/null +++ b/src/main/java/com/grubnest/game/core/DatabaseHandler/ConnectionPoolManager.java @@ -0,0 +1,93 @@ +package com.grubnest.game.core.DatabaseHandler; + +import com.grubnest.game.core.DatabaseHandler.Utils.Deactivated; +import com.grubnest.game.core.DatabaseHandler.Utils.Disabler; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** Handles connection from mysql and uses HikariCP + * @author tamilpp25 + * @version 1.0 at 15-5-2022 + */ +public class ConnectionPoolManager implements Deactivated { + private final MySQLData data; + private HikariDataSource dataSource; + + + /** + * Construct mysql HikariCP + * @param data MysqlDATA + */ + public ConnectionPoolManager(MySQLData data) { + this.data = data; + setupPool(); + Disabler.getInstance().registerDeactivated(this); + } + + /** + * Setup mysql connection Pool with HikariCP to get multiple connections. + */ + private void setupPool() { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl( + "jdbc:mysql://" + + data.HOST + + ":" + + data.PORT+ + "/" + + data.DATABASE + ); + config.setDriverClassName("com.mysql.jdbc.Driver"); + config.setUsername(data.USERNAME); + config.setPassword(data.PASSWORD); + config.setMinimumIdle(data.minimumConnections); + config.setMaximumPoolSize(data.maximumConnections); + config.setConnectionTimeout(data.connectionTimeout); + config.setConnectionTestQuery("SELECT 1"); + dataSource = new HikariDataSource(config); + } + + /** + * Get MySQL Pool connection + * @return Sql Connection + * @throws SQLException if some error.. + */ + public Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + /** + * Temp close method can close using try-with statement too + * Recommending using try-with statement to close automatically! + * @param conn Connection + * @param ps PreparedStatement + * @param res ResultSet + */ + public void close(Connection conn, PreparedStatement ps, ResultSet res) { + if (conn != null) try { conn.close(); } catch (SQLException ignored) {} + if (ps != null) try { ps.close(); } catch (SQLException ignored) {} + if (res != null) try { res.close(); } catch (SQLException ignored) {} + } + + /** + * Close pool when everything done... + */ + public void closePool() { + if (dataSource != null && !dataSource.isClosed()) { + dataSource.close(); + } + } + + /** + * Auto disable classes on disable if multiple instance are there too + */ + @Override + public void onDisable() { + closePool(); + } +} diff --git a/src/main/java/com/grubnest/game/core/DatabaseHandler/MySQL.java b/src/main/java/com/grubnest/game/core/DatabaseHandler/MySQL.java new file mode 100644 index 0000000..5212c0e --- /dev/null +++ b/src/main/java/com/grubnest/game/core/DatabaseHandler/MySQL.java @@ -0,0 +1,55 @@ +package com.grubnest.game.core.DatabaseHandler; + +import com.grubnest.game.core.GrubnestCorePlugin; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Main class for handling mysql data + * make your methods for getting / setting here in async! + * new MySQL class can be initialized outside main class too! + * + * Recommend using getMySQL method from main class as it properly does the work and closes connection on disable! + * make a new instance of this if you really know what you are doing! + * + * @author tamilpp25 + * @version 1.0 at 15-5-2022 + */ +public class MySQL extends ConnectionPoolManager { + public MySQL(MySQLData data) { + super(data); + } + + /** + * This is an example table query for creating queries make a similar method like this! + * MAKE SURE that all queries are run async so that it doesn't freeze the main thread + * you don't have to worry about closing a connection since its auto closed by + */ + public void testTableQuery() { + GrubnestCorePlugin.getInstance().getServer().getScheduler().runTaskAsynchronously(GrubnestCorePlugin.getInstance(),()->{ + try (Connection conn = getConnection()){ + PreparedStatement statement = conn.prepareStatement( + "CREATE TABLE IF NOT EXISTS `Test` " + + "(" + + "UUID varchar(30)" + + ")" + ); + statement.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + } + + //You make method to fetch / add data in this class + + /** + * Close pool on plugin Disable + */ + public void onDisable(){ + closePool(); + } + +} diff --git a/src/main/java/com/grubnest/game/core/DatabaseHandler/MySQLData.java b/src/main/java/com/grubnest/game/core/DatabaseHandler/MySQLData.java new file mode 100644 index 0000000..93eead0 --- /dev/null +++ b/src/main/java/com/grubnest/game/core/DatabaseHandler/MySQLData.java @@ -0,0 +1,28 @@ +package com.grubnest.game.core.DatabaseHandler; + +/** MysqlData object to store credentials etc + * @author tamilpp25 + * @version 1.0 at 15-5-2022 + */ +public class MySQLData { + public final String HOST; + public final String USERNAME; + public final String PASSWORD; + public final String PORT; + public final String DATABASE; + + public final int minimumConnections; + public final int maximumConnections; + public final long connectionTimeout; + + public MySQLData(String host, String username, String password, String port, String database, int minimumConnections, int maximumConnections, long connectionTimeout) { + HOST = host; + USERNAME = username; + PASSWORD = password; + PORT = port; + DATABASE = database; + this.minimumConnections = minimumConnections; + this.maximumConnections = maximumConnections; + this.connectionTimeout = connectionTimeout; + } +} diff --git a/src/main/java/com/grubnest/game/core/DatabaseHandler/Utils/Deactivated.java b/src/main/java/com/grubnest/game/core/DatabaseHandler/Utils/Deactivated.java new file mode 100644 index 0000000..007acdb --- /dev/null +++ b/src/main/java/com/grubnest/game/core/DatabaseHandler/Utils/Deactivated.java @@ -0,0 +1,12 @@ +package com.grubnest.game.core.DatabaseHandler.Utils; + +/** + * Utility class to prevent Memory leak. + * @author tamilpp25 + * @version 1.0 at 15-5-2022 + */ +public interface Deactivated { + + void onDisable(); + +} diff --git a/src/main/java/com/grubnest/game/core/DatabaseHandler/Utils/Disabler.java b/src/main/java/com/grubnest/game/core/DatabaseHandler/Utils/Disabler.java new file mode 100644 index 0000000..c15c82a --- /dev/null +++ b/src/main/java/com/grubnest/game/core/DatabaseHandler/Utils/Disabler.java @@ -0,0 +1,44 @@ +package com.grubnest.game.core.DatabaseHandler.Utils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Disabler instance to auto disable any used instance of sql + * @author tamilpp25 + * @version 1.0 at 15-5-2022 + */ +public class Disabler { + private static Disabler instance = null; + private final List toDisable = new ArrayList<>(); + + public static Disabler getInstance() { + if (instance != null) return instance; + return instance = new Disabler(); + } + + /** + * close all active mysql connections + */ + public void disableAll() { + for (Deactivated toDeactivated : toDisable) + toDeactivated.onDisable(); + } + + /** + * Disable specific class with mysql connection + * @param sqlinstance SQL instance + */ + public void disable(Deactivated sqlinstance) { + sqlinstance.onDisable(); + } + + /** + * Register a class as mysql connection pool + * @param pDeactivated Class to be registered + */ + public void registerDeactivated(Deactivated pDeactivated) { + toDisable.add(pDeactivated); + } + +} \ No newline at end of file diff --git a/src/main/java/com/grubnest/game/core/GrubnestCorePlugin.java b/src/main/java/com/grubnest/game/core/GrubnestCorePlugin.java index f16b6bd..7acf009 100644 --- a/src/main/java/com/grubnest/game/core/GrubnestCorePlugin.java +++ b/src/main/java/com/grubnest/game/core/GrubnestCorePlugin.java @@ -1,15 +1,27 @@ package com.grubnest.game.core; +import com.grubnest.game.core.DatabaseHandler.MySQL; +import com.grubnest.game.core.DatabaseHandler.MySQLData; +import com.grubnest.game.core.DatabaseHandler.Utils.Disabler; import org.bukkit.plugin.java.JavaPlugin; public class GrubnestCorePlugin extends JavaPlugin { - + private MySQL sql; + private static GrubnestCorePlugin instance; /** * Runs when plugin is enabled */ @Override public void onEnable() { + instance = this; + + //Register Plugin messaging channels on enable + this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new PluginMessage()); + getConfig().options().copyDefaults(true); + saveConfig(); + sql = new MySQL(dataInitializer()); } /** @@ -17,6 +29,42 @@ public void onEnable() { */ @Override public void onDisable() { + Disabler.getInstance().disableAll(); + //Unregister channels on disable + this.getServer().getMessenger().unregisterOutgoingPluginChannel(this); + this.getServer().getMessenger().unregisterIncomingPluginChannel(this); + } + + /** + * Initialize data from config.yml + * @return MySQLData + */ + private MySQLData dataInitializer(){ + String host = getConfig().getString("Database.hostname"); + String port = getConfig().getString("Database.port"); + String database = getConfig().getString("Database.database"); + String username = getConfig().getString("Database.username"); + String password = getConfig().getString("Database.password"); + + int minimumConnections = getConfig().getInt("Database.minimumConnections"); + int maximumConnections = getConfig().getInt("Database.maximumConnections"); + long connectionTimeout = getConfig().getLong("Database.connectionTimeout"); + return new MySQLData(host,username,password,port,database,minimumConnections,maximumConnections,connectionTimeout); + } + /** + * Get SQL Object + * @return SQL object + */ + public MySQL getMySQL(){ + return sql; + } + + /** + * Get Plugin Instance + * @return Plugin Instance + */ + public static GrubnestCorePlugin getInstance(){ + return instance; } } diff --git a/src/main/java/com/grubnest/game/core/PluginMessage.java b/src/main/java/com/grubnest/game/core/PluginMessage.java new file mode 100644 index 0000000..157b392 --- /dev/null +++ b/src/main/java/com/grubnest/game/core/PluginMessage.java @@ -0,0 +1,312 @@ +package com.grubnest.game.core; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; + +import java.io.*; + +/** + * @author tamilpp25 + * @created 15/05/2022 + */ +public class PluginMessage implements PluginMessageListener { + + /** + * Plugin message receiver + * triggers when there is a response from a subchannel + * //todo fix forward method later + * @param channel Subchannel as given in spigot docs + * @param player Player that send plugin message to server + * @param message Message + */ + @Override + public void onPluginMessageReceived(String channel, Player player, byte[] message) { + if (!channel.equals("BungeeCord")) { + return; + } + ByteArrayDataInput in = ByteStreams.newDataInput(message); + String subchannel = in.readUTF(); + if (subchannel.equals("PlayerCount")) { + String server = in.readUTF(); // Name of server, as given in the arguments + int playercount = in.readInt(); // list of players + } else if(subchannel.equals("PlayerList")){ + String server = in.readUTF(); // The name of the server you got the player list of, as given in args. + String[] playerList = in.readUTF().split(", "); // gets all players in the server or ALL for whole proxy + } else if(subchannel.equals("GetServers")){ + String[] serverList = in.readUTF().split(", "); // Gets the list of servers defined + } else if(subchannel.equals("GetServer")){ + String servername = in.readUTF(); // get the current server name + }else if(subchannel.equals("UUID")){ + String uuid = in.readUTF(); // get the uuid of sent player + }else if(subchannel.equals("UUIDOther")){ + String playerName = in.readUTF(); // player name + String uuid = in.readUTF(); // player uuid + } else if(subchannel.equals("ServerIP")){ + String serverName = in.readUTF(); + String ip = in.readUTF(); + int port = in.readUnsignedShort(); + }else if(subchannel.equals("Custom sub channel")){ // todo change later the name for custom sub channel + short len = in.readShort(); + byte[] msgbytes = new byte[len]; + in.readFully(msgbytes); + + DataInputStream msgin = new DataInputStream(new ByteArrayInputStream(msgbytes)); + try { + String somedata = msgin.readUTF(); // Read the data in the same way you wrote it + short somenumber = msgin.readShort(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * Connects a player to said subserver. + * + * @receiver player to be teleported + * + * @param player Player to send plugin message to + * @param server name of server to connect to, as defined in BungeeCord config.yml + */ + public static void connect(Player player, String server){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("Connect"); + output.writeUTF(server); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + player.sendMessage(ChatColor.DARK_GRAY + "sending you to " + server + "..."); + } + + /** + * Connect a named player to said subserver + * + * @receiver any player + * + * @param player Player to send plugin message to + * @param named_player name of the player to teleport + * @param server name of server to connect to, as defined in BungeeCord config.yml + */ + public static void connectOther(Player player, String named_player, String server){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("ConnectOther"); + output.writeUTF(named_player); + output.writeUTF(server); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Get the amount of players on a certain server, or on ALL the servers. + * + * @receiver any player + * + * @param player packet carrier + * @param server the name of the server to get the player count of, or ALL to get the global player count + */ + public static void getPlayerCount(Player player, String server){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("PlayerCount"); + output.writeUTF(server); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Get a list of players connected on a certain server, or on ALL of the servers. + * + * @receiver any player + * + * @param player packet carrier + * @param server the name of the server to get the list of connected players, or ALL for global online player list + */ + public static void getPlayerList(Player player, String server){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("PlayerList"); + output.writeUTF(server); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Get a list of server name strings, as defined in BungeeCord's config.yml + * + * @receiver any player + * + * @param player packet carrier + */ + public static void getServers(Player player){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("GetServers"); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Send a message (as in, a chat message) to the specified player. + * + * @receiver any player + * + * @param player packet carrier + * @param proxyPlayer the name of the player to send the chat message, or ALL to send to all players + * @param message the message to send to the player , supports color codes with "&" as translator + */ + public static void sendMessage(Player player,String proxyPlayer,String message){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("Message"); + output.writeUTF(proxyPlayer); + output.writeUTF(ChatColor.translateAlternateColorCodes('&',message)); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Send a raw message (as in, a chat message) to the specified player. The advantage of this method over Message + * is that you can include click events and hover events. + * + * @receiver any player + * + * @param player packet carrier + * @param proxyPlayer the name of the player to send the chat message, or ALL to send to all players + * @param message the message to send to the player + */ + public static void sendRawMessage(Player player,String proxyPlayer,String message){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("MessageRaw"); + output.writeUTF(proxyPlayer); + output.writeUTF(message); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Get this server's name, as defined in BungeeCord's config.yml + * + * @receiver any player + * + * @param player packet carrier + */ + public static void getServer(Player player){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("GetServer"); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Request the UUID of this player + * + * @receiver The player whose UUID you requested + * + * @param player player + */ + public static void getUUID(Player player){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("UUID"); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Request the UUID of any player connected to the BungeeCord proxy + * + * @receiver the sender + * + * @param player packet carrier and receiver + * @param oplayername the name of the player whose UUID you would like + */ + public static void getUUIDOther(Player player,String oplayername){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("UUIDOther"); + output.writeUTF(oplayername); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Request the IP of any server on this proxy + * + * @receiver any player + * + * @param player packet carrier + * @param server the name of the server + */ + public static void getServerIP(Player player,String server){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("ServerIP"); + output.writeUTF(server); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Kick any player on this proxy + * + * @receiver any player + * + * @param player packet carrier + * @param playerToKick player to be kicked + * @param reason reason for kick + */ + public static void kickPlayer(Player player,String playerToKick,String reason){ + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.writeUTF("KickPlayer"); + output.writeUTF(playerToKick); + output.writeUTF(reason); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", output.toByteArray()); + } + + /** + * Send a custom plugin message to said server. This is one of the most useful channels ever. + * Remember, the sending and receiving server(s) need to have a player online. + * + * @receiver any player + * + * @param player packet carrier + * @param server server to send to, ALL to send to every server (except the one sending the plugin message), + * or ONLINE to send to every server that's online (except the one sending the plugin message) + * @param subchannel Subchannel for plugin usage. + * @param data message to send //todo change stuff so can send anything + */ + public static void forward(Player player,String server,String subchannel,String data){ + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Forward"); // So BungeeCord knows to forward it + out.writeUTF(server); // server to send to + out.writeUTF(subchannel); // The channel name to check if this your data + + ByteArrayOutputStream msgbytes = new ByteArrayOutputStream(); + DataOutputStream msgout = new DataOutputStream(msgbytes); + try { + //THESE ARE DATA WE WANT TO SEND (example) // remove later + msgout.writeUTF(data); // You can do anything you want with msgout + msgout.writeShort(123); // checking needed + } catch (IOException exception){ + exception.printStackTrace(); + } + + out.writeShort(msgbytes.toByteArray().length); + out.write(msgbytes.toByteArray()); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", out.toByteArray()); + } + + /** + * Send a custom plugin message to specific player. + * @param player packet carrier + * @param playerToForwardTo Playername to send to. + * @param subchannel Subchannel for plugin usage. + * @param data message to send. //todo change stuff so can send anything + */ + public static void forwardToPlayer(Player player,String playerToForwardTo,String subchannel,String data){ + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("ForwardToPlayer"); // So BungeeCord knows to forward it + out.writeUTF(playerToForwardTo); // server to send to + out.writeUTF(subchannel); // The channel name to check if this your data + + ByteArrayOutputStream msgbytes = new ByteArrayOutputStream(); + DataOutputStream msgout = new DataOutputStream(msgbytes); + try { + //THESE ARE DATA WE WANT TO SEND (example) //remove later + msgout.writeUTF(data); // You can do anything you want with msgout + msgout.writeShort(123); // checking needed + } catch (IOException exception){ + exception.printStackTrace(); + } + + out.writeShort(msgbytes.toByteArray().length); + out.write(msgbytes.toByteArray()); + player.sendPluginMessage(GrubnestCorePlugin.getInstance(), "BungeeCord", out.toByteArray()); + } + +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..0ca59d5 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,10 @@ +# DEFAULT VALUES FOR MYSQL DATABASE update later!! +Database: + hostname: localhost + port: 3306 + database: grubnest + username: root + password: 1234 + minimumConnections: 5 + maximumConnections: 10 + connectionTimeout: 5000 \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 2cb97c0..9ed2e37 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,4 +3,5 @@ main: com.grubnest.game.core.GrubnestCorePlugin version: 1.0 authors: - Theeef -api-version: 1.13 \ No newline at end of file + - tamilpp25 +api-version: 1.18 \ No newline at end of file