diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c682f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.directory +.gradle/ +.idea/ +build/ +*.iml diff --git a/build.gradle b/build.gradle index 982b47b..42e15f6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,10 @@ -apply plugin: 'java' +plugins { + id "com.github.johnrengelman.shadow" version "2.0.4" + id "java" + id "maven" + id "net.ltgt.apt" version "0.10" + id "io.franzbecker.gradle-lombok" version "1.14" +} group = pluginGroup version = 1.1 @@ -6,27 +12,36 @@ version = 1.1 sourceCompatibility = 1.8 targetCompatibility = 1.8 +lombok { + version = '1.18.2' + sha256 = "" +} + repositories { mavenCentral() + mavenLocal() maven { name = 'spigotmc-repo' url = 'https://hub.spigotmc.org/nexus/content/groups/public/' } + maven { name = 'sonatype' url = 'https://oss.sonatype.org/content/groups/public/' } + maven { name = 'nexus-hc' - url = 'http://nexus.hc.to/content/repositories/pub_releases' + url = 'http://nexus.hc.to/content/repositories/pub_releases/' } } dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' - compile 'org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT' + compile 'org.spigotmc:spigot:1.8.8-R0.1-SNAPSHOT' compile 'net.milkbowl.vault:VaultAPI:1.6' + compile 'org.bukkit:bukkit:1.8.8-R0.1-SNAPSHOT' compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.23.1' + apt 'org.projectlombok:lombok:1.14.8' } import org.apache.tools.ant.filters.ReplaceTokens diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..742fabf Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..348ffd3 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jul 18 10:02:10 CEST 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip diff --git a/gradlew.bat b/gradlew.bat index f955316..e95643d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,84 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/com/nametagedit/plugin/NametagManager.java b/src/main/java/com/nametagedit/plugin/NametagManager.java new file mode 100644 index 0000000..9cd7e05 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/NametagManager.java @@ -0,0 +1,167 @@ +package com.nametagedit.plugin; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +import com.nametagedit.plugin.api.data.FakeTeam; +import com.nametagedit.plugin.packets.PacketWrapper; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class NametagManager { + + private final HashMap TEAMS = new HashMap<>(); + private final HashMap CACHED_FAKE_TEAMS = new HashMap<>(); + + /** + * Gets the current team given a prefix and suffix + * If there is no team similar to this, then a new + * team is created. + */ + private FakeTeam getFakeTeam(String prefix, String suffix) { + for (FakeTeam fakeTeam : TEAMS.values()) { + if (fakeTeam.isSimilar(prefix, suffix)) { + return fakeTeam; + } + } + + return null; + } + + /** + * Adds a player to a FakeTeam. If they are already on this team, + * we do NOT change that. + */ + private void addPlayerToTeam(String player, String prefix, String suffix, int sortPriority, boolean playerTag) { + FakeTeam previous = getFakeTeam(player); + + if (previous != null && previous.isSimilar(prefix, suffix)) { + return; + } + + reset(player); + + FakeTeam joining = getFakeTeam(prefix, suffix); + if (joining != null) { + joining.addMember(player); + } else { + joining = new FakeTeam(prefix, suffix, sortPriority, playerTag); + joining.addMember(player); + TEAMS.put(joining.getName(), joining); + addTeamPackets(joining); + } + + Player adding = Bukkit.getPlayerExact(player); + if (adding != null) { + addPlayerToTeamPackets(joining, adding.getName()); + cache(adding.getName(), joining); + } else { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player); + addPlayerToTeamPackets(joining, offlinePlayer.getName()); + cache(offlinePlayer.getName(), joining); + } + } + + public FakeTeam reset(String player) { + return reset(player, decache(player)); + } + + private FakeTeam reset(String player, FakeTeam fakeTeam) { + if (fakeTeam != null && fakeTeam.getMembers().remove(player)) { + boolean delete; + Player removing = Bukkit.getPlayerExact(player); + if (removing != null) { + delete = removePlayerFromTeamPackets(fakeTeam, removing.getName()); + } else { + OfflinePlayer toRemoveOffline = Bukkit.getOfflinePlayer(player); + delete = removePlayerFromTeamPackets(fakeTeam, toRemoveOffline.getName()); + } + + if (delete) { + removeTeamPackets(fakeTeam); + TEAMS.remove(fakeTeam.getName()); + } + } + + return fakeTeam; + } + + // ============================================================== + // Below are public methods to modify the cache + // ============================================================== + private FakeTeam decache(String player) { + return CACHED_FAKE_TEAMS.remove(player); + } + + public FakeTeam getFakeTeam(String player) { + return CACHED_FAKE_TEAMS.get(player); + } + + private void cache(String player, FakeTeam fakeTeam) { + CACHED_FAKE_TEAMS.put(player, fakeTeam); + } + + // ============================================================== + // Below are public methods to modify certain data + // ============================================================== + public void setNametag(String player, String prefix, String suffix) { + setNametag(player, prefix, suffix, -1); + } + + void setNametag(String player, String prefix, String suffix, int sortPriority) { + setNametag(player, prefix, suffix, sortPriority, false); + } + + void setNametag(String player, String prefix, String suffix, int sortPriority, boolean playerTag) { + addPlayerToTeam(player, prefix != null ? prefix : "", suffix != null ? suffix : "", sortPriority, playerTag); + } + + public void sendTeams(Player player) { + for (FakeTeam fakeTeam : TEAMS.values()) { + new PacketWrapper(fakeTeam.getName(), fakeTeam.getPrefix(), fakeTeam.getSuffix(), 0, fakeTeam.getMembers()).send(player); + } + } + + void reset() { + for (FakeTeam fakeTeam : TEAMS.values()) { + removePlayerFromTeamPackets(fakeTeam, fakeTeam.getMembers()); + removeTeamPackets(fakeTeam); + } + CACHED_FAKE_TEAMS.clear(); + TEAMS.clear(); + } + + // ============================================================== + // Below are private methods to construct a new Scoreboard packet + // ============================================================== + private void removeTeamPackets(FakeTeam fakeTeam) { + new PacketWrapper(fakeTeam.getName(), fakeTeam.getPrefix(), fakeTeam.getSuffix(), 1, new ArrayList<>()).send(); + } + + private boolean removePlayerFromTeamPackets(FakeTeam fakeTeam, String... players) { + return removePlayerFromTeamPackets(fakeTeam, Arrays.asList(players)); + } + + private boolean removePlayerFromTeamPackets(FakeTeam fakeTeam, List players) { + new PacketWrapper(fakeTeam.getName(), 4, players).send(); + fakeTeam.getMembers().removeAll(players); + return fakeTeam.getMembers().isEmpty(); + } + + private void addTeamPackets(FakeTeam fakeTeam) { + new PacketWrapper(fakeTeam.getName(), fakeTeam.getPrefix(), fakeTeam.getSuffix(), 0, fakeTeam.getMembers()).send(); + } + + private void addPlayerToTeamPackets(FakeTeam fakeTeam, String player) { + new PacketWrapper(fakeTeam.getName(), 3, Collections.singletonList(player)).send(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/api/INametagApi.java b/src/main/java/com/nametagedit/plugin/api/INametagApi.java new file mode 100644 index 0000000..ef4d928 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/api/INametagApi.java @@ -0,0 +1,121 @@ +package com.nametagedit.plugin.api; + +import com.nametagedit.plugin.api.data.FakeTeam; +import com.nametagedit.plugin.api.data.Nametag; +import org.bukkit.entity.Player; + +public interface INametagApi { + + /** + * Function gets the fake team data for + * player. + * + * @param player the player to check + * @return the fake team + */ + FakeTeam getFakeTeam(Player player); + + /** + * Function gets the nametag for a player if + * it exists. This will never return a null. + * + * @param player the player to check + * @return the nametag for the player + */ + Nametag getNametag(Player player); + + /** + * Removes a player's nametag in memory + * only. + *

+ * Note: Only affects memory, does NOT + * add/remove from storage. + * + * @param player whose nametag to clear + */ + void clearNametag(Player player); + + /** + * Removes a player's nametag in memory + * only. + *

+ * Note: Only affects memory, does NOT + * add/remove from storage. + * + * @param player whose nametag to clear + */ + void clearNametag(String player); + + /** + * Sets the prefix for a player. The previous + * suffix is kept if it exists. + *

+ * Note: Only affects memory, does NOT + * add/remove from storage. + * + * @param player the player whose nametag to change + * @param prefix the prefix to change to + */ + void setPrefix(Player player, String prefix); + + /** + * Sets the suffix for a player. The previous + * prefix is kept if it exists. + *

+ * Note: Only affects memory, does NOT + * add/remove from storage. + * + * @param player the player whose nametag to change + * @param suffix the suffix to change to + */ + void setSuffix(Player player, String suffix); + + /** + * Sets the prefix for a player. The previous + * suffix is kept if it exists. + *

+ * Note: Only affects memory, does NOT + * add/remove from storage. + * + * @param player the player whose nametag to change + * @param prefix the prefix to change to + */ + void setPrefix(String player, String prefix); + + /** + * Sets the suffix for a player. The previous + * prefix is kept if it exists. + *

+ * Note: Only affects memory, does NOT + * add/remove from storage. + * + * @param player the player whose nametag to change + * @param suffix the suffix to change to + */ + void setSuffix(String player, String suffix); + + /** + * Sets the nametag for a player. + *

+ * Note: Only affects memory, does NOT + * add/remove from storage. + * + * @param player the player whose nametag to change + * @param prefix the prefix to change to + * @param suffix the suffix to change to + */ + void setNametag(Player player, String prefix, String suffix); + + /** + * Sets the nametag for a player. + *

+ * Note: Only affects memory, does NOT + * add/remove from storage. + * + * @param player the player whose nametag to change + * @param prefix the prefix to change to + * @param suffix the suffix to change to + */ + void setNametag(String player, String prefix, String suffix); + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/api/NametagAPI.java b/src/main/java/com/nametagedit/plugin/api/NametagAPI.java new file mode 100644 index 0000000..f990252 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/api/NametagAPI.java @@ -0,0 +1,98 @@ +package com.nametagedit.plugin.api; + +import com.nametagedit.plugin.NametagManager; +import com.nametagedit.plugin.api.data.FakeTeam; +import com.nametagedit.plugin.api.data.Nametag; +import com.nametagedit.plugin.api.events.NametagEvent; +import lombok.AllArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +/** + * Implements the INametagAPI interface. There only + * exists one instance of this class. + */ +@AllArgsConstructor +public final class NametagAPI implements INametagApi { + + public NametagManager manager; + + @Override + public FakeTeam getFakeTeam(Player player) { + return manager.getFakeTeam(player.getName()); + } + + @Override + public Nametag getNametag(Player player) { + FakeTeam team = manager.getFakeTeam(player.getName()); + boolean nullTeam = team == null; + return new Nametag(nullTeam ? "" : team.getPrefix(), nullTeam ? "" : team.getSuffix()); + } + + @Override + public void clearNametag(Player player) { + if (shouldFireEvent(player, NametagEvent.ChangeType.CLEAR)) { + manager.reset(player.getName()); + } + } + + @Override + public void clearNametag(String player) { + manager.reset(player); + } + + @Override + public void setPrefix(Player player, String prefix) { + FakeTeam fakeTeam = manager.getFakeTeam(player.getName()); + setNametagAlt(player, prefix, fakeTeam == null ? null : fakeTeam.getSuffix()); + } + + @Override + public void setSuffix(Player player, String suffix) { + FakeTeam fakeTeam = manager.getFakeTeam(player.getName()); + setNametagAlt(player, fakeTeam == null ? null : fakeTeam.getPrefix(), suffix); + } + + @Override + public void setPrefix(String player, String prefix) { + FakeTeam fakeTeam = manager.getFakeTeam(player); + manager.setNametag(player, prefix, fakeTeam == null ? null : fakeTeam.getSuffix()); + } + + @Override + public void setSuffix(String player, String suffix) { + FakeTeam fakeTeam = manager.getFakeTeam(player); + manager.setNametag(player, fakeTeam == null ? null : fakeTeam.getPrefix(), suffix); + } + + @Override + public void setNametag(Player player, String prefix, String suffix) { + setNametagAlt(player, prefix, suffix); + } + + @Override + public void setNametag(String player, String prefix, String suffix) { + manager.setNametag(player, prefix, suffix); + } + + /** + * Private helper function to reduce redundancy + */ + private boolean shouldFireEvent(Player player, NametagEvent.ChangeType type) { + NametagEvent event = new NametagEvent(player.getName(), "", getNametag(player), type); + Bukkit.getPluginManager().callEvent(event); + return !event.isCancelled(); + } + + /** + * Private helper function to reduce redundancy + */ + private void setNametagAlt(Player player, String prefix, String suffix) { + Nametag nametag = new Nametag(prefix, suffix); + + NametagEvent event = new NametagEvent(player.getName(), prefix, nametag, NametagEvent.ChangeType.UNKNOWN); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + manager.setNametag(player.getName(), nametag.getPrefix(), nametag.getSuffix()); + } +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/api/data/FakeTeam.java b/src/main/java/com/nametagedit/plugin/api/data/FakeTeam.java new file mode 100644 index 0000000..92f8400 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/api/data/FakeTeam.java @@ -0,0 +1,66 @@ +package com.nametagedit.plugin.api.data; + +import com.nametagedit.plugin.utils.Utils; +import lombok.Data; + +import java.util.ArrayList; + +/** + * This class represents a Scoreboard Team. It is used + * to keep track of the current members of a Team, and + * is responsible for + */ +@Data +public class FakeTeam { + + // Because some networks use NametagEdit on multiple servers, we may have clashes + // with the same Team names. The UNIQUE_ID ensures there will be no clashing. + private static final String UNIQUE_ID = Utils.generateUUID(); + // This represents the number of FakeTeams that have been created. + // It is used to generate a unique Team name. + private static int ID = 0; + private final ArrayList members = new ArrayList<>(); + private String name; + private String prefix = ""; + private String suffix = ""; + + public FakeTeam(String prefix, String suffix, int sortPriority, boolean playerTag) { + this.name = UNIQUE_ID + "_" + getNameFromInput(sortPriority) + ++ID + (playerTag ? "+P" : ""); + // It is possible the names of the Team exceeded the length of 16 in the past, + // and caused crashes as a result. This is a layer of protection against that. + this.name = this.name.length() > 16 ? this.name.substring(0, 16) : this.name; + this.prefix = prefix; + this.suffix = suffix; + } + + public void addMember(String player) { + if (!members.contains(player)) { + members.add(player); + } + } + + public boolean isSimilar(String prefix, String suffix) { + return this.prefix.equals(prefix) && this.suffix.equals(suffix); + } + + /** + * This is a special method to sort nametags in + * the tablist. It takes a priority and converts + * it to an alphabetic representation to force a + * specific sort. + * + * @param input the sort priority + * @return the team name + */ + private String getNameFromInput(int input) { + if (input < 0) return "Z"; + char letter = (char) ((input / 5) + 65); + int repeat = input % 5 + 1; + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < repeat; i++) { + builder.append(letter); + } + return builder.toString(); + } + +} diff --git a/src/main/java/com/nametagedit/plugin/api/data/GroupData.java b/src/main/java/com/nametagedit/plugin/api/data/GroupData.java new file mode 100644 index 0000000..89fac5f --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/api/data/GroupData.java @@ -0,0 +1,37 @@ +package com.nametagedit.plugin.api.data; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; + +/** + * This class represents a group nametag. There + * are several properties available. + */ +@Data +@AllArgsConstructor +public class GroupData implements INametag { + + private String groupName; + private String prefix; + private String suffix; + private String permission; + private Permission bukkitPermission; + private int sortPriority; + + public GroupData() { + + } + + public void setPermission(String permission) { + this.permission = permission; + bukkitPermission = new Permission(permission, PermissionDefault.FALSE); + } + + @Override + public boolean isPlayerTag() { + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/api/data/INametag.java b/src/main/java/com/nametagedit/plugin/api/data/INametag.java new file mode 100644 index 0000000..2a6c265 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/api/data/INametag.java @@ -0,0 +1,11 @@ +package com.nametagedit.plugin.api.data; + +public interface INametag { + String getPrefix(); + + String getSuffix(); + + int getSortPriority(); + + boolean isPlayerTag(); +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/api/data/Nametag.java b/src/main/java/com/nametagedit/plugin/api/data/Nametag.java new file mode 100644 index 0000000..6ad6914 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/api/data/Nametag.java @@ -0,0 +1,13 @@ +package com.nametagedit.plugin.api.data; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class Nametag { + private String prefix; + private String suffix; +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/api/data/PlayerData.java b/src/main/java/com/nametagedit/plugin/api/data/PlayerData.java new file mode 100644 index 0000000..d75583f --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/api/data/PlayerData.java @@ -0,0 +1,45 @@ +package com.nametagedit.plugin.api.data; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.util.UUID; + +/** + * This class represents a player nametag. There + * are several properties available. + */ +@Getter +@Setter +@AllArgsConstructor +public class PlayerData implements INametag { + + private String name; + private UUID uuid; + private String prefix; + private String suffix; + private int sortPriority; + + public PlayerData() { + + } + + public static PlayerData fromFile(String key, YamlConfiguration file) { + if (!file.contains("Players." + key)) return null; + PlayerData data = new PlayerData(); + data.setUuid(UUID.fromString(key)); + data.setName(file.getString("Players." + key + ".Name")); + data.setPrefix(file.getString("Players." + key + ".Prefix", "")); + data.setSuffix(file.getString("Players." + key + ".Suffix", "")); + data.setSortPriority(file.getInt("Players." + key + ".SortPriority", -1)); + return data; + } + + @Override + public boolean isPlayerTag() { + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/api/events/NametagEvent.java b/src/main/java/com/nametagedit/plugin/api/events/NametagEvent.java new file mode 100644 index 0000000..acf82f4 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/api/events/NametagEvent.java @@ -0,0 +1,101 @@ +package com.nametagedit.plugin.api.events; + +import com.nametagedit.plugin.api.data.Nametag; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * This class represents an Event that is fired when a + * nametag is changed. + */ +public class NametagEvent extends Event implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + + private boolean cancelled; + + @Getter + @Setter + @Deprecated + private String value; + + @Getter + @Setter + private Nametag nametag; + + @Getter + @Setter + private String player; + + @Getter + @Setter + private ChangeType changeType; + + @Getter + @Setter + private ChangeReason changeReason; + + @Getter + @Setter + private StorageType storageType; + + public NametagEvent(String player, String value) { + this(player, value, ChangeType.UNKNOWN); + } + + public NametagEvent(String player, String value, Nametag nametag, ChangeType type) { + this(player, value, type); + this.nametag = nametag; + } + + public NametagEvent(String player, String value, ChangeType changeType) { + this(player, value, changeType, StorageType.MEMORY, ChangeReason.UNKNOWN); + } + + public NametagEvent(String player, String value, ChangeType changeType, ChangeReason changeReason) { + this(player, value, changeType, StorageType.MEMORY, changeReason); + } + + public NametagEvent(String player, String value, ChangeType changeType, StorageType storageType, ChangeReason changeReason) { + this.player = player; + this.value = value; + this.changeType = changeType; + this.storageType = storageType; + this.changeReason = changeReason; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public enum ChangeReason { + API, PLUGIN, UNKNOWN + } + + public enum ChangeType { + PREFIX, SUFFIX, GROUP, CLEAR, PREFIX_AND_SUFFIX, RELOAD, UNKNOWN + } + + public enum StorageType { + MEMORY, PERSISTENT + } + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/api/events/NametagFirstLoadedEvent.java b/src/main/java/com/nametagedit/plugin/api/events/NametagFirstLoadedEvent.java new file mode 100644 index 0000000..ca172d0 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/api/events/NametagFirstLoadedEvent.java @@ -0,0 +1,32 @@ +package com.nametagedit.plugin.api.events; + +import com.nametagedit.plugin.api.data.INametag; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * This class represents an Event that is fired when a + * player joins the server and receives their nametag. + */ +@Getter +@AllArgsConstructor +public class NametagFirstLoadedEvent extends Event { + + private static final HandlerList HANDLERS = new HandlerList(); + + private Player player; + private INametag nametag; + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/packets/PacketAccessor.java b/src/main/java/com/nametagedit/plugin/packets/PacketAccessor.java new file mode 100644 index 0000000..39b174d --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/packets/PacketAccessor.java @@ -0,0 +1,146 @@ +package com.nametagedit.plugin.packets; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +class PacketAccessor { + + private static List legacyVersions = Arrays.asList("v1_7_R1","v1_7_R2","v1_7_R3","v1_7_R4","v1_8_R1","v1_8_R2","v1_8_R3","v1_9_R1","v1_9_R2","v1_10_R1","v1_11_R1","v1_12_R1"); + private static boolean CAULDRON_SERVER = false; + private static boolean LEGACY_SERVER = false; + + static Field MEMBERS; + static Field PREFIX; + static Field SUFFIX; + static Field TEAM_NAME; + static Field PARAM_INT; + static Field PACK_OPTION; + static Field DISPLAY_NAME; + static Field TEAM_COLOR; + static Field PUSH; + static Field VISIBILITY; + + private static Method getHandle; + private static Method sendPacket; + private static Field playerConnection; + + private static Class packetClass; + + static { + try { + Class.forName("cpw.mods.fml.common.Mod"); + CAULDRON_SERVER = true; + } catch (ClassNotFoundException ignored) { + // This is not a cauldron server + } + + try { + String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + + if (legacyVersions.contains(version)) + LEGACY_SERVER = true; + + Class typeCraftPlayer = Class.forName("org.bukkit.craftbukkit." + version + ".entity.CraftPlayer"); + getHandle = typeCraftPlayer.getMethod("getHandle"); + + if (CAULDRON_SERVER) { + packetClass = Class.forName("net.minecraft.server.v1_7_R4.PacketPlayOutScoreboardTeam"); + Class typeNMSPlayer = Class.forName("net.minecraft.server.v1_7_R4.EntityPlayer"); + Class typePlayerConnection = Class.forName("net.minecraft.server.v1_7_R4.PlayerConnection"); + playerConnection = typeNMSPlayer.getField("field_71135_a"); + sendPacket = typePlayerConnection.getMethod("func_147359_a", Class.forName("net.minecraft.server.v1_7_R4.Packet")); + } else { + packetClass = Class.forName("net.minecraft.server." + version + ".PacketPlayOutScoreboardTeam"); + Class typeNMSPlayer = Class.forName("net.minecraft.server." + version + ".EntityPlayer"); + Class typePlayerConnection = Class.forName("net.minecraft.server." + version + ".PlayerConnection"); + playerConnection = typeNMSPlayer.getField("playerConnection"); + sendPacket = typePlayerConnection.getMethod("sendPacket", Class.forName("net.minecraft.server." + version + ".Packet")); + } + + PacketData currentVersion = null; + for (PacketData packetData : PacketData.values()) { + if (version.contains(packetData.name())) { + currentVersion = packetData; + } + } + + if (CAULDRON_SERVER) { + currentVersion = PacketData.cauldron; + } + + if (currentVersion != null) { + PREFIX = getNMS(currentVersion.getPrefix()); + SUFFIX = getNMS(currentVersion.getSuffix()); + MEMBERS = getNMS(currentVersion.getMembers()); + TEAM_NAME = getNMS(currentVersion.getTeamName()); + PARAM_INT = getNMS(currentVersion.getParamInt()); + PACK_OPTION = getNMS(currentVersion.getPackOption()); + DISPLAY_NAME = getNMS(currentVersion.getDisplayName()); + + if (!isLegacyVersion()) { + TEAM_COLOR = getNMS(currentVersion.getColor()); + } + + if (isPushVersion(version)) { + PUSH = getNMS(currentVersion.getPush()); + } + + if (isVisibilityVersion(version)) { + VISIBILITY = getNMS(currentVersion.getVisibility()); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static boolean isLegacyVersion() { + return LEGACY_SERVER; + } + + private static boolean isPushVersion(String version) { + return Integer.parseInt(version.split("_")[1]) >= 9; + } + + private static boolean isVisibilityVersion(String version) { + return Integer.parseInt(version.split("_")[1]) >= 8; + } + + private static Field getNMS(String path) throws Exception { + Field field = packetClass.getDeclaredField(path); + field.setAccessible(true); + return field; + } + + static Object createPacket() { + try { + return packetClass.newInstance(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + static void sendPacket(Collection players, Object packet) { + for (Player player : players) { + sendPacket(player, packet); + } + } + + static void sendPacket(Player player, Object packet) { + try { + Object nmsPlayer = getHandle.invoke(player); + Object connection = playerConnection.get(nmsPlayer); + sendPacket.invoke(connection, packet); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/packets/PacketData.java b/src/main/java/com/nametagedit/plugin/packets/PacketData.java new file mode 100644 index 0000000..9597aaf --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/packets/PacketData.java @@ -0,0 +1,31 @@ +package com.nametagedit.plugin.packets; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +enum PacketData { + + v1_7("e", "c", "d", "a", "f", "g", "b", "NA", "NA", "NA"), + cauldron("field_149317_e", "field_149319_c", "field_149316_d", "field_149320_a", + "field_149314_f", "field_149315_g", "field_149318_b", "NA", "NA", "NA"), + v1_8("g", "c", "d", "a", "h", "i", "b", "NA", "NA", "e"), + v1_9("h", "c", "d", "a", "i", "j", "b", "NA", "f", "e"), + v1_10("h", "c", "d", "a", "i", "j", "b", "NA", "f", "e"), + v1_11("h", "c", "d", "a", "i", "j", "b", "NA", "f", "e"), + v1_12("h", "c", "d", "a", "i", "j", "b", "NA", "f", "e"), + v1_13("h", "c", "d", "a", "i", "j", "b", "g", "f", "e"); + + private String members; + private String prefix; + private String suffix; + private String teamName; + private String paramInt; + private String packOption; + private String displayName; + private String color; + private String push; + private String visibility; + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/packets/PacketWrapper.java b/src/main/java/com/nametagedit/plugin/packets/PacketWrapper.java new file mode 100644 index 0000000..91b7649 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/packets/PacketWrapper.java @@ -0,0 +1,119 @@ +package com.nametagedit.plugin.packets; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import com.nametagedit.plugin.utils.Utils; + +public class PacketWrapper { + + public String error; + private Object packet = PacketAccessor.createPacket(); + + private static Constructor ChatComponentText; + private static Class typeEnumChatFormat; + + static { + try { + if (!PacketAccessor.isLegacyVersion()) { + String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + + Class typeChatComponentText = Class.forName("net.minecraft.server." + version + ".ChatComponentText"); + ChatComponentText = typeChatComponentText.getConstructor(String.class); + typeEnumChatFormat = (Class) Class.forName("net.minecraft.server." + version + ".EnumChatFormat"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public PacketWrapper(String name, int param, List members) { + if (param != 3 && param != 4) { + throw new IllegalArgumentException("Method must be join or leave for player constructor"); + } + setupDefaults(name, param); + setupMembers(members); + } + + @SuppressWarnings("unchecked") + public PacketWrapper(String name, String prefix, String suffix, int param, Collection players) { + setupDefaults(name, param); + if (param == 0 || param == 2) { + try { + if (PacketAccessor.isLegacyVersion()) { + PacketAccessor.DISPLAY_NAME.set(packet, name); + PacketAccessor.PREFIX.set(packet, prefix); + PacketAccessor.SUFFIX.set(packet, suffix); + } else { + String color = ChatColor.getLastColors(prefix); + String colorCode = null; + + if (!color.isEmpty()) { + colorCode = color.substring(color.length() - 1); + String chatColor = ChatColor.getByChar(colorCode).name(); + + if (chatColor.equalsIgnoreCase("MAGIC")) + chatColor = "OBFUSCATED"; + + Enum colorEnum = Enum.valueOf(typeEnumChatFormat, chatColor); + PacketAccessor.TEAM_COLOR.set(packet, colorEnum); + } + + PacketAccessor.DISPLAY_NAME.set(packet, ChatComponentText.newInstance(name)); + PacketAccessor.PREFIX.set(packet, ChatComponentText.newInstance(prefix)); + + if (colorCode != null) + suffix = ChatColor.getByChar(colorCode) + suffix; + + PacketAccessor.SUFFIX.set(packet, ChatComponentText.newInstance(suffix)); + } + + PacketAccessor.PACK_OPTION.set(packet, 1); + + if (PacketAccessor.VISIBILITY != null) { + PacketAccessor.VISIBILITY.set(packet, "always"); + } + + if (param == 0) { + ((Collection) PacketAccessor.MEMBERS.get(packet)).addAll(players); + } + } catch (Exception e) { + error = e.getMessage(); + } + } + } + + @SuppressWarnings("unchecked") + private void setupMembers(Collection players) { + try { + players = players == null || players.isEmpty() ? new ArrayList<>() : players; + ((Collection) PacketAccessor.MEMBERS.get(packet)).addAll(players); + } catch (Exception e) { + error = e.getMessage(); + } + } + + private void setupDefaults(String name, int param) { + try { + PacketAccessor.TEAM_NAME.set(packet, name); + PacketAccessor.PARAM_INT.set(packet, param); + } catch (Exception e) { + error = e.getMessage(); + } + } + + public void send() { + PacketAccessor.sendPacket(Utils.getOnline(), packet); + } + + public void send(Player player) { + PacketAccessor.sendPacket(player, packet); + } + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/utils/Configuration.java b/src/main/java/com/nametagedit/plugin/utils/Configuration.java new file mode 100644 index 0000000..7f6ff5b --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/utils/Configuration.java @@ -0,0 +1,247 @@ +package com.nametagedit.plugin.utils; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.bukkit.Bukkit; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.regex.Pattern; + +public class Configuration extends YamlConfiguration { + + private final Map> headers = Maps.newConcurrentMap(); + private final File file; + private List mainHeader = Lists.newArrayList(); + private boolean loadHeaders; + + public Configuration(File file) { + this.file = file; + } + + /** + * Set the main header displayed at top of config. + * + * @param header header + */ + public void mainHeader(String... header) { + mainHeader = Arrays.asList(header); + } + + /** + * Get main header displayed at top of config. + * + * @return header + */ + public List mainHeader() { + return mainHeader; + } + + /** + * Set option header. + * + * @param key of option (or section) + * @param header of option (or section) + */ + public void header(String key, String... header) { + headers.put(key, Arrays.asList(header)); + } + + /** + * Get header of option + * + * @param key of option (or section) + * @return Header + */ + public List header(String key) { + return headers.get(key); + } + + public T get(String key, Class type) { + return type.cast(get(key)); + } + + /** + * Reload config from file. + */ + public void reload() { + reload(headers.isEmpty()); + } + + /** + * Reload config from file. + * + * @param loadHeaders Whether or not to load headers. + */ + public void reload(boolean loadHeaders) { + this.loadHeaders = loadHeaders; + try { + load(file); + } catch (Exception e) { + Bukkit.getLogger().log(Level.WARNING, "failed to reload file", e); + } + } + + @Override + public void loadFromString(String contents) throws InvalidConfigurationException { + if (!loadHeaders) { + super.loadFromString(contents); + return; + } + + StringBuilder memoryData = new StringBuilder(); + + // Parse headers + final int indentLength = options().indent(); + final String pathSeparator = Character.toString(options().pathSeparator()); + int currentIndents = 0; + String key = ""; + List headers = Lists.newArrayList(); + for (String line : contents.split("\n")) { + if (line.isEmpty()) continue; // Skip empty lines + int indent = getSuccessiveCharCount(line, ' '); + String subline = indent > 0 ? line.substring(indent) : line; + if (subline.startsWith("#")) { + if (subline.startsWith("#>")) { + String txt = subline.startsWith("#> ") ? subline.substring(3) : subline.substring(2); + mainHeader.add(txt); + continue; // Main header, handled by bukkit + } + + // Add header to list + String txt = subline.startsWith("# ") ? subline.substring(2) : subline.substring(1); + headers.add(txt); + continue; + } + + int indents = indent / indentLength; + if (indents <= currentIndents) { + // Remove last section of key + String[] array = key.split(Pattern.quote(pathSeparator)); + int backspace = currentIndents - indents + 1; + key = join(array, options().pathSeparator(), 0, array.length - backspace); + } + + // Add new section to key + String separator = key.length() > 0 ? pathSeparator : ""; + String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line; + key += separator + lineKey.substring(indent); + + currentIndents = indents; + + memoryData.append(line).append('\n'); + if (!headers.isEmpty()) { + this.headers.put(key, headers); + headers = Lists.newArrayList(); + } + } + + // Parse remaining text + super.loadFromString(memoryData.toString()); + + // Clear bukkit header + options().header(null); + } + + /** + * Save config to file + */ + public void save() { + if (headers.isEmpty() && mainHeader.isEmpty()) { + try { + super.save(file); + } catch (IOException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to save file", e); + } + return; + } + + // Custom save + final int indentLength = options().indent(); + final String pathSeparator = Character.toString(options().pathSeparator()); + String content = saveToString(); + StringBuilder fileData = new StringBuilder(buildHeader()); + int currentIndents = 0; + String key = ""; + for (String h : mainHeader) { + // Append main header to top of file + fileData.append("#> ").append(h).append('\n'); + } + + for (String line : content.split("\n")) { + if (line.isEmpty()) continue; // Skip empty lines + int indent = getSuccessiveCharCount(line, ' '); + int indents = indent / indentLength; + String indentText = indent > 0 ? line.substring(0, indent) : ""; + if (indents <= currentIndents) { + // Remove last section of key + String[] array = key.split(Pattern.quote(pathSeparator)); + int backspace = currentIndents - indents + 1; + key = join(array, options().pathSeparator(), 0, array.length - backspace); + } + + // Add new section to key + String separator = key.length() > 0 ? pathSeparator : ""; + String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line; + key += separator + lineKey.substring(indent); + + currentIndents = indents; + + List header = headers.get(key); + String headerText = header != null ? addHeaderTags(header, indentText) : ""; + fileData.append(headerText).append(line).append('\n'); + } + + // Write data to file + FileWriter writer = null; + try { + writer = new FileWriter(file); + writer.write(fileData.toString()); + writer.flush(); + } catch (IOException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to save file", e); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException ignored) { + } + } + } + } + + private String addHeaderTags(List header, String indent) { + StringBuilder builder = new StringBuilder(); + for (String line : header) { + builder.append(indent).append("# ").append(line).append('\n'); + } + return builder.toString(); + } + + private String join(String[] array, char joinChar, int start, int length) { + String[] copy = new String[length - start]; + System.arraycopy(array, start, copy, 0, length - start); + return Joiner.on(joinChar).join(copy); + } + + private int getSuccessiveCharCount(String text, char key) { + int count = 0; + for (int i = 0; i < text.length(); i++) { + if (text.charAt(i) == key) { + count += 1; + } else { + break; + } + } + return count; + } + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/utils/UUIDFetcher.java b/src/main/java/com/nametagedit/plugin/utils/UUIDFetcher.java new file mode 100644 index 0000000..88f9bbd --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/utils/UUIDFetcher.java @@ -0,0 +1,117 @@ +package com.nametagedit.plugin.utils; + +import com.google.common.collect.ImmutableList; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; +import java.util.concurrent.Callable; + +/** + * This class is responsible for retrieving UUIDs from Names + * + * @author evilmidget38 + */ +public class UUIDFetcher implements Callable> { + + private static final double PROFILES_PER_REQUEST = 100; + private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft"; + private final JSONParser jsonParser = new JSONParser(); + private final List names; + private final boolean rateLimiting; + + private UUIDFetcher(List names, boolean rateLimiting) { + this.names = ImmutableList.copyOf(names); + this.rateLimiting = rateLimiting; + } + + private UUIDFetcher(List names) { + this(names, true); + } + + public static void lookupUUID(final String name, final Plugin plugin, final UUIDLookup uuidLookup) { + new BukkitRunnable() { + @Override + public void run() { + UUID response = null; + try { + response = getUUIDOf(name); + } catch (Exception e) { + // Swallow + } + + final UUID finalResponse = response; + new BukkitRunnable() { + @Override + public void run() { + uuidLookup.response(finalResponse); + } + }.runTask(plugin); + } + }.runTaskAsynchronously(plugin); + } + + private static void writeBody(HttpURLConnection connection, String body) throws Exception { + try (OutputStream stream = connection.getOutputStream()) { + stream.write(body.getBytes()); + stream.flush(); + } + } + + private static HttpURLConnection createConnection() throws Exception { + URL url = new URL(PROFILE_URL); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setUseCaches(false); + connection.setDoInput(true); + connection.setDoOutput(true); + return connection; + } + + private static UUID getUUID(String id) { + return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + + "-" + id.substring(16, 20) + "-" + id.substring(20, 32)); + } + + private static UUID getUUIDOf(String name) throws Exception { + return new UUIDFetcher(Collections.singletonList(name)).call().get(name); + } + + @Override + public Map call() throws Exception { + Map uuidMap = new HashMap<>(); + int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST); + for (int i = 0; i < requests; i++) { + HttpURLConnection connection = createConnection(); + String body = JSONArray.toJSONString(names.subList(i * 100, Math.min((i + 1) * 100, names.size()))); + writeBody(connection, body); + JSONArray array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream())); + + for (Object profile : array) { + JSONObject jsonProfile = (JSONObject) profile; + String id = (String) jsonProfile.get("id"); + String name = (String) jsonProfile.get("name"); + UUID uuid = UUIDFetcher.getUUID(id); + uuidMap.put(name, uuid); + } + + if (rateLimiting && i != requests - 1) { + Thread.sleep(100L); + } + } + return uuidMap; + } + + public interface UUIDLookup { + void response(UUID uuid); + } + +} \ No newline at end of file diff --git a/src/main/java/com/nametagedit/plugin/utils/Utils.java b/src/main/java/com/nametagedit/plugin/utils/Utils.java new file mode 100644 index 0000000..e806af3 --- /dev/null +++ b/src/main/java/com/nametagedit/plugin/utils/Utils.java @@ -0,0 +1,88 @@ +package com.nametagedit.plugin.utils; + +import org.apache.commons.lang.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.io.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Utils { + + public static String format(String[] text, int to, int from) { + return StringUtils.join(text, ' ', to, from).replace("'", ""); + } + + public static String deformat(String input) { + return input.replace("ยง", "&"); + } + + public static String format(String input) { + return format(input, false); + } + + public static String format(String input, boolean limitChars) { + String colored = ChatColor.translateAlternateColorCodes('&', input); + return limitChars && colored.length() > 16 ? colored.substring(0, 16) : colored; + } + + public static List getOnline() { + List list = new ArrayList<>(); + + for (World world : Bukkit.getWorlds()) { + list.addAll(world.getPlayers()); + } + + return Collections.unmodifiableList(list); + } + + public static YamlConfiguration getConfig(File file) { + try { + if (!file.exists()) { + file.createNewFile(); + } + } catch (IOException e) { + e.printStackTrace(); + } + + return YamlConfiguration.loadConfiguration(file); + } + + public static YamlConfiguration getConfig(File file, String resource, Plugin plugin) { + try { + if (!file.exists()) { + file.createNewFile(); + InputStream inputStream = plugin.getResource(resource); + OutputStream outputStream = new FileOutputStream(file); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + inputStream.close(); + outputStream.flush(); + outputStream.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + + return YamlConfiguration.loadConfiguration(file); + } + + public static String generateUUID() { + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 5; i++) { + builder.append(chars.charAt((int) (Math.random() * chars.length()))); + } + return builder.toString(); + } + +} diff --git a/src/main/java/com/tidefactions/chat/Commands/CommandAdminChat.java b/src/main/java/com/tidefactions/chat/Commands/CommandAdminChat.java index a3996b9..45c910e 100644 --- a/src/main/java/com/tidefactions/chat/Commands/CommandAdminChat.java +++ b/src/main/java/com/tidefactions/chat/Commands/CommandAdminChat.java @@ -15,7 +15,7 @@ public class CommandAdminChat implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { - if (sender.hasPermission(ChatMode.ADMIN.getPermission())) { + if (sender.hasPermission(ChatMode.ADMIN.getPermission() + ".send")) { if (args.length >= 1) { if (sender instanceof Player) { ChatUtils.sendMessageInChannel( diff --git a/src/main/java/com/tidefactions/chat/Commands/CommandAlert.java b/src/main/java/com/tidefactions/chat/Commands/CommandAlert.java new file mode 100644 index 0000000..fa375b7 --- /dev/null +++ b/src/main/java/com/tidefactions/chat/Commands/CommandAlert.java @@ -0,0 +1,48 @@ +package com.tidefactions.chat.Commands; + +import com.tidefactions.chat.Messages; +import com.tidefactions.chat.Types.ChatMode; +import com.tidefactions.chat.Utils.ChatUtils; +import org.apache.commons.lang.StringUtils; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class CommandAlert implements CommandExecutor { + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String s, String[] args) { + if (sender.hasPermission(ChatMode.ALERT.getPermission() + ".send")) { + if (args.length >= 1) { + if (sender instanceof Player) { + ChatUtils.sendMessageInChannel( + ((Player) sender), + ChatMode.ALERT, + StringUtils.join(args, " "), + "Command"); + } else { + ChatUtils.sendMessageInChannelAsConsole( + ChatMode.ALERT, + StringUtils.join(args, " "), + "Command"); + } + } else { + if (sender instanceof Player) { + if (ChatUtils.getChatMode(((Player) sender)) == ChatMode.ALERT) { + ChatUtils.setChatMode(((Player) sender), ChatMode.PUBLIC); + sender.sendMessage(Messages.PREFIX + Messages.ALERT_TOGGLE_OFF); + } else { + ChatUtils.setChatMode(((Player) sender), ChatMode.ALERT); + sender.sendMessage(Messages.PREFIX + Messages.ALERT_TOGGLE_ON); + } + } else { + sender.sendMessage(Messages.PREFIX + Messages.ALERT_CONSOLE); + } + } + } else { + sender.sendMessage(Messages.PREFIX + Messages.NO_PERMISSION); + } + return true; + } +} diff --git a/src/main/java/com/tidefactions/chat/Commands/CommandBuilderChat.java b/src/main/java/com/tidefactions/chat/Commands/CommandBuilderChat.java index 05f8d87..df61e84 100644 --- a/src/main/java/com/tidefactions/chat/Commands/CommandBuilderChat.java +++ b/src/main/java/com/tidefactions/chat/Commands/CommandBuilderChat.java @@ -14,7 +14,7 @@ public class CommandBuilderChat implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { - if (sender.hasPermission(ChatMode.BUILDER.getPermission())) { + if (sender.hasPermission(ChatMode.BUILDER.getPermission() + ".send")) { if (args.length >= 1) { if (sender instanceof Player) { ChatUtils.sendMessageInChannel( diff --git a/src/main/java/com/tidefactions/chat/Commands/CommandStaffChat.java b/src/main/java/com/tidefactions/chat/Commands/CommandStaffChat.java index 165976c..f95f96d 100644 --- a/src/main/java/com/tidefactions/chat/Commands/CommandStaffChat.java +++ b/src/main/java/com/tidefactions/chat/Commands/CommandStaffChat.java @@ -14,7 +14,7 @@ public class CommandStaffChat implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { - if (sender.hasPermission(ChatMode.STAFF.getPermission())) { + if (sender.hasPermission(ChatMode.STAFF.getPermission() + ".send")) { if (args.length >= 1) { if (sender instanceof Player) { ChatUtils.sendMessageInChannel( diff --git a/src/main/java/com/tidefactions/chat/Config.java b/src/main/java/com/tidefactions/chat/Config.java index a9bb2d9..9e55e5a 100644 --- a/src/main/java/com/tidefactions/chat/Config.java +++ b/src/main/java/com/tidefactions/chat/Config.java @@ -5,22 +5,49 @@ import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; import java.io.IOException; -import java.util.HashMap; import static com.tidefactions.chat.Main.colorCodes; public class Config { + // Settings + public static Boolean LOG_CHAT_TO_CONSOLE; + + // Channel prefixes + public static String GLOBAL_CHANNEL_PREFIX; + public static String STAFF_CHANNEL_PREFIX; + public static String ADMIN_CHANNEL_PREFIX; + public static String BUILDER_CHANNEL_PREFIX; + public static String ALERT_CHANNEL_PREFIX; + // Chat formats public static String GLOBAL_CHAT_FORMAT; public static String STAFF_CHAT_FORMAT; public static String ADMIN_CHAT_FORMAT; public static String BUILDER_CHAT_FORMAT; + public static String ALERT_CHAT_FORMAT; + + // Name + public static String NAME_PREFIX_FORMAT; + public static String NAME_SUFFIX_FORMAT; // Console chat settings public static String CONSOLE_PREFIX; public static String CONSOLE_TITLE; + public static String CONSOLE_NAME_COLOR; public static String CONSOLE_CHAT_COLOR; + // Discord + public static Boolean DISCORD_ENABLED; + public static String DISCORD_ALERT_IMAGE; + public static String DISCORD_ALERT_NAME; + public static String DISCORD_CONSOLE_IMAGE; + public static String DISCORD_PLAYER_IMAGE; + public static String DISCORD_GLOBAL_WEBHOOK; + public static String DISCORD_STAFF_WEBHOOK; + public static String DISCORD_ADMIN_WEBHOOK; + public static String DISCORD_BUILDER_WEBHOOK; + public static String DISCORD_ALERT_WEBHOOK; + public static void load(File file) { try { if (!file.getParentFile().exists()) @@ -30,15 +57,46 @@ public class Config { YamlConfiguration config = new YamlConfiguration(); config.load(file); + // Settings + LOG_CHAT_TO_CONSOLE = config.getBoolean("settings.log-chat-to-console"); + + // Channel prefixes + GLOBAL_CHANNEL_PREFIX = colorCodes(config.getString("prefixes.global")); + STAFF_CHANNEL_PREFIX = colorCodes(config.getString("prefixes.staff")); + ADMIN_CHANNEL_PREFIX = colorCodes(config.getString("prefixes.admin")); + BUILDER_CHANNEL_PREFIX = colorCodes(config.getString("prefixes.builder")); + ALERT_CHANNEL_PREFIX = colorCodes(config.getString("prefixes.alert")); + // Chat formats GLOBAL_CHAT_FORMAT = colorCodes(config.getString("format.global")); STAFF_CHAT_FORMAT = colorCodes(config.getString("format.staff")); ADMIN_CHAT_FORMAT = colorCodes(config.getString("format.admin")); BUILDER_CHAT_FORMAT = colorCodes(config.getString("format.builder")); + ALERT_CHAT_FORMAT = colorCodes(config.getString("format.alert")); + + + // Nametag + NAME_PREFIX_FORMAT = colorCodes(config.getString("name.prefix-format")); + NAME_SUFFIX_FORMAT = colorCodes(config.getString("name.suffix-format")); + // Console chat settings CONSOLE_PREFIX = colorCodes(config.getString("console.prefix")); CONSOLE_TITLE = colorCodes(config.getString("console.title")); + CONSOLE_NAME_COLOR = colorCodes(config.getString("console.name-color")); CONSOLE_CHAT_COLOR = colorCodes(config.getString("console.chat-color")); + + // Discord + DISCORD_ENABLED = config.getBoolean("discord.enable"); + DISCORD_ALERT_NAME = config.getString("discord.alert-name"); + DISCORD_ALERT_IMAGE = config.getString("discord.alert-image"); + DISCORD_CONSOLE_IMAGE = config.getString("discord.console-image"); + DISCORD_PLAYER_IMAGE = config.getString("discord.player-image"); + DISCORD_GLOBAL_WEBHOOK = config.getString("discord.webhooks.global"); + DISCORD_STAFF_WEBHOOK = config.getString("discord.webhooks.staff"); + DISCORD_ADMIN_WEBHOOK = config.getString("discord.webhooks.admin"); + DISCORD_BUILDER_WEBHOOK = config.getString("discord.webhooks.builder"); + DISCORD_ALERT_WEBHOOK = config.getString("discord.webhooks.alert"); + } catch (IOException | InvalidConfigurationException e) { e.printStackTrace(); } diff --git a/src/main/java/com/tidefactions/chat/Databases/PrefixDatabase.java b/src/main/java/com/tidefactions/chat/Databases/PrefixDatabase.java index 1a5dc30..985e58c 100644 --- a/src/main/java/com/tidefactions/chat/Databases/PrefixDatabase.java +++ b/src/main/java/com/tidefactions/chat/Databases/PrefixDatabase.java @@ -11,55 +11,79 @@ public class PrefixDatabase { private static String url; public static void init(String path) { - File dbFile = new File(path + "/playerData.db"); - url = "jdbc:sqlite:" + path + "/playerData.db"; - if (!dbFile.getParentFile().exists()) - dbFile.getParentFile().mkdirs(); - if (!dbFile.exists()) { - createTable(); + try { + Class.forName("org.sqlite.JDBC"); + + File dbFile = new File(path + "/playerData.db"); + url = "jdbc:sqlite:" + path + "/playerData.db"; + if (!dbFile.getParentFile().exists()) + dbFile.getParentFile().mkdirs(); + if (!dbFile.exists()) { + createTable(); + } + } catch (Exception e) { + e.printStackTrace(); } } public static void createTable() { - // SQL statement for creating a new table - String sql = "CREATE TABLE IF NOT EXISTS prefixes (\n" - + " uuid TEXT PRIMARY KEY,\n" - + " prefix TEXT NOT NULL\n" - + ");"; + try { + Class.forName("org.sqlite.JDBC"); - try (Connection conn = DriverManager.getConnection(url); - Statement stmt = conn.createStatement()) { - stmt.execute(sql); - } catch (SQLException e) { + // SQL statement for creating a new table + String sql = "CREATE TABLE IF NOT EXISTS prefixes (\n" + + " uuid TEXT PRIMARY KEY,\n" + + " prefix TEXT NOT NULL\n" + + ");"; + + try (Connection conn = DriverManager.getConnection(url); + Statement stmt = conn.createStatement()) { + stmt.execute(sql); + } catch (SQLException e) { + e.printStackTrace(); + } + } catch (Exception e) { e.printStackTrace(); } } public static String getPrefixIDForPlayer(Player player) { - try (Connection conn = DriverManager.getConnection(url); - Statement stmt = conn.createStatement()) { - ResultSet result = stmt.executeQuery("SELECT * FROM prefixes WHERE uuid='" + player.getUniqueId() + "'"); - while (result.next()) { - return result.getString("prefix"); + try { + Class.forName("org.sqlite.JDBC"); + + try (Connection conn = DriverManager.getConnection(url); + Statement stmt = conn.createStatement()) { + ResultSet result = stmt.executeQuery("SELECT * FROM prefixes WHERE uuid='" + player.getUniqueId() + "'"); + while (result.next()) { + return result.getString("prefix"); + } + } catch (SQLException e) { + e.printStackTrace(); } - } catch (SQLException e) { + } catch (Exception e) { e.printStackTrace(); } return "Default"; } public static void setPrefix(Player player, Prefix prefix) { - try (Connection conn = DriverManager.getConnection(url); - Statement stmt = conn.createStatement()) { - stmt.execute("DELETE FROM prefixes WHERE uuid='" + player.getUniqueId() + "'"); - } catch (SQLException e) { - e.printStackTrace(); - } + try { + Class.forName("org.sqlite.JDBC"); - try (Connection conn = DriverManager.getConnection(url); - Statement stmt = conn.createStatement()) { - stmt.execute("INSERT INTO prefixes ('uuid', 'prefix') VALUES ('" + player.getUniqueId() + "', '" + prefix.getName() + "')"); - } catch (SQLException e) { + try (Connection conn = DriverManager.getConnection(url); + Statement stmt = conn.createStatement()) { + stmt.execute("DELETE FROM prefixes WHERE uuid='" + player.getUniqueId() + "'"); + } catch (SQLException e) { + e.printStackTrace(); + } + + try (Connection conn = DriverManager.getConnection(url); + Statement stmt = conn.createStatement()) { + stmt.execute("INSERT INTO prefixes ('uuid', 'prefix') VALUES ('" + player.getUniqueId() + "', '" + prefix.getName() + "')"); + } catch (SQLException e) { + e.printStackTrace(); + } + } catch (Exception e) { e.printStackTrace(); } } diff --git a/src/main/java/com/tidefactions/chat/EventHandlers/ChatHandler.java b/src/main/java/com/tidefactions/chat/EventHandlers/ChatHandler.java index 4aa6511..1c25b65 100644 --- a/src/main/java/com/tidefactions/chat/EventHandlers/ChatHandler.java +++ b/src/main/java/com/tidefactions/chat/EventHandlers/ChatHandler.java @@ -1,8 +1,11 @@ package com.tidefactions.chat.EventHandlers; -import com.tidefactions.chat.Types.ChatMode; +import com.tidefactions.chat.Config; +import com.tidefactions.chat.Events.MessageSendInChannelEvent; +import com.tidefactions.chat.Intergration.Discord; import com.tidefactions.chat.Utils.ChatUtils; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -13,6 +16,9 @@ public class ChatHandler implements Listener { @EventHandler(priority = EventPriority.HIGH) public void onAsyncChatEvent(AsyncPlayerChatEvent event) { + if (event.isCancelled()) + return; + Player player = event.getPlayer(); ChatUtils.sendMessageInChannel( player, @@ -20,5 +26,27 @@ public class ChatHandler implements Listener { event.getMessage(), "Chat"); event.setCancelled(true); + event.setMessage(ChatColor.RED + "[THIS EVENT WAS CANCELED BY CHAT]"); + } + + @EventHandler + public void onMessageSendInChannelEvent(MessageSendInChannelEvent event) { + if (Config.LOG_CHAT_TO_CONSOLE) { + String finalMessage; + if (event.getPlayer().equals("CONSOLE")) { + finalMessage = ChatUtils.getMessageForChannelAsConsole( + event.getChatMode(), + event.getMessage()); + } else { + finalMessage = ChatUtils.getMessageForChannel( + Bukkit.getPlayer(event.getPlayer()), + event.getChatMode(), + event.getMessage()); + } + Bukkit.getConsoleSender().sendMessage("[CHAT] " + finalMessage); + } + if (Config.DISCORD_ENABLED) { + Discord.forward(event); + } } } diff --git a/src/main/java/com/tidefactions/chat/EventHandlers/JoinHandler.java b/src/main/java/com/tidefactions/chat/EventHandlers/JoinHandler.java new file mode 100644 index 0000000..23f1bb5 --- /dev/null +++ b/src/main/java/com/tidefactions/chat/EventHandlers/JoinHandler.java @@ -0,0 +1,18 @@ +package com.tidefactions.chat.EventHandlers; + +import com.tidefactions.chat.Main; +import com.tidefactions.chat.Utils.NameUtils; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +public class JoinHandler implements Listener { + + @EventHandler + public void onPlayerJoinEvent(PlayerJoinEvent event) { + Bukkit.getScheduler().scheduleSyncDelayedTask(Main.plugin, () -> { + NameUtils.updatePlayer(event.getPlayer()); + }, 100); + } +} diff --git a/src/main/java/com/tidefactions/chat/Events/MessageSendInChannelEvent.java b/src/main/java/com/tidefactions/chat/Events/MessageSendInChannelEvent.java index 1004b47..98ba6c6 100644 --- a/src/main/java/com/tidefactions/chat/Events/MessageSendInChannelEvent.java +++ b/src/main/java/com/tidefactions/chat/Events/MessageSendInChannelEvent.java @@ -41,4 +41,9 @@ public class MessageSendInChannelEvent extends Event { public HandlerList getHandlers() { return handlerList; } + + public static HandlerList getHandlerList() { + return handlerList; + } + } diff --git a/src/main/java/com/tidefactions/chat/GUI/PrefixGui.java b/src/main/java/com/tidefactions/chat/GUI/PrefixGui.java index 24fe947..9165b18 100644 --- a/src/main/java/com/tidefactions/chat/GUI/PrefixGui.java +++ b/src/main/java/com/tidefactions/chat/GUI/PrefixGui.java @@ -18,14 +18,16 @@ public class PrefixGui { public static List views = new ArrayList<>(); public void open(Player player) { - int size = (int) (Math.ceil(PrefixUtils.getPrefixes().size() / 9) * 9); - if (size < 9) - size = 9; + int goal = PrefixUtils.getPrefixes().size(); + int size = 9; + while (size < goal) + size = size+9; Inventory inv = Bukkit.createInventory(null, size, ChatColor.GREEN + "Prefix " + ChatColor.DARK_GRAY + " selection"); - String currentPrefixID = null; - if (PrefixUtils.getPrefixForPlayer(player) != null) { + String currentPrefixID; + if (PrefixUtils.getPrefixForPlayer(player) == null) + currentPrefixID = PrefixUtils.getPrefixByID("default").getName(); + else currentPrefixID = PrefixUtils.getPrefixForPlayer(player).getName(); - } for (Prefix prefix : PrefixUtils.getPrefixes()) { if (player.hasPermission(prefix.getPermission())) { ItemStack is = new ItemStack( @@ -45,7 +47,6 @@ public class PrefixGui { inv.addItem(is); } } - views.add(player.openInventory(inv)); } } diff --git a/src/main/java/com/tidefactions/chat/Intergration/Discord.java b/src/main/java/com/tidefactions/chat/Intergration/Discord.java new file mode 100644 index 0000000..c0d9c30 --- /dev/null +++ b/src/main/java/com/tidefactions/chat/Intergration/Discord.java @@ -0,0 +1,57 @@ +package com.tidefactions.chat.Intergration; + +import com.tidefactions.chat.Config; +import com.tidefactions.chat.Events.MessageSendInChannelEvent; +import com.tidefactions.chat.Types.ChatMode; +import org.bukkit.Bukkit; +import org.json.simple.JSONObject; + +import javax.net.ssl.HttpsURLConnection; +import java.io.DataOutputStream; +import java.io.OutputStreamWriter; +import java.net.URL; + +public class Discord { + + @SuppressWarnings("all") + public static void forward(MessageSendInChannelEvent event) { + System.out.println("Forwarding message from " + event.getPlayer() + " to discord."); + if (event.getChatMode().getWebhook() != null && !event.getChatMode().getWebhook().equals("")) { + try { + // Open connection + URL url = new URL(event.getChatMode().getWebhook()); + HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); + + // Set properties + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setRequestMethod("POST"); + conn.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:49.0) Gecko/20100101 Firefox/49.0"); + conn.setRequestProperty("Content-Type", "application/json; charset=utf-8"); + + // Create JSON data + JSONObject data = new JSONObject(); + data.put("username", event.getChatMode() == ChatMode.ALERT ? + Config.DISCORD_ALERT_NAME : + event.getPlayer()); + data.put("avatar_url", event.getChatMode() == ChatMode.ALERT ? + Config.DISCORD_ALERT_IMAGE : + event.getPlayer() == "CONSOLE" ? + Config.DISCORD_CONSOLE_IMAGE : + Config.DISCORD_PLAYER_IMAGE.replaceAll("", + Bukkit.getPlayer(event.getPlayer()).getUniqueId().toString())); + data.put("content", event.getMessage()); + + // Send data + conn.setRequestProperty("Content-length", String.valueOf(data.toString().length())); + DataOutputStream stream = new DataOutputStream(conn.getOutputStream()); + stream.writeBytes(data.toString()); + stream.flush(); + stream.close(); + conn.getResponseMessage(); + } catch (Exception e) { + System.out.println("There was a problem forwarding chat to discord. Is the webhook url correct?"); + } + } + } +} diff --git a/src/main/java/com/tidefactions/chat/Main.java b/src/main/java/com/tidefactions/chat/Main.java index e2a6c1b..9c5c339 100644 --- a/src/main/java/com/tidefactions/chat/Main.java +++ b/src/main/java/com/tidefactions/chat/Main.java @@ -1,12 +1,12 @@ package com.tidefactions.chat; -import com.tidefactions.chat.Commands.CommandAdminChat; -import com.tidefactions.chat.Commands.CommandBuilderChat; -import com.tidefactions.chat.Commands.CommandPrefix; -import com.tidefactions.chat.Commands.CommandStaffChat; +import com.nametagedit.plugin.NametagManager; +import com.nametagedit.plugin.api.NametagAPI; +import com.tidefactions.chat.Commands.*; import com.tidefactions.chat.Databases.PrefixDatabase; import com.tidefactions.chat.EventHandlers.ChatHandler; import com.tidefactions.chat.EventHandlers.InventoryHandler; +import com.tidefactions.chat.EventHandlers.JoinHandler; import com.tidefactions.chat.Utils.PrefixUtils; import net.milkbowl.vault.chat.Chat; import net.milkbowl.vault.permission.Permission; @@ -24,30 +24,32 @@ public final class Main extends JavaPlugin { public static Logger logger; public static Main plugin; + public static NametagAPI nametagAPI; public static Permission perms = null; public static Chat chat = null; //TODO list - //- Save and load current prefix - //- Announcement support //- Titles @Override public void onEnable() { logger = this.getLogger(); plugin = this; + nametagAPI = new NametagAPI(new NametagManager()); setupPermissions(); PrefixDatabase.init(getDataFolder().getPath()); Config.load(new File(getDataFolder(), "config.yml")); Messages.load(new File(getDataFolder(), "messages.yml")); PrefixUtils.loadPrefixes(new File(plugin.getDataFolder(), "prefixes.yml")); getServer().getPluginManager().registerEvents(new ChatHandler(), this); + getServer().getPluginManager().registerEvents(new JoinHandler(), this); getServer().getPluginManager().registerEvents(new InventoryHandler(), this); getCommand("bc").setExecutor(new CommandBuilderChat()); getCommand("sc").setExecutor(new CommandStaffChat()); getCommand("ac").setExecutor(new CommandAdminChat()); + getCommand("alert").setExecutor(new CommandAlert()); getCommand("prefix").setExecutor(new CommandPrefix()); } diff --git a/src/main/java/com/tidefactions/chat/Messages.java b/src/main/java/com/tidefactions/chat/Messages.java index c56a4ca..48e77d9 100644 --- a/src/main/java/com/tidefactions/chat/Messages.java +++ b/src/main/java/com/tidefactions/chat/Messages.java @@ -21,6 +21,9 @@ public class Messages { public static String BC_CONSOLE; public static String BC_TOGGLE_ON; public static String BC_TOGGLE_OFF; + public static String ALERT_CONSOLE; + public static String ALERT_TOGGLE_ON; + public static String ALERT_TOGGLE_OFF; public static void load(File file) { @@ -50,6 +53,11 @@ public class Messages { BC_CONSOLE = colorCodes(config.getString("bc-console")); BC_TOGGLE_ON = colorCodes(config.getString("bc-toggle-on")); BC_TOGGLE_OFF = colorCodes(config.getString("bc-toggle-off")); + + // Alert messages + ALERT_CONSOLE = colorCodes(config.getString("alert-console")); + ALERT_TOGGLE_ON = colorCodes(config.getString("alert-toggle-on")); + ALERT_TOGGLE_OFF = colorCodes(config.getString("alert-toggle-off")); } catch (IOException | InvalidConfigurationException e) { e.printStackTrace(); } diff --git a/src/main/java/com/tidefactions/chat/Types/ChatMode.java b/src/main/java/com/tidefactions/chat/Types/ChatMode.java index 986028b..7bb3dcc 100644 --- a/src/main/java/com/tidefactions/chat/Types/ChatMode.java +++ b/src/main/java/com/tidefactions/chat/Types/ChatMode.java @@ -1,22 +1,24 @@ package com.tidefactions.chat.Types; import com.tidefactions.chat.Config; -import org.bukkit.ChatColor; public enum ChatMode { - PUBLIC("chat.public", "", Config.GLOBAL_CHAT_FORMAT), - STAFF("chat.staff", ChatColor.DARK_GRAY + "[" + ChatColor.AQUA + "STAFF" + ChatColor.DARK_GRAY + "] " + ChatColor.RESET, Config.STAFF_CHAT_FORMAT), - ADMIN("chat.admin", ChatColor.DARK_GRAY + "[" + ChatColor.RED + "ADMIN" + ChatColor.DARK_GRAY + "] " + ChatColor.RESET, Config.ADMIN_CHAT_FORMAT), - BUILDER("chat.builder", ChatColor.DARK_GRAY + "[" + ChatColor.YELLOW + "BUILDER" + ChatColor.DARK_GRAY + "] " + ChatColor.RESET, Config.BUILDER_CHAT_FORMAT); + PUBLIC("chat.public", Config.GLOBAL_CHANNEL_PREFIX, Config.GLOBAL_CHAT_FORMAT, Config.DISCORD_GLOBAL_WEBHOOK), + STAFF("chat.staff", Config.STAFF_CHANNEL_PREFIX, Config.STAFF_CHAT_FORMAT, Config.DISCORD_STAFF_WEBHOOK), + ADMIN("chat.admin", Config.ADMIN_CHANNEL_PREFIX, Config.ADMIN_CHAT_FORMAT, Config.DISCORD_ADMIN_WEBHOOK), + BUILDER("chat.builder", Config.BUILDER_CHANNEL_PREFIX, Config.BUILDER_CHAT_FORMAT, Config.DISCORD_BUILDER_WEBHOOK), + ALERT("chat.alert", Config.ALERT_CHANNEL_PREFIX, Config.ALERT_CHAT_FORMAT, Config.DISCORD_ALERT_WEBHOOK); private String permission; private String prefix; private String format; + private String webhook; - ChatMode(String permission, String prefix, String format) { + ChatMode(String permission, String prefix, String format, String webhook) { this.permission = permission; this.prefix = prefix; this.format = format; + this.webhook = webhook; } public String getPermission() { @@ -30,4 +32,8 @@ public enum ChatMode { public String getFormat() { return format; } + + public String getWebhook() { + return webhook; + } } diff --git a/src/main/java/com/tidefactions/chat/Types/Prefix.java b/src/main/java/com/tidefactions/chat/Types/Prefix.java index f087fdc..efa87a2 100644 --- a/src/main/java/com/tidefactions/chat/Types/Prefix.java +++ b/src/main/java/com/tidefactions/chat/Types/Prefix.java @@ -9,15 +9,21 @@ public class Prefix { private String name; private String permission; private String prefix; + private String shortPrefix; + private ChatColor nameColor1; + private ChatColor nameColor2; private Material item; private Short itemMeta; private List description; private ChatColor chatColor; - public Prefix(String name, String permission, String prefix, Material item, Integer itemMeta, List description, ChatColor chatColor) { + public Prefix(String name, String permission, String prefix, String shortPrefix, ChatColor nameColor1, ChatColor nameColor2, Material item, Integer itemMeta, List description, ChatColor chatColor) { this.name = name; this.permission = permission; this.prefix = prefix; + this.shortPrefix = shortPrefix; + this.nameColor1 = nameColor1; + this.nameColor2 = nameColor2; this.item = item; this.itemMeta = itemMeta.shortValue(); this.description = description; @@ -36,6 +42,18 @@ public class Prefix { return prefix; } + public String getshortPrefix() { + return shortPrefix; + } + + public ChatColor getNameColor1() { + return nameColor1; + } + + public ChatColor getNameColor2() { + return nameColor2; + } + public Material getItem() { return item; } diff --git a/src/main/java/com/tidefactions/chat/Utils/ChatUtils.java b/src/main/java/com/tidefactions/chat/Utils/ChatUtils.java index 305f352..ca8749b 100644 --- a/src/main/java/com/tidefactions/chat/Utils/ChatUtils.java +++ b/src/main/java/com/tidefactions/chat/Utils/ChatUtils.java @@ -19,7 +19,37 @@ public class ChatUtils { String finalMessage = getMessageForChannel(player, mode, message); MessageSendInChannelEvent event = new MessageSendInChannelEvent(player.getName(), mode, message, source); Bukkit.getPluginManager().callEvent(event); - Bukkit.broadcast(finalMessage, mode.getPermission()); + Bukkit.broadcast(finalMessage, mode.getPermission() + ".receive"); + parseMentions(message); + + } + + public static void sendMessageInChannelAsConsole(ChatMode mode, String message, String source) { + String finalMessage = getMessageForChannelAsConsole(mode, message); + MessageSendInChannelEvent event = new MessageSendInChannelEvent("CONSOLE", mode, message, source); + Bukkit.getPluginManager().callEvent(event); + Bukkit.broadcast(finalMessage, mode.getPermission() + ".receive"); + parseMentions(message); + } + + public static String getMessageForChannel(Player player, ChatMode mode, String message) { + return FormatUtils.format(mode.getFormat() + .replaceAll("", mode.getPrefix()) + .replaceAll("", message), player); + } + + public static String getMessageForChannelAsConsole(ChatMode mode, String message) { + return mode.getFormat() + .replaceAll("", mode.getPrefix()) + .replaceAll("", Config.CONSOLE_PREFIX) + .replaceAll("", Config.CONSOLE_NAME_COLOR) + .replaceAll("", "CONSOLE") + .replaceAll("", Config.CONSOLE_TITLE) + .replaceAll("<CHAT_COLOR>", Config.CONSOLE_CHAT_COLOR) + .replaceAll("<MESSAGE>", message); + } + + public static void parseMentions(String message) { if (message.contains("@")) { String[] array = message.split(" "); for (String word : array) @@ -31,34 +61,6 @@ public class ChatUtils { } } - public static void sendMessageInChannelAsConsole(ChatMode mode, String message, String source) { - String finalMessage = getMessageForChannelAsConsole(mode, message); - MessageSendInChannelEvent event = new MessageSendInChannelEvent("CONSOLE", mode, message, source); - Bukkit.getPluginManager().callEvent(event); - Bukkit.broadcast(finalMessage, mode.getPermission()); - } - - public static String getMessageForChannel(Player player, ChatMode mode, String message) { - Prefix prefix = PrefixUtils.getPrefixForPlayer(player); - return mode.getFormat() - .replaceAll("<MODE_PREFIX>", mode.getPrefix()) - .replaceAll("<PREFIX>", prefix.getPrefix()) - .replaceAll("<PLAYER_NAME>", player.getName()) - .replaceAll("<TITLE>", TitleUtils.getTitleForPlayer(player)) - .replaceAll("<CHAT_COLOR>", prefix.getChatColor().toString()) - .replaceAll("<MESSAGE>", message); - } - - public static String getMessageForChannelAsConsole(ChatMode mode, String message) { - return mode.getFormat() - .replaceAll("<MODE_PREFIX>", mode.getPrefix()) - .replaceAll("<PREFIX>", Config.CONSOLE_PREFIX) - .replaceAll("<PLAYER_NAME>", "CONSOLE") - .replaceAll("<TITLE>", Config.CONSOLE_TITLE) - .replaceAll("<CHAT_COLOR>", Config.CONSOLE_CHAT_COLOR) - .replaceAll("<MESSAGE>", message); - } - public static void setChatMode(Player player, ChatMode mode) { currentChatMode.put(player.getUniqueId(), mode); } diff --git a/src/main/java/com/tidefactions/chat/Utils/FormatUtils.java b/src/main/java/com/tidefactions/chat/Utils/FormatUtils.java new file mode 100644 index 0000000..f8ebd66 --- /dev/null +++ b/src/main/java/com/tidefactions/chat/Utils/FormatUtils.java @@ -0,0 +1,35 @@ +package com.tidefactions.chat.Utils; + +import com.tidefactions.chat.Types.Prefix; +import org.bukkit.entity.Player; + +public class FormatUtils { + + /* + PREFIX + SHORT_PREFIX + NAME_COLOR_1 + NAME_COLOR_2 + PLAYER_NAME + CHAT_COLOR + */ + public static String format(String format, Player player) { + Prefix pr = PrefixUtils.getPrefixForPlayer(player); + return format(format, player, pr); + } + + public static String format(String format, Player player, Prefix pr) { + + // Standard placeholders + format = format + .replaceAll("<PREFIX>", pr.getPrefix()) + .replaceAll("<SHORT_PREFIX>", pr.getshortPrefix()) + .replaceAll("<NAME_COLOR_1>", pr.getNameColor1().toString()) + .replaceAll("<NAME_COLOR_2>", pr.getNameColor2().toString()) + .replaceAll("<PLAYER_NAME>", player.getName()) + .replaceAll("<CHAT_COLOR>", pr.getChatColor().toString()) + .replaceAll("<TITLE>", TitleUtils.getTitleForPlayer(player)); + + return format; + } +} diff --git a/src/main/java/com/tidefactions/chat/Utils/NameUtils.java b/src/main/java/com/tidefactions/chat/Utils/NameUtils.java new file mode 100644 index 0000000..c853dea --- /dev/null +++ b/src/main/java/com/tidefactions/chat/Utils/NameUtils.java @@ -0,0 +1,22 @@ +package com.tidefactions.chat.Utils; + +import com.tidefactions.chat.Config; +import com.tidefactions.chat.Main; +import com.tidefactions.chat.Types.Prefix; +import org.bukkit.entity.Player; + +public class NameUtils { + + public static void updatePlayer(Player player) { + Prefix pr = PrefixUtils.getPrefixForPlayer(player); + Main.nametagAPI.setPrefix( + player, + FormatUtils.format(Config.NAME_PREFIX_FORMAT, player, pr) + ); + Main.nametagAPI.setSuffix( + player, + FormatUtils.format(Config.NAME_SUFFIX_FORMAT, player, pr) + ); + Main.nametagAPI.manager.sendTeams(player); + } +} diff --git a/src/main/java/com/tidefactions/chat/Utils/PrefixUtils.java b/src/main/java/com/tidefactions/chat/Utils/PrefixUtils.java index 1c15d40..acee32b 100644 --- a/src/main/java/com/tidefactions/chat/Utils/PrefixUtils.java +++ b/src/main/java/com/tidefactions/chat/Utils/PrefixUtils.java @@ -13,12 +13,13 @@ import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.HashMap; +import java.util.LinkedHashMap; import static com.tidefactions.chat.Main.logger; public class PrefixUtils { - private static HashMap<String, Prefix> prefixes = new HashMap<>(); + private static LinkedHashMap<String, Prefix> prefixes = new LinkedHashMap<>(); public static void loadPrefixes(File prefixFile) { if (!prefixFile.getParentFile().exists()) @@ -33,6 +34,9 @@ public class PrefixUtils { key, config.getString(key + ".permission"), ChatColor.translateAlternateColorCodes('&', config.getString(key + ".prefix")), + ChatColor.translateAlternateColorCodes('&', config.getString(key + ".short-prefix")), + ChatColor.valueOf(config.getString(key + ".name-color-1")), + ChatColor.valueOf(config.getString(key + ".name-color-1")), Material.getMaterial(config.getString(key + ".item")), config.getInt(key + ".item-meta"), config.getStringList(key + ".description"), @@ -52,6 +56,7 @@ public class PrefixUtils { public static void setPrefixForPlayer(Player player, Prefix prefix) { PrefixDatabase.setPrefix(player, prefix); + NameUtils.updatePlayer(player); } public static Prefix getPrefixByID(String id) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 0166925..7ec3d9c 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,9 +1,34 @@ +settings: + log-chat-to-console: true +prefixes: + global: "" + staff: "&8[&dSTAFF&8]&r" + admin: "&8[&cADMIN&8]&r" + builder: "&8[&eBUILDER&8]&r" + alert: "&8[&4ALERT&8]&r" format: - global: "<MODE_PREFIX><PREFIX> &r<PLAYER_NAME><TITLE>&7: <CHAT_COLOR><MESSAGE>" - staff: "<MODE_PREFIX><PREFIX> &r<PLAYER_NAME>&7: <CHAT_COLOR><MESSAGE>" - admin: "<MODE_PREFIX><PREFIX> &r<PLAYER_NAME>&7: <CHAT_COLOR><MESSAGE>" - builder: "<MODE_PREFIX><PREFIX> &r<PLAYER_NAME>&7: <CHAT_COLOR><MESSAGE>" + global: "<CHANNEL_PREFIX><PREFIX> <NAME_COLOR><PLAYER_NAME><TITLE>&7: <CHAT_COLOR><MESSAGE>" + staff: "<CHANNEL_PREFIX> <PREFIX> <NAME_COLOR><PLAYER_NAME>&7: <CHAT_COLOR><MESSAGE>" + admin: "<CHANNEL_PREFIX> <PREFIX> <NAME_COLOR><PLAYER_NAME>&7: <CHAT_COLOR><MESSAGE>" + builder: "<CHANNEL_PREFIX> <PREFIX> <NAME_COLOR><PLAYER_NAME>&7: <CHAT_COLOR><MESSAGE>" + alert: "<CHANNEL_PREFIX> <MESSAGE>" +name: + prefix-format: "<TABLIST_PREFIX> <NAME_COLOR>" + suffix-format: " <TITLE>" console: prefix: "&8[&4CONSOLE&8]" title: "" - chat-color: "&4" \ No newline at end of file + name-color: "&f" + chat-color: "&4" +discord: + enable: false + alert-name: "Alert" + alert-image: "http://www.clker.com/cliparts/P/u/5/K/W/c/alert-icon-red-md.png" + console-image: "" + player-image: "https://crafatar.com/avatars/<UUID>?overlay" + webhooks: + global: "" + staff: "" + admin: "" + builder: "" + alert: "" \ No newline at end of file diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index e97a04b..58963b8 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -9,3 +9,6 @@ sc-toggle-off: "You turned off staff chat." bc-console: "Console cant use /bc without arguments it has to use /bc <message>." bc-toggle-on: "You turned on builder chat." bc-toggle-off: "You turned off builder chat." +alert-console: "Console cant use /alert without argument it has to use /alert <message>." +alert-toggle-on: "You turned on alert mode." +alert-toggle-off: "You turned off alert mode." diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index ec4ff72..844a2e3 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: Chat -version: @version@ +version: 1.1 main: com.tidefactions.chat.Main authors: [dragontamerfred] depend: [Vault] @@ -7,4 +7,5 @@ commands: bc: sc: ac: - prefix: \ No newline at end of file + alert: + prefix: diff --git a/src/main/resources/prefixes.yml b/src/main/resources/prefixes.yml index 5cde82c..b483d51 100644 --- a/src/main/resources/prefixes.yml +++ b/src/main/resources/prefixes.yml @@ -1,6 +1,9 @@ Default: permission: 'prefix.default' prefix: '&8[&fMember&8]' + short-prefix: '&8[&fM&8]' + name-color-1: '&f' + name-color-2: '&f' item: 'WOOL' item-meta: 0 chat-color: "GRAY" @@ -10,6 +13,9 @@ Default: Builder: permission: 'prefix.builder' prefix: '&8[&9Builder&8]' + short-prefix: '&8[&9B&8]' + name-color-1: '&f' + name-color-2: '&9' item: 'WOOL' item-meta: 11 chat-color: "BLUE" @@ -20,6 +26,9 @@ Builder: Helper: permission: 'prefix.helper' prefix: '&8[&eHelper&8]' + short-prefix: '&8[&eH&8]' + name-color-1: '&f' + name-color-2: '&e' item: 'WOOL' item-meta: 4 chat-color: "YELLOW" @@ -30,6 +39,9 @@ Helper: Moderator: permission: 'prefix.moderator' prefix: '&8[&6Moderator&8]' + short-prefix: '&8[&6M&8]' + name-color-1: '&f' + name-color-2: '&6' item: 'STAINED_CLAY' item-meta: 4 chat-color: "GOLD" @@ -40,6 +52,9 @@ Moderator: Sr-Moderator: permission: 'prefix.sr-moderator' prefix: '&8[&6Sr.Moderator&8]' + short-prefix: '&8[&6SM&8]' + name-color-1: '&f' + name-color-2: '&6' item: 'GOLD_BLOCK' item-meta: 0 chat-color: "GOLD" @@ -51,6 +66,9 @@ Sr-Moderator: Admin: permission: 'prefix.admin' prefix: '&8[&cAdmin&8]' + short-prefix: '&8[&cA&8]' + name-color-1: '&f' + name-color-2: '&c' item: 'WOOL' item-meta: 14 chat-color: "RED" @@ -61,6 +79,9 @@ Admin: Developer: permission: 'prefix.developer' prefix: '&8[&bDeveloper&8]' + short-prefix: '&8[&bD&8]' + name-color-1: '&f' + name-color-2: '&b' item: 'WOOL' item-meta: 3 chat-color: "AQUA" @@ -72,6 +93,9 @@ Developer: Owner: permission: 'prefix.owner' prefix: '&8[&4Owner&8]' + short-prefix: '&8[&4O&8]' + name-color-1: '&f' + name-color-2: '&4' item: 'WOOL' item-meta: 14 chat-color: "DARK_RED"