diff --git a/pom.xml b/pom.xml index 672242a363335d2541635f25d89d54217f428b66..c886200d7d3e8815a7ca315c1dce5ef0685318ac 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ <groupId>com.slprojects</groupId> <artifactId>SLCraftPlugin</artifactId> - <version>1.3</version> + <version>1.4</version> <packaging>jar</packaging> <name>SLCraftPlugin</name> diff --git a/src/main/java/com/slprojects/slcraftplugin/Main.java b/src/main/java/com/slprojects/slcraftplugin/Main.java index e3b1b3fe01cb3a578de0740eb5b833e6ddf593d7..45f9473981479c95f98290f45cd09299985559c9 100644 --- a/src/main/java/com/slprojects/slcraftplugin/Main.java +++ b/src/main/java/com/slprojects/slcraftplugin/Main.java @@ -2,6 +2,7 @@ package com.slprojects.slcraftplugin; +import com.slprojects.slcraftplugin.commandes.linkCodeCommand; import com.slprojects.slcraftplugin.commandes.wildCommand; import org.bukkit.ChatColor; import org.bukkit.Sound; @@ -15,14 +16,12 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.java.JavaPlugin; import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; import org.mariadb.jdbc.MariaDbPoolDataSource; -import java.io.FileReader; import java.sql.*; -import java.text.SimpleDateFormat; import java.time.Duration; import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -60,12 +59,15 @@ public final class Main extends JavaPlugin implements Listener { // On initialise la base de donnée initDatabase(); - wildCommandActiveUsers = new ArrayList<UUID>(); - playTimeUsersIndexes = new ArrayList<UUID>(); - playTimeUsersDate = new ArrayList<LocalDateTime>(); + wildCommandActiveUsers = new ArrayList<>(); + playTimeUsersIndexes = new ArrayList<>(); + playTimeUsersDate = new ArrayList<>(); wildCommand wildCommand = new wildCommand(this); getCommand("wild").setExecutor(wildCommand); + linkCodeCommand linkCodeCommand = new linkCodeCommand(this); + getCommand("getLinkCode").setExecutor(linkCodeCommand); + getLogger().info(ChatColor.GREEN+"SL-Craft | Plugin démarré"); } @@ -82,9 +84,11 @@ public final class Main extends JavaPlugin implements Listener { playTimeUsersIndexes.add(e.getPlayer().getUniqueId()); playTimeUsersDate.add(LocalDateTime.now()); + // On affiche le message de bienvenue String welcomeMessage = PlaceholderAPI.setPlaceholders(e.getPlayer(), getConfig().getString("player-join-message")); - e.setJoinMessage(welcomeMessage); + // Et on joue un petit son chez tous les joueurs for(Player p : getServer().getOnlinePlayers()){ + p.sendMessage(welcomeMessage); if(getConfig().getBoolean("player-join-playSound")){ p.playSound(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 0); } @@ -96,9 +100,8 @@ public final class Main extends JavaPlugin implements Listener { public void onPlayerQuit(PlayerQuitEvent e) { savePlayer(e.getPlayer()); String quitMessage = PlaceholderAPI.setPlaceholders(e.getPlayer(), getConfig().getString("player-quit-message")); - e.setQuitMessage(quitMessage); for(Player p : getServer().getOnlinePlayers()){ - //p.sendMessage(quitMessage); + p.sendMessage(quitMessage); } } @@ -106,25 +109,30 @@ public final class Main extends JavaPlugin implements Listener { @SuppressWarnings("unchecked") public void savePlayer(Player player) { JSONObject target = new JSONObject(); + // On ajoute l'uuid et son nom target.put("uuid", player.getUniqueId().toString()); + target.put("name", player.getName()); + + // La date de join (locale, au cas où CoreProtect ne l'a pas) + target.put("joinedDate", playTimeUsersDate.get(playTimeUsersIndexes.indexOf(player.getUniqueId())).toString()); + // On calcule le temps de jeu LocalDateTime timeNow = LocalDateTime.now(); Duration duration = Duration.between(timeNow, playTimeUsersDate.get(playTimeUsersIndexes.indexOf(player.getUniqueId()))); long playedTimeInSeconds = Math.abs(duration.toSeconds()); - target.put("time", playedTimeInSeconds); + // On ajoute le temps de jeu au joueur + target.put("playedTime", playedTimeInSeconds); playTimeUsersDate.remove(playTimeUsersIndexes.indexOf(player.getUniqueId())); playTimeUsersIndexes.remove(player.getUniqueId()); - target.put("joins", Integer.valueOf(player.getStatistic(Statistic.LEAVE_GAME) + 1)); + target.put("joins", player.getStatistic(Statistic.LEAVE_GAME) + 1); + target.put("hasPlayedBefore", player.hasPlayedBefore()); writePlayer(target); } - @SuppressWarnings("unchecked") private void writePlayer(JSONObject target) { - JSONParser jsonParser = new JSONParser(); - // On ouvre la bdd Connection con = bddOpenConn(); try { @@ -139,29 +147,107 @@ public final class Main extends JavaPlugin implements Listener { // On insère la dernière date de join PreparedStatement insertionLastJoin = con.prepareStatement("INSERT INTO site_userSetting (`uuid`, `name`, `value`) VALUES (?,'playedTime',?)"); insertionLastJoin.setString(1, target.get("uuid").toString()); - insertionLastJoin.setString(2, target.get("time").toString()); - resultat = insertionLastJoin.executeQuery(); + insertionLastJoin.setString(2, target.get("playedTime").toString()); + insertionLastJoin.executeQuery(); // On insère le nombre de connexions PreparedStatement insertionNbJoins = con.prepareStatement("INSERT INTO site_userSetting (`uuid`, `name`, `value`) VALUES (?,'joins',?)"); insertionNbJoins.setString(1, target.get("uuid").toString()); insertionNbJoins.setString(2, target.get("joins").toString()); - resultat = insertionNbJoins.executeQuery(); + insertionNbJoins.executeQuery(); + + // On va regarder si l'utilisateur a déjà joué avant (vu qu'on avait pas de données sur ce joueur) + if(target.get("hasPlayedBefore").toString().equals("true")){ + // On va piocher la date d'inscription chez CoreProtect (si elle existe) + // On la prend chez CoreProtect car le plugin a été installé dans les premières semaines du serveur. Il a donc bcp plus de données que nous concernant les anciens joueurs. + PreparedStatement rechercheDateInscription = con.prepareStatement("SELECT time FROM co_user WHERE uuid = ?"); + rechercheDateInscription.setString(1, target.get("uuid").toString()); + resultat = rechercheDateInscription.executeQuery(); + + if(resultat.next()){ + // On insère la date d'inscription + PreparedStatement insertionDateInscription = con.prepareStatement("INSERT INTO site_userSetting (`uuid`, `name`, `value`) VALUES (?,'joinedDate',?)"); + insertionDateInscription.setString(1, target.get("uuid").toString()); + insertionDateInscription.setString(2, java.sql.Timestamp.valueOf(LocalDateTime.ofEpochSecond(Long.parseLong(resultat.getString("time")), 0, ZoneOffset.UTC)).toString()); // Il faut convertir le timestamp (epoch second) en date + insertionDateInscription.executeQuery(); + + // On va précisier que la date d'inscription a été trouvée chez CoreProtect + getLogger().info("L'utilisateur "+ChatColor.GOLD+target.get("name").toString()+ChatColor.RESET+" n'avait pas de données sur sa date d'inscription dans dans la table des paramètres utilisateurs. On lui a donc attribué comme date de création du compte, celle que détenait CoreProtect."); + } else { + // On insère la date d'inscription (du coup on considère que l'utilisateur n'a pas joué avant, malgré la condition) + PreparedStatement insertionDateInscription = con.prepareStatement("INSERT INTO site_userSetting (`uuid`, `name`, `value`) VALUES (?,'joinedDate',?)"); + insertionDateInscription.setString(1, target.get("uuid").toString()); + insertionDateInscription.setString(2, java.sql.Timestamp.valueOf(target.get("joinedDate").toString()).toString()); + insertionDateInscription.executeQuery(); + + // On va préciser que la date d'inscription n'a pas été trouvée chez CoreProtect + PreparedStatement insertionInaccurrateJoinedDate = con.prepareStatement("INSERT INTO site_userSetting (`uuid`, `name`, `value`) VALUES (?,'inaccurrateJoinedDate',?)"); + insertionInaccurrateJoinedDate.setString(1, target.get("uuid").toString()); + insertionInaccurrateJoinedDate.setString(2, "true"); + insertionInaccurrateJoinedDate.executeQuery(); + + getLogger().info("L'utilisateur "+ChatColor.GOLD+target.get("name").toString()+ChatColor.RESET+" n'avait pas de données sur sa date d'inscription dans dans la table des paramètres utilisateurs, ni dans la table des utilisateurs de CoreProtect. On lui a donc attribué comme date de création du compte, la date du début de sa partie."); + } + }else{ + // C'est un nouvel utilisateur, on peut lui attribuer la date d'inscription précédement calculée + PreparedStatement insertionDateInscription = con.prepareStatement("INSERT INTO site_userSetting (`uuid`, `name`, `value`) VALUES (?,'joinedDate',?)"); + insertionDateInscription.setString(1, target.get("uuid").toString()); + insertionDateInscription.setString(2, java.sql.Timestamp.valueOf(target.get("joinedDate").toString()).toString()); + insertionDateInscription.executeQuery(); + } }else{ PreparedStatement tempsJeuJoueur = con.prepareStatement("SELECT value FROM site_userSetting WHERE uuid = ? AND name = 'playedTime'"); tempsJeuJoueur.setString(1, target.get("uuid").toString()); resultat = tempsJeuJoueur.executeQuery(); if(resultat.next()) { - int totalPlayedTime = parseInt(resultat.getString(1)) + parseInt(target.get("time").toString()); + int totalPlayedTime = parseInt(resultat.getString(1)) + parseInt(target.get("playedTime").toString()); PreparedStatement modifyPlayedTime = con.prepareStatement("UPDATE `site_userSetting` SET `value`=? WHERE `uuid`=? AND `name`='playedTime'"); modifyPlayedTime.setInt(1, totalPlayedTime); modifyPlayedTime.setString(2, target.get("uuid").toString()); - resultat = modifyPlayedTime.executeQuery(); + modifyPlayedTime.executeQuery(); PreparedStatement modifyNbJoins = con.prepareStatement("UPDATE `site_userSetting` SET `value`=? WHERE `uuid`=? AND `name`='joins'"); modifyNbJoins.setString(1, target.get("joins").toString()); modifyNbJoins.setString(2, target.get("uuid").toString()); - resultat = modifyNbJoins.executeQuery(); + modifyNbJoins.executeQuery(); + + // On va regarder s'il a sa date d'inscription de renseignée + PreparedStatement rechercheDateInscription = con.prepareStatement("SELECT * FROM site_userSetting WHERE uuid = ? AND name = 'joinedDate'"); + rechercheDateInscription.setString(1, target.get("uuid").toString()); + resultat = rechercheDateInscription.executeQuery(); + + if(!resultat.next()){ + // On va regarder si l'on dispose de sa date d'inscription chez CoreProtect + rechercheDateInscription = con.prepareStatement("SELECT time FROM co_user WHERE uuid = ?"); + rechercheDateInscription.setString(1, target.get("uuid").toString()); + resultat = rechercheDateInscription.executeQuery(); + + if(resultat.next()){ + // On insère la date d'inscription + PreparedStatement insertionDateInscription = con.prepareStatement("INSERT INTO site_userSetting (`uuid`, `name`, `value`) VALUES (?,'joinedDate',?)"); + insertionDateInscription.setString(1, target.get("uuid").toString()); + insertionDateInscription.setString(2, java.sql.Timestamp.valueOf(LocalDateTime.ofEpochSecond(Long.parseLong(resultat.getString("time")), 0, ZoneOffset.UTC)).toString()); // Il faut convertir le timestamp (epoch second) en date + insertionDateInscription.executeQuery(); + + // On va précisier que la date d'inscription a été trouvée chez CoreProtect + getLogger().info("L'utilisateur "+ChatColor.GOLD+target.get("name").toString()+ChatColor.RESET+" n'avait pas de données sur sa date d'inscription dans dans la table des paramètres utilisateurs. On lui a donc attribué comme date de création du compte, celle que détenait CoreProtect."); + } else { + // On insère la date d'inscription (du coup, comme précédement, on prend la date d'inscription locale) + PreparedStatement insertionDateInscription = con.prepareStatement("INSERT INTO site_userSetting (`uuid`, `name`, `value`) VALUES (?,'joinedDate',?)"); + insertionDateInscription.setString(1, target.get("uuid").toString()); + insertionDateInscription.setString(2, java.sql.Timestamp.valueOf(target.get("joinedDate").toString()).toString()); + insertionDateInscription.executeQuery(); + + // On va préciser que la date d'inscription n'a pas été trouvée chez CoreProtect + PreparedStatement insertionInaccurrateJoinedDate = con.prepareStatement("INSERT INTO site_userSetting (`uuid`, `name`, `value`) VALUES (?,'inaccurrateJoinedDate',?)"); + insertionInaccurrateJoinedDate.setString(1, target.get("uuid").toString()); + insertionInaccurrateJoinedDate.setString(2, "true"); + insertionInaccurrateJoinedDate.executeQuery(); + + getLogger().info("L'utilisateur "+ChatColor.GOLD+target.get("name").toString()+ChatColor.RESET+" n'avait pas de données sur sa date d'inscription dans dans la table des paramètres utilisateurs, ni dans la table des utilisateurs de CoreProtect. On lui a donc attribué comme date de création du compte, la date du début de sa partie."); + } + } + }else{ getLogger().warning(ChatColor.RED+"Erreur, nous n'avons pas de resultats pour la requête: SELECT value FROM site_userSetting WHERE uuid = '"+target.get("uuid")+"' AND name = playedTime"); } @@ -206,7 +292,7 @@ public final class Main extends JavaPlugin implements Listener { try { MariaDbPoolDataSource dataSource = new MariaDbPoolDataSource("jdbc:mariadb://"+config.getString("database.host")+"/"+config.getString("database.database")+"?user="+config.getString("database.user")+"&password="+config.getString("database.password")+"&maxPoolSize=10"); conn = dataSource.getConnection(); - getLogger().info(ChatColor.GREEN+"Connexion à la base de données réussie!"); + //getLogger().info(ChatColor.GREEN+"Connexion à la base de données réussie!"); }// ou les saisir catch (SQLException e) { getLogger().warning(ChatColor.RED+"Erreur lors de la connexion à la base de données."); @@ -224,7 +310,16 @@ public final class Main extends JavaPlugin implements Listener { " `value` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,\n" + " PRIMARY KEY (`uuid`,`name`) USING BTREE\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); - ResultSet rs=ps.executeQuery(); + ps.executeQuery(); + ps=con.prepareStatement("CREATE TABLE IF NOT EXISTS `site_linkCode` (\n" + + " `uuid` VARCHAR(36) NOT NULL,\n" + + " `code` VARCHAR(8) NOT NULL,\n" + + " `time` TIMESTAMP NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\n" + + " `used` BOOLEAN,\n" + + " PRIMARY KEY (`uuid`),\n" + + " UNIQUE INDEX `code` (`code`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); + ps.executeQuery(); con.close(); }catch(Exception e){ getLogger().warning(ChatColor.RED+"Erreur lors de l'exécution de initDatabase(): "+e); diff --git a/src/main/java/com/slprojects/slcraftplugin/commandes/linkCodeCommand.java b/src/main/java/com/slprojects/slcraftplugin/commandes/linkCodeCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..6618511320e50d040d63530bfc8727d0ff6d9bd9 --- /dev/null +++ b/src/main/java/com/slprojects/slcraftplugin/commandes/linkCodeCommand.java @@ -0,0 +1,78 @@ +package com.slprojects.slcraftplugin.commandes; + +import com.slprojects.slcraftplugin.Main; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.time.LocalDateTime; +import java.util.Random; + +public class linkCodeCommand implements CommandExecutor { + + // Variables + private final Main plugin; + + public linkCodeCommand(Main plugin){ + // On récupère la classe parente pour les paramètres + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args){ + if (sender instanceof Player){ + Player player = (Player) sender; + + // On ouvre la bdd + Connection con = plugin.bddOpenConn(); + + try{ + // On créé le code + int leftLimit = 48; // numeral '0' + int rightLimit = 122; // letter 'z' + int targetStringLength = 8; + Random random = new Random(); + + String generatedString = random.ints(leftLimit, rightLimit + 1) + .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)) + .limit(targetStringLength) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + + // On va regarder si l'utilisateur a déjà généré un code auparavant + PreparedStatement rechercheLinkingCode = con.prepareStatement("SELECT * FROM site_linkCode WHERE uuid = ?"); + rechercheLinkingCode.setString(1, player.getUniqueId().toString()); + ResultSet resultat = rechercheLinkingCode.executeQuery(); + + if(resultat.next()){ + PreparedStatement modifyAccountLinkingCode = con.prepareStatement("UPDATE `site_linkCode` SET `code`=?, `time`=?, `used`='0' WHERE `uuid`=?"); + modifyAccountLinkingCode.setString(1, generatedString); + modifyAccountLinkingCode.setString(2, java.sql.Timestamp.valueOf(LocalDateTime.now()).toString()); + modifyAccountLinkingCode.setString(3, player.getUniqueId().toString()); + modifyAccountLinkingCode.executeQuery(); + + }else{ + PreparedStatement insertionAccountLinkingCode = con.prepareStatement("INSERT INTO site_linkCode (`uuid`, `code`, `time`, `used`) VALUES (?, ?, ?, '0')"); + insertionAccountLinkingCode.setString(1, player.getUniqueId().toString()); + insertionAccountLinkingCode.setString(2, generatedString); + insertionAccountLinkingCode.setString(3, java.sql.Timestamp.valueOf(LocalDateTime.now()).toString()); + insertionAccountLinkingCode.executeQuery(); + } + player.sendMessage("Utilise ce code pour lier ton compte: "+ChatColor.GREEN+generatedString); + player.sendMessage(ChatColor.GRAY+"Ce code à usage unique expirera dans 5 minutes."); + plugin.getLogger().info("Le joueur "+ChatColor.GOLD+player.getName()+ChatColor.RESET+" a généré le code "+ChatColor.GREEN+generatedString+ChatColor.RESET+ChatColor.GRAY+" - Il expirera le "+java.sql.Timestamp.valueOf(LocalDateTime.now().plusMinutes(5)).toString()); + + }catch (Exception e){ + e.printStackTrace(); + } + + } + return true; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 888b33ed59a856fdb8bc1000583c48bdae346f3a..59d20de2c394777f349990e02ee79f3beacb112b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -11,4 +11,10 @@ commands: description: Te permet de te téléporter à une coordonnée aléatoire aliases: [wild, tpr, tprandom] usage: /wild - permission: slcraft.wild \ No newline at end of file + permission: slcraft.wild + + getlinkcode: + description: Te permet d'obtenir un code pour associer ton compte Minecraft au site internet du serveur.' + aliases: [ getlinkcode ] + usage: /getlinkcode + permission: slcraft.getlinkcode \ No newline at end of file diff --git a/target/classes/com/slprojects/slcraftplugin/Main.class b/target/classes/com/slprojects/slcraftplugin/Main.class index dede04c7759f58dd94d7996915db5017addd35b0..369cff01cfd21bed898d96352ea3e53d30472f15 100644 Binary files a/target/classes/com/slprojects/slcraftplugin/Main.class and b/target/classes/com/slprojects/slcraftplugin/Main.class differ diff --git a/target/classes/com/slprojects/slcraftplugin/commandes/linkCodeCommand.class b/target/classes/com/slprojects/slcraftplugin/commandes/linkCodeCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..d88f9a248516da416cd959a48ca284dcb4bdc4b4 Binary files /dev/null and b/target/classes/com/slprojects/slcraftplugin/commandes/linkCodeCommand.class differ diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml index b53f9a6bedc5bdc2de4fd397fd4233a014d67b88..91a79f001d7abd3cf4d181ffdefc235dcd5fcdd5 100644 --- a/target/classes/plugin.yml +++ b/target/classes/plugin.yml @@ -1,5 +1,5 @@ name: SLCraftPlugin -version: '1.3' +version: '1.4' main: com.slprojects.slcraftplugin.Main depend: [PlaceholderAPI] api-version: 1.18 @@ -11,4 +11,10 @@ commands: description: Te permet de te téléporter à une coordonnée aléatoire aliases: [wild, tpr, tprandom] usage: /wild - permission: slcraft.wild \ No newline at end of file + permission: slcraft.wild + + getlinkcode: + description: Te permet d'obtenir un code pour associer ton compte Minecraft au site internet du serveur.' + aliases: [ getlinkcode ] + usage: /getlinkcode + permission: slcraft.getlinkcode \ No newline at end of file diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties index 7e0909c6c17714a652db36e8c65689af8f93398e..f16796ffa1c1603473ed8a7485c9781d515561d8 100644 --- a/target/maven-archiver/pom.properties +++ b/target/maven-archiver/pom.properties @@ -1,5 +1,5 @@ #Generated by Maven -#Fri Jan 28 14:26:51 CET 2022 +#Sat Mar 05 15:11:15 CET 2022 groupId=com.slprojects artifactId=SLCraftPlugin -version=1.3 +version=1.4 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 b075f29ed7bf1e7128495ed6bdab5dd21f2736da..06d009572d24221a2002882ef11161456953aeb6 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,3 +1,4 @@ com\slprojects\slcraftplugin\commandes\wildCommand.class com\slprojects\slcraftplugin\Main.class +com\slprojects\slcraftplugin\commandes\linkCodeCommand.class com\slprojects\slcraftplugin\commandes\wildCommand$1.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 73ca63b79c7c2738a103fd257e5175cc4c4ebf47..22518611b697ad035e84d33c9400b6528a0c4b59 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,2 +1,3 @@ C:\Users\sofia\Documents\Minecraft Plugin Workspace\SL-Craft Plugin\src\main\java\com\slprojects\slcraftplugin\commandes\wildCommand.java C:\Users\sofia\Documents\Minecraft Plugin Workspace\SL-Craft Plugin\src\main\java\com\slprojects\slcraftplugin\Main.java +C:\Users\sofia\Documents\Minecraft Plugin Workspace\SL-Craft Plugin\src\main\java\com\slprojects\slcraftplugin\commandes\linkCodeCommand.java