├── version.txt ├── lib ├── PermissionsEx.jar ├── zPermissions.jar ├── LibsDisguises-10.0.25.jar └── EssentialsGroupManager.jar ├── .travis.yml ├── src └── main │ ├── resources │ ├── groups.yml │ ├── plugin.yml │ ├── players.yml │ └── config.yml │ └── java │ └── com │ └── nametagedit │ └── plugin │ ├── api │ ├── data │ │ ├── INametag.java │ │ ├── Nametag.java │ │ ├── GroupData.java │ │ ├── PlayerData.java │ │ └── FakeTeam.java │ ├── events │ │ ├── NametagFirstLoadedEvent.java │ │ └── NametagEvent.java │ ├── NametagAPI.java │ └── INametagApi.java │ ├── hooks │ ├── HookZPermissions.java │ ├── HookGroupManager.java │ ├── HookGuilds.java │ ├── HookPermissionsEX.java │ ├── HookLibsDisguise.java │ └── HookLuckPerms.java │ ├── invisibility │ └── InvisibilityTask.java │ ├── storage │ ├── AbstractConfig.java │ ├── database │ │ ├── tasks │ │ │ ├── GroupDeleter.java │ │ │ ├── PlayerDeleter.java │ │ │ ├── GroupPriority.java │ │ │ ├── PlayerPriority.java │ │ │ ├── GroupConfigUpdater.java │ │ │ ├── GroupAdd.java │ │ │ ├── GroupSaver.java │ │ │ ├── PlayerSaver.java │ │ │ ├── PlayerLoader.java │ │ │ ├── DataDownloader.java │ │ │ └── DatabaseUpdater.java │ │ └── DatabaseConfig.java │ └── flatfile │ │ └── FlatFileConfig.java │ ├── packets │ ├── PacketData.java │ ├── VersionChecker.java │ ├── PacketWrapper.java │ └── PacketAccessor.java │ ├── NametagMessages.java │ ├── NametagEdit.java │ ├── converter │ ├── Converter.java │ └── ConverterTask.java │ ├── utils │ ├── Utils.java │ ├── UUIDFetcher.java │ └── Configuration.java │ ├── NametagManager.java │ ├── NametagHandler.java │ └── NametagCommand.java ├── ISSUE_TEMPLATE.md ├── SECURITY.md ├── documentation ├── Permissions.creole ├── Support.creole ├── Commands.creole ├── Developers.creole └── Configuration.creole ├── .gitignore ├── README.md └── pom.xml /version.txt: -------------------------------------------------------------------------------- 1 | 4.5.23 2 | -------------------------------------------------------------------------------- /lib/PermissionsEx.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgtcaze/NametagEdit/HEAD/lib/PermissionsEx.jar -------------------------------------------------------------------------------- /lib/zPermissions.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgtcaze/NametagEdit/HEAD/lib/zPermissions.jar -------------------------------------------------------------------------------- /lib/LibsDisguises-10.0.25.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgtcaze/NametagEdit/HEAD/lib/LibsDisguises-10.0.25.jar -------------------------------------------------------------------------------- /lib/EssentialsGroupManager.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgtcaze/NametagEdit/HEAD/lib/EssentialsGroupManager.jar -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: [ openjdk8 ] 3 | dist: xenial 4 | os: linux 5 | sudo: false 6 | script: mvn clean verify 7 | -------------------------------------------------------------------------------- /src/main/resources/groups.yml: -------------------------------------------------------------------------------- 1 | Groups: 2 | Admin: 3 | Permission: nte.admin 4 | Prefix: '&c' 5 | Suffix: '&f' 6 | SortPriority: 1 7 | Moderator: 8 | Permission: nte.moderator 9 | Prefix: '&2' 10 | Suffix: '&f' 11 | SortPriority: 2 -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/api/data/INametag.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.api.data; 2 | 3 | public interface INametag { 4 | String getPrefix(); 5 | 6 | String getSuffix(); 7 | 8 | int getSortPriority(); 9 | 10 | boolean isPlayerTag(); 11 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/api/data/Nametag.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.api.data; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @Getter 8 | @Setter 9 | @AllArgsConstructor 10 | public class Nametag { 11 | private String prefix; 12 | private String suffix; 13 | } -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: NametagEdit 2 | main: com.nametagedit.plugin.NametagEdit 3 | version: ${project.version} 4 | api-version: 1.13 5 | authors: [sgtcaze, Cory, Lorenzo0111, Aurelien30000] 6 | softdepend: [zPermissions, PermissionsEX, GroupManager, LuckPerms, LibsDisguises, Guilds, PlaceholderAPI] 7 | commands: 8 | ne: 9 | description: NametagEdit commands 10 | aliases: [nte, nametagedit] 11 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Bug Report 2 | 3 | **Type /ver NametagEdit and post the output.** 4 | 5 | 6 | 7 | **What version of Bukkit/Spigot/PaperSpigot are you using? Type /ver** 8 | 9 | 10 | 11 | **What plugins are you using? Type /plugins** 12 | 13 | 14 | 15 | **Please explain your issue. How do you trigger it?** 16 | 17 | 18 | 19 | **Are there any errors in the console? Please use: https://pastebin.com** 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/players.yml: -------------------------------------------------------------------------------- 1 | Players: 2 | b5ccebaa-0623-4370-af73-0ec985dfa3b0: 3 | Name: sgtcazeyt 4 | Prefix: '&b' 5 | Suffix: '&c' 6 | SortPriority: -1 7 | 3716427e-76ac-4df4-a50a-706618a70f0c: 8 | Name: Cory 9 | Prefix: '&b' 10 | Suffix: '&c' 11 | SortPriority: 3 12 | 6818c4ce-df40-4083-a82f-ff38010b7977: 13 | Name: Lorenzo0111 14 | Prefix: '&b' 15 | Suffix: '&c' 16 | SortPriority: 3 17 | 88a10822-c3e4-47e8-8f53-1069858fedc4: 18 | Name: Aurelien30000 19 | Prefix: '&b' 20 | Suffix: '&c' 21 | SortPriority: 3 -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 4.4.x | :white_check_mark: | 11 | | 4.3.x | :white_check_mark: | 12 | | < 4.0.x | :x: | 13 | 14 | ## Reporting a Vulnerability 15 | 16 | Use this section to tell people how to report a vulnerability. 17 | 18 | Tell them where to go, how often they can expect to get an update on a 19 | reported vulnerability, what to expect if the vulnerability is accepted or 20 | declined, etc. 21 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/hooks/HookZPermissions.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.hooks; 2 | 3 | import com.nametagedit.plugin.NametagHandler; 4 | import lombok.AllArgsConstructor; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.Listener; 7 | import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsPlayerUpdateEvent; 8 | 9 | @AllArgsConstructor 10 | public class HookZPermissions implements Listener { 11 | 12 | private final NametagHandler handler; 13 | 14 | @EventHandler 15 | public void onZPermissionsRankChangeEvent(ZPermissionsPlayerUpdateEvent event) { 16 | handler.applyTagToPlayer(event.getPlayer(), false); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /documentation/Permissions.creole: -------------------------------------------------------------------------------- 1 | == Permissions 2 | 3 | | Permission | Description | 4 | | nametagedit.use | Allows a player to use the NametagEdit plugin | 5 | | nametagedit.reload | Allows a user to reload the plugin's data | 6 | | nametagedit.priority | Allows a user to view SortPriority information | 7 | | nametagedit.clear.others | Allows a player to clear ANYONE's prefix/suffix | 8 | | nametagedit.clear.self | Allows a player to only clear their own prefix/suffix | 9 | | nametagedit.edit.others | Allows a player to edit ANYONE's prefix/suffix | 10 | | nametagedit.edit.self | Allows a player to only edit their own prefix/suffix | 11 | | nametagedit.groups | Allows usage of ALL group subcommands (create, edit, remove) | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/hooks/HookGroupManager.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.hooks; 2 | 3 | import com.nametagedit.plugin.NametagHandler; 4 | import lombok.AllArgsConstructor; 5 | import org.anjocaido.groupmanager.events.GMUserEvent; 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.Listener; 9 | 10 | @AllArgsConstructor 11 | public class HookGroupManager implements Listener { 12 | 13 | private final NametagHandler handler; 14 | 15 | @EventHandler 16 | public void onGMUserEvent(GMUserEvent event) { 17 | Player player = event.getUser().getBukkitPlayer(); 18 | if (player != null) { 19 | handler.applyTagToPlayer(player, false); 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/hooks/HookGuilds.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.hooks; 2 | 3 | import com.nametagedit.plugin.NametagHandler; 4 | import lombok.AllArgsConstructor; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.Listener; 9 | import me.glaremasters.guilds.api.events.base.GuildEvent; 10 | 11 | @AllArgsConstructor 12 | public class HookGuilds implements Listener { 13 | 14 | private final NametagHandler handler; 15 | 16 | @EventHandler 17 | public void onGuildEvent(GuildEvent event) { 18 | Player player = Bukkit.getPlayerExact(event.getPlayer().getName()); 19 | if (player != null) { 20 | handler.applyTagToPlayer(player, false); 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/hooks/HookPermissionsEX.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.hooks; 2 | 3 | import com.nametagedit.plugin.NametagHandler; 4 | import lombok.AllArgsConstructor; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.Listener; 9 | import ru.tehkode.permissions.events.PermissionEntityEvent; 10 | 11 | @AllArgsConstructor 12 | public class HookPermissionsEX implements Listener { 13 | 14 | private final NametagHandler handler; 15 | 16 | @EventHandler 17 | public void onPermissionEntityEvent(PermissionEntityEvent event) { 18 | Player player = Bukkit.getPlayerExact(event.getEntity().getName()); 19 | if (player != null) { 20 | handler.applyTagToPlayer(player, false); 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/api/events/NametagFirstLoadedEvent.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.api.events; 2 | 3 | import com.nametagedit.plugin.api.data.INametag; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.event.Event; 8 | import org.bukkit.event.HandlerList; 9 | 10 | /** 11 | * This class represents an Event that is fired when a 12 | * player joins the server and receives their nametag. 13 | */ 14 | @Getter 15 | @AllArgsConstructor 16 | public class NametagFirstLoadedEvent extends Event { 17 | 18 | private static final HandlerList HANDLERS = new HandlerList(); 19 | 20 | private final Player player; 21 | private final INametag nametag; 22 | 23 | public static HandlerList getHandlerList() { 24 | return HANDLERS; 25 | } 26 | 27 | @Override 28 | public HandlerList getHandlers() { 29 | return HANDLERS; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/invisibility/InvisibilityTask.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.invisibility; 2 | 3 | import com.nametagedit.plugin.NametagEdit; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.potion.PotionEffectType; 7 | import org.bukkit.scheduler.BukkitRunnable; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class InvisibilityTask extends BukkitRunnable { 13 | 14 | @Override 15 | public void run(){ 16 | List players = new ArrayList<>(Bukkit.getOnlinePlayers()); 17 | if(players.isEmpty()){ 18 | return; 19 | } 20 | 21 | players.forEach(player ->{ 22 | if(player.hasPotionEffect(PotionEffectType.INVISIBILITY)){ 23 | NametagEdit.getApi().hideNametag(player); 24 | }else{ 25 | NametagEdit.getApi().showNametag(player); 26 | } 27 | }); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/api/data/GroupData.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.api.data; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import org.bukkit.permissions.Permission; 6 | import org.bukkit.permissions.PermissionDefault; 7 | 8 | /** 9 | * This class represents a group nametag. There 10 | * are several properties available. 11 | */ 12 | @Data 13 | @AllArgsConstructor 14 | public class GroupData implements INametag { 15 | 16 | private String groupName; 17 | private String prefix; 18 | private String suffix; 19 | private String permission; 20 | private Permission bukkitPermission; 21 | private int sortPriority; 22 | 23 | public GroupData() { 24 | 25 | } 26 | 27 | public void setPermission(String permission) { 28 | this.permission = permission; 29 | bukkitPermission = new Permission(permission, PermissionDefault.FALSE); 30 | } 31 | 32 | @Override 33 | public boolean isPlayerTag() { 34 | return false; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/AbstractConfig.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage; 2 | 3 | import com.nametagedit.plugin.api.data.GroupData; 4 | import com.nametagedit.plugin.api.data.PlayerData; 5 | import org.bukkit.command.CommandSender; 6 | import org.bukkit.entity.Player; 7 | 8 | import java.util.List; 9 | import java.util.UUID; 10 | 11 | /** 12 | * This is responsible for abstracting 13 | * a database/flat file storage 14 | */ 15 | public interface AbstractConfig { 16 | 17 | void load(); 18 | 19 | void reload(); 20 | 21 | void shutdown(); 22 | 23 | void load(Player player, boolean loggedIn); 24 | 25 | void save(PlayerData... playerData); 26 | 27 | void save(GroupData... groupData); 28 | 29 | void savePriority(boolean playerTag, String key, int priority); 30 | 31 | void delete(GroupData groupData); 32 | 33 | void add(GroupData groupData); 34 | 35 | void clear(UUID uuid, String targetName); 36 | 37 | void orderGroups(CommandSender commandSender, List order); 38 | 39 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/hooks/HookLibsDisguise.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.hooks; 2 | 3 | import com.nametagedit.plugin.NametagEdit; 4 | import lombok.AllArgsConstructor; 5 | import me.libraryaddict.disguise.events.DisguiseEvent; 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.Listener; 9 | import org.bukkit.scheduler.BukkitRunnable; 10 | 11 | @AllArgsConstructor 12 | public class HookLibsDisguise implements Listener { 13 | 14 | private final NametagEdit plugin; 15 | 16 | @EventHandler 17 | public void onDisguiseEvent(final DisguiseEvent event) { 18 | if (event.getEntity() instanceof Player) { 19 | plugin.getHandler().getNametagManager().reset(event.getEntity().getName()); 20 | new BukkitRunnable() { 21 | @Override 22 | public void run() { 23 | plugin.getHandler().applyTagToPlayer((Player) event.getEntity(), false); 24 | } 25 | }.runTaskLater(plugin, 3); 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/GroupDeleter.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import lombok.AllArgsConstructor; 6 | import org.bukkit.scheduler.BukkitRunnable; 7 | 8 | import java.sql.Connection; 9 | import java.sql.PreparedStatement; 10 | import java.sql.SQLException; 11 | 12 | @AllArgsConstructor 13 | public class GroupDeleter extends BukkitRunnable { 14 | 15 | private final String groupName; 16 | private final HikariDataSource hikari; 17 | 18 | @Override 19 | public void run() { 20 | try (Connection connection = hikari.getConnection()) { 21 | final String QUERY = "DELETE FROM " + DatabaseConfig.TABLE_GROUPS + " WHERE `name`=?"; 22 | PreparedStatement delete = connection.prepareStatement(QUERY); 23 | delete.setString(1, groupName); 24 | delete.execute(); 25 | delete.close(); 26 | } catch (SQLException e) { 27 | e.printStackTrace(); 28 | } 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/PlayerDeleter.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import lombok.AllArgsConstructor; 6 | import org.bukkit.scheduler.BukkitRunnable; 7 | 8 | import java.sql.Connection; 9 | import java.sql.PreparedStatement; 10 | import java.sql.SQLException; 11 | import java.util.UUID; 12 | 13 | @AllArgsConstructor 14 | public class PlayerDeleter extends BukkitRunnable { 15 | 16 | private final UUID uuid; 17 | private final HikariDataSource hikari; 18 | 19 | @Override 20 | public void run() { 21 | try (Connection connection = hikari.getConnection()) { 22 | final String QUERY = "DELETE FROM " + DatabaseConfig.TABLE_PLAYERS + " WHERE `uuid`=?"; 23 | PreparedStatement delete = connection.prepareStatement(QUERY); 24 | delete.setString(1, uuid.toString()); 25 | delete.execute(); 26 | delete.close(); 27 | } catch (SQLException e) { 28 | e.printStackTrace(); 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/hooks/HookLuckPerms.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.hooks; 2 | 3 | import com.nametagedit.plugin.NametagHandler; 4 | import net.luckperms.api.LuckPerms; 5 | import net.luckperms.api.event.EventBus; 6 | import net.luckperms.api.event.user.UserDataRecalculateEvent; 7 | import net.luckperms.api.model.user.User; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.Listener; 11 | 12 | public class HookLuckPerms implements Listener { 13 | 14 | private final NametagHandler handler; 15 | 16 | public HookLuckPerms(NametagHandler handler) { 17 | this.handler = handler; 18 | EventBus eventBus = Bukkit.getServicesManager().load(LuckPerms.class).getEventBus(); 19 | eventBus.subscribe(handler.getPlugin(), UserDataRecalculateEvent.class, this::onUserDataRecalculateEvent); 20 | } 21 | 22 | private void onUserDataRecalculateEvent(UserDataRecalculateEvent event) { 23 | User user = event.getUser(); 24 | Player player = Bukkit.getPlayer(user.getUniqueId()); 25 | if (player != null) { 26 | handler.applyTagToPlayer(player, false); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/GroupPriority.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import lombok.AllArgsConstructor; 6 | import org.bukkit.scheduler.BukkitRunnable; 7 | 8 | import java.sql.Connection; 9 | import java.sql.PreparedStatement; 10 | import java.sql.SQLException; 11 | 12 | @AllArgsConstructor 13 | public class GroupPriority extends BukkitRunnable { 14 | 15 | private final String group; 16 | private final int priority; 17 | private final HikariDataSource hikari; 18 | 19 | @Override 20 | public void run() { 21 | try (Connection connection = hikari.getConnection()) { 22 | PreparedStatement preparedStatement = connection.prepareStatement("UPDATE " + DatabaseConfig.TABLE_GROUPS + " SET `priority`=? WHERE `name`=?"); 23 | preparedStatement.setInt(1, priority); 24 | preparedStatement.setString(2, group); 25 | preparedStatement.execute(); 26 | preparedStatement.close(); 27 | } catch (SQLException e) { 28 | e.printStackTrace(); 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/PlayerPriority.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import lombok.AllArgsConstructor; 6 | import org.bukkit.scheduler.BukkitRunnable; 7 | 8 | import java.sql.Connection; 9 | import java.sql.PreparedStatement; 10 | import java.sql.SQLException; 11 | import java.util.UUID; 12 | 13 | @AllArgsConstructor 14 | public class PlayerPriority extends BukkitRunnable { 15 | 16 | private final UUID player; 17 | private final int priority; 18 | private final HikariDataSource hikari; 19 | 20 | @Override 21 | public void run() { 22 | try (Connection connection = hikari.getConnection()) { 23 | PreparedStatement preparedStatement = connection.prepareStatement("UPDATE " + DatabaseConfig.TABLE_PLAYERS + " SET `priority`=? WHERE `uuid`=?"); 24 | preparedStatement.setInt(1, priority); 25 | preparedStatement.setString(2, player.toString()); 26 | preparedStatement.execute(); 27 | preparedStatement.close(); 28 | } catch (SQLException e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/GroupConfigUpdater.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import lombok.AllArgsConstructor; 6 | import org.bukkit.scheduler.BukkitRunnable; 7 | 8 | import java.sql.Connection; 9 | import java.sql.PreparedStatement; 10 | import java.sql.SQLException; 11 | 12 | @AllArgsConstructor 13 | public class GroupConfigUpdater extends BukkitRunnable { 14 | 15 | private final String setting; 16 | private final String value; 17 | private final HikariDataSource hikari; 18 | 19 | @Override 20 | public void run() { 21 | try (Connection connection = hikari.getConnection()) { 22 | final String QUERY = "INSERT INTO " + DatabaseConfig.TABLE_GROUPS + " VALUES(?, ?) ON DUPLICATE KEY UPDATE `value`=?"; 23 | PreparedStatement update = connection.prepareStatement(QUERY); 24 | update.setString(1, setting); 25 | update.setString(2, value); 26 | update.setString(3, value); 27 | update.execute(); 28 | update.close(); 29 | } catch (SQLException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /documentation/Support.creole: -------------------------------------------------------------------------------- 1 | = Support 2 | Are your nametags not displaying? Are things on fire? Please read below carefully before asking for support! 3 | 4 | == Nametags are assigned to the wrong group! 5 | Chances are you misconfigured your Group Hierarchy. SortPriority and GroupOrder are NOT the same feature! Read up on proper configuration [here](Configuration.creole) 6 | 7 | == Is your client crashing with NametagEdit enabled? 8 | It is most likely that several plugins are manipulating the main scoreboard, and players are being added to several Scoreboard Teams. This is a well known Minecraft issue and not something we can fix, unfortunately. 9 | 10 | == If you are using a minigame or scoreboards plugin 11 | Please send us the name, or check if it is incompatible [here](IncompatiblePlugins.md) 12 | 13 | == Long Tags Don't Work! 14 | The nameplate (above your head) is limited to 48 characters total. 16 characters for the prefix, 16 for your name, and 16 for the suffix. It is NOT a bug that nametags longer than 16 characters are cut off. This is because of a limitation in the minecraft scoreboard code. 15 | 16 | == ViaVersion: 17 | If you're using ViaVersion on 1.8.x and having issues with tags showing up in the tab list. Edit the file plugins/ViaVersion/config.yml 18 | 19 | Change: 20 | {{{ 21 | auto-team: true 22 | }}} 23 | 24 | To: 25 | {{{ 26 | auto-team: false 27 | }}} -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/api/data/PlayerData.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.api.data; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.bukkit.configuration.file.YamlConfiguration; 7 | 8 | import java.util.UUID; 9 | 10 | /** 11 | * This class represents a player nametag. There 12 | * are several properties available. 13 | */ 14 | @Getter 15 | @Setter 16 | @AllArgsConstructor 17 | public class PlayerData implements INametag { 18 | 19 | private String name; 20 | private UUID uuid; 21 | private String prefix; 22 | private String suffix; 23 | private int sortPriority; 24 | 25 | public PlayerData() { 26 | 27 | } 28 | 29 | public static PlayerData fromFile(String key, YamlConfiguration file) { 30 | if (!file.contains("Players." + key)) return null; 31 | PlayerData data = new PlayerData(); 32 | data.setUuid(UUID.fromString(key)); 33 | data.setName(file.getString("Players." + key + ".Name")); 34 | data.setPrefix(file.getString("Players." + key + ".Prefix", "")); 35 | data.setSuffix(file.getString("Players." + key + ".Suffix", "")); 36 | data.setSortPriority(file.getInt("Players." + key + ".SortPriority", -1)); 37 | return data; 38 | } 39 | 40 | @Override 41 | public boolean isPlayerTag() { 42 | return true; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/GroupAdd.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.api.data.GroupData; 4 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 5 | import com.zaxxer.hikari.HikariDataSource; 6 | import lombok.AllArgsConstructor; 7 | import org.bukkit.scheduler.BukkitRunnable; 8 | 9 | import java.sql.Connection; 10 | import java.sql.PreparedStatement; 11 | import java.sql.SQLException; 12 | 13 | @AllArgsConstructor 14 | public class GroupAdd extends BukkitRunnable { 15 | 16 | private final GroupData groupData; 17 | private final HikariDataSource hikari; 18 | 19 | @Override 20 | public void run() { 21 | try (Connection connection = hikari.getConnection()) { 22 | final String QUERY = "INSERT INTO " + DatabaseConfig.TABLE_GROUPS + " VALUES(?, ?, ?, ?, ?)"; 23 | PreparedStatement insert = connection.prepareStatement(QUERY); 24 | insert.setString(1, groupData.getGroupName()); 25 | insert.setString(2, groupData.getPermission()); 26 | insert.setString(3, groupData.getPrefix()); 27 | insert.setString(4, groupData.getSuffix()); 28 | insert.setInt(5, groupData.getSortPriority()); 29 | insert.execute(); 30 | insert.close(); 31 | } catch (SQLException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/GroupSaver.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.api.data.GroupData; 4 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 5 | import com.nametagedit.plugin.utils.Utils; 6 | import com.zaxxer.hikari.HikariDataSource; 7 | import lombok.AllArgsConstructor; 8 | import org.bukkit.scheduler.BukkitRunnable; 9 | 10 | import java.sql.Connection; 11 | import java.sql.PreparedStatement; 12 | import java.sql.SQLException; 13 | 14 | @AllArgsConstructor 15 | public class GroupSaver extends BukkitRunnable { 16 | 17 | private final GroupData[] groupData; 18 | private final HikariDataSource hikari; 19 | 20 | @Override 21 | public void run() { 22 | try (Connection connection = hikari.getConnection()) { 23 | final String QUERY = "UPDATE " + DatabaseConfig.TABLE_GROUPS + " SET `prefix`=?, `suffix`=?, `permission`=?, `priority`=? WHERE `name`=?"; 24 | PreparedStatement update = connection.prepareStatement(QUERY); 25 | 26 | for (GroupData groupData : this.groupData) { 27 | update.setString(1, Utils.deformat(groupData.getPrefix())); 28 | update.setString(2, Utils.deformat(groupData.getSuffix())); 29 | update.setString(3, groupData.getPermission()); 30 | update.setInt(4, groupData.getSortPriority()); 31 | update.setString(5, groupData.getGroupName()); 32 | update.addBatch(); 33 | } 34 | 35 | update.executeBatch(); 36 | update.close(); 37 | } catch (SQLException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/packets/PacketData.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.packets; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | enum PacketData { 9 | 10 | v1_7("e", "c", "d", "a", "f", "g", "b", "NA", "NA", "NA", "NA"), 11 | cauldron("field_149317_e", "field_149319_c", "field_149316_d", "field_149320_a", 12 | "field_149314_f", "field_149315_g", "field_149318_b", "NA", "NA", "NA", "NA"), 13 | v1_8("g", "c", "d", "a", "h", "i", "b", "NA", "NA", "e", "NA"), 14 | v1_9("h", "c", "d", "a", "i", "j", "b", "NA", "f", "e", "NA"), 15 | v1_10("h", "c", "d", "a", "i", "j", "b", "NA", "f", "e", "NA"), 16 | v1_11("h", "c", "d", "a", "i", "j", "b", "NA", "f", "e", "NA"), 17 | v1_12("h", "c", "d", "a", "i", "j", "b", "NA", "f", "e", "NA"), 18 | v1_13("h", "c", "d", "a", "i", "j", "b", "g", "f", "e", "NA"), 19 | v1_14("h", "c", "d", "a", "i", "j", "b", "g", "f", "e", "NA"), 20 | v1_15("h", "c", "d", "a", "i", "j", "b", "g", "f", "e", "NA"), 21 | v1_16("h", "c", "d", "a", "i", "j", "b", "g", "f", "e", "NA"), 22 | v1_17("j", "b", "c", "i", "h", "g", "a", "f", "e", "d", "k"), 23 | v1_18("j", "b", "c", "i", "h", "g", "a", "f", "e", "d", "k"), 24 | v1_19("j", "b", "c", "i", "h", "g", "a", "f", "e", "d", "k"), 25 | v1_20("j", "b", "c", "i", "h", "g", "a", "f", "e", "d", "k"); 26 | 27 | private final String members; 28 | private final String prefix; 29 | private final String suffix; 30 | private final String teamName; 31 | private final String paramInt; 32 | private final String packOption; 33 | private final String displayName; 34 | private final String color; 35 | private final String push; 36 | private final String visibility; 37 | // 1.17+ 38 | private final String params; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/PlayerSaver.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.api.data.PlayerData; 4 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 5 | import com.nametagedit.plugin.utils.Utils; 6 | import com.zaxxer.hikari.HikariDataSource; 7 | import lombok.AllArgsConstructor; 8 | import org.bukkit.scheduler.BukkitRunnable; 9 | 10 | import java.sql.Connection; 11 | import java.sql.PreparedStatement; 12 | import java.sql.SQLException; 13 | 14 | @AllArgsConstructor 15 | public class PlayerSaver extends BukkitRunnable { 16 | 17 | private final PlayerData[] playerData; 18 | private final HikariDataSource hikari; 19 | 20 | @Override 21 | public void run() { 22 | try (Connection connection = hikari.getConnection()) { 23 | final String QUERY = "INSERT INTO " + DatabaseConfig.TABLE_PLAYERS + " VALUES(?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE `prefix`=?, `suffix`=?, `priority`=?"; 24 | PreparedStatement insertOrUpdate = connection.prepareStatement(QUERY); 25 | 26 | for (PlayerData playerData : this.playerData) { 27 | insertOrUpdate.setString(1, playerData.getUuid().toString()); 28 | insertOrUpdate.setString(2, playerData.getName()); 29 | insertOrUpdate.setString(3, Utils.deformat(playerData.getPrefix())); 30 | insertOrUpdate.setString(4, Utils.deformat(playerData.getSuffix())); 31 | insertOrUpdate.setInt(5, -1); 32 | insertOrUpdate.setString(6, Utils.deformat(playerData.getPrefix())); 33 | insertOrUpdate.setString(7, Utils.deformat(playerData.getSuffix())); 34 | insertOrUpdate.setInt(8, playerData.getSortPriority()); 35 | insertOrUpdate.addBatch(); 36 | } 37 | 38 | insertOrUpdate.executeBatch(); 39 | insertOrUpdate.close(); 40 | } catch (SQLException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/NametagMessages.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin; 2 | 3 | import com.nametagedit.plugin.utils.Utils; 4 | import lombok.AllArgsConstructor; 5 | import org.bukkit.ChatColor; 6 | import org.bukkit.command.CommandSender; 7 | 8 | @AllArgsConstructor 9 | public enum NametagMessages { 10 | 11 | SET_PRIORITY("Set sort priority to %s for %s"), 12 | CLEARED_TEAMS("Empty teams: %s. Purge: %s."), 13 | DEBUG_TOGGLED("NametagEdit debug has been %s"), 14 | LONG_TAGS("Long Nametags has been %s"), 15 | GROUP_EXISTS("The group %s already exists"), 16 | GROUP_VALUE_CLEARED("Cleared the %s for the group %s"), 17 | GROUP_EXISTS_NOT("The group %s does not exist!"), 18 | GROUP_VALUE("Changed %s's %s to %s"), 19 | USAGE_CONVERT("Usage: /nte convert "), 20 | GROUP_REMOVED("Successfully removed group %s"), 21 | MODIFY_OWN_TAG("You can only modify your own tag."), 22 | NO_PERMISSION("You do not have permission to use this."), 23 | RELOADED_DATA("Successfully reloaded plugin data"), 24 | FILE_DOESNT_EXIST("The file %s does not exist"), 25 | UUID_LOOKUP_FAILED("Could not find the uuid for %s"), 26 | CREATED_GROUP("Created group %s"), 27 | NOT_A_NUMBER("Uh-oh! %s does not appear to be a number!"), 28 | FILE_MISCONFIGURED("The file %s is not properly configured. Please read the configuration guide, otherwise conversion will fail."), 29 | CONVERSION("Attempting to convert %s from %s to %s. (Legacy: %s)"); 30 | 31 | private final String text; 32 | 33 | @Override 34 | public String toString() { 35 | return Utils.color("&8» &a" + text); 36 | } 37 | 38 | public void send(CommandSender sender) { 39 | sender.sendMessage(toString()); 40 | } 41 | 42 | public void send(CommandSender sender, String replacement) { 43 | sender.sendMessage(toString().replace("%s", Utils.format(replacement))); 44 | } 45 | 46 | public void send(CommandSender sender, Object... object) { 47 | sender.sendMessage(String.format(toString(), object)); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /documentation/Commands.creole: -------------------------------------------------------------------------------- 1 | == Commands 2 | 3 | | Command | Parameters | Description | 4 | | nte | N/A | Displays all available command arguments | 5 | | nte | debug | Displays debug information | 6 | | nte | reload | Reloads configuration and nametags | 7 | | nte | longtags | Toggles if NametagEdit should attempt long nametags | 8 | | nte | priority | Configure the sort priority of nametags | 9 | | nte | teams | Debug feature to clear empty teams | 10 | | nte | player [player] clear | Clears the prefix and suffix for a player | 11 | | nte | player [player] | Sets the prefix or suffix for a player | 12 | | nte | player [player] priority <#> | Sets the nametag priority | 13 | | nte | group list | Lists all loaded groups | 14 | | nte | group order | Orders the priority of groups. | 15 | | nte | group add [group] | Creates a new group | 16 | | nte | group remove [group] | Removes a group | 17 | | nte | group [group] perm | Sets the default permission for a group | 18 | | nte | group [group] prefix | Sets the prefix for a group | 19 | | nte | group [group] suffix | Sets the suffix for a group | 20 | | nte | group [group] priority | Sets the nametag priority | 21 | | nte | group [group] clear | Clears the suffix for a group | 22 | 23 | == Example Command Usage 24 | 25 | === Ordering Group Priority 26 | Suppose we have 3 groups: Owner, Admin, Default. The first group looked at is the one with the highest priority. We would use: 27 | 28 | {{{ 29 | /ne group order Owner Admin Default 30 | }}} 31 | 32 | NametagEdit will remember the order so groups always load properly. 33 | 34 | === Editing Invidiual Prefixes/Suffixes 35 | Suppose we wish to have a nametag like 36 | 37 | {{{ 38 | [Admin] sgtcazeyt 39 | }}} 40 | 41 | The command would be: 42 | 43 | {{{ 44 | /nte player sgtcazeyt prefix '[Admin] ' 45 | }}} 46 | 47 | The ' character will be automatically removed, and allow you to use spaces 48 | 49 | === Creating/Editing Group Properties 50 | So we want to create, or edit a group. We can start by creating it: 51 | *Run /ne groups add MyGroup 52 | *Assign a permission /ne groups set perm MyGroup my.custom.permission 53 | *Set the prefix: /ne groups set prefix MyGroup '&a[MyGroup] &e' 54 | *Set the suffix: /ne groups set suffix MyGroup ' &cMy Suffix' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Minecraft.gitignore 2 | # You can download it here: git.io/McIgnore 3 | # Version: 1.1 4 | 5 | # User-specific stuff 6 | .idea/ 7 | 8 | *.iml 9 | *.ipr 10 | *.iws 11 | 12 | # IntelliJ 13 | out/ 14 | 15 | # Compiled class file 16 | *.class 17 | 18 | # Log file 19 | *.log 20 | 21 | # BlueJ files 22 | *.ctxt 23 | 24 | # Package Files # 25 | *.war 26 | *.nar 27 | *.ear 28 | *.zip 29 | *.tar.gz 30 | *.rar 31 | 32 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 33 | hs_err_pid* 34 | 35 | *~ 36 | 37 | # temporary files which can be created if a process still has a handle open of a deleted file 38 | .fuse_hidden* 39 | 40 | # KDE directory preferences 41 | .directory 42 | 43 | # Linux trash folder which might appear on any partition or disk 44 | .Trash-* 45 | 46 | # .nfs files are created when an open file is removed but is still being accessed 47 | .nfs* 48 | 49 | # General 50 | .DS_Store 51 | .AppleDouble 52 | .LSOverride 53 | 54 | # Icon must end with two \r 55 | Icon 56 | 57 | # Thumbnails 58 | ._* 59 | 60 | # Files that might appear in the root of a volume 61 | .DocumentRevisions-V100 62 | .fseventsd 63 | .Spotlight-V100 64 | .TemporaryItems 65 | .Trashes 66 | .VolumeIcon.icns 67 | .com.apple.timemachine.donotpresent 68 | 69 | # Directories potentially created on remote AFP share 70 | .AppleDB 71 | .AppleDesktop 72 | Network Trash Folder 73 | Temporary Items 74 | .apdisk 75 | 76 | # Windows thumbnail cache files 77 | Thumbs.db 78 | Thumbs.db:encryptable 79 | ehthumbs.db 80 | ehthumbs_vista.db 81 | 82 | # Dump file 83 | *.stackdump 84 | 85 | # Folder config file 86 | [Dd]esktop.ini 87 | 88 | # Recycle Bin used on file shares 89 | $RECYCLE.BIN/ 90 | 91 | # Windows Installer files 92 | *.cab 93 | *.msi 94 | *.msix 95 | *.msm 96 | *.msp 97 | 98 | # Windows shortcuts 99 | *.lnk 100 | 101 | target/ 102 | 103 | pom.xml.tag 104 | pom.xml.releaseBackup 105 | pom.xml.versionsBackup 106 | pom.xml.next 107 | 108 | release.properties 109 | dependency-reduced-pom.xml 110 | buildNumber.properties 111 | .mvn/timing.properties 112 | .mvn/wrapper/maven-wrapper.jar 113 | .flattened-pom.xml 114 | 115 | # Common working directory 116 | run/ 117 | 118 | # Database and server directory 119 | db/ 120 | server/ -------------------------------------------------------------------------------- /documentation/Developers.creole: -------------------------------------------------------------------------------- 1 | == API and Developers 2 | 3 | To use the NametagEdit API, you have 2 choices: reference NametagEdit in your libraries, or use Maven. 4 | 5 | == Maven 6 | 7 | Add the following repository to your POM.xml: 8 | 9 | {{{ 10 | 11 | upstream 12 | https://ci.nametagedit.com/plugin/repository/everything/ 13 | 14 | }}} 15 | 16 | Then add the dependency: 17 | 18 | {{{ 19 | 20 | com.nametagedit 21 | nametagedit 22 | 4.4.16 23 | 24 | }}} 25 | 26 | == API Usage 27 | 28 | === Events 29 | 30 | You can choose to listen to the NametagEvent. This event fires BEFORE a nametag is changed. 31 | 32 | There are 5 available parameters. The player, value, change type, change reason and storage type. 33 | 34 | Example Usage: 35 | 36 | {{{ 37 | @EventHandler 38 | public void onNametagEvent(NametagEvent event) { 39 | if (event.getChangeReason() == NametagEvent.ChangeReason.PLUGIN) { 40 | if (event.getChangeType() == NametagEvent.ChangeType.PREFIX) { 41 | Player player = Bukkit.getPlayerExact(event.getPlayer()); 42 | player.sendMessage("The value was: " + event.getValue()); 43 | } 44 | } 45 | } 46 | }}} 47 | 48 | {{{ 49 | @EventHandler 50 | public void onNametagFirstLoadedEvent(NametagFirstLoadedEvent event) { 51 | // This event is fired when a player joins the server and their nametag is determined 52 | // This is useful for custom join messages. For general nametag changes, listen to NametagEvent 53 | INametag tag = event.getNametag(); 54 | Player player = event.getPlayer(); 55 | 56 | String prefix = tag.getPrefix(); 57 | String suffix = tag.getSuffix(); 58 | int sortPriority = tag.getSortPriority(); 59 | boolean isPlayerTag = tag.isPlayerTag(); 60 | } 61 | }}} 62 | 63 | === API Methods 64 | 65 | There are various methods available through the API. You can access these methods via NametagEdit.getApi() 66 | 67 | Example Usage: 68 | 69 | {{{ 70 | public void something(Player player) { 71 | NametagEdit.getApi().setPrefix(player, "&c"); 72 | } 73 | }}} 74 | 75 | === Cross-Plugin Compatibility 76 | 77 | If you use or develop a plugin that utilizes scoreboards, there can be issues when displaying the correct nametag for a player. Your client could also crash. 78 | 79 | To get around this, simply use: 80 | 81 | {{{ 82 | FakeTeam team = NametagEdit.getApi().getFakeTeam(String player); 83 | String name = team.getName(); 84 | }}} 85 | 86 | And reuse the name for the new scoreboard. Credit to @Maximvdw for the workaround, which can be found at [[https://github.com/sgtcaze/NametagEdit/pull/229|Adding Compatibility]] 87 | 88 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/api/events/NametagEvent.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.api.events; 2 | 3 | import com.nametagedit.plugin.api.data.Nametag; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.bukkit.event.Cancellable; 7 | import org.bukkit.event.Event; 8 | import org.bukkit.event.HandlerList; 9 | 10 | /** 11 | * This class represents an Event that is fired when a 12 | * nametag is changed. 13 | */ 14 | public class NametagEvent extends Event implements Cancellable { 15 | 16 | private static final HandlerList HANDLERS = new HandlerList(); 17 | 18 | private boolean cancelled; 19 | 20 | @Getter 21 | @Setter 22 | @Deprecated 23 | private String value; 24 | 25 | @Getter 26 | @Setter 27 | private Nametag nametag; 28 | 29 | @Getter 30 | @Setter 31 | private String player; 32 | 33 | @Getter 34 | @Setter 35 | private ChangeType changeType; 36 | 37 | @Getter 38 | @Setter 39 | private ChangeReason changeReason; 40 | 41 | @Getter 42 | @Setter 43 | private StorageType storageType; 44 | 45 | public NametagEvent(String player, String value) { 46 | this(player, value, ChangeType.UNKNOWN); 47 | } 48 | 49 | public NametagEvent(String player, String value, Nametag nametag, ChangeType type) { 50 | this(player, value, type); 51 | this.nametag = nametag; 52 | } 53 | 54 | public NametagEvent(String player, String value, ChangeType changeType) { 55 | this(player, value, changeType, StorageType.MEMORY, ChangeReason.UNKNOWN); 56 | } 57 | 58 | public NametagEvent(String player, String value, ChangeType changeType, ChangeReason changeReason) { 59 | this(player, value, changeType, StorageType.MEMORY, changeReason); 60 | } 61 | 62 | public NametagEvent(String player, String value, ChangeType changeType, StorageType storageType, ChangeReason changeReason) { 63 | this.player = player; 64 | this.value = value; 65 | this.changeType = changeType; 66 | this.storageType = storageType; 67 | this.changeReason = changeReason; 68 | } 69 | 70 | public static HandlerList getHandlerList() { 71 | return HANDLERS; 72 | } 73 | 74 | @Override 75 | public HandlerList getHandlers() { 76 | return HANDLERS; 77 | } 78 | 79 | @Override 80 | public boolean isCancelled() { 81 | return cancelled; 82 | } 83 | 84 | @Override 85 | public void setCancelled(boolean cancelled) { 86 | this.cancelled = cancelled; 87 | } 88 | 89 | public enum ChangeReason { 90 | API, PLUGIN, UNKNOWN 91 | } 92 | 93 | public enum ChangeType { 94 | PREFIX, SUFFIX, GROUP, CLEAR, PREFIX_AND_SUFFIX, RELOAD, UNKNOWN 95 | } 96 | 97 | public enum StorageType { 98 | MEMORY, PERSISTENT 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------- 2 | # NametagEdit by sgtcaze and Cory 3 | #--------------------------------------------------------- 4 | # Development Builds: https://ci.nametagedit.com 5 | # Source Code: http://www.github.com/sgtcaze/NametagEdit 6 | #--------------------------------------------------------- 7 | # 8 | # These settings allow you to customize the behavior of the 9 | # tab list. 10 | Tablist: 11 | # If Enabled, NametagEdit will format the Tab menu 12 | Enabled: true 13 | # !! EXPERIMENTAL !! 14 | # This feature will use the PlayerListName to set your 15 | # nametag. It will allow you to bypass the 16 character limit 16 | # for prefix/suffix. Be warned: This does not work on earlier versions 17 | # of Minecraft. Please report issues to our GitHub, and we'll try to help. 18 | LongTags: false 19 | # 20 | # If enabled, this plugin will send small data MCStats.org. 21 | MetricsEnabled: true 22 | # 23 | # If enabled, players with NametagEdit nametags will not be able to push each other (1.9+) 24 | DisablePush: false 25 | # 26 | # Do you have strange, unexplained lag emanating from NametagEdit? Likely it's a few plugins 27 | # manipulating the primary scoreboard, and not clearing the resources. This setting will 28 | # clear empty teams on a timer. You can also clear it with /nte teams clear. This setting 29 | # will be in seconds. e.g. every 300 seconds clear empty teams (5 minutes). Set this to < 1 30 | # to disable this feature. 31 | ClearEmptyTeamsInterval: -1 32 | # 33 | # If enabled, NametagEdit will turn your console into spam 34 | # heaven. Useful for reporting issues! 35 | Debug: false 36 | # 37 | # Some servers have different permissions per world, and therefore different potential nametags 38 | # per world. If you want to refresh a player's nametag when they change worlds, set 39 | # this option to true. 40 | RefreshTagOnWorldChange: false 41 | # 42 | # If enabled, NametagEdit will refresh nametags on an interval. Note: this does NOT do any 43 | # reloading. This will not pickup changes made from the file or database. Instead, it will 44 | # take any changes made by commands and/or placeholder changes. Set this to < 1 to disable 45 | # this feature. This feature is in seconds. NOTE: We do not recommend this. 46 | RefreshInterval: 0 47 | # 48 | # If enabled, NametagEdit will use database support 49 | MySQL: 50 | Enabled: false 51 | Hostname: localhost 52 | Port: 3306 53 | Username: myUsername 54 | Password: myPassword 55 | Database: myDatabase 56 | # WARNING: If you do not know what this is for, do not change it! 57 | # This allows users to customize their schema to their liking. 58 | GroupsTable: nte_groups 59 | PlayersTable: nte_players 60 | ConfigTable: nte_config 61 | # Minimum connection pool size 62 | # WARNING: If you have no idea what this is for, do not change. 63 | # To see the optimal connections for your setup, please visit: 64 | # https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing 65 | # And review the pool size configuration (Tn x (Cm - 1) + 1) 66 | MinimumPoolSize: 10 -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/api/data/FakeTeam.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.api.data; 2 | 3 | import com.nametagedit.plugin.packets.VersionChecker; 4 | import com.nametagedit.plugin.utils.Utils; 5 | import lombok.Data; 6 | import lombok.Getter; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * This class represents a Scoreboard Team. It is used 13 | * to keep track of the current members of a Team, and 14 | * is responsible for 15 | */ 16 | @Data 17 | public class FakeTeam { 18 | 19 | @Getter private static final List createdTeamsNames = new ArrayList<>(); 20 | 21 | // Because some networks use NametagEdit on multiple servers, we may have clashes 22 | // with the same Team names. The UNIQUE_ID ensures there will be no clashing. 23 | private static final String UNIQUE_ID = Utils.generateUUID(); 24 | // This represents the number of FakeTeams that have been created. 25 | // It is used to generate a unique Team name. 26 | private static int ID = 0; 27 | private final ArrayList members = new ArrayList<>(); 28 | private String name; 29 | private String prefix; 30 | private String suffix; 31 | private boolean visible = true; 32 | 33 | public FakeTeam(String prefix, String suffix, int sortPriority, boolean playerTag) { 34 | String generatedName = UNIQUE_ID + "_" + getNameFromInput(sortPriority) + ++ID + (playerTag ? "+P" : ""); 35 | while(createdTeamsNames.contains(generatedName)){ 36 | generatedName = Utils.generateUUID() + "_" + getNameFromInput(sortPriority) + ++ID + (playerTag ? "+P" : ""); 37 | } 38 | this.name = generatedName; 39 | 40 | switch (VersionChecker.getBukkitVersion()) { 41 | case v1_8_R1: case v1_8_R2: case v1_8_R3: case v1_9_R1: case v1_9_R2: 42 | case v1_10_R1: case v1_11_R1: case v1_12_R1: 43 | this.name = this.name.length() > 16 ? this.name.substring(0, 16) : this.name; 44 | default: 45 | this.name = this.name.length() > 256 ? this.name.substring(0, 256) : this.name; 46 | } 47 | 48 | this.prefix = prefix; 49 | this.suffix = suffix; 50 | 51 | this.createdTeamsNames.add(this.name); 52 | } 53 | 54 | public void addMember(String player) { 55 | if (!members.contains(player)) { 56 | members.add(player); 57 | } 58 | } 59 | 60 | public boolean isSimilar(String prefix, String suffix, boolean visible) { 61 | return this.prefix.equals(prefix) && this.suffix.equals(suffix) && this.visible == visible; 62 | } 63 | 64 | /** 65 | * This is a special method to sort nametags in 66 | * the tablist. It takes a priority and converts 67 | * it to an alphabetic representation to force a 68 | * specific sort. 69 | * 70 | * @param input the sort priority 71 | * @return the team name 72 | */ 73 | private String getNameFromInput(int input) { 74 | if (input < 0) return "Z"; 75 | char letter = (char) ((input / 5) + 65); 76 | int repeat = input % 5 + 1; 77 | StringBuilder builder = new StringBuilder(); 78 | for (int i = 0; i < repeat; i++) { 79 | builder.append(letter); 80 | } 81 | return builder.toString(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/PlayerLoader.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import java.sql.Connection; 4 | import java.sql.PreparedStatement; 5 | import java.sql.ResultSet; 6 | import java.sql.SQLException; 7 | import java.util.UUID; 8 | 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.plugin.Plugin; 12 | import org.bukkit.scheduler.BukkitRunnable; 13 | 14 | import com.nametagedit.plugin.NametagHandler; 15 | import com.nametagedit.plugin.api.data.PlayerData; 16 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 17 | import com.zaxxer.hikari.HikariDataSource; 18 | 19 | import lombok.AllArgsConstructor; 20 | 21 | @AllArgsConstructor 22 | public class PlayerLoader extends BukkitRunnable { 23 | 24 | private UUID uuid; 25 | private Plugin plugin; 26 | private NametagHandler handler; 27 | private HikariDataSource hikari; 28 | private boolean loggedIn; 29 | 30 | @Override 31 | public void run() { 32 | String tempPrefix = null; 33 | String tempSuffix = null; 34 | int priority = -1; 35 | boolean found = false; 36 | 37 | try (Connection connection = hikari.getConnection()) { 38 | final String QUERY = "SELECT `prefix`, `suffix`, `priority` FROM " + DatabaseConfig.TABLE_PLAYERS + " WHERE `uuid`=?"; 39 | 40 | try (PreparedStatement select = connection.prepareStatement(QUERY)) { 41 | select.setString(1, uuid.toString()); 42 | 43 | ResultSet resultSet = select.executeQuery(); 44 | if (resultSet.next()) { 45 | tempPrefix = resultSet.getString("prefix"); 46 | tempSuffix = resultSet.getString("suffix"); 47 | priority = resultSet.getInt("priority"); 48 | found = true; 49 | } 50 | 51 | resultSet.close(); 52 | } 53 | } catch (SQLException e) { 54 | e.printStackTrace(); 55 | } finally { 56 | final String prefix = tempPrefix == null ? "" : tempPrefix; 57 | final String suffix = tempSuffix == null ? "" : tempSuffix; 58 | final boolean finalFound = found; 59 | 60 | final int finalPriority = priority; 61 | new BukkitRunnable() { 62 | @Override 63 | public void run() { 64 | Player player = Bukkit.getPlayer(uuid); 65 | if (player != null) { 66 | if (finalFound) { 67 | PlayerData data = handler.getPlayerData(player); 68 | if (data == null) { 69 | data = new PlayerData(player.getName(), player.getUniqueId(), prefix, suffix, finalPriority); 70 | handler.storePlayerData(player.getUniqueId(), data); 71 | } else { 72 | data.setPrefix(prefix); 73 | data.setSuffix(suffix); 74 | } 75 | } 76 | 77 | handler.applyTagToPlayer(player, loggedIn); 78 | } 79 | } 80 | }.runTask(plugin); 81 | } 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NametagEdit 2 | 3 | [![Dev Builds](https://img.shields.io/badge/Jenkins-Development%20Builds-lightgrey.svg)](https://ci.nametagedit.com/job/NametagEdit) 4 | [![Support](https://img.shields.io/badge/Minecraft-1.7--1.19-green.svg)](documentation/Support) 5 | [![Spigot](https://img.shields.io/badge/Spigot-Project%20Page-yellow.svg)](https://www.spigotmc.org/resources/nametagedit.3836/) 6 | [![JDK](https://img.shields.io/badge/JDK-1.8-blue.svg)](https://jdk.java.net/java-se-ri/8-MR3) 7 | [![NametagEditAPI](https://img.shields.io/badge/NTE-Developer%20API-ff69b4.svg)](documentation/Developers.creole) 8 | 9 | This plugin allows users to add up to 16 characters before and after their name. Individual tags can be created for players, or a group can be created that can be joined via permissions. 10 | 11 | * Minecraft `1.7.x to 1.12.x` has a max 16-character limit. 12 | * Minecraft `1.13.x to 1.20.x` has a max 256-character limit. 13 | * Minecraft `1.16.x to 1.20.x` has `hex color` support. 14 | 15 | NametagEdit has support for EssentialsGroupManager, PermissionsEx, zPermissions, LuckPerms and LibsDisguises. If a user changes groups or permissions, their tag is automatically updated. 16 | 17 | * [Official Project Page](https://www.spigotmc.org/resources/nametagedit.3836/) 18 | * [Development Builds](https://ci.nametagedit.com/job/NametagEdit) 19 | 20 | # Quick Links 21 | * [API & Developers](documentation/Developers.creole) 22 | * [Permissions](documentation/Permissions.creole) 23 | * [Commands](documentation/Commands.creole) 24 | * [Configuration](documentation/Configuration.creole) 25 | * [Common Issues](documentation/Support.creole) 26 | 27 | # Features 28 | ✔ Converters to and from MySQL and FlatFile 29 | 30 | ✔ Efficient Flatfile support and MySQL connection pooling 31 | 32 | ✔ PermissionsEX, zPermissions, GroupManager, LuckPerms (https://www.spigotmc.org/resources/luckperms-an-advanced-permissions-plugin.28140/) support 33 | 34 | ✔ Sortable Group/Player Tags in tab 35 | 36 | ✔ [MVdW Placeholder API](https://www.spigotmc.org/resources/mvdwplaceholderapi.11182/) Support 37 | 38 | ✔ [Clip Placeholder API](https://www.spigotmc.org/resources/placeholderapi.6245/) Support 39 | 40 | ✔ [Guilds](https://www.spigotmc.org/resources/guilds.66176/) Support 41 | 42 | # Frequently Asked Questions 43 | #### Q: Will this allow me to change my skin and name? 44 | **A:** No. This plugin creates fake scoreboard teams with packets. 45 | 46 | #### Q: My client crashes with the reason "Cannot remove from ID#". Why is this? 47 | **A:** Due to how scoreboards were implemented in Minecraft, a player cannot belong to two teams. Any two plugins that use packets or the Bukkit API which alter team prefixes/suffixes will have conflicts. There is currently no way around this. 48 | 49 | #### Q: My nametag is cut short, even with LongTags enabled! 50 | **A:** LongTags is only able to disable a longer nametag in the tablist. The name above your head has a different limit (16 characters for prefix and suffix.) We are unable to change this, as this limit is imposed by Mojang. 51 | 52 | #### Q: Can I sort nametags in the tab list? 53 | **A:** Yes. Read up on how to use it [here](documentation/Configuration.creole) 54 | 55 | # TODO 56 | 57 | # Incompatible Plugins 58 | ✖ Any plugin that creates NPCs that share the same username as players who have 'NametagEdit' nametags 59 | 60 | ✖ Any plugin that uses Team color sidebars without specifically supporting NametagEdit 61 | -------------------------------------------------------------------------------- /documentation/Configuration.creole: -------------------------------------------------------------------------------- 1 | == Configuration 2 | Three configurable files will be generated if they do not exist. They are: "config.yml", "groups.yml" and "players.yml". 3 | 4 | === config.yml 5 | 6 | | Option | Value(s) | Description | 7 | | ClearEmptyTeamsInterval | Integer | This feature clears empty teams left behind by other plugins (might fix lag!). This setting is expressed as an interval in seconds. | 8 | | DatabaseVersion | Integer | Do NOT change this setting! | 9 | | Debug | true/false | If enabled, NametagEdit will print out debug information to the console. | 10 | | DisablePush | true/false | If 'true', NametagEdit will prevent player pushing. This only works if both players have NametagEdit nametags. | 11 | | MetricsEnabled | true/false | If 'true', small amounts of data will be sent to MCstats.org so we can keep track of the plugin's popularity. | 12 | | MySQL | true/false | If 'true', NametagEdit will use MySQL to store group/player data | 13 | | RefreshTagOnWorldChange | true/false | If enabled, NametagEdit will refresh a player's nametag when they change worlds. | 14 | | RefreshInterval | Integer | This feature will refresh all player's nametags on an interval expressed in seconds. | 15 | | Tablist.Enabled | true/false | If enabled, NametagEdit will alter a user's name in the tab. | 16 | | Tablist.LongTags | true/false | If enabled, NametagEdit will format the user's PlayerListName for longer nametags. | 17 | 18 | === groups.yml 19 | Place your HIGHEST permission group at the top. At the top of the file there should be a group like "Owner" while the bottom should have something similar to "Default". 20 | 21 | {{{ 22 | Groups: 23 | Owner: # This is the 'key' or the name to distinguish this group 24 | Permission: nte.owner # This is the permission required to have this nametag. You can change this to whatever. 25 | Prefix: '&2' # Both Prefix/Suffix are REQUIRED, even if they are blank. String length will be automatically trimmed 26 | Suffix: '&f' 27 | SortPriority: 1 28 | }}} 29 | 30 | === players.yml 31 | 32 | {{{ 33 | Players: 34 | b5ccebaa-0623-4370-af73-0ec985dfa3b0: # This is the 'key' to identify the players 35 | Name: sgtcazeyt # This is the friendly name of the player 36 | Prefix: '&b' # Both Prefix/Suffix are REQUIRED, even if they are blank. String length will be automatically trimmed 37 | Suffix: '&c' 38 | SortPriority: 1 39 | }}} 40 | 41 | == Sorting Nametags 42 | 43 | Server owners might want to arrange the tab list so nametags follow a certain order. You can accomplish this for both Groups & Players: 44 | {{{ 45 | SortPriority: 1 46 | }}} 47 | 48 | SortPriority represents the position in TAB. If you set this to "1" you're saying, "Display this first". If you set it to "2", you're saying, "Display this second" -- and so on and so forth. 49 | 50 | Suppose you have 2 ranks: 51 | {{{ 52 | Groups: 53 | Admin: 54 | Permission: nte.admin 55 | Prefix: '&c' 56 | Suffix: '&f' 57 | Moderator: 58 | Permission: nte.moderator 59 | Prefix: '&2' 60 | Suffix: '&f' 61 | }}} 62 | 63 | And you want to display Admin first. You might do something like: 64 | {{{ 65 | Groups: 66 | Admin: 67 | Permission: nte.admin 68 | Prefix: '&c' 69 | Suffix: '&f' 70 | SortPriority: 1 # 1 means it will display first 71 | Moderator: 72 | Permission: nte.moderator 73 | Prefix: '&2' 74 | Suffix: '&f' 75 | SortPriority: 2 # Means this will display second 76 | }}} 77 | 78 | To make the Moderator tag display first, we would simply reverse the Sort Priority. If there is no Sort Priority defined, then tags will randomly format in the tab. 79 | Scoreboard Team names are limited to 16 characters each. You can have several hundred distinct Sort Priorities without issues. 80 | 81 | === Players and Groups conflict 82 | The sort priority MUST be UNIQUE to be used properly. For example, if you have SortPriority: 1 in both your Groups/Players files, then nametags will not be guaranteed to arrange properly. For example, if we have a rank 83 | such as "Server Owner", we could set the SortPriority to 1, and then below that an "Admin" groups can have a SortPriority of 2. The same is true for if a player shares the same Sort Priority as a group. -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/NametagEdit.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin; 2 | 3 | import com.nametagedit.plugin.api.INametagApi; 4 | import com.nametagedit.plugin.api.NametagAPI; 5 | import com.nametagedit.plugin.hooks.*; 6 | import com.nametagedit.plugin.invisibility.InvisibilityTask; 7 | import com.nametagedit.plugin.packets.PacketWrapper; 8 | import com.nametagedit.plugin.packets.VersionChecker; 9 | import lombok.Getter; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.plugin.PluginManager; 12 | import org.bukkit.plugin.java.JavaPlugin; 13 | 14 | import java.util.ArrayList; 15 | 16 | /** 17 | * TODO: 18 | * - Better uniform message format + more messages 19 | * - Code cleanup 20 | * - Add language support 21 | */ 22 | @Getter 23 | public class NametagEdit extends JavaPlugin { 24 | 25 | private static NametagEdit instance; 26 | 27 | private static INametagApi api; 28 | 29 | private NametagHandler handler; 30 | private NametagManager manager; 31 | private VersionChecker.BukkitVersion version; 32 | 33 | public static INametagApi getApi() { 34 | return api; 35 | } 36 | 37 | @Override 38 | public void onEnable() { 39 | testCompat(); 40 | if (!isEnabled()) return; 41 | 42 | instance = this; 43 | 44 | version = VersionChecker.getBukkitVersion(); 45 | 46 | getLogger().info("Successfully loaded using bukkit version: " + version.name()); 47 | 48 | manager = new NametagManager(this); 49 | handler = new NametagHandler(this, manager); 50 | 51 | PluginManager pluginManager = Bukkit.getPluginManager(); 52 | 53 | if (checkShouldRegister("zPermissions")) { 54 | pluginManager.registerEvents(new HookZPermissions(handler), this); 55 | } else if (checkShouldRegister("PermissionsEx")) { 56 | pluginManager.registerEvents(new HookPermissionsEX(handler), this); 57 | } else if (checkShouldRegister("GroupManager")) { 58 | pluginManager.registerEvents(new HookGroupManager(handler), this); 59 | } else if (checkShouldRegister("LuckPerms")) { 60 | pluginManager.registerEvents(new HookLuckPerms(handler), this); 61 | } 62 | 63 | if (pluginManager.getPlugin("LibsDisguises") != null) { 64 | pluginManager.registerEvents(new HookLibsDisguise(this), this); 65 | } 66 | if (pluginManager.getPlugin("Guilds") != null) { 67 | pluginManager.registerEvents(new HookGuilds(handler), this); 68 | } 69 | 70 | getCommand("ne").setExecutor(new NametagCommand(handler)); 71 | 72 | if (api == null) { 73 | api = new NametagAPI(handler, manager); 74 | } 75 | 76 | if (version.name().startsWith("v1_8_")) 77 | new InvisibilityTask().runTaskTimerAsynchronously(this, 100L, 20L); 78 | } 79 | 80 | public static NametagEdit getInstance(){ 81 | return instance; 82 | } 83 | 84 | @Override 85 | public void onDisable() { 86 | manager.reset(); 87 | handler.getAbstractConfig().shutdown(); 88 | } 89 | 90 | void debug(String message) { 91 | if (handler != null && handler.debug()) { 92 | getLogger().info("[DEBUG] " + message); 93 | } 94 | } 95 | 96 | private boolean checkShouldRegister(String plugin) { 97 | if (Bukkit.getPluginManager().getPlugin(plugin) == null) return false; 98 | getLogger().info("Found " + plugin + "! Hooking in."); 99 | return true; 100 | } 101 | 102 | private void testCompat() { 103 | PacketWrapper wrapper = new PacketWrapper("TEST", "&f", "", 0, new ArrayList<>(), true); 104 | wrapper.send(); 105 | if (wrapper.error == null) return; 106 | Bukkit.getPluginManager().disablePlugin(this); 107 | getLogger().severe("\n------------------------------------------------------\n" + 108 | "[WARNING] NametagEdit v" + getDescription().getVersion() + " Failed to load! [WARNING]" + 109 | "\n------------------------------------------------------" + 110 | "\nThis might be an issue with reflection. REPORT this:\n> " + 111 | wrapper.error + 112 | "\nThe plugin will now self destruct.\n------------------------------------------------------"); 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/converter/Converter.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.converter; 2 | 3 | import com.nametagedit.plugin.NametagMessages; 4 | import com.nametagedit.plugin.utils.Utils; 5 | import org.apache.commons.lang.WordUtils; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.OfflinePlayer; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.configuration.file.YamlConfiguration; 10 | import org.bukkit.plugin.Plugin; 11 | 12 | import java.io.BufferedReader; 13 | import java.io.File; 14 | import java.io.FileReader; 15 | import java.io.IOException; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | * Converts from v2.4 of NametagEdit to 21 | * use the new storage method introduced 22 | * in v3.0. 23 | */ 24 | public class Converter { 25 | 26 | private List getLines(File file) { 27 | List lines = new ArrayList<>(); 28 | try (BufferedReader reader = new BufferedReader(new FileReader(file))) { 29 | String line; 30 | while ((line = reader.readLine()) != null) { 31 | lines.add(line); 32 | } 33 | return lines; 34 | } catch (IOException e) { 35 | return new ArrayList<>(); 36 | } 37 | } 38 | 39 | private List getLines(CommandSender commandSender, Plugin plugin, String oldFileName) { 40 | File oldFile = new File(plugin.getDataFolder(), oldFileName); 41 | if (!oldFile.exists()) { 42 | NametagMessages.FILE_DOESNT_EXIST.send(commandSender, oldFileName); 43 | return new ArrayList<>(); 44 | } 45 | 46 | return getLines(oldFile); 47 | } 48 | 49 | public void legacyConversion(CommandSender sender, Plugin plugin) { 50 | try { 51 | handleFile(plugin, sender, "groups"); 52 | handleFile(plugin, sender, "players"); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | 58 | private void handleFile(Plugin plugin, CommandSender sender, String fileType) throws IOException { 59 | final boolean GROUP = fileType.equals("groups"); 60 | File nametagConfigFile = new File(plugin.getDataFolder(), fileType + ".yml"); 61 | YamlConfiguration nametagConfig = Utils.getConfig(nametagConfigFile); 62 | for (String line : getLines(sender, plugin, fileType + ".txt")) { 63 | if (!line.contains("=")) continue; // If the special token is missing, skip. Malformed line. 64 | if (GROUP) { 65 | handleGroup(nametagConfig, line); 66 | } else { 67 | handlePlayer(nametagConfig, line); 68 | } 69 | } 70 | 71 | nametagConfig.save(nametagConfigFile); 72 | } 73 | 74 | private void handleGroup(YamlConfiguration config, String line) { 75 | String[] lineContents = line.replace("=", "").split(" "); 76 | String[] permissionSplit = lineContents[0].split("\\."); 77 | String group = WordUtils.capitalizeFully(permissionSplit[permissionSplit.length - 1]); 78 | String permission = lineContents[0]; 79 | String type = lineContents[1]; 80 | String value = line.substring(line.indexOf("\"") + 1); 81 | value = value.substring(0, value.indexOf("\"")); 82 | config.set("Groups." + group + ".Permission", permission); 83 | config.set("Groups." + group + ".SortPriority", -1); 84 | if (type.equals("prefix")) { 85 | config.set("Groups." + group + ".Prefix", value); 86 | } else { 87 | config.set("Groups." + group + ".Suffix", value); 88 | } 89 | } 90 | 91 | private void handlePlayer(YamlConfiguration config, String line) { 92 | String[] initialSplit = line.split("="); 93 | String prefix = initialSplit[1].trim().split("\"")[1]; 94 | String[] whiteSpaces = initialSplit[0].trim().split(" "); 95 | String playerName = whiteSpaces[0]; 96 | String type = whiteSpaces[1]; 97 | OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName); 98 | String uuid = offlinePlayer.getUniqueId().toString(); 99 | config.set("Players." + uuid + ".Name", playerName); 100 | config.set("Players." + uuid + "." + type.substring(0, 1).toUpperCase() + type.substring(1).toLowerCase(), prefix); 101 | config.set("Players." + uuid + ".SortPriority", -1); 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.utils; 2 | 3 | import com.nametagedit.plugin.packets.VersionChecker; 4 | import org.apache.commons.lang.StringUtils; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.ChatColor; 7 | import org.bukkit.World; 8 | import org.bukkit.configuration.file.YamlConfiguration; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.plugin.Plugin; 11 | 12 | import java.io.*; 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.UUID; 17 | import java.util.regex.Matcher; 18 | import java.util.regex.Pattern; 19 | 20 | public class Utils { 21 | 22 | private static final Pattern hexPattern = Pattern.compile("&#([A-Fa-f0-9]{6})"); 23 | 24 | public static String format(String[] text, int to, int from) { 25 | return StringUtils.join(text, ' ', to, from).replace("'", ""); 26 | } 27 | 28 | public static String deformat(String input) { 29 | return input.replace("§", "&"); 30 | } 31 | 32 | public static String format(String input) { 33 | return format(input, false); 34 | } 35 | 36 | public static String format(String input, boolean limitChars) { 37 | String colored = color(input); 38 | 39 | switch (VersionChecker.getBukkitVersion()) { 40 | case v1_8_R1: case v1_8_R2: case v1_8_R3: case v1_9_R1: case v1_9_R2: 41 | case v1_10_R1: case v1_11_R1: case v1_12_R1: 42 | return limitChars && colored.length() > 16 ? colored.substring(0, 16) : colored; 43 | default: 44 | return limitChars && colored.length() > 256 ? colored.substring(0, 256) : colored; 45 | } 46 | } 47 | 48 | public static String color(String text) { 49 | if (text == null) return ""; 50 | 51 | text = ChatColor.translateAlternateColorCodes('&', text); 52 | 53 | if (VersionChecker.canHex()) { 54 | final char colorChar = ChatColor.COLOR_CHAR; 55 | 56 | final Matcher matcher = hexPattern.matcher(text); 57 | final StringBuffer buffer = new StringBuffer(text.length() + 4 * 8); 58 | 59 | while (matcher.find()) { 60 | final String group = matcher.group(1); 61 | 62 | matcher.appendReplacement(buffer, colorChar + "x" 63 | + colorChar + group.charAt(0) + colorChar + group.charAt(1) 64 | + colorChar + group.charAt(2) + colorChar + group.charAt(3) 65 | + colorChar + group.charAt(4) + colorChar + group.charAt(5)); 66 | } 67 | 68 | text = matcher.appendTail(buffer).toString(); 69 | } 70 | 71 | return text; 72 | } 73 | 74 | public static List getOnline() { 75 | List list = new ArrayList<>(); 76 | 77 | for (World world : Bukkit.getWorlds()) { 78 | list.addAll(world.getPlayers()); 79 | } 80 | 81 | return Collections.unmodifiableList(list); 82 | } 83 | 84 | public static YamlConfiguration getConfig(File file) { 85 | try { 86 | if (!file.exists()) { 87 | file.createNewFile(); 88 | } 89 | } catch (IOException e) { 90 | e.printStackTrace(); 91 | } 92 | 93 | return YamlConfiguration.loadConfiguration(file); 94 | } 95 | 96 | public static YamlConfiguration getConfig(File file, String resource, Plugin plugin) { 97 | try { 98 | if (!file.exists()) { 99 | file.createNewFile(); 100 | InputStream inputStream = plugin.getResource(resource); 101 | OutputStream outputStream = new FileOutputStream(file); 102 | byte[] buffer = new byte[1024]; 103 | int bytesRead; 104 | while ((bytesRead = inputStream.read(buffer)) != -1) { 105 | outputStream.write(buffer, 0, bytesRead); 106 | } 107 | inputStream.close(); 108 | outputStream.flush(); 109 | outputStream.close(); 110 | } 111 | } catch (IOException e) { 112 | e.printStackTrace(); 113 | } 114 | 115 | return YamlConfiguration.loadConfiguration(file); 116 | } 117 | 118 | public static String generateUUID() { 119 | return UUID.randomUUID().toString().substring(0, 8).toUpperCase(); 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/utils/UUIDFetcher.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.utils; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.bukkit.plugin.Plugin; 5 | import org.bukkit.scheduler.BukkitRunnable; 6 | import org.json.simple.JSONArray; 7 | import org.json.simple.JSONObject; 8 | import org.json.simple.parser.JSONParser; 9 | 10 | import java.io.InputStreamReader; 11 | import java.io.OutputStream; 12 | import java.net.HttpURLConnection; 13 | import java.net.URL; 14 | import java.util.*; 15 | import java.util.concurrent.Callable; 16 | 17 | /** 18 | * This class is responsible for retrieving UUIDs from Names 19 | * 20 | * @author evilmidget38 21 | */ 22 | public class UUIDFetcher implements Callable> { 23 | 24 | private static final double PROFILES_PER_REQUEST = 100; 25 | private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft"; 26 | private final JSONParser jsonParser = new JSONParser(); 27 | private final List names; 28 | private final boolean rateLimiting; 29 | 30 | private UUIDFetcher(List names, boolean rateLimiting) { 31 | this.names = ImmutableList.copyOf(names); 32 | this.rateLimiting = rateLimiting; 33 | } 34 | 35 | private UUIDFetcher(List names) { 36 | this(names, true); 37 | } 38 | 39 | public static void lookupUUID(final String name, final Plugin plugin, final UUIDLookup uuidLookup) { 40 | new BukkitRunnable() { 41 | @Override 42 | public void run() { 43 | UUID response = null; 44 | try { 45 | response = getUUIDOf(name); 46 | } catch (Exception e) { 47 | // Swallow 48 | } 49 | 50 | final UUID finalResponse = response; 51 | new BukkitRunnable() { 52 | @Override 53 | public void run() { 54 | uuidLookup.response(finalResponse); 55 | } 56 | }.runTask(plugin); 57 | } 58 | }.runTaskAsynchronously(plugin); 59 | } 60 | 61 | private static void writeBody(HttpURLConnection connection, String body) throws Exception { 62 | try (OutputStream stream = connection.getOutputStream()) { 63 | stream.write(body.getBytes()); 64 | stream.flush(); 65 | } 66 | } 67 | 68 | private static HttpURLConnection createConnection() throws Exception { 69 | URL url = new URL(PROFILE_URL); 70 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 71 | connection.setRequestMethod("POST"); 72 | connection.setRequestProperty("Content-Type", "application/json"); 73 | connection.setUseCaches(false); 74 | connection.setDoInput(true); 75 | connection.setDoOutput(true); 76 | return connection; 77 | } 78 | 79 | private static UUID getUUID(String id) { 80 | return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) 81 | + "-" + id.substring(16, 20) + "-" + id.substring(20, 32)); 82 | } 83 | 84 | private static UUID getUUIDOf(String name) throws Exception { 85 | return new UUIDFetcher(Collections.singletonList(name)).call().get(name); 86 | } 87 | 88 | @Override 89 | public Map call() throws Exception { 90 | Map uuidMap = new HashMap<>(); 91 | int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST); 92 | for (int i = 0; i < requests; i++) { 93 | HttpURLConnection connection = createConnection(); 94 | String body = JSONArray.toJSONString(names.subList(i * 100, Math.min((i + 1) * 100, names.size()))); 95 | writeBody(connection, body); 96 | JSONArray array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream())); 97 | 98 | for (Object profile : array) { 99 | JSONObject jsonProfile = (JSONObject) profile; 100 | String id = (String) jsonProfile.get("id"); 101 | String name = (String) jsonProfile.get("name"); 102 | UUID uuid = UUIDFetcher.getUUID(id); 103 | uuidMap.put(name, uuid); 104 | } 105 | 106 | if (rateLimiting && i != requests - 1) { 107 | Thread.sleep(100L); 108 | } 109 | } 110 | return uuidMap; 111 | } 112 | 113 | public interface UUIDLookup { 114 | void response(UUID uuid); 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/packets/VersionChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All rights by DomeDD (2018) 3 | * You are allowed to modify this code 4 | * You are allowed to use this code in your plugins for private projects 5 | * You are allowed to publish your plugin including this code as long as your plugin is for free and as long as you mention me (DomeDD) 6 | * You are NOT allowed to claim this plugin (BetterNick) as your own 7 | * You are NOT allowed to publish this plugin (BetterNick) or your modified version of this plugin (BetterNick) 8 | * 9 | */ 10 | package com.nametagedit.plugin.packets; 11 | 12 | import org.bukkit.Bukkit; 13 | 14 | public class VersionChecker { 15 | 16 | private static final BukkitVersion bukkitVersion; 17 | 18 | static { 19 | final String version = Bukkit.getVersion(); 20 | if (version.contains("(MC: 1.8)") || version.contains("(MC: 1.8.1)") || version.contains("(MC: 1.8.2)")) 21 | bukkitVersion = BukkitVersion.v1_8_R1; 22 | else if (version.contains("(MC: 1.8.3)")) 23 | bukkitVersion = BukkitVersion.v1_8_R2; 24 | else if (version.contains("(MC: 1.8.4)") || version.contains("(MC: 1.8.5)") || version.contains("(MC: 1.8.6)") || version.contains("(MC: 1.8.7)") || version.contains("(MC: 1.8.8)") || version.contains("(MC: 1.8.9)")) 25 | bukkitVersion = BukkitVersion.v1_8_R3; 26 | else if (version.contains("(MC: 1.9)") || version.contains("(MC: 1.9.1)") || version.contains("(MC: 1.9.2)") || version.contains("(MC: 1.9.3)")) 27 | bukkitVersion = BukkitVersion.v1_9_R1; 28 | else if (version.contains("(MC: 1.9.4)")) 29 | bukkitVersion = BukkitVersion.v1_9_R2; 30 | else if (version.contains("(MC: 1.10)") || version.contains("(MC: 1.10.1)") || version.contains("(MC: 1.10.2)")) 31 | bukkitVersion = BukkitVersion.v1_10_R1; 32 | else if (version.contains("(MC: 1.11)") || version.contains("(MC: 1.11.1)") || version.contains("(MC: 1.11.2)")) 33 | bukkitVersion = BukkitVersion.v1_11_R1; 34 | else if (version.contains("(MC: 1.12)") || version.contains("(MC: 1.12.1)") || version.contains("(MC: 1.12.2)")) 35 | bukkitVersion = BukkitVersion.v1_12_R1; 36 | else if (version.contains("(MC: 1.13)")) 37 | bukkitVersion = BukkitVersion.v1_13_R1; 38 | else if (version.contains("(MC: 1.13.1)") || version.contains("(MC: 1.13.2)")) 39 | bukkitVersion = BukkitVersion.v1_13_R2; 40 | else if (version.contains("(MC: 1.14)") || version.contains("(MC: 1.14.1)") || version.contains("(MC: 1.14.2)") || version.contains("(MC: 1.14.3)")) 41 | bukkitVersion = BukkitVersion.v1_14_R1; 42 | else if (version.contains("(MC: 1.14.4)")) 43 | bukkitVersion = BukkitVersion.v1_14_R2; 44 | else if (version.contains("(MC: 1.15)") || version.contains("(MC: 1.15.1)") || version.contains("(MC: 1.15.2)")) 45 | bukkitVersion = BukkitVersion.v1_15_R1; 46 | else if (version.contains("(MC: 1.16.1)")) 47 | bukkitVersion = BukkitVersion.v1_16_R1; 48 | else if (version.contains("(MC: 1.16.2)") || version.contains("(MC: 1.16.3)")) 49 | bukkitVersion = BukkitVersion.v1_16_R2; 50 | else if (version.contains("(MC: 1.16.4)") || version.contains("(MC: 1.16.5)")) 51 | bukkitVersion = BukkitVersion.v1_16_R3; 52 | else if (version.contains("(MC: 1.17)") || version.contains("(MC: 1.17.1)")) 53 | bukkitVersion = BukkitVersion.v1_17_R1; 54 | else if (version.contains("(MC: 1.18)") || version.contains("(MC: 1.18.1)") || version.contains("(MC: 1.18.2)")) 55 | bukkitVersion = BukkitVersion.v1_18_R1; 56 | else if (version.contains("(MC: 1.19)") || version.contains("(MC: 1.19.1)") || version.contains("(MC: 1.19.2)")) 57 | bukkitVersion = BukkitVersion.v1_19_R1; 58 | else if (version.contains("(MC: 1.19.3)")) 59 | bukkitVersion = BukkitVersion.v1_19_R2; 60 | else if (version.contains("(MC: 1.19.4)")) 61 | bukkitVersion = BukkitVersion.v1_19_R3; 62 | else if (version.contains("(MC: 1.20)") || version.contains("(MC: 1.20.1)")) 63 | bukkitVersion = BukkitVersion.v1_20_R1; 64 | else if (version.contains("(MC: 1.20.2)")) 65 | bukkitVersion = BukkitVersion.v1_20_R2; 66 | else if (version.contains("(MC: 1.20.3)") || version.contains("(MC: 1.20.4)")) 67 | bukkitVersion = BukkitVersion.v1_20_R3; 68 | else 69 | bukkitVersion = null; 70 | } 71 | 72 | public static BukkitVersion getBukkitVersion() { 73 | return bukkitVersion; 74 | } 75 | 76 | public static boolean canHex() { 77 | String[] split = Bukkit.getBukkitVersion().split("-")[0].split("\\."); 78 | String minorVer = split[1]; 79 | return Integer.parseInt(minorVer) >= 16; 80 | } 81 | 82 | public enum BukkitVersion { 83 | v1_8_R1, v1_8_R2, v1_8_R3, v1_9_R1, v1_9_R2, v1_10_R1, v1_11_R1, v1_12_R1, v1_13_R1, v1_13_R2, v1_14_R1, v1_14_R2, v1_15_R1, v1_16_R1, v1_16_R2, v1_16_R3, v1_17_R1, v1_18_R1, v1_19_R1, v1_19_R2, v1_19_R3, v1_20_R1, v1_20_R2, v1_20_R3 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/DatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database; 2 | 3 | import com.nametagedit.plugin.NametagEdit; 4 | import com.nametagedit.plugin.NametagHandler; 5 | import com.nametagedit.plugin.api.data.GroupData; 6 | import com.nametagedit.plugin.api.data.PlayerData; 7 | import com.nametagedit.plugin.storage.AbstractConfig; 8 | import com.nametagedit.plugin.storage.database.tasks.*; 9 | import com.nametagedit.plugin.utils.Configuration; 10 | import com.nametagedit.plugin.utils.UUIDFetcher; 11 | import com.zaxxer.hikari.HikariDataSource; 12 | import org.bukkit.command.CommandSender; 13 | import org.bukkit.configuration.file.FileConfiguration; 14 | import org.bukkit.entity.Player; 15 | 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.UUID; 19 | 20 | public class DatabaseConfig implements AbstractConfig { 21 | 22 | private NametagEdit plugin; 23 | private NametagHandler handler; 24 | private HikariDataSource hikari; 25 | 26 | // These are used if the user wants to customize the 27 | // schema structure. Perhaps more cosmetic. 28 | public static String TABLE_GROUPS; 29 | public static String TABLE_PLAYERS; 30 | public static String TABLE_CONFIG; 31 | 32 | public DatabaseConfig(NametagEdit plugin, NametagHandler handler, Configuration config) { 33 | this.plugin = plugin; 34 | this.handler = handler; 35 | TABLE_GROUPS = "`" + config.getString("MySQL.GroupsTable", "nte_groups") + "`"; 36 | TABLE_PLAYERS = "`" + config.getString("MySQL.PlayersTable", "nte_players") + "`"; 37 | TABLE_CONFIG = "`" + config.getString("MySQL.ConfigTable", "nte_config") + "`"; 38 | } 39 | 40 | @Override 41 | public void load() { 42 | FileConfiguration config = handler.getConfig(); 43 | shutdown(); 44 | hikari = new HikariDataSource(); 45 | hikari.setMaximumPoolSize(config.getInt("MinimumPoolSize", 10)); 46 | hikari.setPoolName("NametagEdit Pool"); 47 | 48 | String port = "3306"; 49 | 50 | if (config.isSet("MySQL.Port")) { 51 | port = config.getString("MySQL.Port"); 52 | } 53 | 54 | hikari.setJdbcUrl("jdbc:mysql://" + config.getString("MySQL.Hostname") + ":" + port + "/" + config.getString("MySQL.Database")); 55 | hikari.addDataSourceProperty("useSSL", false); 56 | hikari.addDataSourceProperty("requireSSL", false); 57 | hikari.addDataSourceProperty("verifyServerCertificate", false); 58 | hikari.addDataSourceProperty("user", config.getString("MySQL.Username")); 59 | hikari.addDataSourceProperty("password", config.getString("MySQL.Password")); 60 | 61 | hikari.setUsername(config.getString("MySQL.Username")); 62 | hikari.setPassword(config.getString("MySQL.Password")); 63 | 64 | new DatabaseUpdater(handler, hikari, plugin).runTaskAsynchronously(plugin); 65 | } 66 | 67 | @Override 68 | public void reload() { 69 | new DataDownloader(handler, hikari).runTaskAsynchronously(plugin); 70 | } 71 | 72 | @Override 73 | public void shutdown() { 74 | if (hikari != null) { 75 | hikari.close(); 76 | } 77 | } 78 | 79 | @Override 80 | public void load(Player player, boolean loggedIn) { 81 | new PlayerLoader(player.getUniqueId(), plugin, handler, hikari, loggedIn).runTaskAsynchronously(plugin); 82 | } 83 | 84 | @Override 85 | public void save(PlayerData... playerData) { 86 | new PlayerSaver(playerData, hikari).runTaskAsynchronously(plugin); 87 | } 88 | 89 | @Override 90 | public void save(GroupData... groupData) { 91 | new GroupSaver(groupData, hikari).runTaskAsynchronously(plugin); 92 | } 93 | 94 | @Override 95 | public void savePriority(boolean playerTag, String key, final int priority) { 96 | if (playerTag) { 97 | UUIDFetcher.lookupUUID(key, plugin, uuid -> { 98 | if (uuid != null) { 99 | new PlayerPriority(uuid, priority, hikari).runTaskAsynchronously(plugin); 100 | } else { 101 | plugin.getLogger().severe("An error has occurred while looking for UUID."); 102 | } 103 | }); 104 | } else { 105 | new GroupPriority(key, priority, hikari).runTaskAsynchronously(plugin); 106 | } 107 | } 108 | 109 | @Override 110 | public void delete(GroupData groupData) { 111 | new GroupDeleter(groupData.getGroupName(), hikari).runTaskAsynchronously(plugin); 112 | } 113 | 114 | @Override 115 | public void add(GroupData groupData) { 116 | new GroupAdd(groupData, hikari).runTaskAsynchronously(plugin); 117 | } 118 | 119 | @Override 120 | public void clear(UUID uuid, String targetName) { 121 | new PlayerDeleter(uuid, hikari).runTaskAsynchronously(plugin); 122 | } 123 | 124 | @Override 125 | public void orderGroups(CommandSender commandSender, List order) { 126 | String formatted = Arrays.toString(order.toArray()); 127 | formatted = formatted.substring(1, formatted.length() - 1).replace(",", ""); 128 | new GroupConfigUpdater("order", formatted, hikari).runTaskAsynchronously(handler.getPlugin()); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/DataDownloader.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.NametagHandler; 4 | import com.nametagedit.plugin.api.data.GroupData; 5 | import com.nametagedit.plugin.api.data.PlayerData; 6 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 7 | import com.nametagedit.plugin.utils.Utils; 8 | import com.zaxxer.hikari.HikariDataSource; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.permissions.Permission; 11 | import org.bukkit.permissions.PermissionDefault; 12 | import org.bukkit.scheduler.BukkitRunnable; 13 | 14 | import java.sql.Connection; 15 | import java.sql.PreparedStatement; 16 | import java.sql.ResultSet; 17 | import java.sql.SQLException; 18 | import java.util.*; 19 | 20 | public class DataDownloader extends BukkitRunnable { 21 | 22 | private final List players = new ArrayList<>(); 23 | private final NametagHandler handler; 24 | private final HikariDataSource hikari; 25 | 26 | public DataDownloader(NametagHandler handler, HikariDataSource hikari) { 27 | this.handler = handler; 28 | this.hikari = hikari; 29 | for (Player player : Utils.getOnline()) { 30 | players.add(player.getUniqueId()); 31 | } 32 | } 33 | 34 | @Override 35 | public void run() { 36 | HashMap settings = new HashMap<>(); 37 | List groupDataUnordered = new ArrayList<>(); 38 | final Map playerData = new HashMap<>(); 39 | 40 | try (Connection connection = hikari.getConnection()) { 41 | ResultSet results = connection.prepareStatement("SELECT `name`, `prefix`, `suffix`, `permission`, `priority` FROM " + DatabaseConfig.TABLE_GROUPS).executeQuery(); 42 | 43 | while (results.next()) { 44 | groupDataUnordered.add(new GroupData( 45 | results.getString("name"), 46 | results.getString("prefix"), 47 | results.getString("suffix"), 48 | results.getString("permission"), 49 | new Permission(results.getString("permission"), PermissionDefault.FALSE), 50 | results.getInt("priority") 51 | )); 52 | } 53 | 54 | PreparedStatement select = connection.prepareStatement("SELECT `uuid`, `prefix`, `suffix`, `priority` FROM " + DatabaseConfig.TABLE_PLAYERS + " WHERE uuid=?"); 55 | for (UUID uuid : players) { 56 | select.setString(1, uuid.toString()); 57 | results = select.executeQuery(); 58 | if (results.next()) { 59 | playerData.put(uuid, new PlayerData( 60 | "", 61 | uuid, 62 | Utils.format(results.getString("prefix"), true), 63 | Utils.format(results.getString("suffix"), true), 64 | results.getInt("priority") 65 | )); 66 | } 67 | } 68 | 69 | results = connection.prepareStatement("SELECT `setting`,`value` FROM " + DatabaseConfig.TABLE_CONFIG).executeQuery(); 70 | while (results.next()) { 71 | settings.put(results.getString("setting"), results.getString("value")); 72 | } 73 | 74 | select.close(); 75 | results.close(); 76 | } catch (SQLException e) { 77 | e.printStackTrace(); 78 | } finally { 79 | // First order the groups before performing assignments 80 | String orderSetting = settings.get("order"); 81 | if (orderSetting != null) { 82 | String[] order = orderSetting.split(" "); 83 | // This will be the order we will use 84 | List current = new ArrayList<>(); 85 | // Determine order for current loaded groups 86 | for (String group : order) { 87 | Iterator itr = groupDataUnordered.iterator(); 88 | while (itr.hasNext()) { 89 | GroupData groupData = itr.next(); 90 | if (groupData.getGroupName().equalsIgnoreCase(group)) { 91 | current.add(groupData); 92 | itr.remove(); 93 | break; 94 | } 95 | } 96 | } 97 | 98 | current.addAll(groupDataUnordered); // Add remaining entries (bad order, wasn't specified) 99 | groupDataUnordered = current; // Reassign the new group order 100 | } 101 | 102 | handler.assignData(groupDataUnordered, playerData); // Safely perform assignments 103 | new BukkitRunnable() { 104 | @Override 105 | public void run() { 106 | for (Player player : Utils.getOnline()) { 107 | PlayerData data = playerData.get(player.getUniqueId()); 108 | if (data != null) { 109 | data.setName(player.getName()); 110 | } 111 | } 112 | 113 | handler.applyTags(); 114 | } 115 | }.runTask(handler.getPlugin()); 116 | } 117 | } 118 | 119 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/api/NametagAPI.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.api; 2 | 3 | import com.nametagedit.plugin.NametagHandler; 4 | import com.nametagedit.plugin.NametagManager; 5 | import com.nametagedit.plugin.api.data.FakeTeam; 6 | import com.nametagedit.plugin.api.data.GroupData; 7 | import com.nametagedit.plugin.api.data.Nametag; 8 | import com.nametagedit.plugin.api.events.NametagEvent; 9 | import lombok.AllArgsConstructor; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.entity.Player; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * Implements the INametagAPI interface. There only 17 | * exists one instance of this class. 18 | */ 19 | @AllArgsConstructor 20 | public final class NametagAPI implements INametagApi { 21 | 22 | private final NametagHandler handler; 23 | private final NametagManager manager; 24 | 25 | @Override 26 | public FakeTeam getFakeTeam(Player player) { 27 | return manager.getFakeTeam(player.getName()); 28 | } 29 | 30 | @Override 31 | public Nametag getNametag(Player player) { 32 | FakeTeam team = manager.getFakeTeam(player.getName()); 33 | boolean nullTeam = team == null; 34 | return new Nametag(nullTeam ? "" : team.getPrefix(), nullTeam ? "" : team.getSuffix()); 35 | } 36 | 37 | @Override 38 | public void clearNametag(Player player) { 39 | if (shouldFireEvent(player, NametagEvent.ChangeType.CLEAR)) { 40 | manager.reset(player.getName()); 41 | } 42 | } 43 | 44 | @Override 45 | public void reloadNametag(Player player) { 46 | if (shouldFireEvent(player, NametagEvent.ChangeType.RELOAD)) { 47 | handler.applyTagToPlayer(player, false); 48 | } 49 | } 50 | 51 | @Override 52 | public void clearNametag(String player) { 53 | manager.reset(player); 54 | } 55 | 56 | @Override 57 | public void setPrefix(Player player, String prefix) { 58 | FakeTeam fakeTeam = manager.getFakeTeam(player.getName()); 59 | setNametagAlt(player, prefix, fakeTeam == null ? null : fakeTeam.getSuffix()); 60 | } 61 | 62 | @Override 63 | public void setSuffix(Player player, String suffix) { 64 | FakeTeam fakeTeam = manager.getFakeTeam(player.getName()); 65 | setNametagAlt(player, fakeTeam == null ? null : fakeTeam.getPrefix(), suffix); 66 | } 67 | 68 | @Override 69 | public void setPrefix(String player, String prefix) { 70 | FakeTeam fakeTeam = manager.getFakeTeam(player); 71 | manager.setNametag(player, prefix, fakeTeam == null ? null : fakeTeam.getSuffix()); 72 | } 73 | 74 | @Override 75 | public void setSuffix(String player, String suffix) { 76 | FakeTeam fakeTeam = manager.getFakeTeam(player); 77 | manager.setNametag(player, fakeTeam == null ? null : fakeTeam.getPrefix(), suffix); 78 | } 79 | 80 | @Override 81 | public void setNametag(Player player, String prefix, String suffix) { 82 | setNametagAlt(player, prefix, suffix); 83 | } 84 | 85 | @Override 86 | public void setNametag(String player, String prefix, String suffix) { 87 | manager.setNametag(player, prefix, suffix); 88 | } 89 | 90 | @Override 91 | public void hideNametag(Player player) { 92 | FakeTeam fakeTeam = manager.getFakeTeam(player.getName()); 93 | manager.setNametag(player.getName(), fakeTeam == null ? null : fakeTeam.getPrefix(), fakeTeam == null ? null : fakeTeam.getSuffix(), false); 94 | } 95 | 96 | @Override 97 | public void hideNametag(String player) { 98 | FakeTeam fakeTeam = manager.getFakeTeam(player); 99 | manager.setNametag(player, fakeTeam == null ? null : fakeTeam.getPrefix(), fakeTeam == null ? null : fakeTeam.getSuffix(), false); 100 | } 101 | 102 | @Override 103 | public void showNametag(Player player) { 104 | FakeTeam fakeTeam = manager.getFakeTeam(player.getName()); 105 | manager.setNametag(player.getName(), fakeTeam == null ? null : fakeTeam.getPrefix(), fakeTeam == null ? null : fakeTeam.getSuffix(), true); 106 | } 107 | 108 | @Override 109 | public void showNametag(String player) { 110 | FakeTeam fakeTeam = manager.getFakeTeam(player); 111 | manager.setNametag(player, fakeTeam == null ? null : fakeTeam.getPrefix(), fakeTeam == null ? null : fakeTeam.getSuffix(), true); 112 | } 113 | 114 | @Override 115 | public List getGroupData() { 116 | return handler.getGroupData(); 117 | } 118 | 119 | @Override 120 | public void saveGroupData(GroupData... groupData) { 121 | handler.getAbstractConfig().save(groupData); 122 | } 123 | 124 | @Override 125 | public void applyTags() { 126 | handler.applyTags(); 127 | } 128 | 129 | @Override 130 | public void applyTagToPlayer(Player player, boolean loggedIn) { 131 | handler.applyTagToPlayer(player,loggedIn); 132 | } 133 | 134 | @Override 135 | public void updatePlayerPrefix(String target, String prefix) { 136 | handler.save(target, NametagEvent.ChangeType.PREFIX, prefix); 137 | } 138 | 139 | @Override 140 | public void updatePlayerSuffix(String target, String suffix) { 141 | handler.save(target, NametagEvent.ChangeType.SUFFIX, suffix); 142 | } 143 | 144 | @Override 145 | public void updatePlayerNametag(String target, String prefix, String suffix) { 146 | handler.save(target, prefix, suffix); 147 | } 148 | 149 | /** 150 | * Private helper function to reduce redundancy 151 | */ 152 | private boolean shouldFireEvent(Player player, NametagEvent.ChangeType type) { 153 | NametagEvent event = new NametagEvent(player.getName(), "", getNametag(player), type); 154 | Bukkit.getPluginManager().callEvent(event); 155 | return !event.isCancelled(); 156 | } 157 | 158 | /** 159 | * Private helper function to reduce redundancy 160 | */ 161 | private void setNametagAlt(Player player, String prefix, String suffix) { 162 | Nametag nametag = new Nametag( 163 | handler.formatWithPlaceholders(player, prefix, true), 164 | handler.formatWithPlaceholders(player, suffix, true) 165 | ); 166 | 167 | NametagEvent event = new NametagEvent(player.getName(), prefix, nametag, NametagEvent.ChangeType.UNKNOWN); 168 | Bukkit.getPluginManager().callEvent(event); 169 | if (event.isCancelled()) return; 170 | manager.setNametag(player.getName(), nametag.getPrefix(), nametag.getSuffix()); 171 | } 172 | 173 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/database/tasks/DatabaseUpdater.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.database.tasks; 2 | 3 | import com.nametagedit.plugin.NametagEdit; 4 | import com.nametagedit.plugin.NametagHandler; 5 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 6 | import com.zaxxer.hikari.HikariDataSource; 7 | import lombok.AllArgsConstructor; 8 | import org.bukkit.scheduler.BukkitRunnable; 9 | 10 | import java.sql.Connection; 11 | import java.sql.PreparedStatement; 12 | import java.sql.ResultSet; 13 | import java.sql.SQLException; 14 | 15 | @AllArgsConstructor 16 | public class DatabaseUpdater extends BukkitRunnable { 17 | 18 | private final NametagHandler handler; 19 | private final HikariDataSource hikari; 20 | private final NametagEdit plugin; 21 | 22 | private static final int CURRENT_DATABASE_VERSION = 5; 23 | 24 | @Override 25 | public void run() { 26 | try (Connection connection = hikari.getConnection()) { 27 | int currentVersion = getCurrentDatabaseVersion(connection); 28 | 29 | createTablesIfNotExists(connection); 30 | 31 | while (currentVersion < CURRENT_DATABASE_VERSION) { 32 | switch (currentVersion) { 33 | case 1: 34 | handleUpdate1(connection); 35 | break; 36 | case 2: 37 | handleUpdate2(connection); 38 | break; 39 | case 3: 40 | handleUpdate3(connection); 41 | break; 42 | case 4: 43 | handleUpdate4(connection); 44 | break; 45 | } 46 | 47 | currentVersion++; 48 | } 49 | 50 | setCurrentDatabaseVersion(connection, CURRENT_DATABASE_VERSION); 51 | } catch (SQLException e) { 52 | e.printStackTrace(); 53 | } finally { 54 | new DataDownloader(handler, hikari).runTaskAsynchronously(plugin); 55 | } 56 | } 57 | 58 | private void createTablesIfNotExists(Connection connection) { 59 | execute(connection, "CREATE TABLE IF NOT EXISTS " + DatabaseConfig.TABLE_CONFIG + " (`setting` varchar(16) NOT NULL, `value` varchar(200) NOT NULL, PRIMARY KEY (`setting`)) ENGINE=InnoDB DEFAULT CHARSET=utf8"); 60 | execute(connection, "CREATE TABLE IF NOT EXISTS " + DatabaseConfig.TABLE_GROUPS + " (`name` varchar(64) NOT NULL, `permission` varchar(64) DEFAULT NULL, `prefix` varchar(256) NOT NULL, `suffix` varchar(256) NOT NULL, `priority` int(11) NOT NULL, PRIMARY KEY (`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8"); 61 | execute(connection, "CREATE TABLE IF NOT EXISTS " + DatabaseConfig.TABLE_PLAYERS + " (`uuid` varchar(64) NOT NULL, `name` varchar(16) NOT NULL, `prefix` varchar(256) NOT NULL, `suffix` varchar(256) NOT NULL, `priority` int(11) NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8"); 62 | } 63 | 64 | private void handleUpdate1(Connection connection) { 65 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_PLAYERS + " ADD `priority` INT NOT NULL"); 66 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_GROUPS + " ADD `priority` INT NOT NULL"); 67 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_GROUPS + " MODIFY `permission` VARCHAR(64)"); 68 | } 69 | 70 | private void handleUpdate2(Connection connection) { 71 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_GROUPS + " CHANGE `prefix` `prefix` VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL;"); 72 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_GROUPS + " CHANGE `suffix` `suffix` VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL;"); 73 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_PLAYERS + " CHANGE `prefix` `prefix` VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL;"); 74 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_PLAYERS + " CHANGE `suffix` `suffix` VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL;"); 75 | } 76 | 77 | private void handleUpdate3(Connection connection) { 78 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_GROUPS + " CONVERT TO CHARACTER SET utf8;"); 79 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_PLAYERS + " CONVERT TO CHARACTER SET utf8;"); 80 | 81 | // TODO: Queries for Issue #230. 82 | } 83 | 84 | private void handleUpdate4(Connection connection) { 85 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_GROUPS + " CHANGE `prefix` `prefix` VARCHAR(256);"); 86 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_GROUPS + " CHANGE `suffix` `suffix` VARCHAR(256);"); 87 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_PLAYERS + " CHANGE `prefix` `prefix` VARCHAR(256);"); 88 | execute(connection, "ALTER TABLE " + DatabaseConfig.TABLE_PLAYERS + " CHANGE `suffix` `suffix` VARCHAR(256);"); 89 | } 90 | 91 | private void setCurrentDatabaseVersion(Connection connection, int currentVersion) { 92 | try (PreparedStatement select = connection.prepareStatement("INSERT INTO " + DatabaseConfig.TABLE_CONFIG + 93 | " VALUES('db_version', ?) ON DUPLICATE KEY UPDATE `value`=?")) { 94 | select.setInt(1, currentVersion); 95 | select.setInt(2, currentVersion); 96 | select.execute(); 97 | } catch (SQLException e) { 98 | handleError(e); 99 | } 100 | } 101 | 102 | private int getCurrentDatabaseVersion(Connection connection) { 103 | try (PreparedStatement select = connection.prepareStatement("SELECT `value` FROM " + DatabaseConfig.TABLE_CONFIG + " WHERE `setting`='db_version'")) { 104 | try (ResultSet resultSet = select.executeQuery()) { 105 | if (resultSet.next()) { 106 | return resultSet.getInt("value"); 107 | } 108 | } 109 | } catch (SQLException e) { 110 | handleError(e); 111 | } 112 | 113 | return 1; 114 | } 115 | 116 | private void execute(Connection connection, String query) { 117 | try (PreparedStatement statement = connection.prepareStatement(query)) { 118 | statement.execute(); 119 | } catch (SQLException e) { 120 | handleError(e); 121 | } 122 | } 123 | 124 | private void handleError(SQLException e) { 125 | if (handler.isDebug()) { 126 | e.printStackTrace(); 127 | } else { 128 | plugin.getLogger().severe("NametagEdit Query Failed - Reason: " + e.getMessage()); 129 | plugin.getLogger().severe("If this is not a connection error, please enable debug with /nte debug and post the error on our GitHub Issue Tracker."); 130 | } 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/api/INametagApi.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.api; 2 | 3 | import com.nametagedit.plugin.api.data.FakeTeam; 4 | import com.nametagedit.plugin.api.data.GroupData; 5 | import com.nametagedit.plugin.api.data.Nametag; 6 | import org.bukkit.entity.Player; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 12 | */ 13 | public interface INametagApi { 14 | 15 | /** 16 | * Function gets the fake team data for 17 | * player. 18 | * 19 | * @param player the player to check 20 | * @return the fake team 21 | */ 22 | FakeTeam getFakeTeam(Player player); 23 | 24 | /** 25 | * Function gets the nametag for a player if 26 | * it exists. This will never return a null. 27 | * 28 | * @param player the player to check 29 | * @return the nametag for the player 30 | */ 31 | Nametag getNametag(Player player); 32 | 33 | /** 34 | * Removes a player's nametag in memory 35 | * only. 36 | *

37 | * Note: Only affects memory, does NOT 38 | * add/remove from storage. 39 | * 40 | * @param player whose nametag to clear 41 | */ 42 | void clearNametag(Player player); 43 | 44 | /** 45 | * Reloads a nametag if the player has a 46 | * custom nametag via the Players or Groups 47 | * configurations. 48 | *

49 | * 50 | * @param player whose nametag to reload 51 | */ 52 | void reloadNametag(Player player); 53 | 54 | /** 55 | * Removes a player's nametag in memory 56 | * only. 57 | *

58 | * Note: Only affects memory, does NOT 59 | * add/remove from storage. 60 | * 61 | * @param player whose nametag to clear 62 | */ 63 | void clearNametag(String player); 64 | 65 | /** 66 | * Sets the prefix for a player. The previous 67 | * suffix is kept if it exists. 68 | *

69 | * Note: Only affects memory, does NOT 70 | * add/remove from storage. 71 | * 72 | * @param player the player whose nametag to change 73 | * @param prefix the prefix to change to 74 | */ 75 | void setPrefix(Player player, String prefix); 76 | 77 | /** 78 | * Sets the suffix for a player. The previous 79 | * prefix is kept if it exists. 80 | *

81 | * Note: Only affects memory, does NOT 82 | * add/remove from storage. 83 | * 84 | * @param player the player whose nametag to change 85 | * @param suffix the suffix to change to 86 | */ 87 | void setSuffix(Player player, String suffix); 88 | 89 | /** 90 | * Sets the prefix for a player. The previous 91 | * suffix is kept if it exists. 92 | *

93 | * Note: Only affects memory, does NOT 94 | * add/remove from storage. 95 | * 96 | * @param player the player whose nametag to change 97 | * @param prefix the prefix to change to 98 | */ 99 | void setPrefix(String player, String prefix); 100 | 101 | /** 102 | * Sets the suffix for a player. The previous 103 | * prefix is kept if it exists. 104 | *

105 | * Note: Only affects memory, does NOT 106 | * add/remove from storage. 107 | * 108 | * @param player the player whose nametag to change 109 | * @param suffix the suffix to change to 110 | */ 111 | void setSuffix(String player, String suffix); 112 | 113 | /** 114 | * Sets the nametag for a player. 115 | *

116 | * Note: Only affects memory, does NOT 117 | * add/remove from storage. 118 | * 119 | * @param player the player whose nametag to change 120 | * @param prefix the prefix to change to 121 | * @param suffix the suffix to change to 122 | */ 123 | void setNametag(Player player, String prefix, String suffix); 124 | 125 | /** 126 | * Sets the nametag for a player. 127 | *

128 | * Note: Only affects memory, does NOT 129 | * add/remove from storage. 130 | * 131 | * @param player the player whose nametag to change 132 | * @param prefix the prefix to change to 133 | * @param suffix the suffix to change to 134 | */ 135 | void setNametag(String player, String prefix, String suffix); 136 | 137 | /** 138 | * Hide the name above the head 139 | * @param player the player whose nametag to hide 140 | */ 141 | void hideNametag(Player player); 142 | 143 | /** 144 | * Hide the name above the head 145 | * @param player he player whose nametag to hide 146 | */ 147 | void hideNametag(String player); 148 | 149 | /** 150 | * Show the name above the head 151 | * @param player he player whose nametag to show 152 | */ 153 | void showNametag(Player player); 154 | 155 | /** 156 | * Show the name above the head 157 | * @param player he player whose nametag to show 158 | */ 159 | void showNametag(String player); 160 | 161 | /** 162 | * Gets the data of all groups 163 | * @return list containing all group data 164 | */ 165 | List getGroupData(); 166 | 167 | 168 | /** 169 | * Saves the provided group data 170 | * @param groupData the group data to save 171 | */ 172 | void saveGroupData(GroupData... groupData); 173 | 174 | /** 175 | * Applies tags to all players 176 | *

177 | * Note: Only affects memory, does NOT 178 | * add/remove from storage. 179 | *

180 | */ 181 | void applyTags(); 182 | 183 | /** 184 | * Applies tags to specific player 185 | *

186 | * Note: Only affects memory, does NOT 187 | * add/remove from storage 188 | *

189 | * @param player the player to apply nametag to 190 | * @param loggedIn is the player logged in 191 | */ 192 | void applyTagToPlayer(Player player, boolean loggedIn); 193 | 194 | /** 195 | * Updates a players prefix 196 | *

197 | * Note: Does affect memory. Automatically applies tags to player 198 | * so no applyTags call is necessary 199 | *

200 | * @param target name of the player to update tag of 201 | * @param prefix prefix to change to 202 | */ 203 | void updatePlayerPrefix(String target, String prefix); 204 | 205 | /** 206 | * Updates a players suffix 207 | *

208 | * Note: Does affect memory. Automatically applies tags to player 209 | * so no applyTags call is necessary 210 | *

211 | * @param target name of the player to update tag of 212 | * @param suffix suffix to change to 213 | */ 214 | void updatePlayerSuffix(String target, String suffix); 215 | 216 | /** 217 | * Updates a players nametag 218 | *

219 | * Note: Does affect memory. Automatically applies tags to player 220 | * so no applyTags call is necessary 221 | *

222 | * @param target name of the player to update tag of 223 | * @param prefix prefix to set to 224 | * @param suffix suffix to set to 225 | */ 226 | void updatePlayerNametag(String target, String prefix, String suffix); 227 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/NametagManager.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin; 2 | 3 | import com.nametagedit.plugin.api.data.FakeTeam; 4 | import com.nametagedit.plugin.packets.PacketWrapper; 5 | import lombok.AllArgsConstructor; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.OfflinePlayer; 8 | import org.bukkit.entity.Player; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | 17 | @AllArgsConstructor 18 | public class NametagManager { 19 | 20 | private final Map TEAMS = new ConcurrentHashMap<>(); 21 | private final Map CACHED_FAKE_TEAMS = new ConcurrentHashMap<>(); 22 | private final NametagEdit plugin; 23 | 24 | /** 25 | * Gets the current team given a prefix and suffix 26 | * If there is no team similar to this, then a new 27 | * team is created. 28 | */ 29 | private FakeTeam getFakeTeam(String prefix, String suffix, boolean visible) { 30 | return TEAMS.values().stream().filter(fakeTeam -> fakeTeam.isSimilar(prefix, suffix, visible)).findFirst().orElse(null); 31 | } 32 | 33 | /** 34 | * Adds a player to a FakeTeam. If they are already on this team, 35 | * we do NOT change that. 36 | */ 37 | private void addPlayerToTeam(String player, String prefix, String suffix, int sortPriority, boolean playerTag, boolean visible) { 38 | FakeTeam previous = getFakeTeam(player); 39 | 40 | if (previous != null && previous.isSimilar(prefix, suffix, visible)) { 41 | plugin.debug(player + " already belongs to a similar team (" + previous.getName() + ")"); 42 | return; 43 | } 44 | 45 | reset(player); 46 | 47 | FakeTeam joining = getFakeTeam(prefix, suffix, visible); 48 | if (joining != null) { 49 | joining.addMember(player); 50 | plugin.debug("Using existing team for " + player); 51 | } else { 52 | joining = new FakeTeam(prefix, suffix, sortPriority, playerTag); 53 | joining.setVisible(visible); 54 | joining.addMember(player); 55 | TEAMS.put(joining.getName(), joining); 56 | addTeamPackets(joining); 57 | plugin.debug("Created FakeTeam " + joining.getName() + ". Size: " + TEAMS.size()); 58 | } 59 | 60 | Player adding = Bukkit.getPlayerExact(player); 61 | if (adding != null) { 62 | addPlayerToTeamPackets(joining, adding.getName()); 63 | cache(adding.getName(), joining); 64 | } else { 65 | OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player); 66 | addPlayerToTeamPackets(joining, offlinePlayer.getName()); 67 | cache(offlinePlayer.getName(), joining); 68 | } 69 | 70 | plugin.debug(player + " has been added to team " + joining.getName()); 71 | } 72 | 73 | public FakeTeam reset(String player) { 74 | return reset(player, decache(player)); 75 | } 76 | 77 | private FakeTeam reset(String player, FakeTeam fakeTeam) { 78 | if (fakeTeam != null && fakeTeam.getMembers().remove(player)) { 79 | boolean delete; 80 | Player removing = Bukkit.getPlayerExact(player); 81 | if (removing != null) { 82 | delete = removePlayerFromTeamPackets(fakeTeam, removing.getName()); 83 | } else { 84 | OfflinePlayer toRemoveOffline = Bukkit.getOfflinePlayer(player); 85 | delete = removePlayerFromTeamPackets(fakeTeam, toRemoveOffline.getName()); 86 | } 87 | 88 | plugin.debug(player + " was removed from " + fakeTeam.getName()); 89 | if (delete) { 90 | removeTeamPackets(fakeTeam); 91 | TEAMS.remove(fakeTeam.getName()); 92 | plugin.debug("FakeTeam " + fakeTeam.getName() + " has been deleted. Size: " + TEAMS.size()); 93 | } 94 | } 95 | 96 | return fakeTeam; 97 | } 98 | 99 | // ============================================================== 100 | // Below are public methods to modify the cache 101 | // ============================================================== 102 | private FakeTeam decache(String player) { 103 | return CACHED_FAKE_TEAMS.remove(player); 104 | } 105 | 106 | public FakeTeam getFakeTeam(String player) { 107 | return CACHED_FAKE_TEAMS.get(player); 108 | } 109 | 110 | private void cache(String player, FakeTeam fakeTeam) { 111 | CACHED_FAKE_TEAMS.put(player, fakeTeam); 112 | } 113 | 114 | // ============================================================== 115 | // Below are public methods to modify certain data 116 | // ============================================================== 117 | public void setNametag(String player, String prefix, String suffix) { 118 | setNametag(player, prefix, suffix, -1); 119 | } 120 | 121 | public void setNametag(String player, String prefix, String suffix, boolean visible) { 122 | setNametag(player, prefix, suffix, -1, false, visible); 123 | } 124 | 125 | void setNametag(String player, String prefix, String suffix, int sortPriority) { 126 | setNametag(player, prefix, suffix, sortPriority, false, true); 127 | } 128 | 129 | void setNametag(String player, String prefix, String suffix, int sortPriority, boolean playerTag, boolean visible) { 130 | addPlayerToTeam(player, prefix != null ? prefix : "", suffix != null ? suffix : "", sortPriority, playerTag, visible); 131 | } 132 | 133 | void sendTeams(Player player) { 134 | for (FakeTeam fakeTeam : TEAMS.values()) { 135 | new PacketWrapper(fakeTeam.getName(), fakeTeam.getPrefix(), fakeTeam.getSuffix(), 0, fakeTeam.getMembers(), fakeTeam.isVisible()).send(player); 136 | } 137 | } 138 | 139 | void reset() { 140 | for (FakeTeam fakeTeam : TEAMS.values()) { 141 | removePlayerFromTeamPackets(fakeTeam, fakeTeam.getMembers()); 142 | removeTeamPackets(fakeTeam); 143 | } 144 | CACHED_FAKE_TEAMS.clear(); 145 | TEAMS.clear(); 146 | } 147 | 148 | // ============================================================== 149 | // Below are private methods to construct a new Scoreboard packet 150 | // ============================================================== 151 | private void removeTeamPackets(FakeTeam fakeTeam) { 152 | new PacketWrapper(fakeTeam.getName(), fakeTeam.getPrefix(), fakeTeam.getSuffix(), 1, new ArrayList<>(), fakeTeam.isVisible()).send(); 153 | } 154 | 155 | private boolean removePlayerFromTeamPackets(FakeTeam fakeTeam, String... players) { 156 | return removePlayerFromTeamPackets(fakeTeam, Arrays.asList(players)); 157 | } 158 | 159 | private boolean removePlayerFromTeamPackets(FakeTeam fakeTeam, List players) { 160 | new PacketWrapper(fakeTeam.getName(), 4, players).send(); 161 | fakeTeam.getMembers().removeAll(players); 162 | return fakeTeam.getMembers().isEmpty(); 163 | } 164 | 165 | private void addTeamPackets(FakeTeam fakeTeam) { 166 | new PacketWrapper(fakeTeam.getName(), fakeTeam.getPrefix(), fakeTeam.getSuffix(), 0, fakeTeam.getMembers(), fakeTeam.isVisible()).send(); 167 | } 168 | 169 | private void addPlayerToTeamPackets(FakeTeam fakeTeam, String player) { 170 | new PacketWrapper(fakeTeam.getName(), 3, Collections.singletonList(player)).send(); 171 | } 172 | 173 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/converter/ConverterTask.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.converter; 2 | 3 | import com.nametagedit.plugin.NametagEdit; 4 | import com.nametagedit.plugin.NametagMessages; 5 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 6 | import com.nametagedit.plugin.utils.Utils; 7 | import lombok.AllArgsConstructor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.configuration.file.FileConfiguration; 10 | import org.bukkit.configuration.file.YamlConfiguration; 11 | import org.bukkit.scheduler.BukkitRunnable; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.sql.*; 16 | 17 | /** 18 | * This class converts to and from Flatfile and MySQL 19 | */ 20 | @AllArgsConstructor 21 | public class ConverterTask extends BukkitRunnable { 22 | 23 | private final boolean databaseToFile; 24 | private final CommandSender sender; 25 | private final NametagEdit plugin; 26 | 27 | @Override 28 | public void run() { 29 | FileConfiguration config = plugin.getHandler().getConfig(); 30 | String connectionString = "jdbc:mysql://" + config.getString("MySQL.Hostname") + ":" + config.getInt("MySQL.Port") + "/" + config.getString("MySQL.Database"); 31 | try (Connection connection = DriverManager.getConnection(connectionString, config.getString("MySQL.Username"), config.getString("MySQL.Password"))) { 32 | if (databaseToFile) { 33 | convertDatabaseToFile(connection); 34 | } else { 35 | convertFilesToDatabase(connection); 36 | } 37 | } catch (SQLException e) { 38 | e.printStackTrace(); 39 | } finally { 40 | new BukkitRunnable() { 41 | @Override 42 | public void run() { 43 | plugin.getHandler().reload(); 44 | } 45 | }.runTask(plugin); 46 | } 47 | } 48 | 49 | private void convertDatabaseToFile(Connection connection) { 50 | try { 51 | final String GROUP_QUERY = "SELECT name, prefix, suffix, permission, priority FROM " + DatabaseConfig.TABLE_GROUPS; 52 | final String PLAYER_QUERY = "SELECT name, uuid, prefix, suffix, priority FROM " + DatabaseConfig.TABLE_PLAYERS; 53 | 54 | final File groupsFile = new File(plugin.getDataFolder(), "groups_CONVERTED.yml"); 55 | final File playersFile = new File(plugin.getDataFolder(), "players_CONVERTED.yml"); 56 | 57 | final YamlConfiguration groups = Utils.getConfig(groupsFile); 58 | final YamlConfiguration players = Utils.getConfig(playersFile); 59 | 60 | ResultSet results = connection.prepareStatement(GROUP_QUERY).executeQuery(); 61 | while (results.next()) { 62 | groups.set("Groups." + results.getString("name") + ".Permission", results.getString("permission")); 63 | groups.set("Groups." + results.getString("name") + ".Prefix", results.getString("prefix")); 64 | groups.set("Groups." + results.getString("name") + ".Suffix", results.getString("suffix")); 65 | groups.set("Groups." + results.getString("name") + ".SortPriority", results.getInt("priority")); 66 | } 67 | 68 | results = connection.prepareStatement(PLAYER_QUERY).executeQuery(); 69 | while (results.next()) { 70 | players.set("Players." + results.getString("uuid") + ".Name", results.getString("name")); 71 | players.set("Players." + results.getString("uuid") + ".Prefix", results.getString("prefix")); 72 | players.set("Players." + results.getString("uuid") + ".Suffix", results.getString("suffix")); 73 | players.set("Players." + results.getString("uuid") + ".SortPriority", results.getInt("priority")); 74 | } 75 | 76 | results.close(); 77 | groups.save(groupsFile); 78 | players.save(playersFile); 79 | } catch (SQLException | IOException e) { 80 | e.printStackTrace(); 81 | } 82 | } 83 | 84 | private void convertFilesToDatabase(Connection connection) { 85 | final File groupsFile = new File(plugin.getDataFolder(), "groups.yml"); 86 | final File playersFile = new File(plugin.getDataFolder(), "players.yml"); 87 | 88 | final YamlConfiguration groups = Utils.getConfig(groupsFile); 89 | final YamlConfiguration players = Utils.getConfig(playersFile); 90 | 91 | if (players != null && checkValid(players, "Players")) { 92 | // Import the player entries from the file 93 | try (PreparedStatement playerInsert = connection.prepareStatement("INSERT INTO " + DatabaseConfig.TABLE_PLAYERS + " VALUES(?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE `prefix`=?, `suffix`=?")) { 94 | for (String key : players.getConfigurationSection("Players").getKeys(false)) { 95 | playerInsert.setString(1, key); 96 | playerInsert.setString(2, players.getString("Players." + key + ".Name")); 97 | playerInsert.setString(3, Utils.deformat(players.getString("Players." + key + ".Prefix", ""))); 98 | playerInsert.setString(4, Utils.deformat(players.getString("Players." + key + ".Suffix", ""))); 99 | playerInsert.setString(5, players.getString("Players." + key + ".SortPriority")); 100 | playerInsert.setString(6, Utils.deformat(players.getString("Players." + key + ".Prefix", ""))); 101 | playerInsert.setString(7, Utils.deformat(players.getString("Players." + key + ".Suffix", ""))); 102 | playerInsert.addBatch(); 103 | } 104 | 105 | playerInsert.executeBatch(); 106 | } catch (SQLException e) { 107 | e.printStackTrace(); 108 | } 109 | } 110 | 111 | if (groups != null && checkValid(groups, "Groups")) { 112 | // Import the player entries from the file 113 | try (PreparedStatement groupInsert = connection.prepareStatement("INSERT INTO " + DatabaseConfig.TABLE_GROUPS + " VALUES(?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE `prefix`=?, `suffix`=?, `permission`=?")) { 114 | for (String key : groups.getConfigurationSection("Groups").getKeys(false)) { 115 | groupInsert.setString(1, key); 116 | groupInsert.setString(2, groups.getString("Groups." + key + ".Permission")); 117 | groupInsert.setString(3, Utils.deformat(groups.getString("Groups." + key + ".Prefix", ""))); 118 | groupInsert.setString(4, Utils.deformat(groups.getString("Groups." + key + ".Suffix", ""))); 119 | groupInsert.setString(5, groups.getString("Groups." + key + ".SortPriority")); 120 | groupInsert.setString(6, Utils.deformat(groups.getString("Groups." + key + ".Prefix", ""))); 121 | groupInsert.setString(7, Utils.deformat(groups.getString("Groups." + key + ".Suffix", ""))); 122 | groupInsert.setString(8, groups.getString("Groups." + key + ".Permission")); 123 | groupInsert.addBatch(); 124 | } 125 | 126 | groupInsert.executeBatch(); 127 | } catch (SQLException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | } 132 | 133 | private boolean checkValid(FileConfiguration configuration, String section) { 134 | if (!configuration.contains(section)) { 135 | NametagMessages.FILE_MISCONFIGURED.send(sender, section + ".yml"); 136 | return false; 137 | } 138 | 139 | return true; 140 | } 141 | 142 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/storage/flatfile/FlatFileConfig.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.storage.flatfile; 2 | 3 | import com.nametagedit.plugin.NametagEdit; 4 | import com.nametagedit.plugin.NametagHandler; 5 | import com.nametagedit.plugin.api.data.GroupData; 6 | import com.nametagedit.plugin.api.data.PlayerData; 7 | import com.nametagedit.plugin.storage.AbstractConfig; 8 | import com.nametagedit.plugin.utils.UUIDFetcher; 9 | import com.nametagedit.plugin.utils.Utils; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.command.CommandSender; 12 | import org.bukkit.configuration.file.YamlConfiguration; 13 | import org.bukkit.entity.Player; 14 | import org.bukkit.scheduler.BukkitRunnable; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.UUID; 21 | 22 | public class FlatFileConfig implements AbstractConfig { 23 | 24 | private File groupsFile; 25 | private File playersFile; 26 | 27 | private YamlConfiguration groups; 28 | private YamlConfiguration players; 29 | 30 | private final NametagEdit plugin; 31 | private final NametagHandler handler; 32 | 33 | public FlatFileConfig(NametagEdit plugin, NametagHandler handler) { 34 | this.plugin = plugin; 35 | this.handler = handler; 36 | } 37 | 38 | @Override 39 | public void load() { 40 | groupsFile = new File(plugin.getDataFolder(), "groups.yml"); 41 | groups = Utils.getConfig(groupsFile, "groups.yml", plugin); 42 | playersFile = new File(plugin.getDataFolder(), "players.yml"); 43 | players = Utils.getConfig(playersFile, "players.yml", plugin); 44 | loadGroups(); 45 | loadPlayers(); 46 | 47 | new BukkitRunnable() { 48 | @Override 49 | public void run() { 50 | handler.applyTags(); 51 | } 52 | }.runTask(plugin); 53 | } 54 | 55 | @Override 56 | public void reload() { 57 | handler.clearMemoryData(); 58 | 59 | new BukkitRunnable() { 60 | @Override 61 | public void run() { 62 | load(); 63 | } 64 | }.runTaskAsynchronously(plugin); 65 | } 66 | 67 | @Override 68 | public void shutdown() { 69 | // NOTE: Nothing to do 70 | } 71 | 72 | @Override 73 | public void load(Player player, boolean loggedIn) { 74 | loadPlayerTag(player); 75 | plugin.getHandler().applyTagToPlayer(player, loggedIn); 76 | } 77 | 78 | @Override 79 | public void save(PlayerData... data) { 80 | for (PlayerData playerData : data) { 81 | UUID uuid = playerData.getUuid(); 82 | String name = playerData.getName(); 83 | players.set("Players." + uuid + ".Name", name); 84 | players.set("Players." + uuid + ".Prefix", Utils.deformat(playerData.getPrefix())); 85 | players.set("Players." + uuid + ".Suffix", Utils.deformat(playerData.getSuffix())); 86 | players.set("Players." + uuid + ".SortPriority", playerData.getSortPriority()); 87 | } 88 | 89 | save(players, playersFile); 90 | } 91 | 92 | @Override 93 | public void save(final GroupData... data) { 94 | new BukkitRunnable() { 95 | @Override 96 | public void run() { 97 | for (GroupData groupData : data) { 98 | storeGroup(groupData); 99 | } 100 | 101 | save(groups, groupsFile); 102 | } 103 | }.runTaskAsynchronously(plugin); 104 | } 105 | 106 | @Override 107 | public void savePriority(boolean playerTag, String key, final int priority) { 108 | if (playerTag) { 109 | final Player target = Bukkit.getPlayerExact(key); 110 | if (target != null) { 111 | if (players.contains("Players." + target.getUniqueId().toString())) { 112 | players.set("Players." + target.getUniqueId().toString(), priority); 113 | save(players, playersFile); 114 | } 115 | return; 116 | } 117 | 118 | UUIDFetcher.lookupUUID(key, plugin, new UUIDFetcher.UUIDLookup() { 119 | @Override 120 | public void response(UUID uuid) { 121 | if (players.contains("Players." + uuid.toString())) { 122 | players.set("Players." + uuid, priority); 123 | save(players, playersFile); 124 | } 125 | } 126 | }); 127 | } 128 | } 129 | 130 | @Override 131 | public void delete(GroupData groupData) { 132 | groups.set("Groups." + groupData.getGroupName(), null); 133 | save(groups, groupsFile); 134 | } 135 | 136 | @Override 137 | public void add(GroupData groupData) { 138 | // NOTE: Nothing to do 139 | } 140 | 141 | @Override 142 | public void clear(UUID uuid, String targetName) { 143 | handler.removePlayerData(uuid); 144 | players.set("Players." + uuid.toString(), null); 145 | save(players, playersFile); 146 | } 147 | 148 | @Override 149 | public void orderGroups(CommandSender commandSender, List order) { 150 | groups.set("Groups", null); 151 | for (String set : order) { 152 | GroupData groupData = handler.getGroupData(set); 153 | if (groupData != null) { 154 | storeGroup(groupData); 155 | } 156 | } 157 | 158 | for (GroupData groupData : handler.getGroupData()) { 159 | if (!groups.contains("Groups." + groupData.getGroupName())) { 160 | storeGroup(groupData); 161 | } 162 | } 163 | 164 | save(groups, groupsFile); 165 | } 166 | 167 | private void save(YamlConfiguration config, File file) { 168 | try { 169 | config.save(file); 170 | } catch (IOException e) { 171 | e.printStackTrace(); 172 | } 173 | } 174 | 175 | private void loadPlayerTag(Player player) { 176 | PlayerData data = PlayerData.fromFile(player.getUniqueId().toString(), players); 177 | if (data != null) { 178 | data.setName(player.getName()); 179 | handler.storePlayerData(player.getUniqueId(), data); 180 | } 181 | } 182 | 183 | private void loadPlayers() { 184 | for (Player player : Utils.getOnline()) { 185 | loadPlayerTag(player); 186 | } 187 | } 188 | 189 | private void loadGroups() { 190 | List groupData = new ArrayList<>(); 191 | for (String groupName : groups.getConfigurationSection("Groups").getKeys(false)) { 192 | GroupData data = new GroupData(); 193 | data.setGroupName(groupName); 194 | data.setPermission(groups.getString("Groups." + groupName + ".Permission", "nte.default")); 195 | data.setPrefix(groups.getString("Groups." + groupName + ".Prefix", "")); 196 | data.setSuffix(groups.getString("Groups." + groupName + ".Suffix", "")); 197 | data.setSortPriority(groups.getInt("Groups." + groupName + ".SortPriority", -1)); 198 | groupData.add(data); 199 | } 200 | 201 | handler.assignGroupData(groupData); 202 | } 203 | 204 | private void storeGroup(GroupData groupData) { 205 | groups.set("Groups." + groupData.getGroupName() + ".Permission", groupData.getPermission()); 206 | groups.set("Groups." + groupData.getGroupName() + ".Prefix", Utils.deformat(groupData.getPrefix())); 207 | groups.set("Groups." + groupData.getGroupName() + ".Suffix", Utils.deformat(groupData.getSuffix())); 208 | groups.set("Groups." + groupData.getGroupName() + ".SortPriority", groupData.getSortPriority()); 209 | } 210 | 211 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/packets/PacketWrapper.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.packets; 2 | 3 | import com.nametagedit.plugin.NametagHandler; 4 | import com.nametagedit.plugin.utils.Utils; 5 | import org.bukkit.ChatColor; 6 | import org.bukkit.entity.Player; 7 | 8 | import java.lang.reflect.Array; 9 | import java.lang.reflect.Method; 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.List; 13 | import java.util.Optional; 14 | 15 | public class PacketWrapper { 16 | 17 | public String error; 18 | private final int param; 19 | private final Object packet = PacketAccessor.createPacket(); 20 | private final Object packetParams = PacketAccessor.createPacketParams(); 21 | 22 | private static Method CraftChatMessage; 23 | private static Class typeEnumChatFormat; 24 | private static Enum RESET_COLOR; 25 | 26 | static { 27 | try { 28 | if (!PacketAccessor.isLegacyVersion()) { 29 | if (!PacketAccessor.isParamsVersion()) { 30 | typeEnumChatFormat = (Class) Class.forName("net.minecraft.server." + PacketAccessor.VERSION + ".EnumChatFormat"); 31 | } else { 32 | // 1.17+ 33 | typeEnumChatFormat = (Class) Class.forName("net.minecraft.EnumChatFormat"); 34 | } 35 | Class typeCraftChatMessage = Class.forName("org.bukkit.craftbukkit." + PacketAccessor.VERSION + ".util.CraftChatMessage"); 36 | CraftChatMessage = typeCraftChatMessage.getMethod("fromString", String.class); 37 | RESET_COLOR = Enum.valueOf(typeEnumChatFormat, "RESET"); 38 | } 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | public PacketWrapper(String name, int param, List members) { 45 | if (param != 3 && param != 4) { 46 | throw new IllegalArgumentException("Method must be join or leave for player constructor"); 47 | } 48 | this.param = param; 49 | setupDefaults(name, param); 50 | setupMembers(members); 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | public PacketWrapper(String name, String prefix, String suffix, int param, Collection players, boolean visible) { 55 | this.param = param; 56 | setupDefaults(name, param); 57 | if (param == 0 || param == 2) { 58 | try { 59 | if (PacketAccessor.isLegacyVersion()) { 60 | PacketAccessor.DISPLAY_NAME.set(packet, name); 61 | PacketAccessor.PREFIX.set(packet, prefix); 62 | PacketAccessor.SUFFIX.set(packet, suffix); 63 | } else { 64 | String color = ChatColor.getLastColors(prefix); 65 | String colorCode = null; 66 | Enum colorEnum = null; 67 | 68 | if (!color.isEmpty()) { 69 | colorCode = color.substring(color.length() - 1); 70 | String chatColor = ChatColor.getByChar(colorCode).name(); 71 | 72 | if (chatColor.equalsIgnoreCase("MAGIC")) 73 | chatColor = "OBFUSCATED"; 74 | 75 | colorEnum = Enum.valueOf(typeEnumChatFormat, chatColor); 76 | } 77 | 78 | if (colorCode != null) 79 | suffix = ChatColor.getByChar(colorCode) + suffix; 80 | 81 | if (!PacketAccessor.isParamsVersion()) { 82 | PacketAccessor.TEAM_COLOR.set(packet, colorEnum == null ? RESET_COLOR : colorEnum); 83 | PacketAccessor.DISPLAY_NAME.set(packet, Array.get(CraftChatMessage.invoke(null, name), 0)); 84 | PacketAccessor.PREFIX.set(packet, Array.get(CraftChatMessage.invoke(null, prefix), 0)); 85 | PacketAccessor.SUFFIX.set(packet, Array.get(CraftChatMessage.invoke(null, suffix), 0)); 86 | } else { 87 | // 1.17+ 88 | PacketAccessor.TEAM_COLOR.set(packetParams, colorEnum == null ? RESET_COLOR : colorEnum); 89 | PacketAccessor.DISPLAY_NAME.set(packetParams, Array.get(CraftChatMessage.invoke(null, name), 0)); 90 | PacketAccessor.PREFIX.set(packetParams, Array.get(CraftChatMessage.invoke(null, prefix), 0)); 91 | PacketAccessor.SUFFIX.set(packetParams, Array.get(CraftChatMessage.invoke(null, suffix), 0)); 92 | } 93 | } 94 | 95 | if (!PacketAccessor.isParamsVersion()) { 96 | PacketAccessor.PACK_OPTION.set(packet, 1); 97 | 98 | if (PacketAccessor.VISIBILITY != null) { 99 | PacketAccessor.VISIBILITY.set(packet, visible ? "always" : "never"); 100 | } 101 | } else { 102 | // 1.17+ 103 | PacketAccessor.PACK_OPTION.set(packetParams, 1); 104 | 105 | if (PacketAccessor.VISIBILITY != null) { 106 | PacketAccessor.VISIBILITY.set(packetParams, visible ? "always" : "never"); 107 | } 108 | } 109 | 110 | if (param == 0) { 111 | ((Collection) PacketAccessor.MEMBERS.get(packet)).addAll(players); 112 | } 113 | } catch (Exception e) { 114 | error = e.getMessage(); 115 | } 116 | } 117 | } 118 | 119 | @SuppressWarnings("unchecked") 120 | private void setupMembers(Collection players) { 121 | try { 122 | players = players == null || players.isEmpty() ? new ArrayList<>() : players; 123 | ((Collection) PacketAccessor.MEMBERS.get(packet)).addAll(players); 124 | } catch (Exception e) { 125 | error = e.getMessage(); 126 | } 127 | } 128 | 129 | private void setupDefaults(String name, int param) { 130 | try { 131 | PacketAccessor.TEAM_NAME.set(packet, name); 132 | PacketAccessor.PARAM_INT.set(packet, param); 133 | 134 | if (PacketAccessor.isParamsVersion()) { 135 | // 1.17+ These null values are not allowed, this initializes them. 136 | PacketAccessor.MEMBERS.set(packet, new ArrayList<>()); 137 | PacketAccessor.PUSH.set(packetParams, ""); 138 | PacketAccessor.VISIBILITY.set(packetParams, ""); 139 | PacketAccessor.TEAM_COLOR.set(packetParams, RESET_COLOR); 140 | } 141 | if (NametagHandler.DISABLE_PUSH_ALL_TAGS && PacketAccessor.PUSH != null) { 142 | if (!PacketAccessor.isParamsVersion()) { 143 | PacketAccessor.PUSH.set(packet, "never"); 144 | } else { 145 | // 1.17+ 146 | PacketAccessor.PUSH.set(packetParams, "never"); 147 | } 148 | } 149 | } catch (Exception e) { 150 | error = e.getMessage(); 151 | } 152 | } 153 | 154 | private void constructPacket() { 155 | try { 156 | if (PacketAccessor.isParamsVersion()) { 157 | // 1.17+ 158 | PacketAccessor.PARAMS.set(packet, param == 0 ? Optional.ofNullable(packetParams) : Optional.empty()); 159 | } 160 | } catch (Exception e) { 161 | error = e.getMessage(); 162 | } 163 | } 164 | 165 | public void send() { 166 | constructPacket(); 167 | PacketAccessor.sendPacket(Utils.getOnline(), packet); 168 | } 169 | 170 | public void send(Player player) { 171 | constructPacket(); 172 | PacketAccessor.sendPacket(player, packet); 173 | } 174 | 175 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/utils/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.utils; 2 | 3 | import com.google.common.base.Joiner; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Maps; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.configuration.InvalidConfigurationException; 8 | import org.bukkit.configuration.file.YamlConfiguration; 9 | 10 | import java.io.File; 11 | import java.io.FileWriter; 12 | import java.io.IOException; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.logging.Level; 17 | import java.util.regex.Pattern; 18 | 19 | public class Configuration extends YamlConfiguration { 20 | 21 | private final Map> headers = Maps.newConcurrentMap(); 22 | private final File file; 23 | private List mainHeader = Lists.newArrayList(); 24 | private boolean loadHeaders; 25 | 26 | public Configuration(File file) { 27 | this.file = file; 28 | } 29 | 30 | /** 31 | * Set the main header displayed at top of config. 32 | * 33 | * @param header header 34 | */ 35 | public void mainHeader(String... header) { 36 | mainHeader = Arrays.asList(header); 37 | } 38 | 39 | /** 40 | * Get main header displayed at top of config. 41 | * 42 | * @return header 43 | */ 44 | public List mainHeader() { 45 | return mainHeader; 46 | } 47 | 48 | /** 49 | * Set option header. 50 | * 51 | * @param key of option (or section) 52 | * @param header of option (or section) 53 | */ 54 | public void header(String key, String... header) { 55 | headers.put(key, Arrays.asList(header)); 56 | } 57 | 58 | /** 59 | * Get header of option 60 | * 61 | * @param key of option (or section) 62 | * @return Header 63 | */ 64 | public List header(String key) { 65 | return headers.get(key); 66 | } 67 | 68 | public T get(String key, Class type) { 69 | return type.cast(get(key)); 70 | } 71 | 72 | /** 73 | * Reload config from file. 74 | */ 75 | public void reload() { 76 | reload(headers.isEmpty()); 77 | } 78 | 79 | /** 80 | * Reload config from file. 81 | * 82 | * @param loadHeaders Whether or not to load headers. 83 | */ 84 | public void reload(boolean loadHeaders) { 85 | this.loadHeaders = loadHeaders; 86 | try { 87 | load(file); 88 | } catch (Exception e) { 89 | Bukkit.getLogger().log(Level.WARNING, "failed to reload file", e); 90 | } 91 | } 92 | 93 | @Override 94 | public void loadFromString(String contents) throws InvalidConfigurationException { 95 | if (!loadHeaders) { 96 | super.loadFromString(contents); 97 | return; 98 | } 99 | 100 | StringBuilder memoryData = new StringBuilder(); 101 | 102 | // Parse headers 103 | final int indentLength = options().indent(); 104 | final String pathSeparator = Character.toString(options().pathSeparator()); 105 | int currentIndents = 0; 106 | String key = ""; 107 | List headers = Lists.newArrayList(); 108 | for (String line : contents.split("\n")) { 109 | if (line.isEmpty()) continue; // Skip empty lines 110 | int indent = getSuccessiveCharCount(line, ' '); 111 | String subline = indent > 0 ? line.substring(indent) : line; 112 | if (subline.startsWith("#")) { 113 | if (subline.startsWith("#>")) { 114 | String txt = subline.startsWith("#> ") ? subline.substring(3) : subline.substring(2); 115 | mainHeader.add(txt); 116 | continue; // Main header, handled by bukkit 117 | } 118 | 119 | // Add header to list 120 | String txt = subline.startsWith("# ") ? subline.substring(2) : subline.substring(1); 121 | headers.add(txt); 122 | continue; 123 | } 124 | 125 | int indents = indent / indentLength; 126 | if (indents <= currentIndents) { 127 | // Remove last section of key 128 | String[] array = key.split(Pattern.quote(pathSeparator)); 129 | int backspace = currentIndents - indents + 1; 130 | key = join(array, options().pathSeparator(), 0, array.length - backspace); 131 | } 132 | 133 | // Add new section to key 134 | String separator = key.length() > 0 ? pathSeparator : ""; 135 | String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line; 136 | key += separator + lineKey.substring(indent); 137 | 138 | currentIndents = indents; 139 | 140 | memoryData.append(line).append('\n'); 141 | if (!headers.isEmpty()) { 142 | this.headers.put(key, headers); 143 | headers = Lists.newArrayList(); 144 | } 145 | } 146 | 147 | // Parse remaining text 148 | super.loadFromString(memoryData.toString()); 149 | 150 | // Clear bukkit header 151 | options().header(null); 152 | } 153 | 154 | /** 155 | * Save config to file 156 | */ 157 | public void save() { 158 | if (headers.isEmpty() && mainHeader.isEmpty()) { 159 | try { 160 | super.save(file); 161 | } catch (IOException e) { 162 | Bukkit.getLogger().log(Level.WARNING, "Failed to save file", e); 163 | } 164 | return; 165 | } 166 | 167 | // Custom save 168 | final int indentLength = options().indent(); 169 | final String pathSeparator = Character.toString(options().pathSeparator()); 170 | String content = saveToString(); 171 | StringBuilder fileData = new StringBuilder(buildHeader()); 172 | int currentIndents = 0; 173 | String key = ""; 174 | for (String h : mainHeader) { 175 | // Append main header to top of file 176 | fileData.append("#> ").append(h).append('\n'); 177 | } 178 | 179 | for (String line : content.split("\n")) { 180 | if (line.isEmpty()) continue; // Skip empty lines 181 | int indent = getSuccessiveCharCount(line, ' '); 182 | int indents = indent / indentLength; 183 | String indentText = indent > 0 ? line.substring(0, indent) : ""; 184 | if (indents <= currentIndents) { 185 | // Remove last section of key 186 | String[] array = key.split(Pattern.quote(pathSeparator)); 187 | int backspace = currentIndents - indents + 1; 188 | key = join(array, options().pathSeparator(), 0, array.length - backspace); 189 | } 190 | 191 | // Add new section to key 192 | String separator = key.length() > 0 ? pathSeparator : ""; 193 | String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line; 194 | key += separator + lineKey.substring(indent); 195 | 196 | currentIndents = indents; 197 | 198 | List header = headers.get(key); 199 | String headerText = header != null ? addHeaderTags(header, indentText) : ""; 200 | fileData.append(headerText).append(line).append('\n'); 201 | } 202 | 203 | // Write data to file 204 | FileWriter writer = null; 205 | try { 206 | writer = new FileWriter(file); 207 | writer.write(fileData.toString()); 208 | writer.flush(); 209 | } catch (IOException e) { 210 | Bukkit.getLogger().log(Level.WARNING, "Failed to save file", e); 211 | } finally { 212 | if (writer != null) { 213 | try { 214 | writer.close(); 215 | } catch (IOException ignored) { 216 | } 217 | } 218 | } 219 | } 220 | 221 | private String addHeaderTags(List header, String indent) { 222 | StringBuilder builder = new StringBuilder(); 223 | for (String line : header) { 224 | builder.append(indent).append("# ").append(line).append('\n'); 225 | } 226 | return builder.toString(); 227 | } 228 | 229 | private String join(String[] array, char joinChar, int start, int length) { 230 | String[] copy = new String[length - start]; 231 | System.arraycopy(array, start, copy, 0, length - start); 232 | return Joiner.on(joinChar).join(copy); 233 | } 234 | 235 | private int getSuccessiveCharCount(String text, char key) { 236 | int count = 0; 237 | for (int i = 0; i < text.length(); i++) { 238 | if (text.charAt(i) == key) { 239 | count += 1; 240 | } else { 241 | break; 242 | } 243 | } 244 | return count; 245 | } 246 | 247 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | com.nametagedit 7 | nametagedit 8 | 4.5.23 9 | jar 10 | NametagEdit 11 | https://maven.apache.org 12 | 13 | UTF-8 14 | 1.20.4-R0.1-SNAPSHOT 15 | 16 | 17 | 18 | 19 | org.spigotmc 20 | spigot-api 21 | ${spigot.version} 22 | provided 23 | 24 | 25 | org.spigotmc 26 | spigot 27 | ${spigot.version} 28 | provided 29 | 30 | 31 | 32 | net.luckperms 33 | api 34 | 5.4 35 | provided 36 | 37 | 38 | 39 | me.clip 40 | placeholderapi 41 | 2.10.9 42 | provided 43 | 44 | 45 | 46 | com.zaxxer 47 | HikariCP 48 | 4.0.3 49 | 50 | 51 | 52 | org.slf4j 53 | slf4j-jdk14 54 | 1.7.32 55 | compile 56 | 57 | 58 | 59 | org.projectlombok 60 | lombok 61 | 1.18.22 62 | provided 63 | 64 | 65 | 66 | com.google.guava 67 | guava 68 | 32.0.0-jre 69 | 70 | 71 | 72 | org.bstats.bStats-Metrics 73 | bstats-bukkit 74 | 1.3 75 | compile 76 | 77 | 78 | 79 | ninja.leaping.permissionsex 80 | permissionsex-parent 81 | 2.0-SNAPSHOT 82 | system 83 | ${basedir}/lib/PermissionsEx.jar 84 | 85 | 86 | 87 | org.tyrannyofheaven.bukkit 88 | zPermissions 89 | 1.3-SNAPSHOT 90 | system 91 | ${basedir}/lib/zPermissions.jar 92 | 93 | 94 | 95 | org.anjocaido.groupmanager 96 | EssentialsGroupManager 97 | 2.x-SNAPSHOT 98 | system 99 | ${basedir}/lib/EssentialsGroupManager.jar 100 | 101 | 102 | 103 | me.glaremasters 104 | guilds 105 | 3.5.6.6-SNAPSHOT 106 | provided 107 | 108 | 109 | 110 | LibsDisguises 111 | LibsDisguises 112 | 10.0.25 113 | system 114 | ${basedir}/lib/LibsDisguises-10.0.25.jar 115 | 116 | 117 | 118 | co.aikar 119 | acf-paper 120 | 0.5.1-SNAPSHOT 121 | 122 | 123 | 124 | 125 | 126 | nms-repo 127 | https://repo.codemc.io/repository/nms/ 128 | 129 | 130 | 131 | spigot-repo 132 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 133 | 134 | 135 | 136 | placeholderapi 137 | https://repo.extendedclip.com/content/repositories/placeholderapi/ 138 | 139 | 140 | 141 | jitpack.io 142 | https://jitpack.io 143 | 144 | 145 | 146 | aikar 147 | https://repo.aikar.co/content/groups/aikar/ 148 | 149 | 150 | 151 | guilds 152 | https://repo.glaremasters.me/repository/public/ 153 | 154 | 155 | 156 | 157 | clean package 158 | NametagEdit 159 | ${basedir}/src/main/java 160 | 161 | 162 | . 163 | true 164 | ${basedir}/src/main/resources/ 165 | 166 | *.* 167 | 168 | 169 | 170 | 171 | 172 | 173 | org.apache.maven.plugins 174 | maven-resources-plugin 175 | 2.7 176 | 177 | 178 | org.apache.maven.shared 179 | maven-filtering 180 | 1.3 181 | 182 | 183 | 184 | UTF-8 185 | 186 | 187 | 188 | 189 | org.apache.maven.plugins 190 | maven-shade-plugin 191 | 3.1.0 192 | 193 | 194 | package 195 | 196 | shade 197 | 198 | 199 | true 200 | false 201 | 202 | 203 | org.slf4j:* 204 | com.zaxxer:* 205 | org.bstats.bStats-Metrics:* 206 | co.aikar:* 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | maven-compiler-plugin 216 | 3.8.1 217 | 218 | 1.8 219 | 1.8 220 | 221 | -parameters 222 | 223 | 224 | 225 | 226 | org.apache.maven.plugins 227 | maven-jar-plugin 228 | 3.2.0 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/packets/PacketAccessor.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin.packets; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.Player; 5 | 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.Method; 8 | import java.util.Arrays; 9 | import java.util.Collection; 10 | import java.util.List; 11 | import java.util.stream.Stream; 12 | 13 | class PacketAccessor { 14 | 15 | protected static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; 16 | protected static final int MINOR_VERSION = Integer.parseInt(VERSION.split("_")[1]); 17 | 18 | private static final 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"); 19 | private static boolean CAULDRON_SERVER = false; 20 | private static boolean LEGACY_SERVER = false; 21 | 22 | private static Object UNSAFE; 23 | private static Method ALLOCATE_INSTANCE; 24 | 25 | static Field MEMBERS; 26 | static Field PREFIX; 27 | static Field SUFFIX; 28 | static Field TEAM_NAME; 29 | static Field PARAM_INT; 30 | static Field PACK_OPTION; 31 | static Field DISPLAY_NAME; 32 | static Field TEAM_COLOR; 33 | static Field PUSH; 34 | static Field VISIBILITY; 35 | // 1.17+ 36 | static Field PARAMS; 37 | 38 | private static Method getHandle; 39 | private static Method sendPacket; 40 | private static Field playerConnection; 41 | 42 | private static Class packetClass; 43 | // 1.17+ 44 | private static Class packetParamsClass; 45 | 46 | static { 47 | try { 48 | Class.forName("cpw.mods.fml.common.Mod"); 49 | CAULDRON_SERVER = true; 50 | } catch (ClassNotFoundException ignored) { 51 | // This is not a cauldron server 52 | } 53 | 54 | try { 55 | Class unsafeClass = Class.forName("sun.misc.Unsafe"); 56 | Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe"); 57 | theUnsafeField.setAccessible(true); 58 | UNSAFE = theUnsafeField.get(null); 59 | ALLOCATE_INSTANCE = UNSAFE.getClass().getMethod("allocateInstance", Class.class); 60 | 61 | if (legacyVersions.contains(VERSION)) 62 | LEGACY_SERVER = true; 63 | 64 | Class typeCraftPlayer = Class.forName("org.bukkit.craftbukkit." + VERSION + ".entity.CraftPlayer"); 65 | getHandle = typeCraftPlayer.getMethod("getHandle"); 66 | 67 | if (CAULDRON_SERVER) { 68 | packetClass = Class.forName("net.minecraft.server.v1_7_R4.PacketPlayOutScoreboardTeam"); 69 | Class typeNMSPlayer = Class.forName("net.minecraft.server.v1_7_R4.EntityPlayer"); 70 | Class typePlayerConnection = Class.forName("net.minecraft.server.v1_7_R4.PlayerConnection"); 71 | playerConnection = typeNMSPlayer.getField("field_71135_a"); 72 | sendPacket = typePlayerConnection.getMethod("func_147359_a", Class.forName("net.minecraft.server.v1_7_R4.Packet")); 73 | } else if (!isParamsVersion()) { 74 | packetClass = Class.forName("net.minecraft.server." + VERSION + ".PacketPlayOutScoreboardTeam"); 75 | Class typeNMSPlayer = Class.forName("net.minecraft.server." + VERSION + ".EntityPlayer"); 76 | Class typePlayerConnection = Class.forName("net.minecraft.server." + VERSION + ".PlayerConnection"); 77 | playerConnection = typeNMSPlayer.getField("playerConnection"); 78 | sendPacket = typePlayerConnection.getMethod("sendPacket", Class.forName("net.minecraft.server." + VERSION + ".Packet")); 79 | } else { 80 | // 1.17+ 81 | packetClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam"); 82 | packetParamsClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam$b"); 83 | Class typeNMSPlayer = Class.forName("net.minecraft.server.level.EntityPlayer"); 84 | Class typePlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection"); 85 | if (MINOR_VERSION >= 20) { 86 | // 1.20 87 | playerConnection = typeNMSPlayer.getField("c"); 88 | } else { 89 | // 1.17-1.19 90 | playerConnection = typeNMSPlayer.getField("b"); 91 | } 92 | Class[] sendPacketParameters = new Class[]{Class.forName("net.minecraft.network.protocol.Packet")}; 93 | sendPacket = Stream.concat( 94 | Arrays.stream(typePlayerConnection.getSuperclass().getMethods()), // 1.20.2+ priority to packet sending 95 | Arrays.stream(typePlayerConnection.getMethods()) 96 | ) 97 | .filter(method -> Arrays.equals(method.getParameterTypes(), sendPacketParameters)) 98 | .findFirst().orElseThrow(NoSuchMethodException::new); 99 | } 100 | 101 | PacketData currentVersion = null; 102 | for (PacketData packetData : PacketData.values()) { 103 | if (VERSION.contains(packetData.name())) { 104 | currentVersion = packetData; 105 | } 106 | } 107 | 108 | if (CAULDRON_SERVER) { 109 | currentVersion = PacketData.cauldron; 110 | } 111 | 112 | if (currentVersion != null) { 113 | if (!isParamsVersion()) { 114 | PREFIX = getNMS(currentVersion.getPrefix()); 115 | SUFFIX = getNMS(currentVersion.getSuffix()); 116 | MEMBERS = getNMS(currentVersion.getMembers()); 117 | TEAM_NAME = getNMS(currentVersion.getTeamName()); 118 | PARAM_INT = getNMS(currentVersion.getParamInt()); 119 | PACK_OPTION = getNMS(currentVersion.getPackOption()); 120 | DISPLAY_NAME = getNMS(currentVersion.getDisplayName()); 121 | 122 | if (!isLegacyVersion()) { 123 | TEAM_COLOR = getNMS(currentVersion.getColor()); 124 | } 125 | 126 | if (isPushVersion()) { 127 | PUSH = getNMS(currentVersion.getPush()); 128 | } 129 | 130 | if (isVisibilityVersion()) { 131 | VISIBILITY = getNMS(currentVersion.getVisibility()); 132 | } 133 | } else { 134 | // 1.17+ 135 | PARAM_INT = getNMS(currentVersion.getParamInt()); 136 | TEAM_NAME = getNMS(currentVersion.getTeamName()); 137 | MEMBERS = getNMS(currentVersion.getMembers()); 138 | PARAMS = getNMS(currentVersion.getParams()); 139 | 140 | PREFIX = getParamNMS(currentVersion.getPrefix()); 141 | SUFFIX = getParamNMS(currentVersion.getSuffix()); 142 | PACK_OPTION = getParamNMS(currentVersion.getPackOption()); 143 | DISPLAY_NAME = getParamNMS(currentVersion.getDisplayName()); 144 | TEAM_COLOR = getParamNMS(currentVersion.getColor()); 145 | PUSH = getParamNMS(currentVersion.getPush()); 146 | VISIBILITY = getParamNMS(currentVersion.getVisibility()); 147 | } 148 | } 149 | } catch (Exception e) { 150 | e.printStackTrace(); 151 | } 152 | } 153 | 154 | public static boolean isLegacyVersion() { 155 | return LEGACY_SERVER; 156 | } 157 | 158 | public static boolean isParamsVersion() { 159 | return MINOR_VERSION >= 17; 160 | } 161 | 162 | private static boolean isPushVersion() { 163 | return MINOR_VERSION >= 9; 164 | } 165 | 166 | private static boolean isVisibilityVersion() { 167 | return MINOR_VERSION >= 8; 168 | } 169 | 170 | private static Field getNMS(String path) throws Exception { 171 | Field field = packetClass.getDeclaredField(path); 172 | field.setAccessible(true); 173 | return field; 174 | } 175 | 176 | // 1.17+ 177 | private static Field getParamNMS(String path) throws Exception { 178 | Field field = packetParamsClass.getDeclaredField(path); 179 | field.setAccessible(true); 180 | return field; 181 | } 182 | 183 | static Object createPacket() { 184 | try { 185 | if (!isParamsVersion()) { 186 | return packetClass.newInstance(); 187 | } else { 188 | return ALLOCATE_INSTANCE.invoke(UNSAFE, packetClass); 189 | } 190 | } catch (Exception e) { 191 | e.printStackTrace(); 192 | return null; 193 | } 194 | } 195 | 196 | static Object createPacketParams() { 197 | try { 198 | if (!isParamsVersion()) { 199 | return null; 200 | } else { 201 | return ALLOCATE_INSTANCE.invoke(UNSAFE, packetParamsClass); 202 | } 203 | } catch (Exception e) { 204 | e.printStackTrace(); 205 | return null; 206 | } 207 | } 208 | 209 | static void sendPacket(Collection players, Object packet) { 210 | for (Player player : players) { 211 | sendPacket(player, packet); 212 | } 213 | } 214 | 215 | static void sendPacket(Player player, Object packet) { 216 | try { 217 | Object nmsPlayer = getHandle.invoke(player); 218 | Object connection = playerConnection.get(nmsPlayer); 219 | sendPacket.invoke(connection, packet); 220 | } catch (Exception e) { 221 | e.printStackTrace(); 222 | } 223 | } 224 | 225 | } 226 | -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/NametagHandler.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin; 2 | 3 | import com.nametagedit.plugin.api.data.GroupData; 4 | import com.nametagedit.plugin.api.data.INametag; 5 | import com.nametagedit.plugin.api.data.PlayerData; 6 | import com.nametagedit.plugin.api.events.NametagEvent; 7 | import com.nametagedit.plugin.api.events.NametagFirstLoadedEvent; 8 | import com.nametagedit.plugin.metrics.Metrics; 9 | import com.nametagedit.plugin.storage.AbstractConfig; 10 | import com.nametagedit.plugin.storage.database.DatabaseConfig; 11 | import com.nametagedit.plugin.storage.flatfile.FlatFileConfig; 12 | import com.nametagedit.plugin.utils.Configuration; 13 | import com.nametagedit.plugin.utils.UUIDFetcher; 14 | import com.nametagedit.plugin.utils.Utils; 15 | import lombok.AccessLevel; 16 | import lombok.Getter; 17 | import lombok.Setter; 18 | import me.clip.placeholderapi.PlaceholderAPIPlugin; 19 | import org.bukkit.Bukkit; 20 | import org.bukkit.ChatColor; 21 | import org.bukkit.command.CommandSender; 22 | import org.bukkit.entity.Player; 23 | import org.bukkit.event.EventHandler; 24 | import org.bukkit.event.EventPriority; 25 | import org.bukkit.event.Listener; 26 | import org.bukkit.event.player.PlayerChangedWorldEvent; 27 | import org.bukkit.event.player.PlayerJoinEvent; 28 | import org.bukkit.event.player.PlayerQuitEvent; 29 | import org.bukkit.plugin.Plugin; 30 | import org.bukkit.scheduler.BukkitRunnable; 31 | import org.bukkit.scheduler.BukkitTask; 32 | 33 | import java.io.File; 34 | import java.util.*; 35 | import java.util.concurrent.locks.ReadWriteLock; 36 | import java.util.concurrent.locks.ReentrantReadWriteLock; 37 | 38 | @Getter 39 | @Setter 40 | public class NametagHandler implements Listener { 41 | 42 | // Multiple threads access resources. We need to make sure we avoid concurrency issues. 43 | private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 44 | 45 | public static boolean DISABLE_PUSH_ALL_TAGS = false; 46 | private boolean debug; 47 | @Getter(AccessLevel.NONE) 48 | private boolean tabListEnabled; 49 | private boolean longNametagsEnabled; 50 | private boolean refreshTagOnWorldChange; 51 | 52 | private BukkitTask clearEmptyTeamTask; 53 | private BukkitTask refreshNametagTask; 54 | private AbstractConfig abstractConfig; 55 | 56 | private Configuration config; 57 | 58 | @Getter(AccessLevel.NONE) 59 | @Setter(AccessLevel.NONE) 60 | private List groupData = new ArrayList<>(); 61 | 62 | @Getter(AccessLevel.NONE) 63 | @Setter(AccessLevel.NONE) 64 | private Map playerData = new HashMap<>(); 65 | 66 | private NametagEdit plugin; 67 | private NametagManager nametagManager; 68 | 69 | public NametagHandler(NametagEdit plugin, NametagManager nametagManager) { 70 | this.config = getCustomConfig(plugin); 71 | this.plugin = plugin; 72 | this.nametagManager = nametagManager; 73 | Bukkit.getPluginManager().registerEvents(this, plugin); 74 | 75 | // Apply config properties 76 | this.applyConfig(); 77 | 78 | if (config.getBoolean("MySQL.Enabled")) { 79 | abstractConfig = new DatabaseConfig(plugin, this, config); 80 | } else { 81 | abstractConfig = new FlatFileConfig(plugin, this); 82 | } 83 | 84 | new BukkitRunnable() { 85 | @Override 86 | public void run() { 87 | abstractConfig.load(); 88 | } 89 | }.runTaskAsynchronously(plugin); 90 | } 91 | 92 | /** 93 | * This function loads our custom config with comments, and includes changes 94 | */ 95 | private Configuration getCustomConfig(Plugin plugin) { 96 | File file = new File(plugin.getDataFolder(), "config.yml"); 97 | if (!file.exists()) { 98 | plugin.saveDefaultConfig(); 99 | 100 | Configuration newConfig = new Configuration(file); 101 | newConfig.reload(true); 102 | return newConfig; 103 | } else { 104 | Configuration oldConfig = new Configuration(file); 105 | oldConfig.reload(false); 106 | 107 | file.delete(); 108 | plugin.saveDefaultConfig(); 109 | 110 | Configuration newConfig = new Configuration(file); 111 | newConfig.reload(true); 112 | 113 | for (String key : oldConfig.getKeys(false)) { 114 | if (newConfig.contains(key)) { 115 | newConfig.set(key, oldConfig.get(key)); 116 | } 117 | } 118 | 119 | newConfig.save(); 120 | return newConfig; 121 | } 122 | } 123 | 124 | /** 125 | * Cleans up any nametag data on the server to prevent memory leaks 126 | */ 127 | @EventHandler 128 | public void onQuit(PlayerQuitEvent event) { 129 | nametagManager.reset(event.getPlayer().getName()); 130 | } 131 | 132 | /** 133 | * Applies tags to a player 134 | */ 135 | @EventHandler(priority = EventPriority.HIGHEST) 136 | public void onPlayerJoin(PlayerJoinEvent event) { 137 | final Player player = event.getPlayer(); 138 | nametagManager.sendTeams(player); 139 | 140 | new BukkitRunnable() { 141 | @Override 142 | public void run() { 143 | abstractConfig.load(player, true); 144 | } 145 | }.runTaskLaterAsynchronously(plugin, 1); 146 | } 147 | 148 | /** 149 | * Some users may have different permissions per world. 150 | * If this is enabled, their tag will be reloaded on TP. 151 | */ 152 | @EventHandler 153 | public void onTeleport(final PlayerChangedWorldEvent event) { 154 | if (!refreshTagOnWorldChange) return; 155 | 156 | new BukkitRunnable() { 157 | @Override 158 | public void run() { 159 | applyTagToPlayer(event.getPlayer(), false); 160 | } 161 | }.runTaskLater(plugin, 3); 162 | } 163 | 164 | private void handleClear(UUID uuid, String player) { 165 | removePlayerData(uuid); 166 | nametagManager.reset(player); 167 | abstractConfig.clear(uuid, player); 168 | } 169 | 170 | public void clearMemoryData() { 171 | try { 172 | readWriteLock.writeLock().lock(); 173 | groupData.clear(); 174 | playerData.clear(); 175 | } finally { 176 | readWriteLock.writeLock().unlock(); 177 | } 178 | } 179 | 180 | public void removePlayerData(UUID uuid) { 181 | try { 182 | readWriteLock.writeLock().lock(); 183 | playerData.remove(uuid); 184 | } finally { 185 | readWriteLock.writeLock().unlock(); 186 | } 187 | } 188 | 189 | public void storePlayerData(UUID uuid, PlayerData data) { 190 | try { 191 | readWriteLock.writeLock().lock(); 192 | playerData.put(uuid, data); 193 | } finally { 194 | readWriteLock.writeLock().unlock(); 195 | } 196 | } 197 | 198 | public void assignGroupData(List groupData) { 199 | try { 200 | readWriteLock.writeLock().lock(); 201 | this.groupData = groupData; 202 | } finally { 203 | readWriteLock.writeLock().unlock(); 204 | } 205 | } 206 | 207 | public void assignData(List groupData, Map playerData) { 208 | try { 209 | readWriteLock.writeLock().lock(); 210 | this.groupData = groupData; 211 | this.playerData = playerData; 212 | } finally { 213 | readWriteLock.writeLock().unlock(); 214 | } 215 | } 216 | 217 | // ========================================== 218 | // Below are methods used by the API/Commands 219 | // ========================================== 220 | boolean debug() { 221 | return debug; 222 | } 223 | 224 | void toggleDebug() { 225 | debug = !debug; 226 | config.set("Debug", debug); 227 | config.save(); 228 | } 229 | 230 | void toggleLongTags() { 231 | longNametagsEnabled = !longNametagsEnabled; 232 | config.set("Tablist.LongTags", longNametagsEnabled); 233 | config.save(); 234 | } 235 | 236 | // ================================================= 237 | // Below are methods that we have to be careful with 238 | // as they can be called from different threads 239 | // ================================================= 240 | public PlayerData getPlayerData(Player player) { 241 | return player == null ? null : playerData.get(player.getUniqueId()); 242 | } 243 | 244 | void addGroup(GroupData data) { 245 | abstractConfig.add(data); 246 | 247 | try { 248 | readWriteLock.writeLock().lock(); 249 | groupData.add(data); 250 | } finally { 251 | readWriteLock.writeLock().unlock(); 252 | } 253 | } 254 | 255 | void deleteGroup(GroupData data) { 256 | abstractConfig.delete(data); 257 | 258 | try { 259 | readWriteLock.writeLock().lock(); 260 | groupData.remove(data); 261 | } finally { 262 | readWriteLock.writeLock().unlock(); 263 | } 264 | } 265 | 266 | public List getGroupData() { 267 | try { 268 | readWriteLock.writeLock().lock(); 269 | return new ArrayList<>(groupData); // Create a copy instead of unmodifiable 270 | } finally { 271 | readWriteLock.writeLock().unlock(); 272 | } 273 | } 274 | 275 | public GroupData getGroupData(String key) { 276 | for (GroupData groupData : getGroupData()) { 277 | if (groupData.getGroupName().equalsIgnoreCase(key)) { 278 | return groupData; 279 | } 280 | } 281 | 282 | return null; 283 | } 284 | 285 | /** 286 | * Replaces placeholders when a player tag is created. 287 | * Maxim and Clip's plugins are searched for, and input 288 | * is replaced. We use direct imports to avoid any problems! 289 | * (So don't change that) 290 | */ 291 | public String formatWithPlaceholders(Player player, String input, boolean limitChars) { 292 | plugin.debug("Formatting text.."); 293 | if (input == null) return ""; 294 | if (player == null) return input; 295 | 296 | // The string can become null again at this point. Add another check. 297 | if (input != null && Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { 298 | plugin.debug("Trying to use PlaceholderAPI for placeholders"); 299 | input = me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(player, input); 300 | } 301 | 302 | plugin.debug("Applying colors.."); 303 | return Utils.format(input, limitChars); 304 | } 305 | 306 | private BukkitTask createTask(String path, BukkitTask existing, Runnable runnable) { 307 | if (existing != null) { 308 | existing.cancel(); 309 | } 310 | 311 | if (config.getInt(path, -1) <= 0) return null; 312 | return Bukkit.getScheduler().runTaskTimer(plugin, runnable, 0, 20L * config.getInt(path)); 313 | } 314 | 315 | public void reload() { 316 | config.reload(true); 317 | applyConfig(); 318 | nametagManager.reset(); 319 | abstractConfig.reload(); 320 | } 321 | 322 | private void applyConfig() { 323 | this.debug = config.getBoolean("Debug"); 324 | this.tabListEnabled = config.getBoolean("Tablist.Enabled"); 325 | this.longNametagsEnabled = config.getBoolean("Tablist.LongTags"); 326 | this.refreshTagOnWorldChange = config.getBoolean("RefreshTagOnWorldChange"); 327 | DISABLE_PUSH_ALL_TAGS = config.getBoolean("DisablePush"); 328 | 329 | if (config.getBoolean("MetricsEnabled")) { 330 | Metrics m = new Metrics(NametagEdit.getPlugin(NametagEdit.class)); 331 | m.addCustomChart(new Metrics.SimplePie("using_spigot", () -> PlaceholderAPIPlugin.getServerVersion().isSpigot() ? "yes" : "no")); 332 | } 333 | 334 | clearEmptyTeamTask = createTask("ClearEmptyTeamsInterval", clearEmptyTeamTask, () -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "nte teams clear")); 335 | 336 | refreshNametagTask = createTask("RefreshInterval", refreshNametagTask, () -> { 337 | nametagManager.reset(); 338 | applyTags(); 339 | }); 340 | } 341 | 342 | public void applyTags() { 343 | if (!Bukkit.isPrimaryThread()) { 344 | new BukkitRunnable() { 345 | @Override 346 | public void run() { 347 | applyTags(); 348 | } 349 | }.runTask(plugin); 350 | return; 351 | } 352 | 353 | for (Player online : Utils.getOnline()) { 354 | if (online != null) { 355 | applyTagToPlayer(online, false); 356 | } 357 | } 358 | 359 | plugin.debug("Applied tags to all online players."); 360 | } 361 | 362 | public void applyTagToPlayer(final Player player, final boolean loggedIn) { 363 | // If on the primary thread, run async 364 | if (Bukkit.isPrimaryThread()) { 365 | new BukkitRunnable() { 366 | @Override 367 | public void run() { 368 | applyTagToPlayer(player, loggedIn); 369 | } 370 | }.runTaskAsynchronously(plugin); 371 | return; 372 | } 373 | 374 | INametag tempNametag = getPlayerData(player); 375 | if (tempNametag == null) { 376 | for (GroupData group : getGroupData()) { 377 | if (player.hasPermission(group.getBukkitPermission())) { 378 | tempNametag = group; 379 | break; 380 | } 381 | } 382 | } 383 | 384 | if (tempNametag == null) return; 385 | plugin.debug("Applying " + (tempNametag.isPlayerTag() ? "PlayerTag" : "GroupTag") + " to " + player.getName()); 386 | 387 | final INametag nametag = tempNametag; 388 | new BukkitRunnable() { 389 | @Override 390 | public void run() { 391 | nametagManager.setNametag(player.getName(), formatWithPlaceholders(player, nametag.getPrefix(), true), 392 | formatWithPlaceholders(player, nametag.getSuffix(), true), nametag.getSortPriority()); 393 | // If the TabList is disabled... 394 | if (!tabListEnabled) { 395 | // apply the default white username to the player. 396 | player.setPlayerListName(Utils.format("&f" + player.getPlayerListName())); 397 | } else { 398 | if (longNametagsEnabled) { 399 | player.setPlayerListName(formatWithPlaceholders(player, nametag.getPrefix() + player.getName() + nametag.getSuffix(), false)); 400 | } else { 401 | player.setPlayerListName(null); 402 | } 403 | } 404 | 405 | if (loggedIn) { 406 | Bukkit.getPluginManager().callEvent(new NametagFirstLoadedEvent(player, nametag)); 407 | } 408 | } 409 | }.runTask(plugin); 410 | } 411 | 412 | void clear(final CommandSender sender, final String player) { 413 | Player target = Bukkit.getPlayerExact(player); 414 | if (target != null) { 415 | handleClear(target.getUniqueId(), player); 416 | return; 417 | } 418 | 419 | UUIDFetcher.lookupUUID(player, plugin, uuid -> { 420 | if (uuid == null) { 421 | NametagMessages.UUID_LOOKUP_FAILED.send(sender); 422 | } else { 423 | handleClear(uuid, player); 424 | } 425 | }); 426 | } 427 | 428 | void save(CommandSender sender, boolean playerTag, String key, int priority) { 429 | if (playerTag) { 430 | Player player = Bukkit.getPlayerExact(key); 431 | 432 | PlayerData data = getPlayerData(player); 433 | if (data == null) { 434 | abstractConfig.savePriority(true, key, priority); 435 | return; 436 | } 437 | 438 | data.setSortPriority(priority); 439 | abstractConfig.save(data); 440 | } else { 441 | GroupData groupData = getGroupData(key); 442 | 443 | if (groupData == null) { 444 | sender.sendMessage(ChatColor.RED + "Group " + key + " does not exist!"); 445 | return; 446 | } 447 | 448 | groupData.setSortPriority(priority); 449 | abstractConfig.save(groupData); 450 | } 451 | } 452 | 453 | public void save(String targetName, NametagEvent.ChangeType changeType, String value) { 454 | save(null, targetName, changeType, value); 455 | } 456 | 457 | // Reduces checks to have this method (ie not saving data twice) 458 | public void save(String targetName, String prefix, String suffix) { 459 | Player player = Bukkit.getPlayerExact(targetName); 460 | 461 | PlayerData data = getPlayerData(player); 462 | if (data == null) { 463 | data = new PlayerData(targetName, null, "", "", -1); 464 | if (player != null) { 465 | storePlayerData(player.getUniqueId(), data); 466 | } 467 | } 468 | 469 | data.setPrefix(prefix); 470 | data.setSuffix(suffix); 471 | 472 | if (player != null) { 473 | applyTagToPlayer(player, false); 474 | data.setUuid(player.getUniqueId()); 475 | abstractConfig.save(data); 476 | return; 477 | } 478 | 479 | final PlayerData finalData = data; 480 | UUIDFetcher.lookupUUID(targetName, plugin, (uuid) -> { 481 | if (uuid != null) { 482 | storePlayerData(uuid, finalData); 483 | finalData.setUuid(uuid); 484 | abstractConfig.save(finalData); 485 | } 486 | }); 487 | } 488 | 489 | void save(final CommandSender sender, String targetName, NametagEvent.ChangeType changeType, String value) { 490 | Player player = Bukkit.getPlayerExact(targetName); 491 | 492 | PlayerData data = getPlayerData(player); 493 | if (data == null) { 494 | data = new PlayerData(targetName, null, "", "", -1); 495 | if (player != null) { 496 | storePlayerData(player.getUniqueId(), data); 497 | } 498 | } 499 | 500 | if (changeType == NametagEvent.ChangeType.PREFIX) { 501 | data.setPrefix(value); 502 | } else { 503 | data.setSuffix(value); 504 | } 505 | 506 | if (player != null) { 507 | applyTagToPlayer(player, false); 508 | data.setUuid(player.getUniqueId()); 509 | abstractConfig.save(data); 510 | return; 511 | } 512 | 513 | final PlayerData finalData = data; 514 | UUIDFetcher.lookupUUID(targetName, plugin, (uuid) -> { 515 | if (uuid == null && sender != null) { // null is passed in api 516 | NametagMessages.UUID_LOOKUP_FAILED.send(sender); 517 | } 518 | else { 519 | storePlayerData(uuid, finalData); 520 | finalData.setUuid(uuid); 521 | abstractConfig.save(finalData); 522 | } 523 | }); 524 | } 525 | 526 | 527 | } -------------------------------------------------------------------------------- /src/main/java/com/nametagedit/plugin/NametagCommand.java: -------------------------------------------------------------------------------- 1 | package com.nametagedit.plugin; 2 | 3 | import com.nametagedit.plugin.api.data.GroupData; 4 | import com.nametagedit.plugin.api.events.NametagEvent; 5 | import com.nametagedit.plugin.converter.Converter; 6 | import com.nametagedit.plugin.converter.ConverterTask; 7 | import com.nametagedit.plugin.utils.Utils; 8 | import lombok.AllArgsConstructor; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.command.Command; 11 | import org.bukkit.command.CommandExecutor; 12 | import org.bukkit.command.CommandSender; 13 | import org.bukkit.command.TabExecutor; 14 | import org.bukkit.entity.Player; 15 | import org.bukkit.permissions.Permission; 16 | import org.bukkit.permissions.PermissionDefault; 17 | import org.bukkit.scoreboard.Team; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | @AllArgsConstructor 24 | public class NametagCommand implements CommandExecutor, TabExecutor { 25 | 26 | private final NametagHandler handler; 27 | 28 | private List getSuggestions(String argument, String... array) { 29 | argument = argument.toLowerCase(); 30 | List suggestions = new ArrayList<>(); 31 | for (String suggestion : array) { 32 | if (suggestion.toLowerCase().startsWith(argument)) { 33 | suggestions.add(suggestion); 34 | } 35 | } 36 | return suggestions; 37 | } 38 | 39 | /** 40 | * Handles auto completions 41 | */ 42 | @Override 43 | public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { 44 | if (args.length == 1) { 45 | return getSuggestions(args[0], "debug", "reload", "convert", "player", "group"); 46 | } else if (args.length == 2 || args.length == 3) { 47 | if (args[0].equalsIgnoreCase("player")) { 48 | if (args.length == 2) { 49 | List suggestions = new ArrayList<>(); 50 | for (Player player : Bukkit.getOnlinePlayers()) { 51 | if (player.getName().toLowerCase().startsWith(args[1].toLowerCase())) { 52 | suggestions.add(player.getName()); 53 | } 54 | } 55 | return suggestions; 56 | } else { 57 | return getSuggestions(args[2], "clear", "prefix", "suffix", "priority"); 58 | } 59 | } else if (args[0].equalsIgnoreCase("group")) { 60 | if (args.length == 2) { 61 | List data = new ArrayList<>(handler.getGroupData().size() + 4); 62 | data.add("list"); 63 | data.add("add"); 64 | data.add("remove"); 65 | data.add("order"); 66 | for (GroupData groupData : handler.getGroupData()) { 67 | data.add(groupData.getGroupName()); 68 | } 69 | 70 | return getSuggestions(args[1], data.toArray(new String[0])); 71 | } else { 72 | return getSuggestions(args[2], "clear", "prefix", "suffix", "permission", "priority"); 73 | } 74 | } 75 | } 76 | 77 | return new ArrayList<>(); 78 | } 79 | 80 | /** 81 | * Base command for NametagEdit. See the Wiki for usage and examples. 82 | */ 83 | @Override 84 | public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { 85 | if (isNotPermissed(sender, "nametagedit.use")) return false; 86 | if (args.length < 1) { 87 | sendUsage(sender); 88 | } else { 89 | switch (args[0].toLowerCase()) { 90 | case "reload": 91 | cmdReload(sender); 92 | break; 93 | case "convert": 94 | cmdConvert(sender, args); 95 | break; 96 | case "debug": 97 | handler.toggleDebug(); 98 | NametagMessages.DEBUG_TOGGLED.send(sender, handler.debug() ? "&aENABLED" : "&cDISABLED"); 99 | break; 100 | case "player": 101 | cmdPlayer(sender, args); 102 | break; 103 | case "group": 104 | cmdGroups(sender, args); 105 | break; 106 | case "longtags": 107 | handler.toggleLongTags(); 108 | NametagMessages.LONG_TAGS.send(sender, handler.isLongNametagsEnabled() ? "&aENABLED" : "&cDISABLED"); 109 | break; 110 | case "teams": 111 | int emptyTeams = 0; 112 | boolean unregister = args.length > 1 && args[1].equalsIgnoreCase("clear"); 113 | for (Team team : Bukkit.getScoreboardManager().getMainScoreboard().getTeams()) { 114 | if (team.getEntries().isEmpty()) { 115 | if (unregister) { 116 | team.unregister(); 117 | } 118 | emptyTeams++; 119 | } 120 | } 121 | 122 | NametagMessages.CLEARED_TEAMS.send(sender, emptyTeams, unregister); 123 | break; 124 | case "priority": 125 | cmdPriority(sender, args); 126 | break; 127 | default: 128 | sendUsage(sender); 129 | break; 130 | } 131 | } 132 | 133 | return false; 134 | } 135 | 136 | private boolean isPermissed(CommandSender sender, String permission) { 137 | return !isNotPermissed(sender, permission); 138 | } 139 | 140 | private boolean isNotPermissed(CommandSender sender, String permission) { 141 | if (!sender.hasPermission(permission)) { 142 | NametagMessages.NO_PERMISSION.send(sender); 143 | return true; 144 | } 145 | 146 | return false; 147 | } 148 | 149 | private void sendUsagePlayer(CommandSender sender) { 150 | sender.sendMessage(Utils.format("\n&8» &a&lNametagEdit Player Help &8«")); 151 | sender.sendMessage(Utils.format("\n\n&8Type a command to get started:")); 152 | sender.sendMessage(Utils.format("&8» &a/nte player clear")); 153 | sender.sendMessage(Utils.format("&8» &a/nte player prefix ")); 154 | sender.sendMessage(Utils.format("&8» &a/nte player suffix ")); 155 | sender.sendMessage(Utils.format("&8» &a/nte player priority <#>")); 156 | } 157 | 158 | private void sendUsageGroup(CommandSender sender) { 159 | sender.sendMessage(Utils.format("\n&8» &a&lNametagEdit Player Help &8«")); 160 | sender.sendMessage(Utils.format("\n\n&8Type a command to get started:")); 161 | sender.sendMessage(Utils.format("&8» &a/nte group list")); 162 | sender.sendMessage(Utils.format("&8» &a/nte group add ")); 163 | sender.sendMessage(Utils.format("&8» &a/nte group remove ")); 164 | sender.sendMessage(Utils.format("&8» &a/nte group order ")); 165 | sender.sendMessage(Utils.format("&8» &a/nte group clear ")); 166 | sender.sendMessage(Utils.format("&8» &a/nte group prefix ")); 167 | sender.sendMessage(Utils.format("&8» &a/nte group suffix ")); 168 | sender.sendMessage(Utils.format("&8» &a/nte group permission ")); 169 | sender.sendMessage(Utils.format("&8» &a/nte group priority <#>")); 170 | } 171 | 172 | private void sendUsage(CommandSender sender) { 173 | sender.sendMessage(Utils.format("\n&8» &a&lNametagEdit Plugin Help &8«")); 174 | sender.sendMessage(Utils.format(" by Cory and sgtcaze")); 175 | sender.sendMessage(Utils.format("\n\n&8Type a command to get started:")); 176 | sender.sendMessage(Utils.format("&8» &a/nte debug")); 177 | sender.sendMessage(Utils.format("&8» &a/nte reload")); 178 | sender.sendMessage(Utils.format("&8» &a/nte convert")); 179 | sender.sendMessage(Utils.format("&8» &a/nte player")); 180 | sender.sendMessage(Utils.format("&8» &a/nte group")); 181 | sender.sendMessage(Utils.format("&8» &a/nte priority")); 182 | sender.sendMessage(Utils.format("&8» &a/nte longtags")); 183 | } 184 | 185 | /** 186 | * Handles /nte reload 187 | */ 188 | private void cmdReload(CommandSender sender) { 189 | if (isPermissed(sender, "nametagedit.reload")) { 190 | handler.reload(); 191 | NametagMessages.RELOADED_DATA.send(sender); 192 | } 193 | } 194 | 195 | /** 196 | * Handles /nte convert 197 | */ 198 | private void cmdConvert(CommandSender sender, String[] args) { 199 | if (isNotPermissed(sender, "nametagedit.convert")) return; 200 | if (args.length != 4) { 201 | NametagMessages.USAGE_CONVERT.send(sender); 202 | } else { 203 | boolean sourceIsFile = args[1].equalsIgnoreCase("file"); 204 | boolean destinationIsSQL = args[2].equalsIgnoreCase("db"); 205 | boolean legacy = args[3].equalsIgnoreCase("true"); 206 | NametagMessages.CONVERSION.send(sender, "groups & players", sourceIsFile ? "file" : "mysql", destinationIsSQL ? "mysql" : "file", legacy); 207 | 208 | if (sourceIsFile && !destinationIsSQL && legacy) { 209 | new Converter().legacyConversion(sender, handler.getPlugin()); 210 | } else if ((destinationIsSQL && sourceIsFile) || (!sourceIsFile && !destinationIsSQL)) { 211 | new ConverterTask(!destinationIsSQL, sender, handler.getPlugin()).runTaskAsynchronously(handler.getPlugin()); 212 | } 213 | } 214 | } 215 | 216 | /** 217 | * Handles /nte priority 218 | */ 219 | private void cmdPriority(CommandSender sender, String[] args) { 220 | if (isNotPermissed(sender, "nametagedit.priority")) return; 221 | // if (args.length == 0) { 222 | // sender.sendMessage(Utils.format("&a&lNametagEdit &7Sort Priority")); 223 | // sender.sendMessage(Utils.format("&7This feature allows you to position Nametags in tab.")); 224 | // sender.sendMessage(Utils.format("&a/nte priority view &7view advanced info")); 225 | // } 226 | // List copyOfGroups = new ArrayList<>(groupData); 227 | // Collections.sort(copyOfGroups, new Comparator() { 228 | // @Override 229 | // public int compare(GroupData group1, GroupData group2) { 230 | // return group1.getSortPriority() - group2.getSortPriority(); 231 | // } 232 | // }); 233 | // 234 | // int adjustedSortPriority = 1; 235 | // 236 | // for (GroupData groupData : copyOfGroups) { 237 | // groupData.setSortPriority(groupData.getSortPriority() < 1 ? -1 : adjustedSortPriority++); 238 | // } 239 | // 240 | // abstractConfig.save(groupData.toArray(new GroupData[groupData.size()])); 241 | } 242 | 243 | /** 244 | * Handles /nte player 245 | */ 246 | private void cmdPlayer(CommandSender sender, String[] args) { 247 | if (args.length == 3) { 248 | if (!args[2].equalsIgnoreCase("clear")) { 249 | sendUsagePlayer(sender); 250 | return; 251 | } 252 | 253 | if (isNotPermissed(sender, "nametagedit.clear.self")) return; 254 | 255 | String targetName = args[1]; 256 | 257 | if (!sender.hasPermission("nametagedit.clear.others") && !targetName.equalsIgnoreCase(sender.getName())) { 258 | NametagMessages.MODIFY_OWN_TAG.send(sender); 259 | return; 260 | } 261 | 262 | handler.clear(sender, targetName); 263 | handler.applyTagToPlayer(Bukkit.getPlayerExact(targetName), false); 264 | } else if (args.length >= 4) { 265 | switch (args[2].toLowerCase()) { 266 | case "prefix": 267 | case "suffix": 268 | if (isNotPermissed(sender, "nametagedit.edit.self")) return; 269 | 270 | String targetName = args[1]; 271 | 272 | if (!sender.hasPermission("nametagedit.edit.others") && !targetName.equalsIgnoreCase(sender.getName())) { 273 | NametagMessages.MODIFY_OWN_TAG.send(sender); 274 | return; 275 | } 276 | 277 | NametagEvent.ChangeType changeType = args[2].equalsIgnoreCase("prefix") ? NametagEvent.ChangeType.PREFIX : NametagEvent.ChangeType.SUFFIX; 278 | handler.save(sender, targetName, changeType, Utils.format(args, 3, args.length)); 279 | break; 280 | case "priority": 281 | if (isNotPermissed(sender, "nametagedit.edit.self")) return; 282 | 283 | String priorityName = args[1]; 284 | 285 | if (!sender.hasPermission("nametagedit.edit.others") && !priorityName.equalsIgnoreCase(sender.getName())) { 286 | NametagMessages.MODIFY_OWN_TAG.send(sender); 287 | break; 288 | } 289 | 290 | setupPriority(sender, true, priorityName, args[3]); 291 | break; 292 | default: 293 | sendUsagePlayer(sender); 294 | } 295 | } else { 296 | sendUsagePlayer(sender); 297 | } 298 | } 299 | 300 | /** 301 | * Modifies groups 302 | */ 303 | private void cmdGroups(CommandSender sender, String[] args) { 304 | if (isNotPermissed(sender, "nametagedit.groups")) return; 305 | if (args.length < 2) { 306 | sendUsageGroup(sender); 307 | } else { 308 | if (args[1].equalsIgnoreCase("list")) { 309 | sender.sendMessage(Utils.format("&f&lLoaded Groups")); 310 | for (GroupData groupData : handler.getGroupData()) { 311 | sender.sendMessage(Utils.format("&6Group: &f" + groupData.getGroupName() + " &6Permission: &f" + groupData.getPermission() 312 | + " &6Formatted: " + groupData.getPrefix() + sender.getName() + groupData.getSuffix())); 313 | } 314 | } else if (args[1].equalsIgnoreCase("order")) { 315 | if (args.length <= 2) { 316 | sendUsageGroup(sender); 317 | return; 318 | } 319 | 320 | List order = new ArrayList<>(Arrays.asList(args).subList(2, args.length)); 321 | handler.getAbstractConfig().orderGroups(sender, order); 322 | 323 | String formatted = Arrays.toString(order.toArray()); 324 | formatted = formatted.substring(1, formatted.length() - 1).replace(",", ""); 325 | sender.sendMessage(Utils.format("&c&lNametagEdit Group Order:")); 326 | sender.sendMessage(formatted); 327 | sender.sendMessage(Utils.format("&cType /ne reload for these changes to take effect")); 328 | } else if (args[1].equalsIgnoreCase("remove")) { 329 | if (args.length == 3) { 330 | String group = args[2]; 331 | 332 | GroupData toDelete = null; 333 | for (GroupData groupData : handler.getGroupData()) { 334 | if (groupData.getGroupName().equalsIgnoreCase(group)) { 335 | toDelete = groupData; 336 | break; 337 | 338 | } 339 | } 340 | 341 | if (toDelete != null) { 342 | handler.deleteGroup(toDelete); 343 | NametagMessages.GROUP_REMOVED.send(sender, group); 344 | } 345 | } 346 | } else if (args[1].equalsIgnoreCase("add")) { 347 | if (args.length == 3) { 348 | String group = args[2]; 349 | 350 | for (GroupData groupData : handler.getGroupData()) { 351 | if (groupData.getGroupName().equalsIgnoreCase(group)) { 352 | NametagMessages.GROUP_EXISTS.send(sender, group); 353 | return; 354 | } 355 | } 356 | 357 | handler.addGroup(new GroupData(group, "", "", "", new Permission("my.perm", PermissionDefault.FALSE), -1)); 358 | NametagMessages.CREATED_GROUP.send(sender, group); 359 | } 360 | } else { 361 | if (args.length >= 4) { 362 | String group = args[1]; 363 | GroupData groupData = null; 364 | 365 | for (GroupData groups : handler.getGroupData()) { 366 | if (groups.getGroupName().equalsIgnoreCase(group)) { 367 | groupData = groups; 368 | break; 369 | } 370 | } 371 | 372 | if (groupData == null) { 373 | NametagMessages.GROUP_EXISTS_NOT.send(sender, group); 374 | return; 375 | } 376 | 377 | if (args[2].equalsIgnoreCase("permission")) { 378 | groupData.setPermission(args[3]); 379 | handler.getAbstractConfig().save(groupData); 380 | NametagMessages.GROUP_VALUE.send(sender, group, "permission", args[3]); 381 | } else if (args[2].equalsIgnoreCase("prefix")) { 382 | String value = Utils.format(args, 3, args.length).replace("\"", ""); 383 | groupData.setPrefix(Utils.format(value)); 384 | handler.applyTags(); 385 | handler.getAbstractConfig().save(groupData); 386 | NametagMessages.GROUP_VALUE.send(sender, group, "prefix", Utils.format(value)); 387 | } else if (args[2].equalsIgnoreCase("suffix")) { 388 | String value = Utils.format(args, 3, args.length).replace("\"", ""); 389 | groupData.setSuffix(Utils.format(value)); 390 | handler.applyTags(); 391 | handler.getAbstractConfig().save(groupData); 392 | NametagMessages.GROUP_VALUE.send(sender, group, "suffix", Utils.format(value)); 393 | } else if (args[2].equalsIgnoreCase("clear")) { 394 | boolean prefix = args[3].equalsIgnoreCase("prefix"); 395 | if (prefix) { 396 | groupData.setPrefix("&f"); 397 | } else { 398 | groupData.setSuffix("&f"); 399 | } 400 | handler.applyTags(); 401 | handler.getAbstractConfig().save(groupData); 402 | NametagMessages.GROUP_VALUE_CLEARED.send(sender, prefix ? "prefix" : "suffix", group); 403 | } else if (args[2].equalsIgnoreCase("priority")) { 404 | setupPriority(sender, false, group, args[3]); 405 | } 406 | } else { 407 | sendUsageGroup(sender); 408 | } 409 | } 410 | } 411 | } 412 | 413 | private void setupPriority(CommandSender sender, boolean player, String key, String number) { 414 | int priority; 415 | 416 | try { 417 | priority = Integer.parseInt(number); 418 | } catch (NumberFormatException e) { 419 | NametagMessages.NOT_A_NUMBER.send(sender, number); 420 | return; 421 | } 422 | 423 | handler.save(sender, player, key, priority); 424 | NametagMessages.SET_PRIORITY.send(sender, priority, key); 425 | } 426 | 427 | } --------------------------------------------------------------------------------