diff --git a/src/main/java/net/momirealms/customnameplates/AdventureManager.java b/src/main/java/net/momirealms/customnameplates/AdventureManager.java
new file mode 100644
index 0000000..06d6390
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/AdventureManager.java
@@ -0,0 +1,24 @@
+package net.momirealms.customnameplates;
+
+import net.kyori.adventure.audience.Audience;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+public class AdventureManager {
+ //发送控制台消息
+ public static void consoleMessage(String s) {
+ Audience au = CustomNameplates.adventure.sender(Bukkit.getConsoleSender());
+ MiniMessage mm = MiniMessage.miniMessage();
+ Component parsed = mm.deserialize(s);
+ au.sendMessage(parsed);
+ }
+ //发送玩家消息
+ public static void playerMessage(Player player, String s){
+ Audience au = CustomNameplates.adventure.player(player);
+ MiniMessage mm = MiniMessage.miniMessage();
+ Component parsed = mm.deserialize(s);
+ au.sendMessage(parsed);
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/ConfigManager.java b/src/main/java/net/momirealms/customnameplates/ConfigManager.java
new file mode 100644
index 0000000..ea8c7ae
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/ConfigManager.java
@@ -0,0 +1,157 @@
+package net.momirealms.customnameplates;
+
+import net.kyori.adventure.key.Key;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+
+public class ConfigManager {
+
+ //根据文件名获取配置文件
+ public static YamlConfiguration getConfig(String configName) {
+ File file = new File(CustomNameplates.instance.getDataFolder(), configName);
+ //文件不存在则生成默认配置
+ if (!file.exists()) {
+ CustomNameplates.instance.saveResource(configName, false);
+ }
+ return YamlConfiguration.loadConfiguration(file);
+ }
+ //主配置文件
+ public static class MainConfig{
+
+ public static String namespace;
+ public static String fontName;
+ public static String start_char;
+ public static String folder_path;
+ public static String font;
+ public static String default_nameplate;
+ public static String player_prefix;
+ public static String player_suffix;
+ public static Key key;
+ public static boolean itemsAdder;
+ public static boolean placeholderAPI;
+ public static boolean show_after;
+ public static String lang;
+ public static Long preview;
+
+ public static void ReloadConfig(){
+ CustomNameplates.instance.saveDefaultConfig();
+ CustomNameplates.instance.reloadConfig();
+ FileConfiguration config = CustomNameplates.instance.getConfig();
+
+ lang = config.getString("config.lang");
+ namespace = config.getString("config.namespace");
+ font = config.getString("config.font");
+ fontName = namespace + ":" + font;
+ start_char = config.getString("config.start-char");
+ folder_path = config.getString("config.folder-path");
+ default_nameplate = config.getString("config.default-nameplate");
+ player_prefix = config.getString("config.prefix");
+ player_suffix = config.getString("config.suffix");
+ itemsAdder =config.getBoolean("config.integrations.ItemsAdder");
+ placeholderAPI = config.getBoolean("config.integrations.PlaceholderAPI");
+ show_after = config.getBoolean("config.show-after-load-resourcepack");
+ key = Key.key(fontName);
+ preview = config.getLong("config.preview-duration");
+ }
+ }
+ //消息文件
+ public static class Message{
+
+ public static String noPerm;
+ public static String prefix;
+ public static String lackArgs;
+ public static String reload;
+ public static String equip;
+ public static String unequip;
+ public static String force_equip;
+ public static String force_unequip;
+ public static String not_exist;
+ public static String not_online;
+ public static String no_console;
+ public static String notAvailable;
+ public static String available;
+ public static String cooldown;
+ public static String preview;
+
+ public static void ReloadConfig(){
+ YamlConfiguration messagesConfig = getConfig("messages/messages_" + MainConfig.lang +".yml");
+ noPerm = messagesConfig.getString("messages.no-perm");
+ prefix = messagesConfig.getString("messages.prefix");
+ lackArgs = messagesConfig.getString("messages.lack-args");
+ reload = messagesConfig.getString("messages.reload");
+ equip = messagesConfig.getString("messages.equip");
+ unequip = messagesConfig.getString("messages.unequip");
+ force_equip = messagesConfig.getString("messages.force-equip");
+ force_unequip = messagesConfig.getString("messages.force-unequip");
+ not_exist = messagesConfig.getString("messages.not-exist");
+ not_online = messagesConfig.getString("messages.not-online");
+ no_console = messagesConfig.getString("messages.no-console");
+ notAvailable = messagesConfig.getString("messages.not-available");
+ available = messagesConfig.getString("messages.available");
+ cooldown = messagesConfig.getString("messages.cooldown");
+ preview = messagesConfig.getString("messages.preview");
+ }
+ }
+ //数据库配置
+ public static class DatabaseConfig{
+
+ public static String user;
+ public static String password;
+ public static String url;
+ public static String ENCODING;
+ public static String tableName;
+ public static boolean enable_pool;
+ public static boolean use_mysql;
+ public static int maximum_pool_size;
+ public static int minimum_idle;
+ public static int maximum_lifetime;
+ public static int idle_timeout;
+
+ public static void LoadConfig(){
+ YamlConfiguration databaseConfig = getConfig("database.yml");
+ String storage_mode = databaseConfig.getString("settings.storage-mode");
+
+ //使用SQLite
+ if(storage_mode.equals("SQLite")){
+ enable_pool = false;
+ use_mysql = false;
+ tableName = "nameplates";
+ }
+ //使用MYSQL
+ else if(storage_mode.equals("MYSQL")){
+
+ use_mysql = true;
+ ENCODING = databaseConfig.getString("MySQL.property.encoding");
+ tableName = databaseConfig.getString("MySQL.table-name");
+ user = databaseConfig.getString("MySQL.user");
+ password = databaseConfig.getString("MySQL.password");
+
+ url = "jdbc:mysql://" + databaseConfig.getString("MySQL.host")
+ + ":" + databaseConfig.getString("MySQL.port") + "/"
+ + databaseConfig.getString("MySQL.database") + "?characterEncoding="
+ + ENCODING + "&useSSL="
+ + databaseConfig.getString("MySQL.property.use-ssl");
+ if (databaseConfig.getString("MySQL.property.timezone") != null &&
+ !databaseConfig.getString("MySQL.property.timezone").equals("")) {
+ url = url + "&serverTimezone=" + databaseConfig.getString("MySQL.property.timezone");
+ }
+ if (databaseConfig.getBoolean("MySQL.property.allowPublicKeyRetrieval")) {
+ url = url + "&allowPublicKeyRetrieval=true";
+ }
+ enable_pool = databaseConfig.getBoolean("settings.use-pool");
+ if(enable_pool){
+ maximum_pool_size = databaseConfig.getInt("Pool-Settings.maximum-pool-size");
+ minimum_idle = databaseConfig.getInt("Pool-Settings.minimum-idle");
+ maximum_lifetime = databaseConfig.getInt("Pool-Settings.maximum-lifetime");
+ idle_timeout = databaseConfig.getInt("Pool-Settings.idle-timeout");
+ }
+ }else {
+ AdventureManager.consoleMessage("这种存储方式不存在");
+ Bukkit.getPluginManager().disablePlugin(CustomNameplates.instance);
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/CustomNameplates.java b/src/main/java/net/momirealms/customnameplates/CustomNameplates.java
new file mode 100644
index 0000000..206ad8d
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/CustomNameplates.java
@@ -0,0 +1,80 @@
+package net.momirealms.customnameplates;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import net.kyori.adventure.platform.bukkit.BukkitAudiences;
+import net.momirealms.customnameplates.commands.Execute;
+import net.momirealms.customnameplates.commands.TabComplete;
+import net.momirealms.customnameplates.data.DataManager;
+import net.momirealms.customnameplates.data.SqlHandler;
+import net.momirealms.customnameplates.listener.PlayerListener;
+import net.momirealms.customnameplates.listener.PacketsListener;
+import net.momirealms.customnameplates.resource.ResourceManager;
+import net.momirealms.customnameplates.scoreboard.ScoreBoardManager;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.Objects;
+
+public final class CustomNameplates extends JavaPlugin {
+
+ public static JavaPlugin instance;
+ public static BukkitAudiences adventure;
+
+ private ResourceManager resourceManager;
+ private DataManager dataManager;
+ private HookManager hookManager;
+ private ScoreBoardManager scoreBoardManager;
+
+ public ResourceManager getResourceManager() {
+ return this.resourceManager;
+ }
+ public DataManager getDataManager() { return this.dataManager; }
+ public HookManager getHookManager() { return this.hookManager; }
+ public ScoreBoardManager getScoreBoardManager() { return this.scoreBoardManager; }
+
+ @Override
+ public void onEnable() {
+ instance = this;
+ adventure = BukkitAudiences.create(this);
+ //重载插件
+ ConfigManager.MainConfig.ReloadConfig();
+ ConfigManager.Message.ReloadConfig();
+ ConfigManager.DatabaseConfig.LoadConfig();
+ //指令注册
+ Objects.requireNonNull(Bukkit.getPluginCommand("customnameplates")).setExecutor(new Execute(this));
+ Objects.requireNonNull(Bukkit.getPluginCommand("customnameplates")).setTabCompleter(new TabComplete(this));
+ //事件注册
+ Bukkit.getPluginManager().registerEvents(new PlayerListener(this),this);
+ ProtocolLibrary.getProtocolManager().addPacketListener(new PacketsListener(this));
+ //新建单例
+ this.resourceManager = new ResourceManager(this);
+ this.dataManager = new DataManager();
+ this.hookManager = new HookManager(this);
+ this.scoreBoardManager = new ScoreBoardManager(this);
+ //生成资源包
+ resourceManager.generateResourcePack();
+ if (!DataManager.create()) {
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to enable Data Manager! Disabling plugin...");
+ instance.getPluginLoader().disablePlugin(instance);
+ return;
+ }
+ //启动完成
+ AdventureManager.consoleMessage("[CustomNameplates] Plugin has been enabled! Author: XiaoMoMi");
+ }
+
+ @Override
+ public void onDisable() {
+ SqlHandler.saveAll();
+ SqlHandler.close();
+ //清除缓存实体
+ Execute.pCache.forEach(Entity::remove);
+ //卸载完成
+ AdventureManager.consoleMessage("[CustomNameplates] Plugin has been disabled! Author: XiaoMoMi");
+ //关闭adventure
+ if(adventure != null) {
+ adventure.close();
+ adventure = null;
+ }
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/HookManager.java b/src/main/java/net/momirealms/customnameplates/HookManager.java
new file mode 100644
index 0000000..56a89c6
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/HookManager.java
@@ -0,0 +1,54 @@
+package net.momirealms.customnameplates;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+public class HookManager {
+
+ private boolean placeholderAPI;
+ private boolean itemsAdder;
+
+ public boolean hasPlaceholderAPI() {
+ return this.placeholderAPI;
+ }
+ public boolean hasItemsAdder() {
+ return this.itemsAdder;
+ }
+
+ public HookManager(CustomNameplates plugin) {
+ this.initializePlaceholderAPI();
+ this.initializeItemsAdder();
+ }
+
+ //Papi Hook检测
+ private void initializePlaceholderAPI() {
+ if(!ConfigManager.MainConfig.placeholderAPI){
+ this.placeholderAPI = false;
+ return;
+ }
+ if(CustomNameplates.instance.getServer().getPluginManager().getPlugin("PlaceholderAPI") != null){
+ this.placeholderAPI = true;
+ AdventureManager.consoleMessage("[CustomNameplates] " + "PlaceholderAPI Hooked!");
+ }
+ }
+ //ItemsAdder Hook检测
+ private void initializeItemsAdder() {
+ if (!ConfigManager.MainConfig.itemsAdder) {
+ this.itemsAdder = false;
+ }
+ if(CustomNameplates.instance.getServer().getPluginManager().getPlugin("ItemsAdder") != null){
+ this.itemsAdder = true;
+ AdventureManager.consoleMessage("[CustomNameplates] " + "ItemsAdder Hooked!");
+ }
+ }
+ /*
+ 解析prefix与suffix
+ */
+ public String parsePlaceholders(Player player, String papi) {
+ String s = StringUtils.replace(StringUtils.replace(papi, "%player_name%", player.getName()), "%player_displayname%", player.getDisplayName());
+ s = PlaceholderAPI.setPlaceholders(player, s);
+ return ChatColor.translateAlternateColorCodes('&', s);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/momirealms/customnameplates/commands/Execute.java b/src/main/java/net/momirealms/customnameplates/commands/Execute.java
new file mode 100644
index 0000000..cd98fc6
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/commands/Execute.java
@@ -0,0 +1,294 @@
+package net.momirealms.customnameplates.commands;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.wrappers.WrappedChatComponent;
+import com.comphenix.protocol.wrappers.WrappedDataWatcher;
+import net.kyori.adventure.key.Key;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.momirealms.customnameplates.ConfigManager;
+import net.momirealms.customnameplates.AdventureManager;
+import net.momirealms.customnameplates.CustomNameplates;
+import net.momirealms.customnameplates.data.DataManager;
+import net.momirealms.customnameplates.scoreboard.NameplatesTeam;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.ArmorStand;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+import org.bukkit.scheduler.BukkitScheduler;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+
+public class Execute implements CommandExecutor {
+
+ private final CustomNameplates plugin;
+ private final HashMap coolDown;
+ {
+ coolDown = new HashMap<>();
+ }
+ public static List pCache;
+ {
+ pCache = new ArrayList<>();
+ }
+
+ public Execute(CustomNameplates plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ @ParametersAreNonnullByDefault
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ //参数不足
+ if (args.length < 1){
+ if (sender instanceof Player){
+ AdventureManager.playerMessage((Player) sender,ConfigManager.Message.prefix + ConfigManager.Message.lackArgs);
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.lackArgs);
+ }
+ return true;
+ }
+ switch (args[0]) {
+ case "reload" -> {
+ if (sender.hasPermission("customnameplates.reload") || sender.isOp()) {
+ ConfigManager.MainConfig.ReloadConfig();
+ ConfigManager.Message.ReloadConfig();
+ if (sender instanceof Player) {
+ AdventureManager.playerMessage((Player) sender, ConfigManager.Message.prefix + ConfigManager.Message.reload);
+ } else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.reload);
+ }
+ } else {
+ AdventureManager.playerMessage((Player) sender, ConfigManager.Message.prefix + ConfigManager.Message.noPerm);
+ }
+ return true;
+ }
+ case "equip" -> {
+ if (sender instanceof Player player) {
+ if (args.length < 2) {
+ AdventureManager.playerMessage((Player) sender, ConfigManager.Message.prefix + ConfigManager.Message.lackArgs);
+ return true;
+ }
+ if (sender.hasPermission("customnameplates.equip." + args[1]) || sender.isOp()) {
+ if (plugin.getResourceManager().getNameplateInfo(args[1]) == null) {
+ AdventureManager.playerMessage((Player) sender, ConfigManager.Message.prefix + ConfigManager.Message.not_exist);
+ return true;
+ }
+ DataManager.cache.get(player.getUniqueId()).equipNameplate(args[1]);
+ this.plugin.getScoreBoardManager().getTeam(player.getName()).updateNameplates();
+ this.plugin.getDataManager().savePlayer(player.getUniqueId());
+ AdventureManager.playerMessage((Player) sender, ConfigManager.Message.prefix + ConfigManager.Message.equip.replace("{Nameplate}", plugin.getResourceManager().getNameplateInfo(args[1]).getConfig().getName()));
+ } else {
+ AdventureManager.playerMessage((Player) sender, ConfigManager.Message.prefix + ConfigManager.Message.notAvailable);
+ }
+ } else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.no_console);
+ }
+ return true;
+ }
+ case "forceequip" -> {
+ if (args.length < 3){
+ if(sender instanceof Player){
+ AdventureManager.playerMessage((Player) sender,ConfigManager.Message.prefix + ConfigManager.Message.lackArgs);
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.lackArgs);
+ }
+ return true;
+ }
+ if (sender.hasPermission("customnameplates.forceequip") || sender.isOp()){
+ if (Bukkit.getPlayer(args[1]) != null){
+ Player player = Bukkit.getPlayer(args[1]);
+ //铭牌是否存在
+ if (plugin.getResourceManager().getNameplateInfo(args[2]) == null){
+ if(sender instanceof Player){
+ AdventureManager.playerMessage((Player) sender,ConfigManager.Message.prefix + ConfigManager.Message.not_exist);
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.not_exist);
+ }
+ return true;
+ }
+ DataManager.cache.get(player.getUniqueId()).equipNameplate(args[2]);
+ this.plugin.getScoreBoardManager().getTeam(args[1]).updateNameplates();
+ this.plugin.getDataManager().savePlayer(player.getUniqueId());
+ if (sender instanceof Player){
+ AdventureManager.playerMessage((Player) sender, ConfigManager.Message.prefix + ConfigManager.Message.force_equip.replace("{Nameplate}", plugin.getResourceManager().getNameplateInfo(args[2]).getConfig().getName()).replace("{Player}", args[1]));
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.force_equip.replace("{Nameplate}", plugin.getResourceManager().getNameplateInfo(args[2]).getConfig().getName()).replace("{Player}", args[1]));
+ }
+ }else {
+ //玩家不存在,不在线
+ if(sender instanceof Player){
+ AdventureManager.playerMessage((Player) sender,ConfigManager.Message.prefix + ConfigManager.Message.not_online.replace("{Player}",args[1]));
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.not_online.replace("{Player}",args[1]));
+ }
+ }
+ }else {
+ AdventureManager.playerMessage((Player) sender,ConfigManager.Message.prefix + ConfigManager.Message.noPerm);
+ }
+ return true;
+ }
+ case "unequip" -> {
+ if (sender instanceof Player player){
+ DataManager.cache.get(player.getUniqueId()).equipNameplate("none");
+ this.plugin.getScoreBoardManager().getTeam(player.getName()).updateNameplates();
+ this.plugin.getDataManager().savePlayer(player.getUniqueId());
+ AdventureManager.playerMessage(player, ConfigManager.Message.prefix + ConfigManager.Message.unequip);
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.no_console);
+ }
+ return true;
+ }
+ case "forceunequip" -> {
+ if (args.length < 2){
+ if(sender instanceof Player){
+ AdventureManager.playerMessage((Player) sender,ConfigManager.Message.prefix + ConfigManager.Message.lackArgs);
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.lackArgs);
+ }
+ return true;
+ }
+ if (sender.hasPermission("customnameplates.forceunequip")){
+ if (Bukkit.getPlayer(args[1]) != null){
+ Player player = Bukkit.getPlayer(args[1]);
+ DataManager.cache.get(player.getUniqueId()).equipNameplate("none");
+ this.plugin.getScoreBoardManager().getTeam(args[1]).updateNameplates();
+ this.plugin.getDataManager().savePlayer(player.getUniqueId());
+ if (sender instanceof Player){
+ AdventureManager.playerMessage((Player) sender, ConfigManager.Message.prefix + ConfigManager.Message.force_unequip.replace("{Player}", args[1]));
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.force_unequip.replace("{Player}", args[1]));
+ }
+ }else {
+ //玩家不存在,不在线
+ if(sender instanceof Player){
+ AdventureManager.playerMessage((Player) sender,ConfigManager.Message.prefix + ConfigManager.Message.not_online.replace("{Player}",args[1]));
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.not_online.replace("{Player}",args[1]));
+ }
+ }
+ }
+ }
+ case "preview" -> {
+ if (sender instanceof Player player){
+ //指令冷却
+ long time = System.currentTimeMillis();
+ //冷却时间判断
+ if (time - (coolDown.getOrDefault(player, time - ConfigManager.MainConfig.preview * 1050)) < ConfigManager.MainConfig.preview * 1050) {
+ AdventureManager.playerMessage(player, ConfigManager.Message.prefix + ConfigManager.Message.cooldown);
+ return true;
+ }
+ //重置冷却时间
+ coolDown.put(player, time);
+ if (player.hasPermission("customnameplates.preview") || player.isOp()){
+ AdventureManager.playerMessage(player,ConfigManager.Message.prefix + ConfigManager.Message.preview);
+ ArmorStand entity = player.getWorld().spawn(player.getLocation().add(0,0.8,0), ArmorStand.class, a -> {
+ a.setInvisible(true);
+ a.setCollidable(false);
+ a.setInvulnerable(true);
+ a.setVisible(false);
+ a.setCustomNameVisible(false);
+ a.setSmall(true);
+ a.setGravity(false);
+ });
+ pCache.add(entity);
+ NameplatesTeam team = this.plugin.getScoreBoardManager().getOrCreateTeam(player);
+ Component full = team.getPrefix().append(Component.text(player.getName()).font(Key.key("default")).append(team.getSuffix()));
+
+ WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher();
+ wrappedDataWatcher.setEntity(entity);
+ WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.get(Boolean.class);
+ wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(2, WrappedDataWatcher.Registry.getChatComponentSerializer(true)), Optional.of(WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(full)).getHandle()));
+ wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(3, serializer), true);
+ PacketContainer packetContainer = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
+ packetContainer.getIntegers().write(0, entity.getEntityId());
+ packetContainer.getWatchableCollectionModifier().write(0, wrappedDataWatcher.getWatchableObjects());
+
+ try {
+ ProtocolLibrary.getProtocolManager().sendServerPacket(player, packetContainer);
+ }
+ catch (Exception e) {
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to preview for "+ player.getName()+"");
+ e.printStackTrace();
+ }
+ BukkitScheduler bukkitScheduler = Bukkit.getScheduler();
+ for (int i = 1; i < ConfigManager.MainConfig.preview * 20; i++){
+ bukkitScheduler.runTaskLater(CustomNameplates.instance,()-> entity.teleport(player.getLocation().add(0,0.8,0)), i);
+ }
+ bukkitScheduler.runTaskLater(CustomNameplates.instance, ()->{
+ entity.remove();
+ pCache.remove(entity);
+ }, ConfigManager.MainConfig.preview * 20L);
+ }else {
+ AdventureManager.playerMessage((Player) sender,ConfigManager.Message.prefix + ConfigManager.Message.noPerm);
+ }
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.no_console);
+ }
+ }
+ case "list" -> {
+ if (sender instanceof Player player){
+ if (player.isOp()){
+ StringBuilder stringBuilder = new StringBuilder();
+ this.plugin.getResourceManager().caches.keySet().forEach(key ->{
+ if(key.equalsIgnoreCase("none")) return;
+ stringBuilder.append(key).append(" ");
+ });
+ AdventureManager.playerMessage(player, ConfigManager.Message.prefix + ConfigManager.Message.available.replace("{Nameplates}", stringBuilder.toString()));
+ }else if(player.hasPermission("customnameplates.list")){
+ StringBuilder stringBuilder = new StringBuilder();
+ for (PermissionAttachmentInfo info : player.getEffectivePermissions()) {
+ String permission = info.getPermission().toLowerCase();
+ if (permission.startsWith("customnameplates.equip.")) {
+ permission = StringUtils.replace(permission, "customnameplates.equip.", "");
+ if (this.plugin.getResourceManager().caches.get(permission) != null){
+ stringBuilder.append(permission).append(" ");
+ }
+ }
+ }
+ AdventureManager.playerMessage(player, ConfigManager.Message.prefix + ConfigManager.Message.available.replace("{Nameplates}", stringBuilder.toString()));
+ }else {
+ AdventureManager.playerMessage(player,ConfigManager.Message.prefix + ConfigManager.Message.noPerm);
+ }
+ }else {
+ AdventureManager.consoleMessage(ConfigManager.Message.prefix + ConfigManager.Message.no_console);
+ }
+ }
+ default -> {
+ if(sender instanceof Player player){
+ if (player.hasPermission("customnameplates.help")){
+ AdventureManager.playerMessage(player,"/nameplates help - show the command list");
+ AdventureManager.playerMessage(player,"/nameplates reload - reload the configuration");
+ AdventureManager.playerMessage(player,"/nameplates equip - equip a specified nameplate");
+ AdventureManager.playerMessage(player,"/nameplates forceequip - force a player to equip a specified nameplate");
+ AdventureManager.playerMessage(player,"/nameplates unequip - unequip your nameplate");
+ AdventureManager.playerMessage(player,"/nameplates forceunequip - force unequip a player's nameplate");
+ AdventureManager.playerMessage(player,"/nameplates preview - preview your nameplate");
+ AdventureManager.playerMessage(player,"/nameplates list - list your available nameplates");
+ }
+ }else {
+ AdventureManager.consoleMessage("/nameplates help - show the command list");
+ AdventureManager.consoleMessage("/nameplates reload - reload the configuration");
+ AdventureManager.consoleMessage("/nameplates equip - equip a specified nameplate");
+ AdventureManager.consoleMessage("/nameplates forceequip - force a player to equip a specified nameplate");
+ AdventureManager.consoleMessage("/nameplates unequip - unequip your nameplate");
+ AdventureManager.consoleMessage("/nameplates forceunequip - force unequip a player's nameplate");
+ AdventureManager.consoleMessage("/nameplates preview - preview your nameplate");
+ AdventureManager.consoleMessage("/nameplates list - list your available nameplates");
+ }
+ return true;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/commands/TabComplete.java b/src/main/java/net/momirealms/customnameplates/commands/TabComplete.java
new file mode 100644
index 0000000..6c891e4
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/commands/TabComplete.java
@@ -0,0 +1,86 @@
+package net.momirealms.customnameplates.commands;
+
+import net.momirealms.customnameplates.CustomNameplates;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.TabCompleter;
+import org.bukkit.entity.Player;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+import org.jetbrains.annotations.Nullable;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TabComplete implements TabCompleter {
+
+ private final CustomNameplates plugin;
+
+ public TabComplete(CustomNameplates plugin){
+ this.plugin = plugin;
+ }
+
+ @Override
+ @ParametersAreNonnullByDefault
+ public @Nullable List onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
+
+ if(1 == args.length){
+ List tab = new ArrayList<>();
+ if (sender.hasPermission("customnameplates.reload")) tab.add("reload");
+ if (sender.hasPermission("customnameplates.help")) tab.add("help");
+ if (sender.hasPermission("customnameplates.equip")) tab.add("equip");
+ if (sender.hasPermission("customnameplates.forceequip")) tab.add("forceequip");
+ if (sender.hasPermission("customnameplates.unequip")) tab.add("unequip");
+ if (sender.hasPermission("customnameplates.forceunequip")) tab.add("forceunequip");
+ if (sender.hasPermission("customnameplates.preview")) tab.add("preview");
+ if (sender.hasPermission("customnameplates.list")) tab.add("list");
+ return tab;
+ }
+ if(2 == args.length){
+ List tab = new ArrayList<>();
+ if (args[0].equalsIgnoreCase("equip")){
+ return availableNameplates(sender);
+ }
+ if (args[0].equalsIgnoreCase("forceunequip") && sender.hasPermission("customnameplates.forceunequip")){
+ return online_players();
+ }
+ if (args[0].equalsIgnoreCase("forceequip") && sender.hasPermission("customnameplates.forceequip")){
+ return online_players();
+ }
+ }
+ if(3 == args.length){
+ if (args[0].equalsIgnoreCase("forceequip") && sender.hasPermission("customnameplates.forceequip")){
+ return nameplates();
+ }
+ }
+ return null;
+ }
+
+ private static List online_players(){
+ List online = new ArrayList<>();
+ Bukkit.getOnlinePlayers().forEach((player -> online.add(player.getName())));
+ return online;
+ }
+
+ private List availableNameplates(CommandSender sender){
+ List availableNameplates = new ArrayList<>();
+ if (sender instanceof Player player){
+ for (PermissionAttachmentInfo info : player.getEffectivePermissions()) {
+ String permission = info.getPermission().toLowerCase();
+ if (permission.startsWith("customnameplates.equip.")) {
+ permission = StringUtils.replace(permission, "customnameplates.equip.", "");
+ if (this.plugin.getResourceManager().caches.get(permission) != null){
+ availableNameplates.add(permission);
+ }
+ }
+ }
+ }
+ return availableNameplates;
+ }
+
+ private List nameplates(){
+ return new ArrayList<>(this.plugin.getResourceManager().caches.keySet());
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/data/DataManager.java b/src/main/java/net/momirealms/customnameplates/data/DataManager.java
new file mode 100644
index 0000000..61f255e
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/data/DataManager.java
@@ -0,0 +1,58 @@
+package net.momirealms.customnameplates.data;
+
+import net.momirealms.customnameplates.AdventureManager;
+import net.momirealms.customnameplates.ConfigManager;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class DataManager {
+
+ public static Map cache;
+ public DataManager() {
+ cache = new HashMap<>();
+ }
+
+ public PlayerData getOrCreate(UUID uuid) {
+ if (cache.containsKey(uuid)) {
+ return cache.get(uuid);
+ }
+ PlayerData playerData = SqlHandler.getPlayerData(uuid);
+ if (playerData == null) {
+ playerData = PlayerData.EMPTY;
+ }
+ cache.put(uuid, playerData);
+ return playerData;
+ }
+
+ public void unloadPlayer(UUID uuid) {
+ if (!cache.containsKey(uuid)) {
+ return;
+ }
+ SqlHandler.save(cache.get(uuid), uuid);
+ cache.remove(uuid);
+ }
+
+ public void savePlayer(UUID uuid) {
+ SqlHandler.save(cache.get(uuid), uuid);
+ }
+
+ public static boolean create() {
+ if(ConfigManager.DatabaseConfig.use_mysql){
+ AdventureManager.consoleMessage("[CustomNameplates] Storage Mode - MYSQL");
+ }else {
+ AdventureManager.consoleMessage("[CustomNameplates] Storage Mode - SQLite");
+ }
+ if (SqlHandler.connect()) {
+ if (ConfigManager.DatabaseConfig.use_mysql) {
+ SqlHandler.getWaitTimeOut();
+ }
+ SqlHandler.createTable();
+ } else {
+ AdventureManager.consoleMessage("//DATA storage ERROR//");
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/momirealms/customnameplates/data/PlayerData.java b/src/main/java/net/momirealms/customnameplates/data/PlayerData.java
new file mode 100644
index 0000000..5e7adc4
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/data/PlayerData.java
@@ -0,0 +1,35 @@
+package net.momirealms.customnameplates.data;
+
+import net.momirealms.customnameplates.ConfigManager;
+
+public class PlayerData {
+
+ public static PlayerData EMPTY;
+ static {
+ EMPTY = new PlayerData(ConfigManager.MainConfig.default_nameplate, 0);
+ }
+
+ private String equipped;
+ private int accepted;
+
+ public PlayerData(String equipped, int accepted) {
+ this.equipped = equipped;
+ this.accepted = accepted;
+ }
+
+ public int getAccepted(){
+ return this.accepted;
+ }
+
+ public void setAccepted(int accepted){
+ this.accepted = accepted;
+ }
+
+ public String getEquippedNameplate() {
+ return this.equipped;
+ }
+
+ public void equipNameplate(String nameplate) {
+ this.equipped = nameplate.toLowerCase();
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/data/SqlHandler.java b/src/main/java/net/momirealms/customnameplates/data/SqlHandler.java
new file mode 100644
index 0000000..4b96f21
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/data/SqlHandler.java
@@ -0,0 +1,132 @@
+package net.momirealms.customnameplates.data;
+
+import net.momirealms.customnameplates.AdventureManager;
+import net.momirealms.customnameplates.ConfigManager;
+import net.momirealms.customnameplates.utils.SqlConnection;
+import org.bukkit.Bukkit;
+
+import java.sql.*;
+import java.util.UUID;
+
+public class SqlHandler {
+
+ public static String tableName = ConfigManager.DatabaseConfig.tableName;
+ public final static SqlConnection database = new SqlConnection();
+
+ public static boolean connect() {
+ return database.setGlobalConnection();
+ }
+
+ public static void close() {
+ database.close();
+ }
+
+ public static void getWaitTimeOut() {
+ if (ConfigManager.DatabaseConfig.use_mysql && !ConfigManager.DatabaseConfig.enable_pool) {
+ try {
+ Connection connection = database.getConnectionAndCheck();
+ String query = "show variables LIKE 'wait_timeout'";
+ PreparedStatement statement = connection.prepareStatement(query);
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ int waitTime = rs.getInt(2);
+ if (waitTime > 50) {
+ database.waitTimeOut = waitTime - 30;
+ }
+ }
+ rs.close();
+ statement.close();
+ database.closeHikariConnection(connection);
+ } catch (SQLException ignored) {
+ AdventureManager.consoleMessage("[CustomNameplates] Failed to get wait time out");
+ }
+ }
+ }
+
+ public static void createTable() {
+ try {
+ Connection connection = database.getConnectionAndCheck();
+ Statement statement = connection.createStatement();
+ if (statement == null) {
+ return;
+ }
+ String query;
+ if (ConfigManager.DatabaseConfig.use_mysql) {
+ query = "CREATE TABLE IF NOT EXISTS " + tableName
+ + "(player VARCHAR(50) NOT NULL, equipped VARCHAR(50) NOT NULL, accepted INT(1) NOT NULL,"
+ + " PRIMARY KEY (player)) DEFAULT charset = " + ConfigManager.DatabaseConfig.ENCODING + ";";
+ } else {
+ query = "CREATE TABLE IF NOT EXISTS " + tableName
+ + "(player VARCHAR(50) NOT NULL, equipped VARCHAR(50) NOT NULL, accepted INT(1) NOT NULL,"
+ + " PRIMARY KEY (player));";
+ }
+ statement.executeUpdate(query);
+ statement.close();
+ database.closeHikariConnection(connection);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static PlayerData getPlayerData(UUID uuid) {
+ PlayerData playerData = null;
+ try {
+ Connection connection = database.getConnectionAndCheck();
+ String sql = "SELECT * FROM " + tableName + " WHERE player = ?";
+ PreparedStatement statement = connection.prepareStatement(sql);
+ statement.setString(1, uuid.toString());
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ playerData = new PlayerData(rs.getString(2), rs.getInt(3));
+ }else {
+ sql = "INSERT INTO nameplates(player,equipped,accepted) values(?,?,?)";
+ statement = connection.prepareStatement(sql);
+ statement.setString(1, uuid.toString());
+ statement.setString(2, "none");
+ statement.setInt(3, 0);
+ statement.executeUpdate();
+ }
+ rs.close();
+ statement.close();
+ database.closeHikariConnection(connection);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ return playerData;
+ }
+
+ public static void save(PlayerData playerData, UUID uuid) {
+ Connection connection = database.getConnectionAndCheck();
+ try {
+ String query = " SET equipped = ?, accepted = ? WHERE player = ?";
+ PreparedStatement statement = connection.prepareStatement("UPDATE " + tableName + query);
+ statement.setString(1, playerData.getEquippedNameplate());
+ statement.setInt(2, playerData.getAccepted());
+ statement.setString(3, uuid.toString());
+ statement.executeUpdate();
+ statement.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ database.closeHikariConnection(connection);
+ }
+
+ public static void saveAll() {
+ Connection connection = database.getConnectionAndCheck();
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ try {
+ PlayerData playerData = DataManager.cache.get(player.getUniqueId());
+ String query = " SET equipped = ?, accepted = ? WHERE player = ?";
+ PreparedStatement statement = connection.prepareStatement("UPDATE " + tableName + query);
+ statement.setString(1, playerData.getEquippedNameplate());
+ statement.setInt(2, playerData.getAccepted());
+ statement.setString(3, String.valueOf(player.getUniqueId()));
+ statement.executeUpdate();
+ statement.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ });
+ database.closeHikariConnection(connection);
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/font/FontCache.java b/src/main/java/net/momirealms/customnameplates/font/FontCache.java
new file mode 100644
index 0000000..31bd6b4
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/font/FontCache.java
@@ -0,0 +1,86 @@
+package net.momirealms.customnameplates.font;
+
+import net.momirealms.customnameplates.nameplates.NameplateConfig;
+
+public record FontCache(String name, FontChar fontChar, NameplateConfig config) {
+
+ public static FontCache EMPTY;
+ static {
+ FontCache.EMPTY = new FontCache("none", new FontChar(' ', ' ', ' '), NameplateConfig.EMPTY);
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public FontChar getChar() {
+ return this.fontChar;
+ }
+ public NameplateConfig getConfig() {
+ return this.config;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof FontCache fontCache)) {
+ return false;
+ }
+ if (!fontCache.canEqual(this)) {
+ return false;
+ }
+ final String name = this.getName();
+ final String name2 = fontCache.getName();
+ Label_a:
+ {
+ if (name == null) {
+ if (name2 == null) {
+ break Label_a;
+ }
+ } else if (name.equals(name2)) {
+ break Label_a;
+ }
+ return false;
+ }
+ FontChar info = this.getChar();
+ FontChar info2 = fontCache.getChar();
+ Label_b:
+ {
+ if (info == null) {
+ if (info2 == null) {
+ break Label_b;
+ }
+ } else if (info.equals(info2)) {
+ break Label_b;
+ }
+ return false;
+ }
+ final NameplateConfig config = this.getConfig();
+ final NameplateConfig config2 = fontCache.getConfig();
+ if (config == null) {
+ return config2 == null;
+ } else return config.equals(config2);
+ }
+
+ @Override
+ public int hashCode() {
+ final int n = 1;
+ final String name = this.getName();
+ final int n2 = n * 59 + ((name == null) ? 43 : name.hashCode());
+ final FontChar fontChar = this.getChar();
+ final int n3 = n2 * 59 + ((fontChar == null) ? 43 : fontChar.hashCode());
+ final NameplateConfig config = this.getConfig();
+ return n3 * 59 + ((config == null) ? 43 : config.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return "FontCache(name=" + this.getName() + ", info=" + this.getChar() + ", config=" + this.getConfig() + ")";
+ }
+
+ private boolean canEqual(final Object other) {
+ return other instanceof FontCache;
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/font/FontChar.java b/src/main/java/net/momirealms/customnameplates/font/FontChar.java
new file mode 100644
index 0000000..584bfb6
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/font/FontChar.java
@@ -0,0 +1,43 @@
+package net.momirealms.customnameplates.font;
+
+
+public record FontChar(char left, char middle, char right) {
+
+ public char getLeft() {
+ return this.left;
+ }
+
+ public char getMiddle() {
+ return this.middle;
+ }
+
+ public char getRight() {
+ return this.right;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof FontChar fontInfo)) {
+ return false;
+ }
+ return this.getLeft() == fontInfo.getLeft() && this.getMiddle() == fontInfo.getMiddle() && this.getRight() == fontInfo.getRight();
+ }
+
+ /*
+ 如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写
+ 比如:有个A类重写了equals方法,但是没有重写hashCode方法,看输出结果,对象a1和对象a2使用equals方法相等,
+ 所以,我们在重写了equals方法后,尽量也重写了hashcode方法,通过一定的算法,使他们在equals相等时,也会有相同的hashcode值。
+ */
+ @Override
+ public int hashCode() {
+ return ((59 + this.getLeft()) * 59 + this.getMiddle()) * 59 + this.getRight();
+ }
+
+ @Override
+ public String toString() {
+ return "FontChar=" + this.getLeft() + this.getMiddle() + this.getRight();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/momirealms/customnameplates/font/FontNegative.java b/src/main/java/net/momirealms/customnameplates/font/FontNegative.java
new file mode 100644
index 0000000..c7153d9
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/font/FontNegative.java
@@ -0,0 +1,97 @@
+package net.momirealms.customnameplates.font;
+
+public enum FontNegative {
+
+ /*
+ 实际向左移的距离是height(尺寸)-2
+ */
+ NEG_1('\uf801', -1, -3),
+ NEG_2('\uf802', -2, -4),
+ NEG_3('\uf803', -3, -5),
+ NEG_4('\uf804', -4, -6),
+ NEG_5('\uf805', -5, -7),
+ NEG_6('\uf806', -6, -8),
+ NEG_7('\uf807', -7, -9),
+ NEG_8('\uf808', -8, -10),
+ NEG_16('\uf809', -16, -18),
+ NEG_32('\uf80a', -32, -34),
+ NEG_64('\uf80b', -64, -66),
+ NEG_128('\uf80c', -128, -130);
+
+ private final char character;
+ private final int space;
+ private final int height;
+
+ FontNegative(char character, int space, int height) {
+ this.character = character;
+ this.space = space;
+ this.height = height;
+ }
+
+ /*
+ 获取最短的负空格字符
+ */
+ public static String getShortestNegChars(int n) {
+ StringBuilder stringBuilder = new StringBuilder();
+ if (n > 128) {
+ stringBuilder.append(FontNegative.NEG_128.getCharacter());
+ n -= 129;
+ }
+ if (n - 64 > 0) {
+ stringBuilder.append(FontNegative.NEG_64.getCharacter());
+ n -= 64;
+ }
+ if (n - 32 > 0) {
+ stringBuilder.append(FontNegative.NEG_32.getCharacter());
+ n -= 32;
+ }
+ if (n - 16 > 0) {
+ stringBuilder.append(FontNegative.NEG_16.getCharacter());
+ n -= 16;
+ }
+ if (n - 8 > 0) {
+ stringBuilder.append(FontNegative.NEG_8.getCharacter());
+ n -= 8;
+ }
+ if (n - 7 > 0) {
+ stringBuilder.append(FontNegative.NEG_7.getCharacter());
+ n -= 7;
+ }
+ if (n - 6 > 0) {
+ stringBuilder.append(FontNegative.NEG_6.getCharacter());
+ n -= 6;
+ }
+ if (n - 5 > 0) {
+ stringBuilder.append(FontNegative.NEG_5.getCharacter());
+ n -= 5;
+ }
+ if (n - 4 > 0) {
+ stringBuilder.append(FontNegative.NEG_4.getCharacter());
+ n -= 4;
+ }
+ if (n - 3 > 0) {
+ stringBuilder.append(FontNegative.NEG_3.getCharacter());
+ n -= 3;
+ }
+ if (n - 2 > 0) {
+ stringBuilder.append(FontNegative.NEG_2.getCharacter());
+ n -= 2;
+ }
+ if (n - 1 > 0) {
+ stringBuilder.append(FontNegative.NEG_1.getCharacter());
+ }
+ return stringBuilder.toString();
+ }
+
+ public char getCharacter() {
+ return this.character;
+ }
+
+ public int getSpace() {
+ return this.space;
+ }
+
+ public int getHeight() {
+ return this.height;
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/font/FontWidth.java b/src/main/java/net/momirealms/customnameplates/font/FontWidth.java
new file mode 100644
index 0000000..e8596cc
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/font/FontWidth.java
@@ -0,0 +1,78 @@
+package net.momirealms.customnameplates.font;
+
+public enum FontWidth {
+
+ A('A', 5), a('a', 5), B('B', 5), b('b', 5),
+ C('C', 5), c('c', 5), D('D', 5), d('d', 5),
+ E('E', 5), e('e', 5), F('F', 5), f('f', 4),
+ G('G', 5), g('g', 5), H('H', 5), h('h', 5),
+ I('I', 3), i('i', 1), J('J', 5), j('j', 5),
+ K('K', 5), k('k', 4), L('L', 5), l('l', 2),
+ M('M', 5), m('m', 5), N('N', 5), n('n', 5),
+ O('O', 5), o('o', 5), P('P', 5), p('p', 5),
+ Q('Q', 5), q('q', 5), R('R', 5), r('r', 5),
+ S('S', 5), s('s', 5), T('T', 5), t('t', 4),
+ U('U', 5), u('u', 5), V('V', 5), v('v', 5),
+ W('W', 5), w('w', 5), X('X', 5), x('x', 5),
+ Y('Y', 5), y('y', 5), Z('Z', 5), z('z', 5),
+ NUM_1('1', 5), NUM_2('2', 5), NUM_3('3', 5), NUM_4('4', 5),
+ NUM_5('5', 5), NUM_6('6', 5), NUM_7('7', 5), NUM_8('8', 5),
+ NUM_9('9', 5), NUM_0('0', 5), EXCLAMATION_POINT('!', 1), AT_SYMBOL('@', 6),
+ NUM_SIGN('#', 5), DOLLAR_SIGN('$', 5), PERCENT('%', 5), UP_ARROW('^', 5),
+ AMPERSAND('&', 5), ASTERISK('*', 5), LEFT_PARENTHESIS('(', 4),
+ RIGHT_PARENTHESIS(')', 4), MINUS('-', 5), UNDERSCORE('_', 5), PLUS_SIGN('+', 5),
+ EQUALS_SIGN('=', 5), LEFT_CURL_BRACE('{', 4), RIGHT_CURL_BRACE('}', 4),
+ LEFT_BRACKET('[', 3), RIGHT_BRACKET(']', 3), COLON(':', 1), SEMI_COLON(';', 1),
+ DOUBLE_QUOTE('\"', 3), SINGLE_QUOTE('\'', 1), LEFT_ARROW('<', 4),
+ RIGHT_ARROW('>', 4), QUESTION_MARK('?', 5), SLASH('/', 5),
+ BACK_SLASH('\\', 5), LINE('|', 1), TILDE('~', 5), TICK('`', 2),
+ PERIOD('.', 1), COMMA(',', 1), SPACE(' ', 3),
+ IN_BETWEEN(' ', 1), DEFAULT('默', 8), DEFAULT2('米', 7);
+
+ private final char character;
+ private final int length;
+
+ FontWidth(char character, int length) {
+ this.character = character;
+ this.length = length;
+ }
+
+ public char getCharacter() {
+ return this.character;
+ }
+
+ public int getLength() {
+ return this.length;
+ }
+
+ public int getBoldLength() {
+ if (this == FontWidth.SPACE) {
+ return this.getLength();
+ }
+ return this.getLength() + 1;
+ }
+
+ /*
+ 获取每个字符的像素宽度
+ */
+ public static FontWidth getInfo(char c) {
+ for (FontWidth minecraftFontWidth : values()) {
+ if (minecraftFontWidth.getCharacter() == c) {
+ return minecraftFontWidth;
+ }
+ }
+ return FontWidth.DEFAULT;
+ }
+
+ /*
+ 计算一个字符串的总宽度
+ */
+ public static int getTotalWidth(String s) {
+ int length = s.length();
+ int n = 0;
+ for (int i = 0; i < length; i++) {
+ n += getInfo(s.charAt(i)).getLength();
+ }
+ return n + FontWidth.IN_BETWEEN.getLength() * (length - 1); //总长还需加上字符间距
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/listener/PacketsListener.java b/src/main/java/net/momirealms/customnameplates/listener/PacketsListener.java
new file mode 100644
index 0000000..8414f4e
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/listener/PacketsListener.java
@@ -0,0 +1,55 @@
+package net.momirealms.customnameplates.listener;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.InternalStructure;
+import com.comphenix.protocol.events.ListenerPriority;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketEvent;
+import com.comphenix.protocol.utility.MinecraftReflection;
+import com.comphenix.protocol.wrappers.WrappedChatComponent;
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.momirealms.customnameplates.ConfigManager;
+import net.momirealms.customnameplates.CustomNameplates;
+import net.momirealms.customnameplates.data.DataManager;
+import net.momirealms.customnameplates.scoreboard.NameplatesTeam;
+import org.bukkit.ChatColor;
+
+import java.util.Optional;
+
+public class PacketsListener extends PacketAdapter {
+
+ private final CustomNameplates plugin;
+
+ public PacketsListener(CustomNameplates plugin) {
+ super(plugin, ListenerPriority.HIGHEST, PacketType.Play.Server.SCOREBOARD_TEAM);
+ this.plugin = plugin;
+ }
+
+ public void onPacketSending(PacketEvent event) {
+ Integer n = event.getPacket().getIntegers().read(0);
+ //create team && update team info
+ if (n != 0 && n != 2) {
+ return;
+ }
+ Optional optional = event.getPacket().getOptionalStructures().read(0);
+ if (optional.isEmpty()) {
+ return;
+ }
+ InternalStructure internalStructure = optional.get();
+ String teamName = event.getPacket().getStrings().read(0);
+ NameplatesTeam team = this.plugin.getScoreBoardManager().getTeam(teamName);
+ if (!this.plugin.getScoreBoardManager().doesTeamExist(teamName)){
+ return;
+ }
+ if (ConfigManager.MainConfig.show_after && DataManager.cache.get(event.getPlayer().getUniqueId()).getAccepted() == 0) {
+ internalStructure.getChatComponents().write(1, WrappedChatComponent.fromJson("{\"text\":\"\"}"));
+ internalStructure.getChatComponents().write(2, WrappedChatComponent.fromJson("{\"text\":\"\"}"));
+ internalStructure.getEnumModifier(ChatColor.class, MinecraftReflection.getMinecraftClass("EnumChatFormat")).write(0,ChatColor.WHITE);
+ return;
+ }
+ //在新建队伍名字的时候其实就是以玩家名命名,所以获得的teamName=playerName
+ internalStructure.getChatComponents().write(1, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(team.getPrefix())));
+ internalStructure.getChatComponents().write(2, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(team.getSuffix())));
+ internalStructure.getEnumModifier(ChatColor.class, MinecraftReflection.getMinecraftClass("EnumChatFormat")).write(0,team.getColor());
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/listener/PlayerListener.java b/src/main/java/net/momirealms/customnameplates/listener/PlayerListener.java
new file mode 100644
index 0000000..d833ebb
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/listener/PlayerListener.java
@@ -0,0 +1,40 @@
+package net.momirealms.customnameplates.listener;
+
+import net.momirealms.customnameplates.CustomNameplates;
+import net.momirealms.customnameplates.data.DataManager;
+import org.bukkit.Bukkit;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.event.player.PlayerResourcePackStatusEvent;
+
+public record PlayerListener(CustomNameplates plugin) implements Listener {
+
+ @EventHandler
+ public void onPreLogin(AsyncPlayerPreLoginEvent event) {
+ this.plugin.getDataManager().getOrCreate(event.getUniqueId());
+ }
+
+ @EventHandler
+ public void onJoin(PlayerJoinEvent event) {
+ this.plugin.getScoreBoardManager().getOrCreateTeam(event.getPlayer());
+ }
+
+ @EventHandler
+ public void onQuit(PlayerQuitEvent event) {
+ this.plugin.getDataManager().unloadPlayer(event.getPlayer().getUniqueId());
+ }
+
+ @EventHandler
+ public void onAccept(PlayerResourcePackStatusEvent event) {
+ if (event.getStatus() == PlayerResourcePackStatusEvent.Status.SUCCESSFULLY_LOADED) {
+ DataManager.cache.get(event.getPlayer().getUniqueId()).setAccepted(1);
+ Bukkit.getOnlinePlayers().forEach(player -> this.plugin.getScoreBoardManager().getTeam(player.getName()).updateNameplates());
+ } else {
+ DataManager.cache.get(event.getPlayer().getUniqueId()).setAccepted(0);
+ Bukkit.getOnlinePlayers().forEach(player -> this.plugin.getScoreBoardManager().getTeam(player.getName()).updateNameplates());
+ }
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/nameplates/NameplateConfig.java b/src/main/java/net/momirealms/customnameplates/nameplates/NameplateConfig.java
new file mode 100644
index 0000000..84ba292
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/nameplates/NameplateConfig.java
@@ -0,0 +1,27 @@
+package net.momirealms.customnameplates.nameplates;
+
+import org.bukkit.ChatColor;
+
+public record NameplateConfig(ChatColor color, int height, String name) {
+
+ public static NameplateConfig EMPTY;
+
+ static {
+ EMPTY = new NameplateConfig(ChatColor.WHITE, 16, "none");
+ }
+
+ //获取Team颜色
+ public ChatColor getColor() {
+ return this.color;
+ }
+
+ //获取自定义font大小
+ public int getHeight() {
+ return this.height;
+ }
+
+ //获取铭牌名
+ public String getName() {
+ return this.name;
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/nameplates/NameplateUtil.java b/src/main/java/net/momirealms/customnameplates/nameplates/NameplateUtil.java
new file mode 100644
index 0000000..29d9d0a
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/nameplates/NameplateUtil.java
@@ -0,0 +1,57 @@
+package net.momirealms.customnameplates.nameplates;
+
+import net.momirealms.customnameplates.font.FontCache;
+import net.momirealms.customnameplates.font.FontNegative;
+import net.momirealms.customnameplates.font.FontWidth;
+import org.bukkit.ChatColor;
+
+public class NameplateUtil {
+
+ private final FontCache fontcache;
+
+ public NameplateUtil(FontCache font) {
+ this.fontcache = font;
+ }
+
+ /*
+ 根据玩家名构造长度适合的铭牌字符
+ 当然这个玩家名是带上前缀与后缀的
+ */
+ public String makeCustomNameplate(String prefix, String name, String suffix) {
+ int totalWidth = FontWidth.getTotalWidth(ChatColor.stripColor(prefix + name + suffix));
+ boolean isEven = totalWidth % 2 == 0; //奇偶判断
+ char left = this.fontcache.getChar().getLeft();
+ char middle = this.fontcache.getChar().getMiddle();
+ char right = this.fontcache.getChar().getRight();
+ char neg_1 = FontNegative.NEG_1.getCharacter();
+ int left_offset = totalWidth + 16 + 1;
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(FontNegative.getShortestNegChars(isEven ? left_offset : left_offset + 1)); //先向左平移一个正方形的距离
+ stringBuilder.append(left).append(neg_1); //将铭牌的左部分拼接
+ int mid_amount = (totalWidth + 1) / 16; //显示名称的总长,如果超过一个正方形则多复制几个正方形
+ for (int i = 0; i < (mid_amount == 0 ? 1 : mid_amount); i++) {
+ stringBuilder.append(middle).append(neg_1); //减一是字符之间的间距(3)
+ }
+ stringBuilder.append(FontNegative.getShortestNegChars(16 - ((totalWidth + 1) % 16 + (isEven ? 0 : 1))));
+ stringBuilder.append(middle).append(neg_1);
+ stringBuilder.append(right).append(neg_1); //将铭牌的右部分拼接
+ stringBuilder.append(FontNegative.getShortestNegChars(isEven ? left_offset : left_offset + 1)); //首尾对称处理,保证铭牌位于正中央
+ return stringBuilder.toString();
+ }
+
+ /*
+ 用于为增加了后缀的玩家名计算负空格
+ 保证铭牌总是位于玩家头顶中央的位置
+ */
+ public String getSuffixLength(String name) {
+ final int totalWidth = FontWidth.getTotalWidth(ChatColor.stripColor(name));
+ return FontNegative.getShortestNegChars(totalWidth + totalWidth % 2 + 1);
+ }
+
+ /*
+ 获取铭牌上玩家名的颜色
+ */
+ public ChatColor getColor() {
+ return this.fontcache.getConfig().getColor();
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/resource/ResourceManager.java b/src/main/java/net/momirealms/customnameplates/resource/ResourceManager.java
new file mode 100644
index 0000000..8d4d454
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/resource/ResourceManager.java
@@ -0,0 +1,237 @@
+package net.momirealms.customnameplates.resource;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import net.momirealms.customnameplates.ConfigManager;
+import net.momirealms.customnameplates.CustomNameplates;
+import net.momirealms.customnameplates.AdventureManager;
+import net.momirealms.customnameplates.font.FontCache;
+import net.momirealms.customnameplates.font.FontChar;
+import net.momirealms.customnameplates.font.FontNegative;
+import net.momirealms.customnameplates.nameplates.NameplateConfig;
+import org.apache.commons.io.FileUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.*;
+
+public class ResourceManager {
+
+ public final HashMap caches;
+ private CustomNameplates plugin;
+
+ public ResourceManager(CustomNameplates plugin) {
+ this.caches = new HashMap<>();
+ this.plugin = plugin;
+ }
+
+ /*
+ 此方法用于生成资源包
+ */
+ public void generateResourcePack() {
+
+ File r_file = new File(CustomNameplates.instance.getDataFolder() + File.separator + "resources");
+ File g_file = new File(CustomNameplates.instance.getDataFolder() + File.separator + "generated");
+ //如果资源文件夹不存在则创建
+ if (!r_file.exists()) {
+ AdventureManager.consoleMessage("[CustomNameplates] Failed to detect resources folder! Generating default resources...");
+ if (!r_file.mkdir()) {
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to create resources folder...");
+ return;
+ }
+ saveDefaultResources();
+ }
+ //获取资源文件夹下的所有png文件
+ File[] pngFiles = r_file.listFiles(file -> file.getName().endsWith(".png"));
+ if (pngFiles == null) {
+ AdventureManager.consoleMessage("[CustomNameplates] Error! No png files detected in resource folder...");
+ return;
+ }
+ Arrays.sort(pngFiles); //将png文件按照首字母进行排序
+ deleteDirectory(g_file); //删除文件夹以重置自动生成的资源
+
+ File f_file = new File(CustomNameplates.instance.getDataFolder() + File.separator + "generated" + File.separatorChar + ConfigManager.MainConfig.namespace + File.separatorChar + "font");
+ File t_file = new File(CustomNameplates.instance.getDataFolder() + File.separator + "generated" + File.separatorChar + ConfigManager.MainConfig.namespace + File.separatorChar + "textures");
+
+ if (!f_file.mkdirs() || !t_file.mkdirs()) {
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to generate resource pack folders...");
+ return;
+ }
+ char start = ConfigManager.MainConfig.start_char.charAt(0); //获取起始字符
+ JsonObject jsonObject_1 = new JsonObject(); //新建json对象
+ JsonArray jsonArray_1 = new JsonArray();
+ jsonObject_1.add("providers", jsonArray_1);
+ for (File png : pngFiles) {
+ JsonObject jsonObject_2 = new JsonObject();
+ char left = start;
+ char middle;
+ char right;
+ start = (char)((right = (char)((middle = (char)(start + '\u0001')) + '\u0001')) + '\u0001'); //依次+1
+ FontChar fontChar = new FontChar(left, middle, right);
+ String pngName = png.getName().substring(0, png.getName().length() - 4); //删除.png后缀
+ NameplateConfig config = this.getConfiguration(pngName);
+ caches.put(pngName, new FontCache(pngName, fontChar, config));
+ jsonObject_2.add("type", new JsonPrimitive("bitmap"));
+ jsonObject_2.add("file", new JsonPrimitive(ConfigManager.MainConfig.namespace + ":" + ConfigManager.MainConfig.folder_path.replaceAll("\\\\","/") + png.getName().toLowerCase()));
+ jsonObject_2.add("ascent", new JsonPrimitive(12));
+ jsonObject_2.add("height", new JsonPrimitive(config.getHeight()));
+ JsonArray jsonArray_2 = new JsonArray();
+ jsonArray_2.add(native2ascii(fontChar.getLeft()) + native2ascii(fontChar.getMiddle()) + native2ascii(fontChar.getRight()));
+ jsonObject_2.add("chars", jsonArray_2);
+ jsonArray_1.add(jsonObject_2);
+ try{
+ FileUtils.copyFile(png, new File(t_file.getPath() + File.separatorChar + ConfigManager.MainConfig.folder_path + png.getName()));
+ }catch (IOException e){
+ e.printStackTrace();
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to copy png files to resource pack...");
+ }
+ }
+ caches.put("none", FontCache.EMPTY);
+ CustomNameplates.instance.saveResource("space_split.png", false); //复制space_split.png
+ try{
+ FileUtils.copyFile(new File(CustomNameplates.instance.getDataFolder(),"space_split.png"), new File(t_file.getPath() + File.separatorChar + ConfigManager.MainConfig.folder_path + "space_split.png"));
+ }catch (IOException e){
+ e.printStackTrace();
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to copy space_split.png to resource pack...");
+ return;
+ }
+ new File(CustomNameplates.instance.getDataFolder(),"space_split.png").delete(); //删除拷贝出的默认文件
+ this.getNegativeFontEnums().forEach(jsonArray_1::add); //添加负空格
+ //存储default.json
+ try (FileWriter fileWriter = new FileWriter(f_file.getPath() + File.separatorChar + ConfigManager.MainConfig.font + ".json")) {
+ fileWriter.write(jsonObject_1.toString().replace("\\\\", "\\"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to generate font json...");
+ return;
+ }
+ //资源包生成成功提示
+ AdventureManager.consoleMessage("[CustomNameplates] Resource pack has been successfully generated! " + this.caches.size() + " nameplates Loaded.");
+ if (this.plugin.getHookManager().hasItemsAdder()){
+ try{
+ FileUtils.copyDirectory(g_file, new File(Bukkit.getPluginManager().getPlugin("ItemsAdder").getDataFolder() + File.separator + "data"+ File.separator + "resource_pack" + File.separator + "assets") );
+ }catch (IOException e){
+ e.printStackTrace();
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to copy files to ItemsAdder...");
+ }
+ }
+ }
+
+ /*
+ 保存插件预设资源
+ */
+ private void saveDefaultResources() {
+ List list = Arrays.asList("cat", "egg", "cheems", "wither");
+ list.forEach(name -> CustomNameplates.instance.saveResource("resources" + File.separatorChar + name + ".png", false));
+ }
+
+ /*
+ 删除文件夹
+ */
+ private void deleteDirectory(File file){
+ if(file.exists()){
+ try{
+ FileUtils.deleteDirectory(file);
+ AdventureManager.consoleMessage("[CustomNameplates] Successfully copy files to ItemsAdder...");
+ }catch (IOException e){
+ e.printStackTrace();
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to delete generated folder..." );
+ }
+ }
+ }
+
+ /*
+ 获取铭牌的config
+ */
+ private NameplateConfig getConfiguration(String nameplate) {
+ try {
+ File file = new File(CustomNameplates.instance.getDataFolder().getPath() + File.separator + "resources" + File.separator + nameplate + ".yml");
+ if (!file.exists()){
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ AdventureManager.consoleMessage("铭牌配置生成出错!");
+ }
+ }
+ YamlConfiguration config = new YamlConfiguration();
+ config.load(CustomNameplates.instance.getDataFolder().getPath() + File.separator + "resources" + File.separator + nameplate + ".yml");
+ if (!config.contains("name")){
+ config.set("name", nameplate);
+ }
+ if (!config.contains("color")){
+ config.set("color","WHITE");
+ }
+ if (!config.contains("size")){
+ config.set("size", 12);
+ }
+ ChatColor color = ChatColor.WHITE;
+ try {
+ color = ChatColor.valueOf(Objects.requireNonNull(config.getString("color")).toUpperCase());
+ }
+ catch (IllegalArgumentException ex) {
+ AdventureManager.consoleMessage("[CustomNameplates] Invalid Color of " + nameplate + "");
+ }
+ int size = config.getInt("size");
+ String name = config.getString("name");
+ config.save(file);
+ return new NameplateConfig(color, size, name);
+ }
+ catch (Exception e) {
+ return NameplateConfig.EMPTY;
+ }
+ }
+
+ /*
+ 获取负空格并返回list
+ */
+ private List getNegativeFontEnums() {
+ ArrayList list = new ArrayList<>();
+ for (FontNegative negativeFont : FontNegative.values()) {
+ list.add(this.getNegativeFontChar(negativeFont.getHeight(), negativeFont.getCharacter()));
+ }
+ return list;
+ }
+ private JsonObject getNegativeFontChar(int height, char character) {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.add("type", new JsonPrimitive("bitmap"));
+ jsonObject.add("file", new JsonPrimitive(ConfigManager.MainConfig.namespace + ":" + ConfigManager.MainConfig.folder_path.replaceAll("\\\\","/") +"space_split.png"));
+ jsonObject.add("ascent", new JsonPrimitive(-5000));
+ jsonObject.add("height", new JsonPrimitive(height));
+ final JsonArray jsonArray = new JsonArray();
+ jsonArray.add(native2ascii(character));
+ jsonObject.add("chars", jsonArray);
+ return jsonObject;
+ }
+
+ /*
+ 根据铭牌名获取铭牌的FontCache
+ */
+ public FontCache getNameplateInfo(String nameplate) {
+ return caches.get(nameplate);
+ }
+
+ /*
+ 字符转换
+ */
+ private String native2ascii(char ch) {
+ if (ch > '\u007f') {
+ StringBuilder stringBuilder_1 = new StringBuilder("\\u");
+ StringBuilder stringBuilder_2 = new StringBuilder(Integer.toHexString(ch));
+ stringBuilder_2.reverse();
+ for (int n = 4 - stringBuilder_2.length(), i = 0; i < n; i++) {
+ stringBuilder_2.append('0');
+ }
+ for (int j = 0; j < 4; j++) {
+ stringBuilder_1.append(stringBuilder_2.charAt(3 - j));
+ }
+ return stringBuilder_1.toString();
+ }
+ return Character.toString(ch);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/momirealms/customnameplates/scoreboard/NameplatesTeam.java b/src/main/java/net/momirealms/customnameplates/scoreboard/NameplatesTeam.java
new file mode 100644
index 0000000..3d008d7
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/scoreboard/NameplatesTeam.java
@@ -0,0 +1,89 @@
+package net.momirealms.customnameplates.scoreboard;
+
+import net.kyori.adventure.key.Key;
+import net.kyori.adventure.text.Component;
+import net.momirealms.customnameplates.ConfigManager;
+import net.momirealms.customnameplates.CustomNameplates;
+import net.momirealms.customnameplates.data.DataManager;
+import net.momirealms.customnameplates.font.FontCache;
+import net.momirealms.customnameplates.nameplates.NameplateUtil;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.bukkit.scoreboard.Scoreboard;
+import org.bukkit.scoreboard.Team;
+
+import java.util.Optional;
+
+public class NameplatesTeam {
+
+ private final CustomNameplates plugin;
+ private final Player player;
+ private final Team team;
+ private Component prefix;
+ private Component suffix;
+ private ChatColor color;
+
+ public void hideNameplate() {
+ this.team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.NEVER);
+ }
+ public void showNameplate() {
+ this.team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.ALWAYS);
+ }
+ public Component getPrefix() {
+ return this.prefix;
+ }
+ public Component getSuffix() {
+ return this.suffix;
+ }
+ public ChatColor getColor() {
+ return this.color;
+ }
+
+ public NameplatesTeam(CustomNameplates plugin, Player player) {
+ this.color = ChatColor.WHITE;
+ this.plugin = plugin;
+ this.player = player;
+ Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
+ String name = player.getName();
+ this.team = Optional.ofNullable(Bukkit.getScoreboardManager().getMainScoreboard().getTeam(name)).orElseGet(() -> scoreboard.registerNewTeam(name));
+ team.addEntry(player.getName());
+ }
+
+ public void updateNameplates() {
+ //获取玩家的铭牌,没有数据则创建数据,没有铭牌则设置为空铭牌
+ String nameplate = (this.plugin.getDataManager().getOrCreate(this.player.getUniqueId())).getEquippedNameplate();
+ //如果是空铭牌直接飞机票送走
+ if (nameplate.equals("none")) {
+ this.prefix = Component.text("");
+ this.suffix = Component.text("");
+ this.color = ChatColor.WHITE;
+ this.team.setPrefix("");
+ return;
+ }
+ //根据铭牌名获取FontCache
+ FontCache fontCache = this.plugin.getResourceManager().getNameplateInfo(nameplate);
+ if (fontCache == null){
+ DataManager.cache.get(player.getUniqueId()).equipNameplate("none");
+ return;
+ }
+ NameplateUtil nameplateUtil = new NameplateUtil(fontCache);
+ String name = this.player.getName();
+ String playerPrefix;
+ String playerSuffix;
+ //有Papi才解析
+ if (plugin.getHookManager().hasPlaceholderAPI()) {
+ playerPrefix = this.plugin.getHookManager().parsePlaceholders(this.player, ConfigManager.MainConfig.player_prefix);
+ playerSuffix = this.plugin.getHookManager().parsePlaceholders(this.player, ConfigManager.MainConfig.player_suffix);
+ }else {
+ playerPrefix = ConfigManager.MainConfig.player_prefix;
+ playerSuffix = ConfigManager.MainConfig.player_suffix;
+ }
+ //最终prefix: 偏移 + 铭牌左 + 偏移 + 铭牌中 + 偏移 + 铭牌右 + 偏移 + 前缀
+ //最终suffix: 偏移 + 后缀
+ this.prefix = Component.text(nameplateUtil.makeCustomNameplate(playerPrefix, name, playerSuffix)).font(ConfigManager.MainConfig.key).append(Component.text(playerPrefix).font(Key.key("default")));
+ this.suffix = Component.text(playerSuffix).append(Component.text(nameplateUtil.getSuffixLength(playerPrefix + name + playerSuffix)).font(ConfigManager.MainConfig.key));
+ this.color = nameplateUtil.getColor();
+ this.team.setPrefix("");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/momirealms/customnameplates/scoreboard/ScoreBoardManager.java b/src/main/java/net/momirealms/customnameplates/scoreboard/ScoreBoardManager.java
new file mode 100644
index 0000000..6c87e7e
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/scoreboard/ScoreBoardManager.java
@@ -0,0 +1,46 @@
+package net.momirealms.customnameplates.scoreboard;
+
+import net.momirealms.customnameplates.CustomNameplates;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ScoreBoardManager {
+
+ private final CustomNameplates plugin;
+ /*
+ 虽然会内存占用会随着玩家数量持续增加,但是这点内存根本不值一提
+ */
+ private final Map teams;
+
+ /*
+ 该类存在的意义是判断玩家是否已经
+ */
+ public ScoreBoardManager(CustomNameplates plugin) {
+ this.teams = new HashMap<>();
+ this.plugin = plugin;
+ }
+
+ public NameplatesTeam getOrCreateTeam(Player player) {
+ if (!this.teams.containsKey(player.getName())) {
+ this.teams.put(player.getName(), new NameplatesTeam(this.plugin, player));
+ }
+ //延后一秒确保数据库完成请求
+ Bukkit.getScheduler().runTaskLater(this.plugin, () -> this.getTeam(player.getName()).updateNameplates(), 20);
+ return this.teams.get(player.getName());
+ }
+
+ public void removeTeam(String playerName) {
+ this.teams.remove(playerName);
+ }
+
+ public NameplatesTeam getTeam(String team) {
+ return this.teams.get(team);
+ }
+
+ public boolean doesTeamExist(String playerName) {
+ return this.teams.containsKey(playerName);
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/utils/SqlConnection.java b/src/main/java/net/momirealms/customnameplates/utils/SqlConnection.java
new file mode 100644
index 0000000..c409104
--- /dev/null
+++ b/src/main/java/net/momirealms/customnameplates/utils/SqlConnection.java
@@ -0,0 +1,184 @@
+package net.momirealms.customnameplates.utils;
+
+import com.zaxxer.hikari.HikariDataSource;
+import net.momirealms.customnameplates.AdventureManager;
+import net.momirealms.customnameplates.ConfigManager;
+import net.momirealms.customnameplates.CustomNameplates;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+public class SqlConnection {
+
+ private String driver = "com.mysql.jdbc.Driver";
+
+ private final File dataFolder = CustomNameplates.instance.getDataFolder();
+
+ private boolean secon = false;
+ private boolean isfirstry = true;
+
+ public int waitTimeOut = 10;
+
+ public File userdata = new File(dataFolder, "data.db");
+ private Connection connection = null;
+ private HikariDataSource hikari = null;
+
+ /*
+ 新建Hikari配置
+ */
+ private void createNewHikariConfiguration() {
+ hikari = new HikariDataSource();
+ hikari.setPoolName("[Nameplates]");
+ hikari.setJdbcUrl(ConfigManager.DatabaseConfig.url);
+ hikari.setUsername(ConfigManager.DatabaseConfig.user);
+ hikari.setPassword(ConfigManager.DatabaseConfig.password);
+ hikari.setMaximumPoolSize(ConfigManager.DatabaseConfig.maximum_pool_size);
+ hikari.setMinimumIdle(ConfigManager.DatabaseConfig.minimum_idle);
+ hikari.setMaxLifetime(ConfigManager.DatabaseConfig.maximum_lifetime);
+ hikari.addDataSourceProperty("cachePrepStmts", "true");
+ hikari.addDataSourceProperty("prepStmtCacheSize", "250");
+ hikari.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
+ hikari.addDataSourceProperty("userServerPrepStmts", "true");
+ if (hikari.getMinimumIdle() < hikari.getMaximumPoolSize()) {
+ hikari.setIdleTimeout(ConfigManager.DatabaseConfig.idle_timeout);
+ } else {
+ hikari.setIdleTimeout(0);
+ }
+ }
+
+ /*
+ 设置驱动,区分Mysql5与8
+ */
+ private void setDriver() {
+ if(ConfigManager.DatabaseConfig.use_mysql){
+ try {
+ Class.forName("com.mysql.cj.jdbc.Driver");
+ } catch (ClassNotFoundException e) {
+ driver = ("com.mysql.jdbc.Driver");
+ return;
+ }
+ driver = ("com.mysql.cj.jdbc.Driver");
+ }else {
+ driver = ("org.sqlite.JDBC");
+ }
+ }
+
+ public boolean setGlobalConnection() {
+ setDriver();
+ try {
+ if (ConfigManager.DatabaseConfig.enable_pool) {
+ createNewHikariConfiguration();
+ Connection connection = getConnection();
+ closeHikariConnection(connection);
+ } else {
+ Class.forName(driver);
+ if(ConfigManager.DatabaseConfig.use_mysql){
+ connection = DriverManager.getConnection(ConfigManager.DatabaseConfig.url, ConfigManager.DatabaseConfig.user, ConfigManager.DatabaseConfig.password);
+ }else {
+ connection = DriverManager.getConnection("jdbc:sqlite:" + userdata.toString());
+ }
+ }
+ if (secon) {
+ AdventureManager.consoleMessage("[CustomNameplates] Successfully reconnect to SQL!");
+ } else {
+ secon = true;
+ }
+ return true;
+ } catch (SQLException e) {
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to connect to SQL!");
+ e.printStackTrace();
+ close();
+ return false;
+ } catch (ClassNotFoundException e) {
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to load JDBC driver");
+ }
+ return false;
+ }
+
+ public Connection getConnectionAndCheck() {
+ if (!canConnect()) {
+ return null;
+ }
+ try {
+ return getConnection();
+ } catch (SQLException e) {
+ if (isfirstry) {
+ isfirstry = false;
+ close();
+ return getConnectionAndCheck();
+ } else {
+ isfirstry = true;
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to connect to SQL!");
+ close();
+ e.printStackTrace();
+ return null;
+ }
+ }
+ }
+
+ public Connection getConnection() throws SQLException {
+ if (ConfigManager.DatabaseConfig.enable_pool) {
+ return hikari.getConnection();
+ } else {
+ return connection;
+ }
+ }
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ public boolean canConnect() {
+ try {
+ if (ConfigManager.DatabaseConfig.enable_pool) {
+ if (hikari == null) {
+ return setGlobalConnection();
+ }
+ if (hikari.isClosed()) {
+ return setGlobalConnection();
+ }
+ } else {
+ if (connection == null) {
+ return setGlobalConnection();
+ }
+ if (connection.isClosed()) {
+ return setGlobalConnection();
+ }
+ if (ConfigManager.DatabaseConfig.use_mysql) {
+ if (!connection.isValid(waitTimeOut)) {
+ secon = false;
+ return setGlobalConnection();
+ }
+ }
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ public void closeHikariConnection(Connection connection) {
+ if (!ConfigManager.DatabaseConfig.enable_pool) {
+ return;
+ }
+ try {
+ connection.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void close() {
+ try {
+ if (connection != null) {
+ connection.close();
+ }
+ if (hikari != null) {
+ hikari.close();
+ }
+ } catch (SQLException e) {
+ AdventureManager.consoleMessage("[CustomNameplates] Error! Failed to close SQL!");
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..f36545c
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,47 @@
+config:
+ # Messages Language
+ lang: en
+
+ # Your namespace
+ # "minecraft" is also
+ namespace: "nameplates"
+
+ # Font Name
+ # Recommended not to set "default" when you are using "minecraft" as your namespace.
+ # You can use font like "nameplate" instead.
+ font: "default"
+
+ # Customize the folder where png files should be generated.
+ # This is useful for those who want to keep their resource pack structure in order.
+ folder-path: 'font\nameplates\'
+
+ # The initial character of all nameplates.
+ # 뀁 is the first character of Korean \ub001.
+ # You can specify any Unicode Character as you want.
+ start-char: '뀁'
+
+ # The duration (in seconds) that the nameplate preview will last for.
+ preview-duration: 5
+
+ # The default nameplate.
+ # "none" represents no default nameplate.
+ default-nameplate: 'none'
+
+ # Recommended to keep this true, otherwise players may see weird characters
+ # above their heads if they have not accepted the resource pack.
+ # This option supports proxy when using Mysql
+ show-after-load-resourcepack: true
+
+ # Placeholder based prefix and suffix system. When enabled, it is recommended
+ # to use PlaceholderAPI to be able to use this feature to the fullest extent.
+ # keep it empty if you don't want to enable this feature.
+ prefix: '%vault_prefix%'
+ suffix: ''
+
+ # Should the plugin hook into other plugins
+ integrations:
+ # When enabled, the plugin will be able to parse prefix and suffix
+ PlaceholderAPI: false
+ # When enabled, the plugin will automatically place the nameplates
+ # folder into the resource pack generated by ItemsAdder.
+ ItemsAdder: false
\ No newline at end of file
diff --git a/src/main/resources/database.yml b/src/main/resources/database.yml
new file mode 100644
index 0000000..1197e54
--- /dev/null
+++ b/src/main/resources/database.yml
@@ -0,0 +1,28 @@
+settings:
+ # Valid Options: SQLite MYSQL
+ # Only MYSQL supports pool.
+ # Restart is a must when you change database configuration.
+ storage-mode: SQLite
+ use-pool: false
+ disable-async: false
+
+# MySQL settings
+MySQL:
+ host: localhost
+ port: 3306
+ user: root
+ password: password
+ database: minecraft
+ table-name: nameplates_data
+ property:
+ use-ssl: false
+ encoding: utf8
+ timezone: ''
+ allowPublicKeyRetrieval: false
+
+# Connection pool settings
+Pool-Settings:
+ maximum-pool-size: 10
+ minimum-idle: 10
+ maximum-lifetime: 180000
+ idle-timeout: 60000
\ No newline at end of file
diff --git a/src/main/resources/messages/messages_cn.yml b/src/main/resources/messages/messages_cn.yml
new file mode 100644
index 0000000..950bcee
--- /dev/null
+++ b/src/main/resources/messages/messages_cn.yml
@@ -0,0 +1,16 @@
+messages:
+ prefix: '[CustomNameplates] '
+ no-perm: '你没有权限!'
+ lack-args: '参数不足!'
+ reload: '插件已重载!'
+ equip: '你已佩戴铭牌 {Nameplate} [点击预览]'
+ force-equip: '你已为玩家 {Player} 强制佩戴了铭牌 {Nameplate}'
+ unequip: '你已卸下铭牌!'
+ force-unequip: '你已强制卸下玩家 {Player} 的铭牌!'
+ preview: '你正在预览你的铭牌(切换到第三人称)'
+ not-exist: '那个铭牌不存在!'
+ not-online: '玩家 {Player} 不在线!'
+ no-console: '这个指令不能由控制台执行!'
+ not-available: '你还未拥有这个铭牌!'
+ available: '可用铭牌: {Nameplates}'
+ cooldown: '上一个预览还没结束!'
\ No newline at end of file
diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml
new file mode 100644
index 0000000..7ed62bc
--- /dev/null
+++ b/src/main/resources/messages/messages_en.yml
@@ -0,0 +1,16 @@
+messages:
+ prefix: '[CustomNameplates] '
+ no-perm: 'No Permission!'
+ lack-args: 'Insufficient parameters!'
+ reload: 'Reloaded!'
+ equip: 'Successfully equipped nameplate {Nameplate}. [click to preview]'
+ force-equip: 'Successfully equipped nameplate {Nameplate} to {Player}.'
+ unequip: 'You have removed your nameplate!'
+ force-unequip: 'Successfully removed {Player}''s nameplate!'
+ preview: 'Now previewing your nameplate (go to third person to view)!'
+ not-exist: 'This nameplate does not exist!'
+ not-online: 'Player {Player} is not online!'
+ no-console: 'This command can be only sent by player!'
+ not-available: 'This nameplate is currently not available!'
+ available: 'Available nameplates: {Nameplates}.'
+ cooldown: 'Previewing is still Ongoing!'
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..d311fe3
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,17 @@
+name: CustomNameplates
+version: '${version}'
+main: net.momirealms.customnameplates.CustomNameplates
+api-version: 1.16
+authors: [ XiaoMoMi ]
+depend:
+ - ProtocolLib
+ - PlaceholderAPI
+softdepend:
+ - ItemsAdder
+ - Oraxen
+ - PlaceholderAPI
+commands:
+ customnameplates:
+ usage: /customnameplates help
+ aliases:
+ - nameplates
\ No newline at end of file
diff --git a/src/main/resources/resources/cat.png b/src/main/resources/resources/cat.png
new file mode 100644
index 0000000..8726888
Binary files /dev/null and b/src/main/resources/resources/cat.png differ
diff --git a/src/main/resources/resources/cheems.png b/src/main/resources/resources/cheems.png
new file mode 100644
index 0000000..0e236bf
Binary files /dev/null and b/src/main/resources/resources/cheems.png differ
diff --git a/src/main/resources/resources/egg.png b/src/main/resources/resources/egg.png
new file mode 100644
index 0000000..71fda1d
Binary files /dev/null and b/src/main/resources/resources/egg.png differ
diff --git a/src/main/resources/resources/wither.png b/src/main/resources/resources/wither.png
new file mode 100644
index 0000000..663857a
Binary files /dev/null and b/src/main/resources/resources/wither.png differ
diff --git a/src/main/resources/space_split.png b/src/main/resources/space_split.png
new file mode 100644
index 0000000..6e70543
Binary files /dev/null and b/src/main/resources/space_split.png differ