+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.api.manager;
+
+import net.momirealms.customnameplates.api.requirement.Condition;
+import net.momirealms.customnameplates.api.requirement.Requirement;
+import net.momirealms.customnameplates.api.requirement.RequirementFactory;
+import org.bukkit.configuration.ConfigurationSection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface RequirementManager {
+
+ /**
+ * Registers a custom requirement type with its corresponding factory.
+ *
+ * @param type The type identifier of the requirement.
+ * @param requirementFactory The factory responsible for creating instances of the requirement.
+ * @return True if registration was successful, false if the type is already registered.
+ */
+ boolean registerRequirement(String type, RequirementFactory requirementFactory);
+
+ /**
+ * Unregisters a custom requirement type.
+ *
+ * @param type The type identifier of the requirement to unregister.
+ * @return True if unregistration was successful, false if the type is not registered.
+ */
+ boolean unregisterRequirement(String type);
+
+ /**
+ * Retrieves an array of requirements based on a configuration section.
+ *
+ * @param section The configuration section containing requirement definitions.
+ * @return An array of Requirement objects based on the configuration section
+ */
+ @Nullable Requirement[] getRequirements(ConfigurationSection section);
+
+ /**
+ * Retrieves a Requirement object based on a configuration section and advanced flag.
+ *
+ * requirement_1: <- section
+ * type: xxx
+ * value: xxx
+ *
+ * @param section The configuration section containing requirement definitions.
+ * @return A Requirement object based on the configuration section, or an EmptyRequirement if the section is null or invalid.
+ */
+ @NotNull Requirement getRequirement(ConfigurationSection section);
+
+ /**
+ * Gets a requirement based on the provided type and value.
+ * If a valid RequirementFactory is found for the type, it is used to create the requirement.
+ * If no factory is found, a warning is logged, and an empty requirement instance is returned.
+ *
+ * world: <- type
+ * - world <- value
+ *
+ * @param type The type representing the requirement type.
+ * @param value The value associated with the requirement.
+ * @return A Requirement instance based on the type and value, or an EmptyRequirement if the type is invalid.
+ */
+ @NotNull Requirement getRequirement(String type, Object value);
+
+ /**
+ * Retrieves a RequirementFactory based on the specified requirement type.
+ *
+ * @param type The requirement type for which to retrieve a factory.
+ * @return A RequirementFactory for the specified type, or null if no factory is found.
+ */
+ @Nullable RequirementFactory getRequirementFactory(String type);
+
+ /**
+ * Checks if an array of requirements is met for a given condition.
+ *
+ * @param condition The Condition object to check against the requirements.
+ * @param requirements An array of Requirement instances to be evaluated.
+ * @return True if all requirements are met, false otherwise. Returns true if the requirements array is null.
+ */
+ static boolean isRequirementMet(Condition condition, Requirement... requirements) {
+ if (requirements == null) return true;
+ for (Requirement requirement : requirements) {
+ if (!requirement.isConditionMet(condition)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/manager/ResourcePackManager.java b/api/src/main/java/net/momirealms/customnameplates/api/manager/ResourcePackManager.java
new file mode 100644
index 0000000..4df85fd
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/manager/ResourcePackManager.java
@@ -0,0 +1,15 @@
+package net.momirealms.customnameplates.api.manager;
+
+import java.io.File;
+
+public interface ResourcePackManager {
+
+ /**
+ * Generate the resource pack
+ */
+ void generateResourcePack();
+
+ void deleteDirectory(File file);
+
+ String native2ascii(char c);
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/manager/StorageManager.java b/api/src/main/java/net/momirealms/customnameplates/api/manager/StorageManager.java
new file mode 100644
index 0000000..5165ca6
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/manager/StorageManager.java
@@ -0,0 +1,88 @@
+package net.momirealms.customnameplates.api.manager;
+
+import net.momirealms.customnameplates.api.data.OnlineUser;
+import net.momirealms.customnameplates.api.data.PlayerData;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+public interface StorageManager {
+
+ /**
+ * Get online users
+ *
+ * @return online users
+ */
+ Collection getOnlineUsers();
+
+ /**
+ * Get a player's data by uuid
+ * The player can be an offline one
+ *
+ * @param uuid uuid
+ * @return player data
+ */
+ CompletableFuture> getPlayerData(UUID uuid);
+
+ /**
+ * Save online players' data
+ *
+ * @param uuid uuid
+ * @return success or not
+ */
+ CompletableFuture saveOnlinePlayerData(UUID uuid);
+
+ /**
+ * Save specified data
+ *
+ * @param uuid uuid
+ * @param playerData playerData
+ * @return success or not
+ */
+ CompletableFuture savePlayerData(UUID uuid, PlayerData playerData);
+
+ /**
+ * Get an online user by uuid
+ *
+ * @param uuid uuid
+ * @return online user
+ */
+ Optional getOnlineUser(UUID uuid);
+
+ /**
+ * Get player data from json
+ *
+ * @param json json
+ * @return data
+ */
+ @NotNull
+ PlayerData fromJson(String json);
+
+ /**
+ * Get player data from bytes
+ *
+ * @param data data
+ * @return data
+ */
+ PlayerData fromBytes(byte[] data);
+
+ /**
+ * Convert player data to bytes
+ *
+ * @param playerData playerData
+ * @return bytes
+ */
+ byte[] toBytes(PlayerData playerData);
+
+ /**
+ * Convert player data to json
+ *
+ * @param playerData playerData
+ * @return json
+ */
+ @NotNull
+ String toJson(@NotNull PlayerData playerData);
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/manager/TeamManager.java b/api/src/main/java/net/momirealms/customnameplates/api/manager/TeamManager.java
new file mode 100644
index 0000000..14e4721
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/manager/TeamManager.java
@@ -0,0 +1,47 @@
+package net.momirealms.customnameplates.api.manager;
+
+import net.kyori.adventure.text.Component;
+import net.momirealms.customnameplates.api.mechanic.team.TeamColor;
+import net.momirealms.customnameplates.api.mechanic.team.TeamTagVisibility;
+import org.bukkit.entity.Player;
+
+public interface TeamManager {
+
+ /**
+ * Create team for a player
+ *
+ * @param player player
+ */
+ void createTeam(Player player);
+
+ /**
+ * Create a team for a player on proxy
+ *
+ * @param player player
+ */
+ void createProxyTeam(Player player);
+
+ /**
+ * Remove a team for a player
+ *
+ * @param player player
+ */
+ void removeTeam(Player player);
+
+ /**
+ * Remove a team for a player on proxy
+ *
+ * @param player player
+ */
+ void removeProxyTeam(Player player);
+
+ void updateTeam(Player owner, Player viewer, Component prefix, Component suffix, TeamColor color, TeamTagVisibility visibility);
+
+ /**
+ * Get the team player in
+ *
+ * @param player player
+ * @return team name
+ */
+ String getTeamName(Player player);
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/manager/TeamTagManager.java b/api/src/main/java/net/momirealms/customnameplates/api/manager/TeamTagManager.java
new file mode 100644
index 0000000..ffa408d
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/manager/TeamTagManager.java
@@ -0,0 +1,29 @@
+package net.momirealms.customnameplates.api.manager;
+
+import net.momirealms.customnameplates.api.mechanic.tag.team.TeamPlayer;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.UUID;
+
+public interface TeamTagManager {
+
+ /**
+ * Create team tag for a player
+ * If failed, the return value would be null
+ * This happens when there already exists a team tag for a player
+ *
+ * @return team tag
+ */
+ @Nullable
+ TeamPlayer createTagForPlayer(Player player, String prefix, String suffix);
+
+ /**
+ * Remove a team tag from map by uuid
+ *
+ * @param uuid uuid
+ * @return team tag
+ */
+ @Nullable
+ TeamPlayer removeTeamPlayerFromMap(UUID uuid);
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/manager/UnlimitedTagManager.java b/api/src/main/java/net/momirealms/customnameplates/api/manager/UnlimitedTagManager.java
new file mode 100644
index 0000000..305e03c
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/manager/UnlimitedTagManager.java
@@ -0,0 +1,56 @@
+package net.momirealms.customnameplates.api.manager;
+
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.NamedEntity;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.UnlimitedObject;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.UnlimitedPlayer;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.UnlimitedTagSetting;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface UnlimitedTagManager {
+
+ /**
+ * Create a named entity (ArmorStand) for a player
+ * To apply the changes, you should add it the named player instance
+ *
+ * @param player player
+ * @param setting setting
+ * @return named entity
+ */
+ @NotNull
+ NamedEntity createNamedEntity(UnlimitedPlayer player, UnlimitedTagSetting setting);
+
+ /**
+ * Create unlimited tags for a player
+ * If failed, the return value would be null
+ * This happens when there already exists an UnlimitedObject for a player
+ *
+ * @param player player
+ * @param settings settings
+ * @return unlimited tag
+ */
+ @Nullable
+ UnlimitedPlayer createTagForPlayer(Player player, List settings);
+
+ /**
+ * Remove UnlimitedObject from map by uuid
+ *
+ * @param uuid uuid
+ * @return The removed unlimited object
+ */
+ @Nullable
+ UnlimitedObject removeUnlimitedObjectFromMap(UUID uuid);
+
+ /**
+ * Get an UnlimitedObject from map by uuid
+ *
+ * @param uuid uuid
+ * @return The unlimited object
+ */
+ @Nullable
+ UnlimitedObject getUnlimitedObject(UUID uuid);
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/manager/VersionManager.java b/api/src/main/java/net/momirealms/customnameplates/api/manager/VersionManager.java
new file mode 100644
index 0000000..261ae9d
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/manager/VersionManager.java
@@ -0,0 +1,26 @@
+package net.momirealms.customnameplates.api.manager;
+
+import java.util.concurrent.CompletionStage;
+
+public interface VersionManager {
+
+ boolean isFolia();
+
+ String getServerVersion();
+
+ CompletionStage checkUpdate();
+
+ boolean isVersionNewerThan1_19_R2();
+
+ boolean isVersionNewerThan1_20();
+
+ boolean isVersionNewerThan1_20_R2();
+
+ String getPluginVersion();
+
+ boolean isLatest();
+
+ boolean isVersionNewerThan1_19();
+
+ int getPackFormat();
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/manager/WidthManager.java b/api/src/main/java/net/momirealms/customnameplates/api/manager/WidthManager.java
new file mode 100644
index 0000000..96b17e7
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/manager/WidthManager.java
@@ -0,0 +1,5 @@
+package net.momirealms.customnameplates.api.manager;
+
+public interface WidthManager {
+
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/background/BackGround.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/background/BackGround.java
new file mode 100644
index 0000000..785c55b
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/background/BackGround.java
@@ -0,0 +1,212 @@
+package net.momirealms.customnameplates.api.mechanic.background;
+
+import net.momirealms.customnameplates.api.mechanic.character.ConfiguredChar;
+import net.momirealms.customnameplates.api.mechanic.font.OffsetFont;
+
+public class BackGround {
+
+ private ConfiguredChar left, offset_1, offset_2, offset_4, offset_8, offset_16, offset_32, offset_64, offset_128, right;
+ private int leftMargin, rightMargin;
+
+ private BackGround() {
+ }
+
+ public BackGround(
+ ConfiguredChar left,
+ ConfiguredChar offset_1,
+ ConfiguredChar offset_2,
+ ConfiguredChar offset_4,
+ ConfiguredChar offset_8,
+ ConfiguredChar offset_16,
+ ConfiguredChar offset_32,
+ ConfiguredChar offset_64,
+ ConfiguredChar offset_128,
+ ConfiguredChar right,
+ int leftMargin,
+ int rightMargin
+ ) {
+ this.left = left;
+ this.offset_1 = offset_1;
+ this.offset_2 = offset_2;
+ this.offset_4 = offset_4;
+ this.offset_8 = offset_8;
+ this.offset_16 = offset_16;
+ this.offset_32 = offset_32;
+ this.offset_64 = offset_64;
+ this.offset_128 = offset_128;
+ this.right = right;
+ this.leftMargin = leftMargin;
+ this.rightMargin = rightMargin;
+ }
+
+ public ConfiguredChar getLeft() {
+ return left;
+ }
+
+ public ConfiguredChar getOffset_1() {
+ return offset_1;
+ }
+
+ public ConfiguredChar getOffset_2() {
+ return offset_2;
+ }
+
+ public ConfiguredChar getOffset_4() {
+ return offset_4;
+ }
+
+ public ConfiguredChar getOffset_8() {
+ return offset_8;
+ }
+
+ public ConfiguredChar getOffset_16() {
+ return offset_16;
+ }
+
+ public ConfiguredChar getOffset_32() {
+ return offset_32;
+ }
+
+ public ConfiguredChar getOffset_64() {
+ return offset_64;
+ }
+
+ public ConfiguredChar getOffset_128() {
+ return offset_128;
+ }
+
+ public ConfiguredChar getRight() {
+ return right;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public String getBackGroundImage(int n) {
+ String offset = OffsetFont.getShortestNegChars(n + rightMargin + 2);
+ n = n + leftMargin + rightMargin + 2;
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(left.getCharacter());
+ while (n >= 128) {
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(offset_128.getCharacter());
+ n -= 128;
+ }
+ if (n - 64 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(offset_64.getCharacter());
+ n -= 64;
+ }
+ if (n - 32 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(offset_32.getCharacter());
+ n -= 32;
+ }
+ if (n - 16 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(offset_16.getCharacter());
+ n -= 16;
+ }
+ if (n - 8 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(offset_8.getCharacter());
+ n -= 8;
+ }
+ if (n - 4 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(offset_4.getCharacter());
+ n -= 4;
+ }
+ if (n - 2 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(offset_2.getCharacter());
+ n -= 2;
+ }
+ if (n - 1 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(offset_1.getCharacter());
+ }
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(right.getCharacter());
+ stringBuilder.append(offset);
+ return stringBuilder.toString();
+ }
+
+ public static class Builder {
+
+ private final BackGround backGround;
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder() {
+ this.backGround = new BackGround();
+ }
+
+ public Builder left(ConfiguredChar configuredChar) {
+ backGround.left = configuredChar;
+ return this;
+ }
+
+ public Builder right(ConfiguredChar configuredChar) {
+ backGround.right = configuredChar;
+ return this;
+ }
+
+ public Builder offset_1(ConfiguredChar configuredChar) {
+ backGround.offset_1 = configuredChar;
+ return this;
+ }
+
+ public Builder offset_2(ConfiguredChar configuredChar) {
+ backGround.offset_2 = configuredChar;
+ return this;
+ }
+
+ public Builder offset_4(ConfiguredChar configuredChar) {
+ backGround.offset_4 = configuredChar;
+ return this;
+ }
+
+ public Builder offset_8(ConfiguredChar configuredChar) {
+ backGround.offset_8 = configuredChar;
+ return this;
+ }
+
+ public Builder offset_16(ConfiguredChar configuredChar) {
+ backGround.offset_16 = configuredChar;
+ return this;
+ }
+
+ public Builder offset_32(ConfiguredChar configuredChar) {
+ backGround.offset_32 = configuredChar;
+ return this;
+ }
+
+ public Builder offset_64(ConfiguredChar configuredChar) {
+ backGround.offset_64 = configuredChar;
+ return this;
+ }
+
+ public Builder offset_128(ConfiguredChar configuredChar) {
+ backGround.offset_128 = configuredChar;
+ return this;
+ }
+
+ public Builder leftMargin(int margin) {
+ backGround.leftMargin = margin;
+ return this;
+ }
+
+ public Builder rightMargin(int margin) {
+ backGround.rightMargin = margin;
+ return this;
+ }
+
+ public BackGround build() {
+ return backGround;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/character/CharacterArranger.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/character/CharacterArranger.java
new file mode 100644
index 0000000..aac98ec
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/character/CharacterArranger.java
@@ -0,0 +1,25 @@
+package net.momirealms.customnameplates.api.mechanic.character;
+
+public class CharacterArranger {
+
+ public static char currentChar;
+
+ public static void increase() {
+ currentChar = (char) (currentChar + '\u0001');
+ }
+
+ public static char getAndIncrease() {
+ char temp = currentChar;
+ increase();
+ return temp;
+ }
+
+ public static char increaseAndGet() {
+ increase();
+ return currentChar;
+ }
+
+ public static void reset(char c) {
+ currentChar = c;
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/character/ConfiguredChar.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/character/ConfiguredChar.java
new file mode 100644
index 0000000..cc70116
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/character/ConfiguredChar.java
@@ -0,0 +1,107 @@
+package net.momirealms.customnameplates.api.mechanic.character;
+
+import net.momirealms.customnameplates.api.util.LogUtils;
+
+public class ConfiguredChar {
+
+ private char character;
+ private String pngFile;
+ private int height;
+ private int width;
+ private int ascent;
+
+ private ConfiguredChar() {
+
+ }
+
+ public ConfiguredChar(char character, String pngFile, int height, int width, int ascent) {
+ this.character = character;
+ this.pngFile = pngFile;
+ this.height = height;
+ this.width = width;
+ this.ascent = ascent;
+ }
+
+ public char getCharacter() {
+ return character;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public String getPngFile() {
+ return pngFile;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getAscent() {
+ return ascent;
+ }
+
+ public String getFile() {
+ return pngFile + ".png";
+ }
+
+ public static class Builder {
+
+ private final ConfiguredChar configuredChar;
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder() {
+ this.configuredChar = new ConfiguredChar();
+ }
+
+ public Builder character(char character) {
+ configuredChar.character = character;
+ return this;
+ }
+
+ public Builder png(String png) {
+ configuredChar.pngFile = png;
+ return this;
+ }
+
+ public Builder height(int height) {
+ configuredChar.height = height;
+ return this;
+ }
+
+ public Builder ascent(int ascent) {
+ configuredChar.ascent = ascent;
+ if (ascent >= configuredChar.height) {
+ LogUtils.warn("Invalid config for " + configuredChar.pngFile);
+ LogUtils.warn("Ascent " + ascent + " should be no higher than Height " + configuredChar.height);
+ }
+ return this;
+ }
+
+ public Builder descent(int descent) {
+ if (descent < 0) {
+ LogUtils.warn("Invalid config for " + configuredChar.pngFile);
+ LogUtils.warn("Descent " + descent + " should be no lower than 0");
+ }
+ configuredChar.ascent = configuredChar.height - descent;
+ return this;
+ }
+
+ public Builder width(int width) {
+ configuredChar.width = width;
+ return this;
+ }
+
+ public ConfiguredChar build() {
+ return configuredChar;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/font/OffsetFont.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/font/OffsetFont.java
new file mode 100644
index 0000000..4d24ab1
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/font/OffsetFont.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.api.mechanic.font;
+
+public enum OffsetFont {
+
+ 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),
+ POS_1('\uf811', 1, -1),
+ POS_2('\uf812', 2, 1),
+ POS_3('\uf813', 3, 2),
+ POS_4('\uf814', 4, 3),
+ POS_5('\uf815', 5, 4),
+ POS_6('\uf816', 6, 5),
+ POS_7('\uf817', 7, 6),
+ POS_8('\uf818', 8, 7),
+ POS_16('\uf819', 16, 15),
+ POS_32('\uf81a', 32, 31),
+ POS_64('\uf81b', 64, 63),
+ POS_128('\uf81c', 128, 127);
+
+ private final char character;
+ private final int space;
+ private final int height;
+
+ OffsetFont(char character, int space, int height) {
+ this.character = character;
+ this.space = space;
+ this.height = height;
+ }
+
+ public char getCharacter() {
+ return this.character;
+ }
+
+ public int getSpace() {
+ return this.space;
+ }
+
+ public int getHeight() {
+ return this.height;
+ }
+
+ public static String getOffsetChars(int offset) {
+ if (offset >= 0) {
+ return getShortestPosChars(offset);
+ } else {
+ return getShortestNegChars(-offset);
+ }
+ }
+
+ public static String getShortestNegChars(int n) {
+ StringBuilder stringBuilder = new StringBuilder();
+ while (n >= 128) {
+ stringBuilder.append(OffsetFont.NEG_128.getCharacter());
+ n -= 128;
+ }
+ if (n - 64 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_64.getCharacter());
+ n -= 64;
+ }
+ if (n - 32 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_32.getCharacter());
+ n -= 32;
+ }
+ if (n - 16 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_16.getCharacter());
+ n -= 16;
+ }
+ if (n - 8 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_8.getCharacter());
+ n -= 8;
+ }
+ if (n - 7 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_7.getCharacter());
+ n -= 7;
+ }
+ if (n - 6 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_6.getCharacter());
+ n -= 6;
+ }
+ if (n - 5 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_5.getCharacter());
+ n -= 5;
+ }
+ if (n - 4 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_4.getCharacter());
+ n -= 4;
+ }
+ if (n - 3 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_3.getCharacter());
+ n -= 3;
+ }
+ if (n - 2 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_2.getCharacter());
+ n -= 2;
+ }
+ if (n - 1 >= 0) {
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ }
+ return stringBuilder.toString();
+ }
+
+ public static String getShortestPosChars(int n) {
+ StringBuilder stringBuilder = new StringBuilder();
+ while (n >= 128) {
+ stringBuilder.append(OffsetFont.POS_128.getCharacter());
+ n -= 128;
+ }
+ if (n - 64 >= 0) {
+ stringBuilder.append(OffsetFont.POS_64.getCharacter());
+ n -= 64;
+ }
+ if (n - 32 >= 0) {
+ stringBuilder.append(OffsetFont.POS_32.getCharacter());
+ n -= 32;
+ }
+ if (n - 16 >= 0) {
+ stringBuilder.append(OffsetFont.POS_16.getCharacter());
+ n -= 16;
+ }
+ if (n - 8 >= 0) {
+ stringBuilder.append(OffsetFont.POS_8.getCharacter());
+ n -= 8;
+ }
+ if (n - 7 >= 0) {
+ stringBuilder.append(OffsetFont.POS_7.getCharacter());
+ n -= 7;
+ }
+ if (n - 6 >= 0) {
+ stringBuilder.append(OffsetFont.POS_6.getCharacter());
+ n -= 6;
+ }
+ if (n - 5 >= 0) {
+ stringBuilder.append(OffsetFont.POS_5.getCharacter());
+ n -= 5;
+ }
+ if (n - 4 >= 0) {
+ stringBuilder.append(OffsetFont.POS_4.getCharacter());
+ n -= 4;
+ }
+ if (n - 3 >= 0) {
+ stringBuilder.append(OffsetFont.POS_3.getCharacter());
+ n -= 3;
+ }
+ if (n - 2 >= 0) {
+ stringBuilder.append(OffsetFont.POS_2.getCharacter());
+ n -= 2;
+ }
+ if (n - 1 >= 0) {
+ stringBuilder.append(OffsetFont.POS_1.getCharacter());
+ }
+ return stringBuilder.toString();
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/misc/ViewerText.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/misc/ViewerText.java
new file mode 100644
index 0000000..ad7af9b
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/misc/ViewerText.java
@@ -0,0 +1,111 @@
+package net.momirealms.customnameplates.api.mechanic.misc;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ViewerText {
+
+ private final Player owner;
+ private String processedText;
+ private final ClaimedText[] placeholders;
+ private final ConcurrentHashMap valueMap;
+
+ public ViewerText(Player owner, String rawText) {
+ this.processedText = rawText;
+ this.valueMap = new ConcurrentHashMap<>();
+ this.owner = owner;
+ List placeholders = CustomNameplatesPlugin.get().getPlaceholderManager().detectPlaceholders(rawText);
+ this.placeholders = new ClaimedText[placeholders.size()];
+ int i = 0;
+ for (String placeholder : placeholders) {
+ processedText = processedText.replace(placeholder, "%s");
+ if (placeholder.startsWith("%viewer_")) {
+ this.placeholders[i] = new ClaimedText(null, "%" + placeholder.substring("%viewer_".length()));
+ } else {
+ this.placeholders[i] = new ClaimedText(owner, placeholder);
+ }
+ i++;
+ }
+ }
+
+ public void updateForOwner() {
+ for (ClaimedText text : placeholders) {
+ text.update();
+ }
+ }
+
+ public boolean updateForViewer(Player viewer) {
+ String string;
+ if ("%s".equals(processedText)) {
+ string = placeholders[0].getValue(viewer);
+ } else if (placeholders.length != 0) {
+ Object[] values = new String[placeholders.length];
+ for (int i = 0; i < placeholders.length; i++) {
+ values[i] = placeholders[i].getValue(viewer);
+ }
+ string = String.format(processedText, values);
+ } else {
+ string = processedText;
+ }
+ var uuid = viewer.getUniqueId();
+ if (!valueMap.containsKey(uuid)) {
+ valueMap.put(uuid, string);
+ return true;
+ }
+ String previousValue = valueMap.get(uuid);
+ if (!previousValue.equals(string)) {
+ valueMap.put(uuid, string);
+ return true;
+ }
+ return false;
+ }
+
+ public void removeViewer(Player viewer) {
+ valueMap.remove(viewer.getUniqueId());
+ }
+
+ public String getProcessedText() {
+ return processedText;
+ }
+
+ public String getLatestValue(Player viewer) {
+ return valueMap.get(viewer.getUniqueId());
+ }
+
+ public Entity getOwner() {
+ return owner;
+ }
+
+ public static class ClaimedText {
+
+ private final String placeholder;
+ private final Player owner;
+ private String latestValue;
+
+ public ClaimedText(Player owner, String placeholder) {
+ this.placeholder = placeholder;
+ this.owner = owner;
+ this.latestValue = null;
+ this.update();
+ }
+
+ public void update() {
+ if (owner == null) return;
+ this.latestValue = PlaceholderAPI.setPlaceholders(owner, placeholder);
+ }
+
+ public String getValue(Player viewer) {
+ return Objects.requireNonNullElseGet(
+ latestValue,
+ () -> PlaceholderAPI.setPlaceholders(owner == null ? viewer : owner, placeholder)
+ );
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/nameplate/CachedNameplate.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/nameplate/CachedNameplate.java
new file mode 100644
index 0000000..40c1e6b
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/nameplate/CachedNameplate.java
@@ -0,0 +1,70 @@
+package net.momirealms.customnameplates.api.mechanic.nameplate;
+
+import net.momirealms.customnameplates.api.mechanic.team.TeamColor;
+
+public class CachedNameplate {
+
+ private TeamColor teamColor;
+ private String tagPrefix;
+ private String tagSuffix;
+ private String namePrefix;
+ private String nameSuffix;
+ private String playerName;
+
+ public CachedNameplate() {
+ this.tagPrefix = "";
+ this.tagSuffix = "";
+ this.namePrefix = "";
+ this.nameSuffix = "";
+ this.playerName = "";
+ this.teamColor = TeamColor.WHITE;
+ }
+
+ public String getTagPrefix() {
+ return tagPrefix;
+ }
+
+ public void setTagPrefix(String prefix) {
+ this.tagPrefix = prefix;
+ }
+
+ public String getTagSuffix() {
+ return tagSuffix;
+ }
+
+ public void setTagSuffix(String suffix) {
+ this.tagSuffix = suffix;
+ }
+
+ public void setNamePrefix(String namePrefix) {
+ this.namePrefix = namePrefix;
+ }
+
+ public void setNameSuffix(String nameSuffix) {
+ this.nameSuffix = nameSuffix;
+ }
+
+ public String getNamePrefix() {
+ return namePrefix;
+ }
+
+ public String getNameSuffix() {
+ return nameSuffix;
+ }
+
+ public TeamColor getTeamColor() {
+ return teamColor;
+ }
+
+ public void setTeamColor(TeamColor teamColor) {
+ this.teamColor = teamColor;
+ }
+
+ public String getPlayerName() {
+ return playerName;
+ }
+
+ public void setPlayerName(String playerName) {
+ this.playerName = playerName;
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/nameplate/Nameplate.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/nameplate/Nameplate.java
new file mode 100644
index 0000000..bdd40e3
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/nameplate/Nameplate.java
@@ -0,0 +1,166 @@
+package net.momirealms.customnameplates.api.mechanic.nameplate;
+
+import net.momirealms.customnameplates.api.mechanic.character.ConfiguredChar;
+import net.momirealms.customnameplates.api.mechanic.font.OffsetFont;
+import net.momirealms.customnameplates.api.mechanic.team.TeamColor;
+import net.momirealms.customnameplates.api.util.FontUtils;
+
+public class Nameplate {
+
+ private String displayName;
+ private TeamColor teamColor;
+ private String namePrefix;
+ private String nameSuffix;
+ private ConfiguredChar left;
+ private ConfiguredChar middle;
+ private ConfiguredChar right;
+
+ private Nameplate() {
+ }
+
+ public Nameplate(
+ String displayName,
+ TeamColor teamColor,
+ String namePrefix,
+ String nameSuffix,
+ ConfiguredChar left,
+ ConfiguredChar middle,
+ ConfiguredChar right
+ ) {
+ this.displayName = displayName;
+ this.teamColor = teamColor;
+ this.left = left;
+ this.middle = middle;
+ this.right = right;
+ this.namePrefix = namePrefix;
+ this.nameSuffix = nameSuffix;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public TeamColor getTeamColor() {
+ return teamColor;
+ }
+
+ public ConfiguredChar getLeft() {
+ return left;
+ }
+
+ public ConfiguredChar getMiddle() {
+ return middle;
+ }
+
+ public ConfiguredChar getRight() {
+ return right;
+ }
+
+ public String getNamePrefix() {
+ if (teamColor == TeamColor.NONE) {
+ return "";
+ }
+ if (teamColor == TeamColor.CUSTOM) {
+ return namePrefix;
+ }
+ return "<" + teamColor.name() + ">";
+ }
+
+ public String getNameSuffix() {
+ if (teamColor == TeamColor.NONE) {
+ return "";
+ }
+ if (teamColor == TeamColor.CUSTOM) {
+ return nameSuffix;
+ }
+ return "" + teamColor.name() + ">";
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public String getPrefixWithFont(int textWidth) {
+ return FontUtils.surroundNameplateFont(getPrefix(textWidth));
+ }
+
+ public String getPrefix(int textWidth) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(OffsetFont.getShortestNegChars(textWidth % 2 == 0 ? textWidth + left.getWidth() : textWidth + left.getWidth() + 1));
+ stringBuilder.append(left.getCharacter());
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ int mid_amount = (textWidth - 1) / (middle.getWidth());
+ if (mid_amount != 0) {
+ for (int i = 0; i < mid_amount; i++) {
+ stringBuilder.append(middle.getCharacter());
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ }
+ stringBuilder.append(OffsetFont.getShortestNegChars(middle.getWidth() - textWidth % middle.getWidth())); // +1
+ }
+ stringBuilder.append(middle.getCharacter());
+ stringBuilder.append(OffsetFont.NEG_1.getCharacter());
+ stringBuilder.append(right.getCharacter());
+ stringBuilder.append(OffsetFont.getShortestNegChars(textWidth + right.getWidth() - 1)); // -1
+ return stringBuilder.toString();
+ }
+
+ public String getSuffixWithFont(int textWidth) {
+ return FontUtils.surroundNameplateFont(getSuffix(textWidth));
+ }
+
+ public String getSuffix(int textWidth) {
+ return OffsetFont.getShortestNegChars(textWidth + textWidth % 2 + 1);
+ }
+
+ public static class Builder {
+
+ private final Nameplate nameplate;
+
+ public Builder() {
+ this.nameplate = new Nameplate();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder displayName(String display) {
+ nameplate.displayName = display;
+ return this;
+ }
+
+ public Builder teamColor(TeamColor teamColor) {
+ nameplate.teamColor = teamColor;
+ return this;
+ }
+
+ public Builder namePrefix(String namePrefix) {
+ nameplate.namePrefix = namePrefix;
+ return this;
+ }
+
+ public Builder nameSuffix(String nameSuffix) {
+ nameplate.nameSuffix = nameSuffix;
+ return this;
+ }
+
+ public Builder left(ConfiguredChar configuredChar) {
+ nameplate.left = configuredChar;
+ return this;
+ }
+
+ public Builder middle(ConfiguredChar configuredChar) {
+ nameplate.middle = configuredChar;
+ return this;
+ }
+
+ public Builder right(ConfiguredChar configuredChar) {
+ nameplate.right = configuredChar;
+ return this;
+ }
+
+ public Nameplate build() {
+ return nameplate;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/nameplate/TagMode.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/nameplate/TagMode.java
new file mode 100644
index 0000000..774c228
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/nameplate/TagMode.java
@@ -0,0 +1,7 @@
+package net.momirealms.customnameplates.api.mechanic.nameplate;
+
+public enum TagMode {
+ TEAM,
+ UNLIMITED,
+ DISABLE
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/BackGroundText.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/BackGroundText.java
new file mode 100644
index 0000000..a359e64
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/BackGroundText.java
@@ -0,0 +1,65 @@
+package net.momirealms.customnameplates.api.mechanic.placeholder;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.customnameplates.api.mechanic.background.BackGround;
+import net.momirealms.customnameplates.api.util.FontUtils;
+import org.bukkit.OfflinePlayer;
+
+public class BackGroundText {
+
+ private String text;
+ private BackGround backGround;
+
+ private BackGroundText() {
+ }
+
+ public BackGroundText(String text, BackGround backGround) {
+ this.text = text;
+ this.backGround = backGround;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public BackGround getBackGround() {
+ return backGround;
+ }
+
+ public String getValue(OfflinePlayer player) {
+ String parsed = PlaceholderAPI.setPlaceholders(player, text);
+ int parsedWidth = FontUtils.getTextWidth(parsed);
+ return backGround.getBackGroundImage(parsedWidth) + parsed;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final BackGroundText text;
+
+ public Builder() {
+ this.text = new BackGroundText();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder text(String value) {
+ text.text = value;
+ return this;
+ }
+
+ public Builder background(BackGround backGround) {
+ text.backGround = backGround;
+ return this;
+ }
+
+ public BackGroundText build() {
+ return text;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/CachedText.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/CachedText.java
new file mode 100644
index 0000000..b52ba7e
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/CachedText.java
@@ -0,0 +1,54 @@
+package net.momirealms.customnameplates.api.mechanic.placeholder;
+
+public class CachedText {
+
+ private long refreshInterval;
+ private String text;
+
+ private CachedText() {
+ }
+
+ public CachedText(long refreshInterval, String text) {
+ this.refreshInterval = refreshInterval;
+ this.text = text;
+ }
+
+ public long getRefreshInterval() {
+ return refreshInterval;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final CachedText text;
+
+ public Builder() {
+ this.text = new CachedText();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder refreshInterval(long time) {
+ this.text.refreshInterval = time;
+ return this;
+ }
+
+ public Builder text(String text) {
+ this.text.text = text;
+ return this;
+ }
+
+ public CachedText build() {
+ return text;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/ConditionalText.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/ConditionalText.java
new file mode 100644
index 0000000..bf64792
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/ConditionalText.java
@@ -0,0 +1,58 @@
+package net.momirealms.customnameplates.api.mechanic.placeholder;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.customnameplates.api.common.Pair;
+import net.momirealms.customnameplates.api.manager.RequirementManager;
+import net.momirealms.customnameplates.api.requirement.Condition;
+import net.momirealms.customnameplates.api.requirement.Requirement;
+import org.bukkit.OfflinePlayer;
+
+import java.util.List;
+
+public class ConditionalText {
+
+ private List> textList;
+
+ private ConditionalText() {
+ }
+
+ public ConditionalText(List> textList) {
+ this.textList = textList;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public String getValue(OfflinePlayer player) {
+ Condition condition = new Condition(player);
+ for (Pair pair : textList) {
+ if (RequirementManager.isRequirementMet(condition, pair.right())) {
+ return PlaceholderAPI.setPlaceholders(player, pair.left());
+ }
+ }
+ return "";
+ }
+
+ public static class Builder {
+
+ private final ConditionalText conditionalText;
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder() {
+ this.conditionalText = new ConditionalText();
+ }
+
+ public Builder textList(List> textList) {
+ conditionalText.textList = textList;
+ return this;
+ }
+
+ public ConditionalText build() {
+ return conditionalText;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/DescentText.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/DescentText.java
new file mode 100644
index 0000000..e3a3379
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/DescentText.java
@@ -0,0 +1,68 @@
+package net.momirealms.customnameplates.api.mechanic.placeholder;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.customnameplates.api.util.FontUtils;
+import org.bukkit.OfflinePlayer;
+
+public class DescentText {
+
+ private int ascent;
+ private String text;
+ private boolean isUnicode;
+
+ public DescentText(int ascent, String text, boolean isUnicode) {
+ this.ascent = ascent;
+ this.text = text;
+ this.isUnicode = isUnicode;
+ }
+
+ private DescentText() {
+
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public String getValue(OfflinePlayer player) {
+ var parsed = PlaceholderAPI.setPlaceholders(player, text);
+ return isUnicode ? FontUtils.surroundAscentUnicodeFont(parsed, ascent) : FontUtils.surroundAscentFont(parsed, ascent);
+ }
+
+ public static class Builder {
+
+ private final DescentText descentText;
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder() {
+ this.descentText = new DescentText();
+ }
+
+ public Builder ascent(int ascent) {
+ descentText.ascent = ascent;
+ return this;
+ }
+
+ public Builder descent(int descent) {
+ descentText.ascent = 8 - descent;
+ return this;
+ }
+
+ public Builder text(String text) {
+ descentText.text = text;
+ return this;
+ }
+
+ public Builder unicode(boolean unicode) {
+ descentText.isUnicode = unicode;
+ return this;
+ }
+
+ public DescentText build() {
+ return descentText;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/NameplateText.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/NameplateText.java
new file mode 100644
index 0000000..932823c
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/NameplateText.java
@@ -0,0 +1,71 @@
+package net.momirealms.customnameplates.api.mechanic.placeholder;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.customnameplates.api.mechanic.nameplate.Nameplate;
+import net.momirealms.customnameplates.api.util.FontUtils;
+import org.bukkit.OfflinePlayer;
+
+public class NameplateText {
+
+ private String text;
+ private Nameplate nameplate;
+
+ private NameplateText() {
+ }
+
+ public NameplateText(String text, Nameplate nameplate) {
+ this.text = text;
+ this.nameplate = nameplate;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public Nameplate getNameplate() {
+ return nameplate;
+ }
+
+ public String getValue(OfflinePlayer player) {
+ String temp;
+ switch (nameplate.getTeamColor()) {
+ case CUSTOM -> temp = nameplate.getNamePrefix() + text + nameplate.getNameSuffix();
+ case NONE -> temp = text;
+ default -> temp = "<" + nameplate.getTeamColor().name() + ">" + text + "" + nameplate.getTeamColor().name() + ">";
+ }
+ String parsed = PlaceholderAPI.setPlaceholders(player, temp);
+ int parsedWidth = FontUtils.getTextWidth(parsed);
+ return nameplate.getPrefixWithFont(parsedWidth) + parsed + nameplate.getSuffixWithFont(parsedWidth);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final NameplateText text;
+
+ public Builder() {
+ this.text = new NameplateText();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder text(String value) {
+ text.text = value;
+ return this;
+ }
+
+ public Builder nameplate(Nameplate value) {
+ text.nameplate = value;
+ return this;
+ }
+
+ public NameplateText build() {
+ return text;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/StaticText.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/StaticText.java
new file mode 100644
index 0000000..a2bf8a4
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/StaticText.java
@@ -0,0 +1,98 @@
+package net.momirealms.customnameplates.api.mechanic.placeholder;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.customnameplates.api.mechanic.font.OffsetFont;
+import net.momirealms.customnameplates.api.util.FontUtils;
+import org.bukkit.OfflinePlayer;
+
+public class StaticText {
+
+ private String text;
+ private int value;
+ private StaticState staticState;
+
+ private StaticText() {
+ }
+
+ public StaticText(String text, int value, StaticState staticState) {
+ this.text = text;
+ this.value = value;
+ this.staticState = staticState;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public StaticState getStaticState() {
+ return staticState;
+ }
+
+ public String getValue(OfflinePlayer player) {
+ String parsed = PlaceholderAPI.setPlaceholders(player, text);
+ int parsedWidth = FontUtils.getTextWidth(parsed);
+ switch (staticState) {
+ case LEFT -> {
+ return parsed + FontUtils.surroundNameplateFont(OffsetFont.getOffsetChars(value - parsedWidth));
+ }
+ case RIGHT -> {
+ return FontUtils.surroundNameplateFont(OffsetFont.getOffsetChars(value - parsedWidth)) + parsed;
+ }
+ case MIDDLE -> {
+ int half = (value - parsedWidth) / 2;
+ String left = FontUtils.surroundNameplateFont(OffsetFont.getOffsetChars(half));
+ String right = FontUtils.surroundNameplateFont(OffsetFont.getOffsetChars(value - parsedWidth - half));
+ return left + parsed + right;
+ }
+ default -> {
+ return "";
+ }
+ }
+ }
+
+ public static class Builder {
+
+ private final StaticText text;
+
+ public Builder() {
+ this.text = new StaticText();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder value(int value) {
+ text.value = value;
+ return this;
+ }
+
+ public Builder text(String value) {
+ text.text = value;
+ return this;
+ }
+
+ public Builder state(StaticState state) {
+ text.staticState = state;
+ return this;
+ }
+
+ public StaticText build() {
+ return text;
+ }
+ }
+
+ public enum StaticState {
+ LEFT,
+ MIDDLE,
+ RIGHT
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/SwitchText.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/SwitchText.java
new file mode 100644
index 0000000..4b54934
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/SwitchText.java
@@ -0,0 +1,63 @@
+package net.momirealms.customnameplates.api.mechanic.placeholder;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import org.bukkit.entity.Player;
+
+import java.util.HashMap;
+
+public class SwitchText {
+
+ private HashMap valueMap;
+ private String toParse;
+ private String defaultValue;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private SwitchText() {
+
+ }
+
+ public SwitchText(HashMap valueMap, String toParse) {
+ this.valueMap = valueMap;
+ this.toParse = toParse;
+ }
+
+ public String getValue(Player player) {
+ String parsed = PlaceholderAPI.setPlaceholders(player, toParse);
+ return valueMap.getOrDefault(parsed, defaultValue);
+ }
+
+ public static class Builder {
+
+ private final SwitchText switchText;
+
+ public Builder() {
+ this.switchText = new SwitchText();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder toParse(String toParse) {
+ this.switchText.toParse = toParse;
+ return this;
+ }
+
+ public Builder defaultValue(String value) {
+ this.switchText.defaultValue = value;
+ return this;
+ }
+
+ public Builder valueMap(HashMap valueMap) {
+ this.switchText.valueMap = valueMap;
+ return this;
+ }
+
+ public SwitchText build() {
+ return switchText;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/VanillaHud.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/VanillaHud.java
new file mode 100644
index 0000000..6a8d137
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/placeholder/VanillaHud.java
@@ -0,0 +1,58 @@
+package net.momirealms.customnameplates.api.mechanic.placeholder;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.util.FontUtils;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import org.bukkit.entity.Player;
+
+public class VanillaHud {
+
+ private final char empty;
+ private final char half;
+ private final char full;
+ private final String maxPapi;
+ private final String currentPapi;
+ private final boolean reverse;
+
+ public VanillaHud(String empty, String half, String full, String maxPapi, String currentPapi, boolean reverse) {
+ this.empty = CustomNameplatesPlugin.get().getImageManager().getImage(empty).getCharacter();
+ this.half = CustomNameplatesPlugin.get().getImageManager().getImage(half).getCharacter();
+ this.full = CustomNameplatesPlugin.get().getImageManager().getImage(full).getCharacter();
+ this.maxPapi = maxPapi;
+ this.currentPapi = currentPapi;
+ this.reverse = reverse;
+ }
+
+ public String getValue(Player player) {
+ double current;
+ double max;
+ try {
+ current= Double.parseDouble(PlaceholderAPI.setPlaceholders(player, currentPapi));
+ max = Double.parseDouble(PlaceholderAPI.setPlaceholders(player, maxPapi));
+ } catch (NumberFormatException e) {
+ current = 1;
+ max = 1;
+ LogUtils.warn("Invalid number format when parsing: " + currentPapi + "/" + maxPapi);
+ }
+ if (current >= max) current = max;
+ if (current < 0) current = 0;
+ int point = (int) ((current / max) * 20);
+ int full_amount = point / 2;
+ int half_amount = point % 2;
+ int empty_amount = 10 - full_amount - half_amount;
+ StringBuilder builder = new StringBuilder();
+ if (reverse) {
+ builder
+ .append(String.valueOf(empty).repeat(empty_amount))
+ .append(String.valueOf(half).repeat(half_amount))
+ .append(String.valueOf(full).repeat(full_amount));
+ } else {
+ builder
+ .append(String.valueOf(full).repeat(full_amount))
+ .append(String.valueOf(half).repeat(half_amount))
+ .append(String.valueOf(empty).repeat(empty_amount));
+ }
+ return FontUtils.surroundNameplateFont(builder.toString());
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/NameplatePlayer.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/NameplatePlayer.java
new file mode 100644
index 0000000..0c33ecf
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/NameplatePlayer.java
@@ -0,0 +1,13 @@
+package net.momirealms.customnameplates.api.mechanic.tag;
+
+import net.momirealms.customnameplates.api.mechanic.nameplate.Nameplate;
+import org.bukkit.entity.Player;
+
+public interface NameplatePlayer {
+
+ void preview();
+
+ void preview(Nameplate nameplate);
+
+ Player getOwner();
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/team/TeamPlayer.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/team/TeamPlayer.java
new file mode 100644
index 0000000..b4813e7
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/team/TeamPlayer.java
@@ -0,0 +1,109 @@
+package net.momirealms.customnameplates.api.mechanic.tag.team;
+
+import net.kyori.adventure.text.Component;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.TeamTagManager;
+import net.momirealms.customnameplates.api.mechanic.misc.ViewerText;
+import net.momirealms.customnameplates.api.mechanic.nameplate.Nameplate;
+import net.momirealms.customnameplates.api.mechanic.tag.NameplatePlayer;
+import net.momirealms.customnameplates.api.mechanic.team.TeamColor;
+import net.momirealms.customnameplates.api.mechanic.team.TeamTagVisibility;
+import org.bukkit.entity.Player;
+
+import java.util.Vector;
+
+public class TeamPlayer implements NameplatePlayer {
+
+ private final TeamTagManager manager;
+ private final Player owner;
+ private final ViewerText prefix;
+ private final ViewerText suffix;
+ private final Vector nearbyPlayers;
+
+ public TeamPlayer(TeamTagManager manager, Player owner, String prefix, String suffix) {
+ this.manager = manager;
+ this.owner = owner;
+ this.prefix = new ViewerText(owner, prefix);
+ this.suffix = new ViewerText(owner, suffix);
+ this.nearbyPlayers = new Vector<>();
+ this.prefix.updateForOwner();
+ this.suffix.updateForOwner();
+ }
+
+ public void updateForNearbyPlayers(boolean force) {
+ this.prefix.updateForOwner();
+ this.suffix.updateForOwner();
+ for (Player viewer : nearbyPlayers) {
+ updateForOne(viewer, force);
+ }
+ }
+
+ public void removeNearbyPlayer(Player player) {
+ if (!nearbyPlayers.contains(player)) {
+ return;
+ }
+ nearbyPlayers.remove(player);
+ removeForOne(player);
+ prefix.removeViewer(player);
+ suffix.removeViewer(player);
+ }
+
+ public void addNearbyPlayer(Player player) {
+ if (nearbyPlayers.contains(player)) {
+ return;
+ }
+ nearbyPlayers.add(player);
+ updateForOne(player, false);
+ }
+
+ public void destroy() {
+ manager.removeTeamPlayerFromMap(owner.getUniqueId());
+ for (Player viewer : nearbyPlayers) {
+ removeForOne(viewer);
+ }
+ nearbyPlayers.clear();
+ }
+
+ private void updateForOne(Player viewer, boolean force) {
+ try {
+ if ((prefix.updateForViewer(viewer) | suffix.updateForViewer(viewer)) || force) {
+ CustomNameplatesPlugin.get().getTeamManager().updateTeam(
+ owner,
+ viewer,
+ CustomNameplatesPlugin.get().getAdventure().getComponentFromMiniMessage(prefix.getLatestValue(viewer)),
+ CustomNameplatesPlugin.get().getAdventure().getComponentFromMiniMessage(suffix.getLatestValue(viewer)),
+ CustomNameplatesPlugin.get().getNameplateManager().getTeamColor(owner),
+ TeamTagVisibility.ALWAYS
+ );
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void removeForOne(Player viewer) {
+ CustomNameplatesPlugin.get().getTeamManager().updateTeam(
+ owner,
+ viewer,
+ Component.text(""),
+ Component.text(""),
+ TeamColor.WHITE,
+ TeamTagVisibility.ALWAYS
+ );
+ }
+
+ @Override
+ public void preview() {
+
+ }
+
+ @Override
+ public void preview(Nameplate nameplate) {
+
+ }
+
+ @Override
+ public Player getOwner() {
+ return owner;
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/NamedEntity.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/NamedEntity.java
new file mode 100644
index 0000000..9627010
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/NamedEntity.java
@@ -0,0 +1,60 @@
+package net.momirealms.customnameplates.api.mechanic.tag.unlimited;
+
+import net.momirealms.customnameplates.api.mechanic.misc.ViewerText;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Pose;
+
+import java.util.UUID;
+
+public interface NamedEntity {
+
+ boolean canSee(Player viewer);
+
+ void timer();
+
+ boolean canShow();
+
+ boolean isShownTo(Player viewer);
+
+ void spawn(Player viewer, Pose pose);
+
+ void spawn(Pose pose);
+
+ void destroy();
+
+ void destroy(Player viewer);
+
+ void teleport(double x, double y, double z, boolean onGround);
+
+ void teleport(Player viewer, double x, double y, double z, boolean onGround);
+
+ void setSneak(boolean sneaking, boolean respawn);
+
+ void removePlayerFromViewers(Player player);
+
+ void addPlayerToViewers(Player player);
+
+ double getOffset();
+
+ void setOffset(double v);
+
+ ViewerText getViewerText();
+
+ int getEntityId();
+
+ void move(short x, short y, short z, boolean onGround);
+
+ void move(Player viewer, short x, short y, short z, boolean onGround);
+
+ void respawn(Player viewer, Pose pose);
+
+ void respawn(Pose pose);
+
+ void updateText();
+
+ void updateText(Player viewer);
+
+ UUID getUuid();
+
+ void handlePose(Pose previous, Pose pose);
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/UnlimitedObject.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/UnlimitedObject.java
new file mode 100644
index 0000000..f31689f
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/UnlimitedObject.java
@@ -0,0 +1,39 @@
+package net.momirealms.customnameplates.api.mechanic.tag.unlimited;
+
+import net.momirealms.customnameplates.api.manager.UnlimitedTagManager;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Pose;
+
+import java.util.Vector;
+
+public abstract class UnlimitedObject {
+
+ protected final UnlimitedTagManager manager;
+ protected final Entity entity;
+ protected final Vector nearbyPlayers;
+
+ public UnlimitedObject(UnlimitedTagManager manager, Entity entity) {
+ this.manager = manager;
+ this.entity = entity;
+ this.nearbyPlayers = new Vector<>();
+ }
+
+ public Vector getNearbyPlayers() {
+ return nearbyPlayers;
+ }
+
+ public abstract void addNearbyPlayer(Player player);
+
+ public abstract void removeNearbyPlayer(Player player);
+
+ public abstract void move(Player receiver, short x, short y, short z, boolean onGround);
+
+ public abstract void teleport(Player receiver, double x, double y, double z, boolean onGround);
+
+ public abstract void destroy();
+
+ public abstract void sneak(boolean sneaking, boolean flying);
+
+ public abstract void handlePose(Pose previous, Pose pose);
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/UnlimitedPlayer.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/UnlimitedPlayer.java
new file mode 100644
index 0000000..dd04925
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/UnlimitedPlayer.java
@@ -0,0 +1,201 @@
+package net.momirealms.customnameplates.api.mechanic.tag.unlimited;
+
+import net.kyori.adventure.text.Component;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.UnlimitedTagManager;
+import net.momirealms.customnameplates.api.mechanic.nameplate.Nameplate;
+import net.momirealms.customnameplates.api.mechanic.tag.NameplatePlayer;
+import net.momirealms.customnameplates.api.mechanic.team.TeamColor;
+import net.momirealms.customnameplates.api.mechanic.team.TeamTagVisibility;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Pose;
+
+import java.util.Vector;
+
+public class UnlimitedPlayer extends UnlimitedObject implements NameplatePlayer {
+
+ private final Player owner;
+ private final Vector tags;
+ private double hatOffset;
+
+ public UnlimitedPlayer(UnlimitedTagManager manager, Player player) {
+ super(manager, player);
+ this.owner = player;
+ this.tags = new Vector<>();
+ }
+
+ /**
+ * Add a tag for a player
+ *
+ * @param tag tag
+ */
+ public void addTag(NamedEntity tag) {
+ if (tags.contains(tag)) {
+ return;
+ }
+ tags.add(tag);
+ for (Player all : nearbyPlayers) {
+ if (tag.canShow() && tag.canSee(all)) {
+ tag.addPlayerToViewers(all);
+ }
+ }
+ }
+
+ /**
+ * Remove a tag for a player
+ *
+ * @param tag tag
+ */
+ public void removeTag(NamedEntity tag) {
+ if (tags.remove(tag)) {
+ tag.destroy();
+ }
+ }
+
+ /**
+ * Get the tags that player own
+ *
+ * @return tags
+ */
+ public Vector getTags() {
+ return tags;
+ }
+
+ /**
+ * Set hat offset. This is useful for cosmetics plugins
+ * because hat might hide the name tags
+ *
+ * @param hatOffset hat offset
+ */
+ public void setHatOffset(double hatOffset) {
+ this.hatOffset = hatOffset;
+ }
+
+ @Override
+ public void preview() {
+
+ }
+
+ @Override
+ public void preview(Nameplate nameplate) {
+
+ }
+
+ /**
+ * Get the owner
+ */
+ @Override
+ public Player getOwner() {
+ return owner;
+ }
+
+ /**
+ * Get the hat offset
+ */
+ public double getHatOffset() {
+ return hatOffset;
+ }
+
+ /**
+ * Add a nearby player so he could see the tag
+ * This process is automatically handled by CustomNameplates
+ *
+ * @param player player
+ */
+ @Override
+ public void addNearbyPlayer(Player player) {
+ if (nearbyPlayers.contains(player)) {
+ return;
+ }
+ nearbyPlayers.add(player);
+ addForOne(player);
+ for (NamedEntity tag : tags) {
+ if (tag.canShow() && tag.canSee(player)) {
+ tag.addPlayerToViewers(player);
+ }
+ }
+ }
+
+ /**
+ * Remove a nearby player so he would no longer receive tag updates
+ * This process is automatically handled by CustomNameplates
+ *
+ * @param player player
+ */
+ @Override
+ public void removeNearbyPlayer(Player player) {
+ if (!nearbyPlayers.contains(player)) {
+ return;
+ }
+ super.nearbyPlayers.remove(player);
+ removeForOne(player);
+ for (NamedEntity tag : tags) {
+ tag.removePlayerFromViewers(player);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ manager.removeUnlimitedObjectFromMap(entity.getUniqueId());
+ for (Player viewer : nearbyPlayers) {
+ removeForOne(viewer);
+ }
+ for (NamedEntity tag : tags) {
+ tag.destroy();
+ }
+ nearbyPlayers.clear();
+ tags.clear();
+ }
+
+ public void move(Player receiver, short x, short y, short z, boolean onGround) {
+ for (NamedEntity tag : tags) {
+ tag.move(receiver, x, y, z, onGround);
+ }
+ }
+
+ public void teleport(Player receiver, double x, double y, double z, boolean onGround) {
+ for (NamedEntity tag : tags) {
+ tag.teleport(receiver, x, y, z, onGround);
+ }
+ }
+
+ public void sneak(boolean sneaking, boolean flying) {
+ for (NamedEntity tag : tags) {
+ tag.setSneak(sneaking, !flying);
+ }
+ }
+
+ public void handlePose(Pose previous, Pose pose) {
+ for (NamedEntity tag : tags) {
+ tag.handlePose(previous, pose);
+ }
+ }
+
+ private void addForOne(Player viewer) {
+ CustomNameplatesPlugin.get().getTeamManager().updateTeam(
+ owner,
+ viewer,
+ Component.text(""),
+ Component.text(""),
+ TeamColor.WHITE,
+ TeamTagVisibility.NEVER
+ );
+ }
+
+ private void removeForOne(Player viewer) {
+ CustomNameplatesPlugin.get().getTeamManager().updateTeam(
+ owner,
+ viewer,
+ Component.text(""),
+ Component.text(""),
+ TeamColor.WHITE,
+ TeamTagVisibility.ALWAYS
+ );
+ }
+
+ public void timer() {
+ for (NamedEntity tag : tags) {
+ tag.timer();
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/UnlimitedTagSetting.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/UnlimitedTagSetting.java
new file mode 100644
index 0000000..8905345
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/tag/unlimited/UnlimitedTagSetting.java
@@ -0,0 +1,106 @@
+package net.momirealms.customnameplates.api.mechanic.tag.unlimited;
+
+import net.momirealms.customnameplates.api.requirement.Requirement;
+
+public class UnlimitedTagSetting {
+
+ private double verticalOffset;
+ private String rawText;
+ private int checkFrequency;
+ private int refreshFrequency;
+ private Requirement[] viewerRequirements;
+ private Requirement[] ownerRequirements;
+
+ private UnlimitedTagSetting() {
+ verticalOffset = 0;
+ rawText = "";
+ checkFrequency = 10;
+ refreshFrequency = 10;
+ viewerRequirements = new Requirement[0];
+ ownerRequirements = new Requirement[0];
+ }
+
+ public UnlimitedTagSetting(double verticalOffset, String rawText, int checkFrequency, int refreshFrequency, Requirement[] viewerRequirements, Requirement[] ownerRequirements) {
+ this.verticalOffset = verticalOffset;
+ this.rawText = rawText;
+ this.checkFrequency = checkFrequency;
+ this.refreshFrequency = refreshFrequency;
+ this.viewerRequirements = viewerRequirements;
+ this.ownerRequirements = ownerRequirements;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public double getVerticalOffset() {
+ return verticalOffset;
+ }
+
+ public String getRawText() {
+ return rawText;
+ }
+
+ public int getCheckFrequency() {
+ return checkFrequency;
+ }
+
+ public int getRefreshFrequency() {
+ return refreshFrequency;
+ }
+
+ public Requirement[] getViewerRequirements() {
+ return viewerRequirements;
+ }
+
+ public Requirement[] getOwnerRequirements() {
+ return ownerRequirements;
+ }
+
+ public static class Builder {
+
+ private final UnlimitedTagSetting setting;
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder() {
+ this.setting = new UnlimitedTagSetting();
+ }
+
+ public Builder checkFrequency(int checkFrequency) {
+ this.setting.checkFrequency = checkFrequency;
+ return this;
+ }
+
+ public Builder refreshFrequency(int refreshFrequency) {
+ this.setting.refreshFrequency = refreshFrequency;
+ return this;
+ }
+
+ public Builder rawText(String rawText) {
+ this.setting.rawText = rawText;
+ return this;
+ }
+
+ public Builder verticalOffset(double verticalOffset) {
+ this.setting.verticalOffset = verticalOffset;
+ return this;
+ }
+
+ public Builder ownerRequirements(Requirement[] ownerRequirements) {
+ this.setting.ownerRequirements = ownerRequirements;
+ return this;
+ }
+
+ public Builder viewerRequirements(Requirement[] viewerRequirements) {
+ this.setting.viewerRequirements = viewerRequirements;
+ return this;
+ }
+
+ public UnlimitedTagSetting build() {
+ return setting;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamCollisionRule.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamCollisionRule.java
new file mode 100644
index 0000000..341ce87
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamCollisionRule.java
@@ -0,0 +1,31 @@
+package net.momirealms.customnameplates.api.mechanic.team;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+
+public enum TeamCollisionRule {
+
+ ALWAYS("always"),
+ NEVER("never"),
+ PUSH_OTHER_TEAMS("pushOtherTeams"),
+ PUSH_OWN_TEAM("pushOwnTeam");
+
+ private final String id;
+
+ TeamCollisionRule(@NotNull String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ @NotNull
+ public static TeamCollisionRule byId(String id) {
+ return Arrays.stream(values())
+ .filter(mode -> mode.id.equals(id))
+ .findFirst()
+ .orElse(ALWAYS);
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamColor.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamColor.java
new file mode 100644
index 0000000..fb20fdf
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamColor.java
@@ -0,0 +1,29 @@
+package net.momirealms.customnameplates.api.mechanic.team;
+
+import java.util.Locale;
+
+public enum TeamColor {
+
+ NONE,
+ BLACK,
+ DARK_BLUE,
+ DARK_GREEN,
+ DARK_AQUA,
+ DARK_RED,
+ DARK_PURPLE,
+ GOLD,
+ GRAY,
+ DARK_GRAY,
+ BLUE,
+ GREEN,
+ AQUA,
+ RED,
+ LIGHT_PURPLE,
+ YELLOW,
+ WHITE,
+ CUSTOM;
+
+ public TeamColor getById(String id) throws IllegalArgumentException {
+ return valueOf(id.toUpperCase(Locale.ENGLISH));
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamCreatePacket.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamCreatePacket.java
new file mode 100644
index 0000000..d1cede2
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamCreatePacket.java
@@ -0,0 +1,154 @@
+package net.momirealms.customnameplates.api.mechanic.team;
+
+import net.kyori.adventure.text.Component;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class TeamCreatePacket {
+
+ // String i
+ private String teamName;
+ // Collection j
+ private Collection members;
+
+ /*
+ Optional k
+ */
+ // IChatBaseComponent a
+ private Component teamDisplay;
+ // IChatBaseComponent b
+ private Component teamPrefix;
+ // IChatBaseComponent c
+ private Component teamSuffix;
+ // String d
+ private TeamTagVisibility tagVisibility;
+ // String e
+ private TeamCollisionRule collisionRule;
+ // Enum f
+ private TeamColor teamColor;
+
+ private TeamCreatePacket() {
+ this.teamName = "";
+ this.members = Collections.singleton("");
+ this.teamDisplay = Component.text("");
+ this.teamPrefix = Component.text("");
+ this.teamSuffix = Component.text("");
+ this.tagVisibility = TeamTagVisibility.ALWAYS;
+ this.collisionRule = TeamCollisionRule.ALWAYS;
+ this.teamColor = TeamColor.WHITE;
+ }
+
+ public TeamCreatePacket(
+ String teamName,
+ Collection members,
+ Component teamDisplay,
+ Component teamPrefix,
+ Component teamSuffix,
+ TeamTagVisibility tagVisibility,
+ TeamCollisionRule collisionRule,
+ TeamColor teamColor
+ ) {
+ this.teamName = teamName;
+ this.members = members;
+ this.teamDisplay = teamDisplay;
+ this.teamPrefix = teamPrefix;
+ this.teamSuffix = teamSuffix;
+ this.tagVisibility = tagVisibility;
+ this.collisionRule = collisionRule;
+ this.teamColor = teamColor;
+ }
+
+ public String getTeamName() {
+ return teamName;
+ }
+
+ public Collection getMembers() {
+ return members;
+ }
+
+ public Component getTeamDisplay() {
+ return teamDisplay;
+ }
+
+ public Component getTeamPrefix() {
+ return teamPrefix;
+ }
+
+ public Component getTeamSuffix() {
+ return teamSuffix;
+ }
+
+ public TeamTagVisibility getTagVisibility() {
+ return tagVisibility;
+ }
+
+ public TeamCollisionRule getCollisionRule() {
+ return collisionRule;
+ }
+
+ public TeamColor getTeamColor() {
+ return teamColor;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final TeamCreatePacket packet;
+
+ public Builder() {
+ this.packet = new TeamCreatePacket();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder teamName(String name) {
+ packet.teamName = name;
+ return this;
+ }
+
+ public Builder members(Collection members) {
+ packet.members = members;
+ return this;
+ }
+
+ public Builder display(Component display) {
+ packet.teamDisplay = display;
+ return this;
+ }
+
+ public Builder prefix(Component prefix) {
+ packet.teamPrefix = prefix;
+ return this;
+ }
+
+ public Builder suffix(Component suffix) {
+ packet.teamSuffix = suffix;
+ return this;
+ }
+
+ public Builder color(TeamColor color) {
+ packet.teamColor = color;
+ return this;
+ }
+
+ public Builder tagVisibility(TeamTagVisibility visibility) {
+ packet.tagVisibility = visibility;
+ return this;
+ }
+
+ public Builder collisionRule(TeamCollisionRule rule) {
+ packet.collisionRule = rule;
+ return this;
+ }
+
+ public TeamCreatePacket build() {
+ return packet;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamRemovePacket.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamRemovePacket.java
new file mode 100644
index 0000000..60d3038
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamRemovePacket.java
@@ -0,0 +1,39 @@
+package net.momirealms.customnameplates.api.mechanic.team;
+
+public class TeamRemovePacket {
+
+ private String teamName;
+
+ private TeamRemovePacket() {
+ }
+
+ public TeamRemovePacket(String teamName) {
+ this.teamName = teamName;
+ }
+
+ public String getTeamName() {
+ return teamName;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final TeamRemovePacket packet;
+
+ public Builder() {
+ this.packet = new TeamRemovePacket();
+ }
+
+ public Builder teamName(String teamName) {
+ packet.teamName = teamName;
+ return this;
+ }
+
+ public TeamRemovePacket build() {
+ return packet;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamTagVisibility.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamTagVisibility.java
new file mode 100644
index 0000000..421d983
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamTagVisibility.java
@@ -0,0 +1,31 @@
+package net.momirealms.customnameplates.api.mechanic.team;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+
+public enum TeamTagVisibility {
+
+ ALWAYS("always"),
+ HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
+ HIDE_FOR_OWN_TEAM("hideForOwnTeam"),
+ NEVER("never");
+
+ private final String id;
+
+ TeamTagVisibility(@NotNull String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ @NotNull
+ public static TeamTagVisibility byId(String id) {
+ return Arrays.stream(values())
+ .filter(mode -> mode.id.equals(id))
+ .findFirst()
+ .orElse(ALWAYS);
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamUpdatePacket.java b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamUpdatePacket.java
new file mode 100644
index 0000000..4b53360
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/mechanic/team/TeamUpdatePacket.java
@@ -0,0 +1,136 @@
+package net.momirealms.customnameplates.api.mechanic.team;
+
+import net.kyori.adventure.text.Component;
+
+public class TeamUpdatePacket {
+
+ private String teamName;
+
+ /*
+ Optional k
+ */
+ // IChatBaseComponent a
+ private Component teamDisplay;
+ // IChatBaseComponent b
+ private Component teamPrefix;
+ // IChatBaseComponent c
+ private Component teamSuffix;
+ // String d
+ private TeamTagVisibility tagVisibility;
+ // String e
+ private TeamCollisionRule collisionRule;
+ // Enum f
+ private TeamColor teamColor;
+
+ private TeamUpdatePacket() {
+ this.teamName = "";
+ this.teamDisplay = Component.text("");
+ this.teamPrefix = Component.text("");
+ this.teamSuffix = Component.text("");
+ this.tagVisibility = TeamTagVisibility.ALWAYS;
+ this.collisionRule = TeamCollisionRule.ALWAYS;
+ this.teamColor = TeamColor.WHITE;
+ }
+
+ public TeamUpdatePacket(
+ String teamName,
+ Component teamDisplay,
+ Component teamPrefix,
+ Component teamSuffix,
+ TeamTagVisibility tagVisibility,
+ TeamCollisionRule collisionRule,
+ TeamColor teamColor
+ ) {
+ this.teamName = teamName;
+ this.teamDisplay = teamDisplay;
+ this.teamPrefix = teamPrefix;
+ this.teamSuffix = teamSuffix;
+ this.tagVisibility = tagVisibility;
+ this.collisionRule = collisionRule;
+ this.teamColor = teamColor;
+ }
+
+ public String getTeamName() {
+ return teamName;
+ }
+
+ public Component getTeamDisplay() {
+ return teamDisplay;
+ }
+
+ public Component getTeamPrefix() {
+ return teamPrefix;
+ }
+
+ public Component getTeamSuffix() {
+ return teamSuffix;
+ }
+
+ public TeamTagVisibility getTagVisibility() {
+ return tagVisibility;
+ }
+
+ public TeamCollisionRule getCollisionRule() {
+ return collisionRule;
+ }
+
+ public TeamColor getTeamColor() {
+ return teamColor;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final TeamUpdatePacket packet;
+
+ public Builder() {
+ this.packet = new TeamUpdatePacket();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder teamName(String name) {
+ packet.teamName = name;
+ return this;
+ }
+
+ public Builder display(Component display) {
+ packet.teamDisplay = display;
+ return this;
+ }
+
+ public Builder prefix(Component prefix) {
+ packet.teamPrefix = prefix;
+ return this;
+ }
+
+ public Builder suffix(Component suffix) {
+ packet.teamSuffix = suffix;
+ return this;
+ }
+
+ public Builder color(TeamColor color) {
+ packet.teamColor = color;
+ return this;
+ }
+
+ public Builder tagVisibility(TeamTagVisibility visibility) {
+ packet.tagVisibility = visibility;
+ return this;
+ }
+
+ public Builder collisionRule(TeamCollisionRule rule) {
+ packet.collisionRule = rule;
+ return this;
+ }
+
+ public TeamUpdatePacket build() {
+ return packet;
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/requirement/Condition.java b/api/src/main/java/net/momirealms/customnameplates/api/requirement/Condition.java
new file mode 100644
index 0000000..60238bb
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/requirement/Condition.java
@@ -0,0 +1,16 @@
+package net.momirealms.customnameplates.api.requirement;
+
+import org.bukkit.OfflinePlayer;
+
+public class Condition {
+
+ private final OfflinePlayer player;
+
+ public Condition(OfflinePlayer player) {
+ this.player = player;
+ }
+
+ public OfflinePlayer getOfflinePlayer() {
+ return player;
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/object/requirements/Requirement.java b/api/src/main/java/net/momirealms/customnameplates/api/requirement/Requirement.java
similarity index 75%
rename from src/main/java/net/momirealms/customnameplates/object/requirements/Requirement.java
rename to api/src/main/java/net/momirealms/customnameplates/api/requirement/Requirement.java
index 8ee8b79..9424121 100644
--- a/src/main/java/net/momirealms/customnameplates/object/requirements/Requirement.java
+++ b/api/src/main/java/net/momirealms/customnameplates/api/requirement/Requirement.java
@@ -15,10 +15,15 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.object.requirements;
-
-import org.bukkit.entity.Player;
+package net.momirealms.customnameplates.api.requirement;
public interface Requirement {
- boolean isConditionMet(Player player);
+
+ /**
+ * Is condition met the requirement
+ *
+ * @param condition condition
+ * @return meet or not
+ */
+ boolean isConditionMet(Condition condition);
}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/requirement/RequirementExpansion.java b/api/src/main/java/net/momirealms/customnameplates/api/requirement/RequirementExpansion.java
new file mode 100644
index 0000000..df9ce14
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/requirement/RequirementExpansion.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.api.requirement;
+
+/**
+ * An abstract class representing a requirement expansion
+ * Requirement expansions are used to define custom requirements for various functionalities.
+ */
+public abstract class RequirementExpansion {
+
+ /**
+ * Get the version of this requirement expansion.
+ *
+ * @return The version of the expansion.
+ */
+ public abstract String getVersion();
+
+ /**
+ * Get the author of this requirement expansion.
+ *
+ * @return The author of the expansion.
+ */
+ public abstract String getAuthor();
+
+ /**
+ * Get the type of requirement provided by this expansion.
+ *
+ * @return The type of requirement.
+ */
+ public abstract String getRequirementType();
+
+ /**
+ * Get the factory for creating requirements defined by this expansion.
+ *
+ * @return The requirement factory.
+ */
+ public abstract RequirementFactory getRequirementFactory();
+}
diff --git a/src/main/java/net/momirealms/customnameplates/object/Function.java b/api/src/main/java/net/momirealms/customnameplates/api/requirement/RequirementFactory.java
similarity index 64%
rename from src/main/java/net/momirealms/customnameplates/object/Function.java
rename to api/src/main/java/net/momirealms/customnameplates/api/requirement/RequirementFactory.java
index 8b0061b..74b2840 100644
--- a/src/main/java/net/momirealms/customnameplates/object/Function.java
+++ b/api/src/main/java/net/momirealms/customnameplates/api/requirement/RequirementFactory.java
@@ -15,24 +15,18 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.object;
+package net.momirealms.customnameplates.api.requirement;
-import org.bukkit.entity.Player;
+/**
+ * An interface for a requirement factory that builds requirements.
+ */
+public interface RequirementFactory {
-public abstract class Function {
-
- public void load() {
- }
-
- public void unload() {
- }
-
- public void disable() {
- }
-
- public void onJoin(Player player) {
- }
-
- public void onQuit(Player player) {
- }
+ /**
+ * Build a requirement with the given arguments.
+ *
+ * @param args The arguments used to build the requirement.
+ * @return The built requirement.
+ */
+ Requirement build(Object args);
}
diff --git a/src/main/java/net/momirealms/customnameplates/object/scheduler/TimerTask.java b/api/src/main/java/net/momirealms/customnameplates/api/scheduler/CancellableTask.java
similarity index 76%
rename from src/main/java/net/momirealms/customnameplates/object/scheduler/TimerTask.java
rename to api/src/main/java/net/momirealms/customnameplates/api/scheduler/CancellableTask.java
index 793de07..d226bf8 100644
--- a/src/main/java/net/momirealms/customnameplates/object/scheduler/TimerTask.java
+++ b/api/src/main/java/net/momirealms/customnameplates/api/scheduler/CancellableTask.java
@@ -15,11 +15,19 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.object.scheduler;
+package net.momirealms.customnameplates.api.scheduler;
-public interface TimerTask {
+public interface CancellableTask {
+ /**
+ * Cancel the task
+ */
void cancel();
+ /**
+ * Get if the task is cancelled or not
+ *
+ * @return cancelled or not
+ */
boolean isCancelled();
}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/scheduler/Scheduler.java b/api/src/main/java/net/momirealms/customnameplates/api/scheduler/Scheduler.java
new file mode 100644
index 0000000..9b20769
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/scheduler/Scheduler.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.api.scheduler;
+
+import org.bukkit.Location;
+
+import java.util.concurrent.TimeUnit;
+
+public interface Scheduler {
+
+ /**
+ * Runs a task synchronously on the main server thread or region thread.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ */
+ void runTaskSync(Runnable runnable, Location location);
+
+ /**
+ * Runs a task synchronously with a specified delay and period.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delayTicks The delay in ticks before the first execution.
+ * @param periodTicks The period between subsequent executions in ticks.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks);
+
+ /**
+ * Runs a task asynchronously with a specified delay.
+ *
+ * @param runnable The task to run.
+ * @param delay The delay before the task execution.
+ * @param timeUnit The time unit for the delay.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ CancellableTask runTaskAsyncLater(Runnable runnable, long delay, TimeUnit timeUnit);
+
+ /**
+ * Runs a task asynchronously.
+ *
+ * @param runnable The task to run.
+ */
+ void runTaskAsync(Runnable runnable);
+
+ /**
+ * Runs a task synchronously with a specified delay.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delay The delay before the task execution.
+ * @param timeUnit The time unit for the delay.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay, TimeUnit timeUnit);
+
+ /**
+ * Runs a task synchronously with a specified delay in ticks.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delayTicks The delay in ticks before the task execution.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks);
+
+ /**
+ * Runs a task asynchronously with a specified delay and period.
+ *
+ * @param runnable The task to run.
+ * @param delay The delay before the first execution.
+ * @param period The period between subsequent executions.
+ * @param timeUnit The time unit for the delay and period.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ CancellableTask runTaskAsyncTimer(Runnable runnable, long delay, long period, TimeUnit timeUnit);
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/util/FontUtils.java b/api/src/main/java/net/momirealms/customnameplates/api/util/FontUtils.java
new file mode 100644
index 0000000..c1afdd0
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/util/FontUtils.java
@@ -0,0 +1,76 @@
+package net.momirealms.customnameplates.api.util;
+
+public class FontUtils {
+
+ private static String namespace;
+ private static String font;
+
+ /**
+ * Surround the text with ascent font
+ *
+ * @param text text
+ * @param ascent ascent
+ * @return ascent font text
+ */
+ public static String surroundAscentFont(String text, int ascent) {
+ return getAscentFontTag(ascent) + text + getFontTagCloser();
+ }
+
+ /**
+ * Surround the text with ascent unicode
+ *
+ * @param text text
+ * @param ascent ascent
+ * @return ascent font text
+ */
+ public static String surroundAscentUnicodeFont(String text, int ascent) {
+ return getAscentUnicodeFontTag(ascent) + text + getFontTagCloser();
+ }
+
+ /**
+ * Surround the text with custom nameplates font
+ *
+ * @param text text
+ * @return font text
+ */
+ public static String surroundNameplateFont(String text) {
+ return getMiniMessageFontTag() + text + getFontTagCloser();
+ }
+
+ private static String getAscentFontTag(int ascent) {
+ return "";
+ }
+
+ private static String getAscentUnicodeFontTag(int ascent) {
+ return "";
+ }
+
+ private static String getMiniMessageFontTag() {
+ return "";
+ }
+
+ private static String getFontTagCloser() {
+ return "";
+ }
+
+ /**
+ * Get a text's width
+ *
+ * @param text text
+ * @return width
+ */
+ public static int getTextWidth(String text) {
+ return 0;
+ }
+
+ /**
+ * Set namespace and font
+ *
+ * @param n namespace
+ * @param f font
+ */
+ public static void setNameSpaceAndFont(String n, String f) {
+ namespace = n;
+ font = f;
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customnameplates/api/util/LogUtils.java b/api/src/main/java/net/momirealms/customnameplates/api/util/LogUtils.java
new file mode 100644
index 0000000..dde8f82
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customnameplates/api/util/LogUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.api.util;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.logging.Level;
+
+public final class LogUtils {
+
+ /**
+ * Log an informational message.
+ *
+ * @param message The message to log.
+ */
+ public static void info(@NotNull String message) {
+ CustomNameplatesPlugin.getInstance().getLogger().info(message);
+ }
+
+ /**
+ * Log a warning message.
+ *
+ * @param message The message to log.
+ */
+ public static void warn(@NotNull String message) {
+ CustomNameplatesPlugin.getInstance().getLogger().warning(message);
+ }
+
+ /**
+ * Log a severe error message.
+ *
+ * @param message The message to log.
+ */
+ public static void severe(@NotNull String message) {
+ CustomNameplatesPlugin.getInstance().getLogger().severe(message);
+ }
+
+ /**
+ * Log a warning message with a throwable exception.
+ *
+ * @param message The message to log.
+ * @param throwable The throwable exception to log.
+ */
+ public static void warn(@NotNull String message, Throwable throwable) {
+ CustomNameplatesPlugin.getInstance().getLogger().log(Level.WARNING, message, throwable);
+ }
+
+ /**
+ * Log a severe error message with a throwable exception.
+ *
+ * @param message The message to log.
+ * @param throwable The throwable exception to log.
+ */
+ public static void severe(@NotNull String message, Throwable throwable) {
+ CustomNameplatesPlugin.getInstance().getLogger().log(Level.SEVERE, message, throwable);
+ }
+
+ private LogUtils() {
+ throw new UnsupportedOperationException("This class cannot be instantiated");
+ }
+}
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 28a7e0e..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,81 +0,0 @@
-plugins {
- id 'java'
- id 'com.github.johnrengelman.shadow' version '7.1.2'
-}
-
-group = 'net.momirealms'
-version = '2.2.3.18.1'
-
-repositories {
- maven {name = "aliyun-repo"; url = "https://maven.aliyun.com/repository/public/"}
- maven {name = "sk89q-repo"; url = "https://maven.enginehub.org/repo/"}
- maven {name = 'sonatype'; url = 'https://oss.sonatype.org/content/groups/public/'}
- maven {name = 'sonatype-snapshot'; url = 'https://s01.oss.sonatype.org/content/repositories/snapshots/'}
- maven {name = "dmulloy2-repo"; url = "https://repo.dmulloy2.net/repository/public/"}
- maven {name = "clip-repo"; url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/'}
- maven {name = "jitpack"; url = 'https://jitpack.io'}
- maven {name = "codecrafter47-repo"; url = 'https://nexus.codecrafter47.de/content/repositories/public/'}
- maven {name = "opencollab-snapshot-repo"; url = 'https://repo.opencollab.dev/main/'}
- maven {name = 'papermc-repo'; url = 'https://papermc.io/repo/repository/maven-public/'}
- maven {name = 'William278-repo'; url = 'https://repo.william278.net/releases/'}
- maven {name = 'kryptonmc-repo'; url = 'https://repo.kryptonmc.org/releases'}
- mavenCentral()
-}
-
-dependencies {
- compileOnly fileTree(dir:'libs',includes:['*.jar'])
- compileOnly('dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT')
- compileOnly ('me.clip:placeholderapi:2.11.3')
- compileOnly ('com.zaxxer:HikariCP:5.0.1')
- compileOnly ('commons-io:commons-io:2.11.0')
- compileOnly ('dev.dejvokep:boosted-yaml:1.3.1')
- compileOnly ("com.comphenix.protocol:ProtocolLib:5.1.0")
- compileOnly ('net.md-5:bungeecord-api:1.19-R0.1-SNAPSHOT')
- compileOnly ('com.github.LoneDev6:api-itemsadder:3.4.1-r4')
- compileOnly ("com.velocitypowered:velocity-api:3.1.1")
- compileOnly ('org.geysermc.geyser:api:2.2.0-SNAPSHOT')
- compileOnly ("net.william278:velocitab:1.5.1")
- compileOnly ('me.neznamy:tab-api:4.0.2')
- compileOnly ('com.github.FrancoBM12:API-MagicCosmetics:2.2.5')
- annotationProcessor ('com.velocitypowered:velocity-api:3.1.1')
-
- compileOnly ('org.apache.commons:commons-lang3:3.12.0')
- implementation ('net.kyori:adventure-api:4.14.0')
- implementation ('net.kyori:adventure-platform-bukkit:4.3.3-SNAPSHOT')
- implementation ('net.kyori:adventure-text-minimessage:4.14.0')
- implementation ('net.kyori:adventure-text-serializer-gson:4.14.0')
- implementation ("org.bstats:bstats-bukkit:3.0.1")
- implementation fileTree (dir:'libs',includes:['BiomeAPI.jar'])
-}
-
-def targetJavaVersion = 17
-java {
- def javaVersion = JavaVersion.toVersion(targetJavaVersion)
- sourceCompatibility = javaVersion
- targetCompatibility = javaVersion
-}
-
-tasks.withType(JavaCompile).configureEach {
- options.release = targetJavaVersion
- options.encoding = "UTF-8"
-}
-
-processResources {
- def props = [version: version]
- inputs.properties props
- filteringCharset 'UTF-8'
- filesMatching('plugin.yml') {
- expand props
- }
-}
-
-shadowJar {
- relocate ('net.kyori', 'net.momirealms.customnameplates.libs.net.kyori')
- relocate ('org.bstats', 'net.momirealms.customnameplates.libs.org.bstats')
- relocate ('net.momirealms.biomeapi', 'net.momirealms.customnameplates.libs.net.momirealms.biomeapi')
-}
-
-tasks.register("delete", Delete).get().delete("build/libs/"+project.name+"-"+project.version+".jar")
-tasks.named("build").get().dependsOn("shadowJar").finalizedBy("delete").doLast {
- println("Deleting: "+ "build/libs/"+project.name+"-"+project.version+".jar")
-}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..1241825
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,80 @@
+plugins {
+ id("java")
+ id("application")
+ id("maven-publish")
+ id("com.github.johnrengelman.shadow") version "8.1.1"
+}
+
+allprojects {
+
+ version = "2.3.0.0"
+
+ apply()
+ apply(plugin = "java")
+ apply(plugin = "application")
+ apply(plugin = "com.github.johnrengelman.shadow")
+ apply(plugin = "org.gradle.maven-publish")
+
+ application {
+ mainClass.set("")
+ }
+
+ repositories {
+ maven("https://maven.aliyun.com/repository/public/")
+ mavenCentral()
+ maven("https://papermc.io/repo/repository/maven-public/")
+ maven("https://oss.sonatype.org/content/groups/public/")
+ maven("https://repo.dmulloy2.net/repository/public/")
+ maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
+ maven("https://repo.codemc.org/repository/maven-public/")
+ maven("https://maven.enginehub.org/repo/")
+ maven("https://jitpack.io/")
+ maven("https://mvn.lumine.io/repository/maven-public/")
+ maven("https://repo.rapture.pw/repository/maven-releases/")
+ maven("https://nexus.phoenixdevt.fr/repository/maven-public/")
+ maven("https://r.irepo.space/maven/")
+ maven("https://repo.auxilor.io/repository/maven-public/")
+ maven("https://betonquest.org/nexus/repository/betonquest/")
+ maven("https://repo.william278.net/releases/")
+ maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
+ maven("https://repo.minebench.de/")
+ maven("https://repo.xenondevs.xyz/releases/")
+ maven("https://repo.kryptonmc.org/releases")
+ maven("https://repo.oraxen.com/releases")
+ }
+}
+
+subprojects {
+ tasks.processResources {
+ val props = mapOf("version" to version)
+ inputs.properties(props)
+ filteringCharset = "UTF-8"
+ filesMatching("*plugin.yml") {
+ expand(props)
+ }
+ }
+
+ tasks.withType {
+ options.encoding = "UTF-8"
+ options.release.set(17)
+ }
+
+ tasks.shadowJar {
+ destinationDirectory.set(file("$rootDir/target"))
+ archiveClassifier.set("")
+ archiveFileName.set("CustomNameplates-" + project.name + "-" + project.version + ".jar")
+ }
+
+ if ("api" == project.name) {
+ publishing {
+ publications {
+ create("mavenJava") {
+ groupId = "net.momirealms"
+ artifactId = "CustomNameplates"
+ version = rootProject.version.toString()
+ artifact(tasks.shadowJar)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/bungeecord/.gitignore b/bungeecord/.gitignore
new file mode 100644
index 0000000..b63da45
--- /dev/null
+++ b/bungeecord/.gitignore
@@ -0,0 +1,42 @@
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/bungeecord/build.gradle.kts b/bungeecord/build.gradle.kts
new file mode 100644
index 0000000..0ce6a16
--- /dev/null
+++ b/bungeecord/build.gradle.kts
@@ -0,0 +1,3 @@
+dependencies {
+
+}
\ No newline at end of file
diff --git a/bungeecord/src/main/resources/plugin.yml b/bungeecord/src/main/resources/plugin.yml
new file mode 100644
index 0000000..5bf5d9b
--- /dev/null
+++ b/bungeecord/src/main/resources/plugin.yml
@@ -0,0 +1,9 @@
+name: CustomNameplates
+version: '${version}'
+main: net.momirealms.customnameplates.paper.Main
+api-version: 1.17
+authors: [ XiaoMoMi ]
+folia-supported: true
+depend:
+ - ProtocolLib
+ - PlaceholderAPI
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7454180..c1962a7 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index fae0804..744c64d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 3da45c1..aeb74cb 100644
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
#!/bin/sh
#
-# Copyright ? 2015-2021 the original authors.
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
-# * expansions ?$var?, ?${var}?, ?${var:-default}?, ?${var+SET}?,
-# ?${var#prefix}?, ?${var%suffix}?, and ?$( cmd )?;
-# * compound commands having a testable exit status, especially ?case?;
-# * various built-in commands including ?command?, ?set?, and ?ulimit?.
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,10 @@ do
esac
done
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
+# This is normally unused
+# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -143,12 +140,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then
done
fi
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
@@ -205,6 +210,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/gradlew.bat b/gradlew.bat
index 107acd3..93e3f59 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/jitpack.yml b/jitpack.yml
new file mode 100644
index 0000000..1e41e00
--- /dev/null
+++ b/jitpack.yml
@@ -0,0 +1,2 @@
+jdk:
+ - openjdk17
\ No newline at end of file
diff --git a/libs/MagicCosmetics-2.3.7-API.jar b/libs/MagicCosmetics-2.3.7-API.jar
deleted file mode 100644
index f13e6cb..0000000
Binary files a/libs/MagicCosmetics-2.3.7-API.jar and /dev/null differ
diff --git a/libs/TrChat-2.0.8.jar b/libs/TrChat-2.0.8.jar
deleted file mode 100644
index e21f105..0000000
Binary files a/libs/TrChat-2.0.8.jar and /dev/null differ
diff --git a/libs/VentureChat-3.6.0.jar b/libs/VentureChat-3.6.0.jar
deleted file mode 100644
index d275083..0000000
Binary files a/libs/VentureChat-3.6.0.jar and /dev/null differ
diff --git a/libs/oraxen-api.jar b/libs/oraxen-api.jar
deleted file mode 100644
index b1a3bc0..0000000
Binary files a/libs/oraxen-api.jar and /dev/null differ
diff --git a/paper/.gitignore b/paper/.gitignore
new file mode 100644
index 0000000..b63da45
--- /dev/null
+++ b/paper/.gitignore
@@ -0,0 +1,42 @@
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts
new file mode 100644
index 0000000..3c4ef7d
--- /dev/null
+++ b/paper/build.gradle.kts
@@ -0,0 +1,62 @@
+dependencies {
+ // server
+ compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT")
+
+ // command
+ compileOnly("dev.jorel:commandapi-bukkit-core:9.3.0")
+
+ // packet
+ compileOnly("com.comphenix.protocol:ProtocolLib:5.2.0-SNAPSHOT")
+
+ // papi
+ compileOnly("me.clip:placeholderapi:2.11.5")
+
+ // config
+ compileOnly("dev.dejvokep:boosted-yaml:1.3.1")
+
+ // team
+ compileOnly("me.neznamy:tab-api:4.0.2")
+ compileOnly(files("libs/CMI-API-9.6.5.0.jar"))
+
+ // Gson
+ compileOnly("com.google.code.gson:gson:2.10.1")
+
+ // database
+ compileOnly("org.xerial:sqlite-jdbc:3.43.0.0")
+ compileOnly("com.h2database:h2:2.2.224")
+ compileOnly("org.mongodb:mongodb-driver-sync:4.11.1")
+ compileOnly("com.zaxxer:HikariCP:5.0.1")
+ compileOnly("redis.clients:jedis:5.1.0")
+
+ // others
+ compileOnly("com.github.LoneDev6:api-itemsadder:3.5.0c-r5")
+ compileOnly("io.th0rgal:oraxen:1.165.0")
+ compileOnly("com.github.FrancoBM12:API-MagicCosmetics:2.2.5")
+
+ // chat channels
+ compileOnly(files("libs/VentureChat-3.7.1.jar"))
+ compileOnly(files("libs/TrChat-2.0.11.jar"))
+
+ // api module
+ implementation(project(":api"))
+
+ // adventure
+ implementation("net.kyori:adventure-api:4.15.0")
+ implementation("net.kyori:adventure-platform-bukkit:4.3.1")
+ implementation("net.kyori:adventure-text-minimessage:4.15.0")
+ implementation("net.kyori:adventure-text-serializer-legacy:4.15.0")
+
+ // bStats
+ implementation("org.bstats:bstats-bukkit:3.0.2")
+
+ // local lib
+ implementation(files("libs/BiomeAPI.jar"))
+}
+
+tasks {
+ shadowJar {
+// relocate ("net.kyori", "net.momirealms.customnameplates.libraries")
+ relocate ("org.bstats", "net.momirealms.customnameplates.libraries.bstats")
+ relocate ("net.momirealms.biomeapi", "net.momirealms.customnameplates.libraries.biomeapi")
+ }
+}
diff --git a/libs/BiomeAPI.jar b/paper/libs/BiomeAPI.jar
similarity index 67%
rename from libs/BiomeAPI.jar
rename to paper/libs/BiomeAPI.jar
index 470bb2c..59096aa 100644
Binary files a/libs/BiomeAPI.jar and b/paper/libs/BiomeAPI.jar differ
diff --git a/paper/libs/CMI-API-9.6.5.0.jar b/paper/libs/CMI-API-9.6.5.0.jar
new file mode 100644
index 0000000..9653148
Binary files /dev/null and b/paper/libs/CMI-API-9.6.5.0.jar differ
diff --git a/paper/libs/TrChat-2.0.11.jar b/paper/libs/TrChat-2.0.11.jar
new file mode 100644
index 0000000..50bd40d
Binary files /dev/null and b/paper/libs/TrChat-2.0.11.jar differ
diff --git a/paper/libs/VentureChat-3.7.1.jar b/paper/libs/VentureChat-3.7.1.jar
new file mode 100644
index 0000000..c646c95
Binary files /dev/null and b/paper/libs/VentureChat-3.7.1.jar differ
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/CustomNameplatesPluginImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/CustomNameplatesPluginImpl.java
new file mode 100644
index 0000000..3e81972
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/CustomNameplatesPluginImpl.java
@@ -0,0 +1,147 @@
+package net.momirealms.customnameplates.paper;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.event.CustomNameplatesReloadEvent;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.adventure.AdventureManagerImpl;
+import net.momirealms.customnameplates.paper.command.CommandManager;
+import net.momirealms.customnameplates.paper.helper.LibraryLoader;
+import net.momirealms.customnameplates.paper.mechanic.actionbar.ActionBarManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.background.BackGroundManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.bossbar.BossBarManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.image.ImageManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.misc.CoolDownManager;
+import net.momirealms.customnameplates.paper.mechanic.misc.PacketManager;
+import net.momirealms.customnameplates.paper.mechanic.misc.VersionManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.NameplateManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.pack.ResourcePackManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.placeholder.PlaceholderManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.requirement.RequirementManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.team.TeamManagerImpl;
+import net.momirealms.customnameplates.paper.scheduler.SchedulerImpl;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import net.momirealms.customnameplates.paper.setting.CNLocale;
+import net.momirealms.customnameplates.paper.storage.StorageManagerImpl;
+import net.momirealms.customnameplates.paper.util.ReflectionUtils;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.util.TimeZone;
+
+public class CustomNameplatesPluginImpl extends CustomNameplatesPlugin {
+
+ private CoolDownManager coolDownManager;
+ private PacketManager packetManager;
+
+ @Override
+ public void onLoad() {
+ this.loadLibraries();
+ ReflectionUtils.load();
+ }
+
+ @Override
+ public void onEnable() {
+ this.adventureManager = new AdventureManagerImpl(this);
+ this.versionManager = new VersionManagerImpl(this);
+ this.scheduler = new SchedulerImpl(this);
+ this.storageManager = new StorageManagerImpl(this);
+ this.requirementManager = new RequirementManagerImpl(this);
+ this.bossBarManager = new BossBarManagerImpl(this);
+ this.imageManager = new ImageManagerImpl(this);
+ this.placeholderManager = new PlaceholderManagerImpl(this);
+ this.backGroundManager = new BackGroundManagerImpl(this);
+ this.resourcePackManager = new ResourcePackManagerImpl(this);
+ this.nameplateManager = new NameplateManagerImpl(this);
+ this.teamManager = new TeamManagerImpl(this);
+ this.actionBarManager = new ActionBarManagerImpl(this);
+ this.coolDownManager = new CoolDownManager(this);
+ this.packetManager = new PacketManager(this);
+ new CommandManager(this).load();
+ this.reload(CNConfig.generatePackOnStart);
+ this.versionManager.checkUpdate().thenAccept(outDated -> {
+ if (!outDated) this.getAdventure().sendConsoleMessage("[CustomNameplates] You are using the latest version.");
+ else this.getAdventure().sendConsoleMessage("[CustomNameplates] Update is available: https://polymart.org/resource/2543");
+ });
+ }
+
+ @Override
+ public void onDisable() {
+ ((SchedulerImpl) this.scheduler).shutdown();
+ ((ActionBarManagerImpl) actionBarManager).unload();
+ ((NameplateManagerImpl) this.nameplateManager).unload();
+ ((TeamManagerImpl) this.teamManager).unload();
+ ((BossBarManagerImpl) this.bossBarManager).unload();
+ ((ImageManagerImpl) this.imageManager).unload();
+ ((BackGroundManagerImpl) this.backGroundManager).unload();
+ ((PlaceholderManagerImpl) this.placeholderManager).unload();
+ ((RequirementManagerImpl) this.requirementManager).unload();
+ ((ResourcePackManagerImpl) this.resourcePackManager).unload();
+ ((StorageManagerImpl) this.storageManager).disable();
+ ((AdventureManagerImpl) this.adventureManager).close();
+ }
+
+ @Override
+ public void reload(boolean generatePack) {
+ CNConfig.load();
+ CNLocale.load();
+ ((SchedulerImpl) this.scheduler).reload();
+ ((NameplateManagerImpl) this.nameplateManager).reload();
+ ((BackGroundManagerImpl) this.backGroundManager).reload();
+ ((TeamManagerImpl) this.teamManager).reload();
+ ((StorageManagerImpl) this.storageManager).reload();
+ ((RequirementManagerImpl) this.requirementManager).reload();
+ ((BossBarManagerImpl) this.bossBarManager).reload();
+ ((ActionBarManagerImpl) actionBarManager).reload();
+ ((ImageManagerImpl) this.imageManager).reload();
+ ((PlaceholderManagerImpl) this.placeholderManager).reload();
+ ((ResourcePackManagerImpl) this.resourcePackManager).reload();
+ this.resourcePackManager.generateResourcePack();
+ CustomNameplatesReloadEvent event = new CustomNameplatesReloadEvent(this);
+ this.getServer().getPluginManager().callEvent(event);
+ }
+
+ @Override
+ public YamlConfiguration getConfig(String file) {
+ File config = new File(this.getDataFolder(), file);
+ if (!config.exists()) this.saveResource(file, false);
+ return YamlConfiguration.loadConfiguration(config);
+ }
+
+ @Override
+ public void debug(String s) {
+ if (CNConfig.debug) {
+ LogUtils.info(s);
+ }
+ }
+
+ public CoolDownManager getCoolDownManager() {
+ return coolDownManager;
+ }
+
+ private void loadLibraries() {
+ String mavenRepo = TimeZone.getDefault().getID().startsWith("Asia") ?
+ "https://maven.aliyun.com/repository/public/" : "https://repo.maven.apache.org/maven2/";
+ LibraryLoader.loadDependencies(
+ "org.apache.commons:commons-pool2:2.12.0", mavenRepo,
+ "redis.clients:jedis:5.1.0", mavenRepo,
+ "dev.dejvokep:boosted-yaml:1.3.1", mavenRepo,
+ "com.zaxxer:HikariCP:5.0.1", mavenRepo,
+ "org.mariadb.jdbc:mariadb-java-client:3.3.0", mavenRepo,
+ "com.mysql:mysql-connector-j:8.2.0", mavenRepo,
+ "commons-io:commons-io:2.14.0", mavenRepo,
+ "com.google.code.gson:gson:2.10.1", mavenRepo,
+ "com.h2database:h2:2.2.224", mavenRepo,
+ "org.mongodb:mongodb-driver-sync:4.11.1", mavenRepo,
+ "org.mongodb:mongodb-driver-core:4.11.1", mavenRepo,
+ "org.mongodb:bson:4.11.1", mavenRepo,
+ "org.xerial:sqlite-jdbc:3.43.2.2", mavenRepo,
+ "dev.jorel:commandapi-bukkit-shade:9.3.0", mavenRepo
+ );
+ }
+
+ public PacketManager getPacketManager() {
+ return packetManager;
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/adventure/AdventureManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/adventure/AdventureManagerImpl.java
new file mode 100644
index 0000000..0cb7e21
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/adventure/AdventureManagerImpl.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.adventure;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.PacketContainer;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import net.kyori.adventure.audience.Audience;
+import net.kyori.adventure.key.Key;
+import net.kyori.adventure.platform.bukkit.BukkitAudiences;
+import net.kyori.adventure.sound.Sound;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+import net.kyori.adventure.title.Title;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.AdventureManager;
+import net.momirealms.customnameplates.paper.mechanic.misc.PacketManager;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import net.momirealms.customnameplates.paper.setting.CNLocale;
+import net.momirealms.customnameplates.paper.util.ReflectionUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.InvocationTargetException;
+import java.time.Duration;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+public class AdventureManagerImpl implements AdventureManager {
+
+ private final BukkitAudiences adventure;
+ private static AdventureManager instance;
+ private final CacheSystem cacheSystem;
+
+ public AdventureManagerImpl(CustomNameplatesPlugin plugin) {
+ this.adventure = BukkitAudiences.create(plugin);
+ this.cacheSystem = new CacheSystem();
+ instance = this;
+ }
+
+ public static AdventureManager getInstance() {
+ return instance;
+ }
+
+ public void close() {
+ if (adventure != null)
+ adventure.close();
+ }
+
+ @Override
+ public Object getIChatComponentFromMiniMessage(String text) {
+ return cacheSystem.getIChatFromCache(text);
+ }
+
+ @Override
+ public String stripTags(String text) {
+ return MiniMessage.miniMessage().stripTags(text);
+ }
+
+ @Override
+ public Component getComponentFromMiniMessage(String text) {
+ if (text == null) {
+ return Component.empty();
+ }
+ if (CNConfig.legacyColorSupport) {
+ return cacheSystem.getComponentFromCache(legacyToMiniMessage(text));
+ } else {
+ return cacheSystem.getComponentFromCache(text);
+ }
+ }
+
+ @Override
+ public void sendMessage(CommandSender sender, String s) {
+ if (s == null) return;
+ if (sender instanceof Player player) sendPlayerMessage(player, s);
+ else if (sender instanceof ConsoleCommandSender) sendConsoleMessage(s);
+ }
+
+ @Override
+ public void sendMessageWithPrefix(CommandSender sender, String s) {
+ if (s == null) return;
+ if (sender instanceof Player player) sendPlayerMessage(player, CNLocale.MSG_PREFIX + s);
+ else if (sender instanceof ConsoleCommandSender) sendConsoleMessage(CNLocale.MSG_PREFIX + s);
+ }
+
+ @Override
+ public void sendConsoleMessage(String s) {
+ if (s == null) return;
+ Audience au = adventure.sender(Bukkit.getConsoleSender());
+ au.sendMessage(getComponentFromMiniMessage(s));
+ }
+
+ @Override
+ public void sendPlayerMessage(Player player, String s) {
+ if (s == null) return;
+ Audience au = adventure.player(player);
+ au.sendMessage(getComponentFromMiniMessage(s));
+ }
+
+ @Override
+ public void sendTitle(Player player, String title, String subtitle, int in, int duration, int out) {
+ Audience au = adventure.player(player);
+ Title.Times times = Title.Times.times(Duration.ofMillis(in), Duration.ofMillis(duration), Duration.ofMillis(out));
+ au.showTitle(Title.title(getComponentFromMiniMessage(title), getComponentFromMiniMessage(subtitle), times));
+ }
+
+ @Override
+ public void sendTitle(Player player, Component title, Component subtitle, int in, int duration, int out) {
+ Audience au = adventure.player(player);
+ Title.Times times = Title.Times.times(Duration.ofMillis(in), Duration.ofMillis(duration), Duration.ofMillis(out));
+ au.showTitle(Title.title(title, subtitle, times));
+ }
+
+ @Override
+ public void sendActionbar(Player player, String text) {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.SET_ACTION_BAR_TEXT);
+ packet.getModifier().write(0, getIChatComponent(componentToJson(getComponentFromMiniMessage(text).append(Component.score().name("np").objective("ab").build()))));
+ PacketManager.getInstance().send(player, packet);
+ }
+
+ @Override
+ public void sendActionbar(Player player, Component component) {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.SET_ACTION_BAR_TEXT);
+ packet.getModifier().write(0, getIChatComponent(componentToJson(component)));
+ PacketManager.getInstance().send(player, packet);
+ }
+
+ @Override
+ public void sendSound(Player player, Sound.Source source, Key key, float volume, float pitch) {
+ Sound sound = Sound.sound(key, source, volume, pitch);
+ Audience au = adventure.player(player);
+ au.playSound(sound);
+ }
+
+ @Override
+ public void sendSound(Player player, Sound sound) {
+ Audience au = adventure.player(player);
+ au.playSound(sound);
+ }
+
+ @Override
+ public String legacyToMiniMessage(String legacy) {
+ StringBuilder stringBuilder = new StringBuilder();
+ char[] chars = legacy.toCharArray();
+ for (int i = 0; i < chars.length; i++) {
+ if (!isColorCode(chars[i])) {
+ stringBuilder.append(chars[i]);
+ continue;
+ }
+ if (i + 1 >= chars.length) {
+ stringBuilder.append(chars[i]);
+ continue;
+ }
+ switch (chars[i+1]) {
+ case '0' -> stringBuilder.append("");
+ case '1' -> stringBuilder.append("");
+ case '2' -> stringBuilder.append("");
+ case '3' -> stringBuilder.append("");
+ case '4' -> stringBuilder.append("");
+ case '5' -> stringBuilder.append("");
+ case '6' -> stringBuilder.append("");
+ case '7' -> stringBuilder.append("");
+ case '8' -> stringBuilder.append("");
+ case '9' -> stringBuilder.append("");
+ case 'a' -> stringBuilder.append("");
+ case 'b' -> stringBuilder.append("");
+ case 'c' -> stringBuilder.append("");
+ case 'd' -> stringBuilder.append("");
+ case 'e' -> stringBuilder.append("");
+ case 'f' -> stringBuilder.append("");
+ case 'r' -> stringBuilder.append("");
+ case 'l' -> stringBuilder.append("");
+ case 'm' -> stringBuilder.append("");
+ case 'o' -> stringBuilder.append("");
+ case 'n' -> stringBuilder.append("");
+ case 'k' -> stringBuilder.append("");
+ case 'x' -> {
+ if (i + 13 >= chars.length
+ || !isColorCode(chars[i+2])
+ || !isColorCode(chars[i+4])
+ || !isColorCode(chars[i+6])
+ || !isColorCode(chars[i+8])
+ || !isColorCode(chars[i+10])
+ || !isColorCode(chars[i+12])) {
+ stringBuilder.append(chars[i]);
+ continue;
+ }
+ stringBuilder
+ .append("<#")
+ .append(chars[i+3])
+ .append(chars[i+5])
+ .append(chars[i+7])
+ .append(chars[i+9])
+ .append(chars[i+11])
+ .append(chars[i+13])
+ .append(">");
+ i += 12;
+ }
+ default -> {
+ stringBuilder.append(chars[i]);
+ continue;
+ }
+ }
+ i++;
+ }
+ return stringBuilder.toString();
+ }
+
+ @Override
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ public boolean isColorCode(char c) {
+ return c == '§' || c == '&';
+ }
+
+ @Override
+ public String componentToLegacy(Component component) {
+ return LegacyComponentSerializer.legacySection().serialize(component);
+ }
+
+ @Override
+ public String componentToJson(Component component) {
+ return GsonComponentSerializer.gson().serialize(component);
+ }
+
+ @Override
+ public Object shadedToOriginal(Component component) {
+ Object cp;
+ try {
+ cp = ReflectionUtils.gsonDeserializeMethod.invoke(ReflectionUtils.gsonInstance, GsonComponentSerializer.gson().serialize(component));
+ } catch (InvocationTargetException | IllegalAccessException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return cp;
+ }
+
+ @Override
+ public String getMiniMessageFormat(Component component) {
+ return MiniMessage.miniMessage().serialize(component);
+ }
+
+ @Override
+ public Object getIChatComponent(String json) {
+ try {
+ return ReflectionUtils.iChatComponentMethod.invoke(null, json);
+ } catch (InvocationTargetException | IllegalAccessException exception) {
+ exception.printStackTrace();
+ return ReflectionUtils.emptyComponent;
+ }
+ }
+
+ public class CacheSystem {
+
+ private final LoadingCache miniMessageToIChatComponentCache;
+ private final LoadingCache miniMessageToComponentCache;
+
+ public CacheSystem() {
+ miniMessageToIChatComponentCache = CacheBuilder.newBuilder()
+ .maximumSize(100)
+ .expireAfterWrite(10, TimeUnit.MINUTES)
+ .build(
+ new CacheLoader<>() {
+ @NotNull
+ @Override
+ public Object load(@NotNull String text) {
+ return fetchIChatData(text);
+ }
+ });
+ miniMessageToComponentCache = CacheBuilder.newBuilder()
+ .maximumSize(100)
+ .expireAfterWrite(10, TimeUnit.MINUTES)
+ .build(
+ new CacheLoader<>() {
+ @NotNull
+ @Override
+ public Component load(@NotNull String text) {
+ return fetchComponent(text);
+ }
+ });
+ }
+
+ @NotNull
+ private Object fetchIChatData(String text) {
+ Component component = getComponentFromMiniMessage(text);
+ return getIChatComponent(GsonComponentSerializer.gson().serialize(component));
+ }
+
+ @NotNull
+ private Component fetchComponent(String text) {
+ return getComponentFromMiniMessage(text);
+ }
+
+ public Object getIChatFromCache(String text) {
+ try {
+ return miniMessageToIChatComponentCache.get(text);
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ return ReflectionUtils.emptyComponent;
+ }
+ }
+
+ public Component getComponentFromCache(String text) {
+ try {
+ return miniMessageToComponentCache.get(text);
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ return Component.empty();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/command/CommandManager.java b/paper/src/main/java/net/momirealms/customnameplates/paper/command/CommandManager.java
new file mode 100644
index 0000000..f8375c9
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/command/CommandManager.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.command;
+
+import dev.jorel.commandapi.CommandAPI;
+import dev.jorel.commandapi.CommandAPIBukkitConfig;
+import dev.jorel.commandapi.CommandAPICommand;
+import dev.jorel.commandapi.arguments.ArgumentSuggestions;
+import dev.jorel.commandapi.arguments.StringArgument;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import net.momirealms.customnameplates.paper.adventure.AdventureManagerImpl;
+import net.momirealms.customnameplates.paper.setting.CNLocale;
+import org.bukkit.entity.Player;
+
+public class CommandManager {
+
+ private final CustomNameplatesPluginImpl plugin;
+
+ public CommandManager(CustomNameplatesPluginImpl plugin) {
+ this.plugin = plugin;
+ if (!CommandAPI.isLoaded())
+ CommandAPI.onLoad(new CommandAPIBukkitConfig(plugin).silentLogs(true));
+ }
+
+ public void load() {
+ new CommandAPICommand("customnameplates")
+ .withAliases("nameplates", "cnameplates")
+ .withPermission("customnameplates.admin")
+ .withSubcommands(
+ getReloadCommand(),
+ getAboutCommand(),
+ getEquipCommand(),
+ getUnEquipCommand()
+ )
+ .register();
+ }
+
+ private CommandAPICommand getEquipCommand() {
+ return new CommandAPICommand("equip")
+ .withArguments(new StringArgument("nameplate").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> plugin.getNameplateManager().getAvailableNameplates((Player) commandSenderSuggestionInfo.sender()).toArray(new String[0]))))
+ .executesPlayer((player, args) -> {
+ String nameplate = (String) args.get("nameplate");
+ if (!plugin.getNameplateManager().hasNameplate(player, nameplate)) {
+ AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, CNLocale.MSG_NAMEPLATE_NOT_AVAILABLE);
+ return;
+ }
+
+ if (!plugin.getNameplateManager().equipNameplate(player, nameplate)) {
+ AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, CNLocale.MSG_NAMEPLATE_NOT_EXISTS);
+ return;
+ }
+
+ AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, CNLocale.MSG_EQUIP_NAMEPLATE);
+ });
+ }
+
+ private CommandAPICommand getUnEquipCommand() {
+ return new CommandAPICommand("unequip")
+ .executesPlayer((player, args) -> {
+ plugin.getNameplateManager().unEquipNameplate(player);
+ AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, CNLocale.MSG_UNEQUIP_NAMEPLATE);
+ });
+ }
+
+ private CommandAPICommand getReloadCommand() {
+ return new CommandAPICommand("reload")
+ .executes((sender, args) -> {
+ long time = System.currentTimeMillis();
+ plugin.reload(true);
+ AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, CNLocale.MSG_RELOAD.replace("{time}", String.valueOf(System.currentTimeMillis()-time)));
+ });
+ }
+
+ private CommandAPICommand getAboutCommand() {
+ return new CommandAPICommand("about").executes((sender, args) -> {
+ AdventureManagerImpl.getInstance().sendMessage(sender, "<#3CB371>⚓ CustomNameplates - <#98FB98>" + CustomNameplatesPlugin.getInstance().getVersionManager().getPluginVersion());
+ AdventureManagerImpl.getInstance().sendMessage(sender, "<#7FFFAA>A plugin that provides adjustable images for texts");
+ AdventureManagerImpl.getInstance().sendMessage(sender, "<#DA70D6>\uD83E\uDDEA Author: <#FFC0CB>XiaoMoMi");
+ AdventureManagerImpl.getInstance().sendMessage(sender, "<#FF7F50>\uD83D\uDD25 Contributors: <#FFA07A>TopOrigin");
+ AdventureManagerImpl.getInstance().sendMessage(sender, "<#FFD700>⭐ Document <#A9A9A9>| <#FAFAD2>⛏ Github <#A9A9A9>| <#48D1CC>\uD83D\uDD14 Polymart");
+ });
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/helper/LibraryLoader.java b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/LibraryLoader.java
similarity index 53%
rename from src/main/java/net/momirealms/customnameplates/helper/LibraryLoader.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/helper/LibraryLoader.java
index d4cbfa4..07ac490 100644
--- a/src/main/java/net/momirealms/customnameplates/helper/LibraryLoader.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/LibraryLoader.java
@@ -23,11 +23,13 @@
* SOFTWARE.
*/
-package net.momirealms.customnameplates.helper;
+package net.momirealms.customnameplates.paper.helper;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
-import net.momirealms.customnameplates.CustomNameplates;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
import java.io.File;
import java.io.InputStream;
@@ -42,11 +44,10 @@ import java.util.StringJoiner;
* Resolves {@link MavenLibrary} annotations for a class, and loads the dependency
* into the classloader.
*/
-@NonnullByDefault
public final class LibraryLoader {
@SuppressWarnings("Guava")
- private static final Supplier URL_INJECTOR = Suppliers.memoize(() -> URLClassLoaderAccess.create((URLClassLoader) CustomNameplates.getInstance().getClass().getClassLoader()));
+ private static final Supplier URL_INJECTOR = Suppliers.memoize(() -> URLClassLoaderAccess.create((URLClassLoader) CustomNameplatesPluginImpl.getInstance().getClass().getClassLoader()));
/**
* Resolves all {@link MavenLibrary} annotations on the given object.
@@ -73,31 +74,38 @@ public final class LibraryLoader {
load(new Dependency(groupId, artifactId, version, repoUrl));
}
- public static void load(Dependency d) {
- //Log.info(String.startFormat("Loading dependency %s:%s:%s from %s", d.getGroupId(), d.getArtifactId(), d.getVersion(), d.getRepoUrl()));
- String name = d.getArtifactId() + "-" + d.getVersion();
+ public static void loadDependencies(String... libs) {
+ if (libs == null || libs.length % 2 != 0)
+ return;
+ for (int i = 0; i < libs.length; i+=2) {
+ String[] split = libs[i].split(":");
+ load(new Dependency(
+ split[0],
+ split[1],
+ split[2],
+ libs[i+1]
+ ));
+ }
+ }
+ public static void load(Dependency d) {
+// LogUtils.info(String.format("Loading dependency %s:%s:%s", d.groupId, d.artifactId, d.version));
+ String name = d.artifactId() + "-" + d.version();
File saveLocation = new File(getLibFolder(d), name + ".jar");
if (!saveLocation.exists()) {
-
try {
- Log.info("Dependency '" + name + "' is not already in the libraries folder. Attempting to download...");
+ LogUtils.info("Dependency '" + name + "' is not already in the libraries folder. Attempting to download...");
URL url = d.getUrl();
-
try (InputStream is = url.openStream()) {
Files.copy(is, saveLocation.toPath());
- Log.info("Dependency '" + name + "' successfully downloaded.");
+ LogUtils.info("Dependency '" + name + "' successfully downloaded.");
}
-
} catch (Exception e) {
e.printStackTrace();
}
}
-
- if (!saveLocation.exists()) {
+ if (!saveLocation.exists())
throw new RuntimeException("Unable to download dependency: " + d);
- }
-
try {
URL_INJECTOR.get().addURL(saveLocation.toURI().toURL());
} catch (Exception e) {
@@ -105,12 +113,13 @@ public final class LibraryLoader {
}
}
+ @SuppressWarnings("all")
private static File getLibFolder(Dependency dependency) {
- File pluginDataFolder = CustomNameplates.getInstance().getDataFolder();
+ File pluginDataFolder = CustomNameplatesPlugin.getInstance().getDataFolder();
File serverDir = pluginDataFolder.getParentFile().getParentFile();
File helperDir = new File(serverDir, "libraries");
- String[] split = dependency.getGroupId().split("\\.");
+ String[] split = dependency.groupId().split("\\.");
File jarDir;
StringJoiner stringJoiner = new StringJoiner(File.separator);
for (String str : split) {
@@ -121,76 +130,53 @@ public final class LibraryLoader {
return jarDir;
}
- @NonnullByDefault
- public static final class Dependency {
- private final String groupId;
- private final String artifactId;
- private final String version;
- private final String repoUrl;
-
- public Dependency(String groupId, String artifactId, String version, String repoUrl) {
- this.groupId = Objects.requireNonNull(groupId, "groupId");
- this.artifactId = Objects.requireNonNull(artifactId, "artifactId");
- this.version = Objects.requireNonNull(version, "version");
- this.repoUrl = Objects.requireNonNull(repoUrl, "repoUrl");
- }
-
- public String getGroupId() {
- return this.groupId;
- }
-
- public String getArtifactId() {
- return this.artifactId;
- }
-
- public String getVersion() {
- return this.version;
- }
-
- public String getRepoUrl() {
- return this.repoUrl;
- }
-
- public URL getUrl() throws MalformedURLException {
- String repo = this.repoUrl;
- if (!repo.endsWith("/")) {
- repo += "/";
+ public record Dependency(String groupId, String artifactId, String version, String repoUrl) {
+ public Dependency(String groupId, String artifactId, String version, String repoUrl) {
+ this.groupId = Objects.requireNonNull(groupId, "groupId");
+ this.artifactId = Objects.requireNonNull(artifactId, "artifactId");
+ this.version = Objects.requireNonNull(version, "version");
+ this.repoUrl = Objects.requireNonNull(repoUrl, "repoUrl");
}
- repo += "%s/%s/%s/%s-%s.jar";
- String url = String.format(repo, this.groupId.replace(".", "/"), this.artifactId, this.version, this.artifactId, this.version);
- return new URL(url);
- }
+ public URL getUrl() throws MalformedURLException {
+ String repo = this.repoUrl;
+ if (!repo.endsWith("/")) {
+ repo += "/";
+ }
+ repo += "%s/%s/%s/%s-%s.jar";
- @Override
- public boolean equals(Object o) {
- if (o == this) return true;
- if (!(o instanceof Dependency)) return false;
- final Dependency other = (Dependency) o;
- return this.getGroupId().equals(other.getGroupId()) &&
- this.getArtifactId().equals(other.getArtifactId()) &&
- this.getVersion().equals(other.getVersion()) &&
- this.getRepoUrl().equals(other.getRepoUrl());
- }
+ String url = String.format(repo, this.groupId.replace(".", "/"), this.artifactId, this.version, this.artifactId, this.version);
+ return new URL(url);
+ }
- @Override
- public int hashCode() {
- final int PRIME = 59;
- int result = 1;
- result = result * PRIME + this.getGroupId().hashCode();
- result = result * PRIME + this.getArtifactId().hashCode();
- result = result * PRIME + this.getVersion().hashCode();
- result = result * PRIME + this.getRepoUrl().hashCode();
- return result;
- }
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof Dependency other)) return false;
+ return this.groupId().equals(other.groupId()) &&
+ this.artifactId().equals(other.artifactId()) &&
+ this.version().equals(other.version()) &&
+ this.repoUrl().equals(other.repoUrl());
+ }
- @Override
- public String toString() {
- return "LibraryLoader.Dependency(" +
- "groupId=" + this.getGroupId() + ", " +
- "artifactId=" + this.getArtifactId() + ", " +
- "version=" + this.getVersion() + ", " +
- "repoUrl=" + this.getRepoUrl() + ")";
+ @Override
+ public int hashCode() {
+ final int PRIME = 59;
+ int result = 1;
+ result = result * PRIME + this.groupId().hashCode();
+ result = result * PRIME + this.artifactId().hashCode();
+ result = result * PRIME + this.version().hashCode();
+ result = result * PRIME + this.repoUrl().hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "LibraryLoader.Dependency(" +
+ "groupId=" + this.groupId() + ", " +
+ "artifactId=" + this.artifactId() + ", " +
+ "version=" + this.version() + ", " +
+ "repoUrl=" + this.repoUrl() + ")";
+ }
}
- }
}
diff --git a/src/main/java/net/momirealms/customnameplates/helper/MavenLibraries.java b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibraries.java
similarity index 93%
rename from src/main/java/net/momirealms/customnameplates/helper/MavenLibraries.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibraries.java
index ca4747c..f4ce9c3 100644
--- a/src/main/java/net/momirealms/customnameplates/helper/MavenLibraries.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibraries.java
@@ -23,9 +23,10 @@
* SOFTWARE.
*/
-package net.momirealms.customnameplates.helper;
+package net.momirealms.customnameplates.paper.helper;
+
+import org.jetbrains.annotations.NotNull;
-import javax.annotation.Nonnull;
import java.lang.annotation.*;
/**
@@ -36,7 +37,7 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface MavenLibraries {
- @Nonnull
+ @NotNull
MavenLibrary[] value() default {};
}
diff --git a/src/main/java/net/momirealms/customnameplates/helper/MavenLibrary.java b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibrary.java
similarity index 93%
rename from src/main/java/net/momirealms/customnameplates/helper/MavenLibrary.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibrary.java
index 27a653a..010f319 100644
--- a/src/main/java/net/momirealms/customnameplates/helper/MavenLibrary.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/MavenLibrary.java
@@ -23,9 +23,10 @@
* SOFTWARE.
*/
-package net.momirealms.customnameplates.helper;
+package net.momirealms.customnameplates.paper.helper;
+
+import org.jetbrains.annotations.NotNull;
-import javax.annotation.Nonnull;
import java.lang.annotation.*;
/**
@@ -42,7 +43,7 @@ public @interface MavenLibrary {
*
* @return the group id of the library
*/
- @Nonnull
+ @NotNull
String groupId();
/**
@@ -50,7 +51,7 @@ public @interface MavenLibrary {
*
* @return the artifact id of the library
*/
- @Nonnull
+ @NotNull
String artifactId();
/**
@@ -58,7 +59,7 @@ public @interface MavenLibrary {
*
* @return the version of the library
*/
- @Nonnull
+ @NotNull
String version();
/**
@@ -66,7 +67,7 @@ public @interface MavenLibrary {
*
* @return the repo where the library can be obtained from
*/
- @Nonnull
+ @NotNull
Repository repo() default @Repository(url = "https://repo1.maven.org/maven2");
}
diff --git a/src/main/java/net/momirealms/customnameplates/helper/Repository.java b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/Repository.java
similarity index 94%
rename from src/main/java/net/momirealms/customnameplates/helper/Repository.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/helper/Repository.java
index 5bb87a5..fa047ec 100644
--- a/src/main/java/net/momirealms/customnameplates/helper/Repository.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/Repository.java
@@ -23,9 +23,8 @@
* SOFTWARE.
*/
-package net.momirealms.customnameplates.helper;
+package net.momirealms.customnameplates.paper.helper;
-import javax.annotation.Nonnull;
import java.lang.annotation.*;
/**
@@ -41,7 +40,6 @@ public @interface Repository {
*
* @return the base url of the repository
*/
- @Nonnull
String url();
}
diff --git a/src/main/java/net/momirealms/customnameplates/helper/URLClassLoaderAccess.java b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/URLClassLoaderAccess.java
similarity index 94%
rename from src/main/java/net/momirealms/customnameplates/helper/URLClassLoaderAccess.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/helper/URLClassLoaderAccess.java
index 2541880..476d11a 100644
--- a/src/main/java/net/momirealms/customnameplates/helper/URLClassLoaderAccess.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/helper/URLClassLoaderAccess.java
@@ -23,9 +23,10 @@
* SOFTWARE.
*/
-package net.momirealms.customnameplates.helper;
+package net.momirealms.customnameplates.paper.helper;
+
+import org.jetbrains.annotations.NotNull;
-import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
@@ -62,7 +63,7 @@ public abstract class URLClassLoaderAccess {
*
* @param url the URL to add
*/
- public abstract void addURL(@Nonnull URL url);
+ public abstract void addURL(@NotNull URL url);
/**
* Accesses using sun.misc.Unsafe, supported on Java 9+.
@@ -116,7 +117,7 @@ public abstract class URLClassLoaderAccess {
}
@Override
- public void addURL(@Nonnull URL url) {
+ public void addURL(@NotNull URL url) {
this.unopenedURLs.add(url);
this.pathURLs.add(url);
}
@@ -130,7 +131,7 @@ public abstract class URLClassLoaderAccess {
}
@Override
- public void addURL(@Nonnull URL url) {
+ public void addURL(@NotNull URL url) {
throw new UnsupportedOperationException();
}
}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/ActionBarConfig.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/ActionBarConfig.java
new file mode 100644
index 0000000..9d2cc96
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/ActionBarConfig.java
@@ -0,0 +1,79 @@
+package net.momirealms.customnameplates.paper.mechanic.actionbar;
+
+import net.momirealms.customnameplates.api.requirement.Requirement;
+import net.momirealms.customnameplates.paper.mechanic.bossbar.BarColor;
+import net.momirealms.customnameplates.paper.mechanic.bossbar.Overlay;
+import net.momirealms.customnameplates.paper.mechanic.misc.TimeLimitText;
+
+public class ActionBarConfig {
+
+ private int checkFrequency;
+ private Requirement[] requirements;
+ private TimeLimitText[] textDisplayOrder;
+
+ private ActionBarConfig() {
+ checkFrequency = 1;
+ requirements = new Requirement[0];
+ textDisplayOrder = new TimeLimitText[0];
+ }
+
+ public ActionBarConfig(
+ Overlay overlay,
+ BarColor barColor,
+ int checkFrequency,
+ Requirement[] requirements,
+ TimeLimitText[] textDisplayOrder
+ ) {
+ this.checkFrequency = checkFrequency;
+ this.requirements = requirements;
+ this.textDisplayOrder = textDisplayOrder;
+ }
+
+ public int getCheckFrequency() {
+ return checkFrequency;
+ }
+
+ public Requirement[] getRequirements() {
+ return requirements;
+ }
+
+ public TimeLimitText[] getTextDisplayOrder() {
+ return textDisplayOrder;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final ActionBarConfig config;
+
+ public Builder() {
+ this.config = new ActionBarConfig();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder checkFrequency(int checkFrequency) {
+ config.checkFrequency = checkFrequency;
+ return this;
+ }
+
+ public Builder requirement(Requirement[] requirements) {
+ config.requirements = requirements;
+ return this;
+ }
+
+ public Builder displayOrder(TimeLimitText[] textDisplayOrder) {
+ config.textDisplayOrder = textDisplayOrder;
+ return this;
+ }
+
+ public ActionBarConfig build() {
+ return config;
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/ActionBarManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/ActionBarManagerImpl.java
new file mode 100644
index 0000000..a3b8031
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/ActionBarManagerImpl.java
@@ -0,0 +1,219 @@
+package net.momirealms.customnameplates.paper.mechanic.actionbar;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import com.comphenix.protocol.wrappers.EnumWrappers;
+import com.comphenix.protocol.wrappers.WrappedChatComponent;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TranslatableComponent;
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.ActionBarManager;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.adventure.AdventureManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.actionbar.listener.ActionBarListener;
+import net.momirealms.customnameplates.paper.mechanic.actionbar.listener.ChatMessageListener;
+import net.momirealms.customnameplates.paper.mechanic.actionbar.listener.SystemChatListener;
+import net.momirealms.customnameplates.paper.mechanic.misc.DisplayController;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import net.momirealms.customnameplates.paper.util.ConfigUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+public class ActionBarManagerImpl implements ActionBarManager, Listener {
+
+ private final ConcurrentHashMap receiverMap;
+ private final CustomNameplatesPlugin plugin;
+ private final ActionBarListener actionBarListener;
+ private ActionBarConfig config;
+ private ChatMessageListener chatMessageListener;
+ private SystemChatListener systemChatListener;
+
+ public ActionBarManagerImpl(CustomNameplatesPlugin plugin) {
+ this.receiverMap = new ConcurrentHashMap<>();
+ this.plugin = plugin;
+ this.actionBarListener = new ActionBarListener(this);
+ if (plugin.getVersionManager().isVersionNewerThan1_19()) {
+ this.systemChatListener = new SystemChatListener(this);
+ } else {
+ this.chatMessageListener = new ChatMessageListener(this);
+ }
+ }
+
+ public void load() {
+ if (!CNConfig.actionBarModule) return;
+ this.loadConfigs();
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ createActionBarFor(player);
+ }
+
+ Bukkit.getPluginManager().registerEvents(this, plugin);
+ if (actionBarListener != null) ProtocolLibrary.getProtocolManager().addPacketListener(actionBarListener);
+ if (systemChatListener != null) ProtocolLibrary.getProtocolManager().addPacketListener(systemChatListener);
+ if (chatMessageListener != null) ProtocolLibrary.getProtocolManager().addPacketListener(chatMessageListener);
+ }
+
+ public void unload() {
+ for (ActionBarReceiver receiver : receiverMap.values()) {
+ receiver.cancelTask();
+ receiver.destroy();
+ }
+ receiverMap.clear();
+
+ HandlerList.unregisterAll(this);
+ if (actionBarListener != null) ProtocolLibrary.getProtocolManager().removePacketListener(actionBarListener);
+ if (systemChatListener != null) ProtocolLibrary.getProtocolManager().removePacketListener(systemChatListener);
+ if (chatMessageListener != null) ProtocolLibrary.getProtocolManager().removePacketListener(chatMessageListener);
+ }
+
+ public void reload() {
+ unload();
+ load();
+ }
+
+ private void loadConfigs() {
+ YamlConfiguration config = plugin.getConfig("configs" + File.separator + "actionbar.yml");
+ for (Map.Entry barEntry : config.getValues(false).entrySet()) {
+ if (!(barEntry.getValue() instanceof ConfigurationSection section))
+ return;
+
+ this.config = ActionBarConfig.Builder.of()
+ .checkFrequency(section.getInt("check-frequency", 10))
+ .requirement(plugin.getRequirementManager().getRequirements(section.getConfigurationSection("conditions")))
+ .displayOrder(ConfigUtils.getTimeLimitTexts(section.getConfigurationSection("text-display-order")))
+ .build();
+ }
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ final Player player = event.getPlayer();
+ if (CNConfig.sendDelay == 0) {
+ createActionBarFor(player);
+ return;
+ }
+ this.plugin.getScheduler().runTaskAsyncLater(() -> {
+ createActionBarFor(player);
+ }, CNConfig.sendDelay * 50L, TimeUnit.MILLISECONDS);
+ }
+
+ private void createActionBarFor(Player player) {
+ if (player == null || !player.isOnline()) {
+ return;
+ }
+ ActionBarReceiver receiver = new ActionBarReceiver(plugin, player, new DisplayController(
+ player, config.getCheckFrequency(), config.getRequirements(), config.getTextDisplayOrder()
+ ));
+ receiver.arrangeTask();
+ this.putReceiverToMap(player.getUniqueId(), receiver);
+ }
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ ActionBarReceiver receiver = receiverMap.remove(event.getPlayer().getUniqueId());
+ if (receiver != null) {
+ receiver.cancelTask();
+ }
+ }
+
+ private void putReceiverToMap(UUID uuid, ActionBarReceiver actionBarReceiver) {
+ ActionBarReceiver previous = this.receiverMap.put(uuid, actionBarReceiver);
+ if (previous != null) {
+ LogUtils.warn("Unexpected error: Duplicated actionbar created");
+ previous.cancelTask();
+ }
+ }
+
+ private ActionBarReceiver getReceiver(UUID uuid) {
+ return this.receiverMap.get(uuid);
+ }
+
+ // 1.19+
+ public void onReceiveSystemChatPacket(PacketEvent event) {
+ PacketContainer packet = event.getPacket();
+ Boolean overlay = packet.getBooleans().readSafely(0);
+ if (overlay != null && overlay) {
+ ActionBarReceiver receiver = getReceiver(event.getPlayer().getUniqueId());
+ if (receiver != null) {
+ event.setCancelled(true);
+ String json = packet.getStrings().readSafely(0);
+ if (json != null && !json.equals("")) {
+ Component component = GsonComponentSerializer.gson().deserialize(json);
+ if (component instanceof TranslatableComponent) {
+ // We can't get TranslatableComponent's width :(
+ return;
+ }
+ receiver.setOtherPluginText(AdventureManagerImpl.getInstance().getMiniMessageFormat(component), System.currentTimeMillis());
+ }
+ }
+ }
+ }
+
+ // lower version
+ public void onReceiveChatMessagePacket(PacketEvent event) {
+ PacketContainer packet = event.getPacket();
+ EnumWrappers.ChatType type = packet.getChatTypes().readSafely(0);
+ if (type == EnumWrappers.ChatType.GAME_INFO) {
+ ActionBarReceiver receiver = getReceiver(event.getPlayer().getUniqueId());
+ if (receiver != null) {
+ event.setCancelled(true);
+ WrappedChatComponent wrappedChatComponent = packet.getChatComponents().read(0);
+ if (wrappedChatComponent != null) {
+ String json = wrappedChatComponent.getJson();
+ Component component = GsonComponentSerializer.gson().deserialize(json);
+ if (component instanceof TranslatableComponent) {
+ // We can't get TranslatableComponent's width :(
+ return;
+ }
+ receiver.setOtherPluginText(AdventureManagerImpl.getInstance().getMiniMessageFormat(component), System.currentTimeMillis());
+ }
+ }
+ }
+ }
+
+ public void onReceiveActionBarPacket(PacketEvent event) {
+ PacketContainer packet = event.getPacket();
+ WrappedChatComponent wrappedChatComponent = packet.getChatComponents().read(0);
+ if (wrappedChatComponent != null) {
+ ActionBarReceiver receiver = getReceiver(event.getPlayer().getUniqueId());
+ if (receiver != null) {
+ String strJson = wrappedChatComponent.getJson();
+ // for better performance
+ if (strJson.contains("\"name\":\"np\",\"objective\":\"ab\"")) {
+ return;
+ }
+ event.setCancelled(true);
+ receiver.setOtherPluginText(
+ AdventureManagerImpl.getInstance().getMiniMessageFormat(
+ GsonComponentSerializer.gson().deserialize(strJson)
+ ), System.currentTimeMillis()
+ );
+ }
+ }
+ }
+
+ @NotNull
+ @Override
+ public String getOtherPluginActionBar(Player player) {
+ ActionBarReceiver receiver = getReceiver(player.getUniqueId());
+ if (receiver != null) {
+ return receiver.getOtherPluginText();
+ }
+ return "";
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/ActionBarReceiver.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/ActionBarReceiver.java
new file mode 100644
index 0000000..67d3740
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/ActionBarReceiver.java
@@ -0,0 +1,102 @@
+package net.momirealms.customnameplates.paper.mechanic.actionbar;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.requirement.Condition;
+import net.momirealms.customnameplates.api.scheduler.CancellableTask;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.adventure.AdventureManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.misc.DisplayController;
+import org.bukkit.entity.Player;
+
+import java.util.concurrent.TimeUnit;
+
+public class ActionBarReceiver {
+
+ private final CustomNameplatesPlugin plugin;
+ private final Player player;
+ private CancellableTask actionBarTask;
+ private final DisplayController controller;
+ private long lastUpdateTime;
+ private String otherPluginText;
+ private long expireTime;
+
+ public ActionBarReceiver(CustomNameplatesPlugin plugin, Player player, DisplayController controller) {
+ this.plugin = plugin;
+ this.player = player;
+ this.controller = controller;
+ this.otherPluginText = "";
+ }
+
+ public void timer() {
+ if (System.currentTimeMillis() > expireTime) {
+ this.otherPluginText = "";
+ }
+ Condition condition = new Condition(player);
+ switch (controller.stateCheck(condition)) {
+ case KEEP -> {
+ if (!controller.isShown()) {
+ return;
+ }
+ long current = System.currentTimeMillis();
+ if (controller.updateText(condition) || shouldSendBeatPack(current)) {
+ AdventureManagerImpl.getInstance().sendActionbar(player, controller.getLatestContent());
+ lastUpdateTime = current;
+ }
+ }
+ case UPDATE -> {
+ if (controller.isShown()) {
+ controller.initialize();
+ AdventureManagerImpl.getInstance().sendActionbar(player, controller.getLatestContent());
+ } else {
+ AdventureManagerImpl.getInstance().sendActionbar(player, "");
+ }
+ }
+ }
+ }
+
+ public void arrangeTask() {
+ if (this.actionBarTask != null && !this.actionBarTask.isCancelled()) {
+ LogUtils.warn("There's already an ActionBar task running");
+ return;
+ }
+ this.actionBarTask = this.plugin.getScheduler().runTaskAsyncTimer(() -> {
+ try {
+ timer();
+ } catch (Exception e) {
+ LogUtils.severe(
+ "Error occurred when sending ActionBars. " +
+ "This might not be a bug in CustomNameplates. Please report " +
+ "to the Plugin on the top of the following " +
+ "stack trace."
+ );
+ e.printStackTrace();
+ }
+ }, 50, 50, TimeUnit.MILLISECONDS);
+ }
+
+ public void cancelTask() {
+ if (this.actionBarTask == null) {
+ LogUtils.warn("ActionBar task has been already cancelled");
+ return;
+ }
+ this.actionBarTask.cancel();
+ this.actionBarTask = null;
+ }
+
+ public boolean shouldSendBeatPack(long current) {
+ return current - lastUpdateTime > 1700;
+ }
+
+ public void setOtherPluginText(String text, long current) {
+ this.otherPluginText = text;
+ this.expireTime = current + 3000;
+ }
+
+ public String getOtherPluginText() {
+ return otherPluginText;
+ }
+
+ public void destroy() {
+ AdventureManagerImpl.getInstance().sendActionbar(player, "");
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/listener/packet/ActionBarListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/listener/ActionBarListener.java
similarity index 70%
rename from src/main/java/net/momirealms/customnameplates/listener/packet/ActionBarListener.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/listener/ActionBarListener.java
index 897bfcd..7518d23 100644
--- a/src/main/java/net/momirealms/customnameplates/listener/packet/ActionBarListener.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/listener/ActionBarListener.java
@@ -15,21 +15,21 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.listener.packet;
+package net.momirealms.customnameplates.paper.mechanic.actionbar.listener;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.manager.ActionBarManager;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.mechanic.actionbar.ActionBarManagerImpl;
public class ActionBarListener extends PacketAdapter {
- private final ActionBarManager actionBarManager;
+ private final ActionBarManagerImpl actionBarManager;
- public ActionBarListener(ActionBarManager actionBarManager) {
- super(CustomNameplates.getInstance(), ListenerPriority.NORMAL, PacketType.Play.Server.SET_ACTION_BAR_TEXT);
+ public ActionBarListener(ActionBarManagerImpl actionBarManager) {
+ super(CustomNameplatesPlugin.getInstance(), ListenerPriority.NORMAL, PacketType.Play.Server.SET_ACTION_BAR_TEXT);
this.actionBarManager = actionBarManager;
}
diff --git a/src/main/java/net/momirealms/customnameplates/listener/packet/ChatMessageListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/listener/ChatMessageListener.java
similarity index 70%
rename from src/main/java/net/momirealms/customnameplates/listener/packet/ChatMessageListener.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/listener/ChatMessageListener.java
index e11f030..ccfbcea 100644
--- a/src/main/java/net/momirealms/customnameplates/listener/packet/ChatMessageListener.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/listener/ChatMessageListener.java
@@ -15,21 +15,21 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.listener.packet;
+package net.momirealms.customnameplates.paper.mechanic.actionbar.listener;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.manager.ActionBarManager;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.mechanic.actionbar.ActionBarManagerImpl;
public class ChatMessageListener extends PacketAdapter {
- private final ActionBarManager actionBarManager;
+ private final ActionBarManagerImpl actionBarManager;
- public ChatMessageListener(ActionBarManager actionBarManager) {
- super(CustomNameplates.getInstance(), ListenerPriority.NORMAL, PacketType.Play.Server.CHAT);
+ public ChatMessageListener(ActionBarManagerImpl actionBarManager) {
+ super(CustomNameplatesPlugin.getInstance(), ListenerPriority.NORMAL, PacketType.Play.Server.CHAT);
this.actionBarManager = actionBarManager;
}
diff --git a/src/main/java/net/momirealms/customnameplates/listener/packet/SystemChatListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/listener/SystemChatListener.java
similarity index 70%
rename from src/main/java/net/momirealms/customnameplates/listener/packet/SystemChatListener.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/listener/SystemChatListener.java
index eb0d6e6..04ac7bb 100644
--- a/src/main/java/net/momirealms/customnameplates/listener/packet/SystemChatListener.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/actionbar/listener/SystemChatListener.java
@@ -15,21 +15,21 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.listener.packet;
+package net.momirealms.customnameplates.paper.mechanic.actionbar.listener;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.manager.ActionBarManager;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.mechanic.actionbar.ActionBarManagerImpl;
public class SystemChatListener extends PacketAdapter {
- private final ActionBarManager actionBarManager;
+ private final ActionBarManagerImpl actionBarManager;
- public SystemChatListener(ActionBarManager actionBarManager) {
- super(CustomNameplates.getInstance(), ListenerPriority.NORMAL, PacketType.Play.Server.SYSTEM_CHAT);
+ public SystemChatListener(ActionBarManagerImpl actionBarManager) {
+ super(CustomNameplatesPlugin.getInstance(), ListenerPriority.NORMAL, PacketType.Play.Server.SYSTEM_CHAT);
this.actionBarManager = actionBarManager;
}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/background/BackGroundManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/background/BackGroundManagerImpl.java
new file mode 100644
index 0000000..4642f39
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/background/BackGroundManagerImpl.java
@@ -0,0 +1,194 @@
+package net.momirealms.customnameplates.paper.mechanic.background;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.BackGroundManager;
+import net.momirealms.customnameplates.api.mechanic.background.BackGround;
+import net.momirealms.customnameplates.api.mechanic.character.CharacterArranger;
+import net.momirealms.customnameplates.api.mechanic.character.ConfiguredChar;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+
+public class BackGroundManagerImpl implements BackGroundManager {
+
+ private final HashMap backGroundMap;
+ private final CustomNameplatesPlugin plugin;
+
+ public BackGroundManagerImpl(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ this.backGroundMap = new HashMap<>();
+ }
+
+ public void reload() {
+ unload();
+ load();
+ }
+
+ public void load() {
+ if (!CNConfig.backgroundModule) return;
+ this.loadConfigs();
+ }
+
+ public void unload() {
+ this.backGroundMap.clear();
+ }
+
+ private void loadConfigs() {
+ File bgFolder = new File(plugin.getDataFolder(),"contents" + File.separator + "backgrounds");
+ if (!bgFolder.exists() && bgFolder.mkdirs()) {
+ saveDefaultBackgrounds();
+ }
+ File[] bgConfigFiles = bgFolder.listFiles(file -> file.getName().endsWith(".yml"));
+ if (bgConfigFiles == null) return;
+ Arrays.sort(bgConfigFiles, Comparator.comparing(File::getName));
+ for (File bgConfigFile : bgConfigFiles) {
+ String key = bgConfigFile.getName().substring(0, bgConfigFile.getName().length() - 4);
+
+ YamlConfiguration config = YamlConfiguration.loadConfiguration(bgConfigFile);
+ int height = config.getInt("middle.height", 14);
+ int ascent = config.getInt("middle.ascent", 8);
+ if (config.contains("middle.descent")) ascent = height - config.getInt("middle.descent");
+
+ var background = BackGround.builder()
+ .leftMargin(config.getInt("left-margin", 1))
+ .rightMargin(config.getInt("right-margin", 1))
+ .left(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("left.image"))
+ .height(config.getInt("left.height"))
+ .ascent(config.getInt("left.ascent"))
+ .width(config.getInt("left.width"))
+ .build()
+ )
+ .offset_1(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("middle.1"))
+ .height(height)
+ .ascent(ascent)
+ .width(1)
+ .build()
+ )
+ .offset_2(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("middle.2"))
+ .height(height)
+ .ascent(ascent)
+ .width(2)
+ .build()
+ )
+ .offset_4(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("middle.4"))
+ .height(height)
+ .ascent(ascent)
+ .width(4)
+ .build()
+ )
+ .offset_8(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("middle.8"))
+ .height(height)
+ .ascent(ascent)
+ .width(8)
+ .build()
+ )
+ .offset_16(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("middle.16"))
+ .height(height)
+ .ascent(ascent)
+ .width(16)
+ .build()
+ )
+ .offset_32(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("middle.32"))
+ .height(height)
+ .ascent(ascent)
+ .width(32)
+ .build()
+ )
+ .offset_64(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("middle.64"))
+ .height(height)
+ .ascent(ascent)
+ .width(64)
+ .build()
+ )
+ .offset_128(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("middle.128"))
+ .height(height)
+ .ascent(ascent)
+ .width(128)
+ .build()
+ )
+ .right(
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("right.image"))
+ .height(config.getInt("right.height"))
+ .ascent(config.getInt("right.ascent"))
+ .width(config.getInt("right.width"))
+ .build()
+ )
+ .build();
+
+ if (!registerBackGround(key, background)) {
+ LogUtils.warn("Found duplicated background: " + key);
+ }
+ }
+ }
+
+ @Override
+ public BackGround getBackGround(@NotNull String key) {
+ return backGroundMap.get(key);
+ }
+
+ @Override
+ public Collection getBackGrounds() {
+ return backGroundMap.values();
+ }
+
+ @Override
+ public boolean registerBackGround(@NotNull String key, @NotNull BackGround backGround) {
+ if (backGroundMap.containsKey(key)) {
+ return false;
+ }
+ backGroundMap.put(key, backGround);
+ return true;
+ }
+
+ @Override
+ public boolean unregisterBackGround(@NotNull String key) {
+ return backGroundMap.remove(key) != null;
+ }
+
+ private void saveDefaultBackgrounds() {
+ String[] bg_list = new String[]{"b0", "b1", "b2", "b4", "b8", "b16","b32","b64","b128"};
+ for (String bg : bg_list) {
+ plugin.saveResource("contents" + File.separator + "backgrounds" + File.separator + bg + ".png", false);
+ }
+ String[] config_list = new String[]{"bedrock_1", "bedrock_2", "bedrock_3"};
+ for (String config : config_list) {
+ plugin.saveResource("contents" + File.separator + "backgrounds" + File.separator + config + ".yml", false);
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BarColor.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BarColor.java
new file mode 100644
index 0000000..e7df8c0
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BarColor.java
@@ -0,0 +1,23 @@
+package net.momirealms.customnameplates.paper.mechanic.bossbar;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Locale;
+
+public enum BarColor {
+ PINK,
+ BLUE,
+ RED,
+ GREEN,
+ YELLOW,
+ PURPLE,
+ WHITE;
+
+ public static BarColor getColor(@NotNull String name) {
+ try {
+ return BarColor.valueOf(name.toUpperCase(Locale.ENGLISH));
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException("Color: " + name + " doesn't exist");
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBar.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBar.java
new file mode 100644
index 0000000..c33c93f
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBar.java
@@ -0,0 +1,87 @@
+package net.momirealms.customnameplates.paper.mechanic.bossbar;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.InternalStructure;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.wrappers.WrappedChatComponent;
+import net.momirealms.customnameplates.paper.adventure.AdventureManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.misc.PacketManager;
+import net.momirealms.customnameplates.paper.util.ReflectionUtils;
+import org.bukkit.entity.Player;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.UUID;
+
+public class BossBar {
+
+ private final Overlay overlay;
+ private final BarColor barColor;
+ private final UUID uuid;
+ private final Player owner;
+ private String latestMiniMessage;
+ private boolean visible;
+
+ public BossBar(Player owner, Overlay overlay, BarColor barColor) {
+ this.owner = owner;
+ this.overlay = overlay;
+ this.barColor = barColor;
+ this.uuid = UUID.randomUUID();
+ this.visible = false;
+ }
+
+ public void show() {
+ PacketManager.getInstance().send(owner, getCreatePacket());
+ this.visible = true;
+ }
+
+ public void hide() {
+ PacketManager.getInstance().send(owner, getRemovePacket());
+ this.visible = false;
+ }
+
+ public void update() {
+ PacketManager.getInstance().send(owner, getUpdatePacket());
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ public void setMiniMessageText(String text) {
+ latestMiniMessage = text;
+ }
+
+ private PacketContainer getCreatePacket() {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS);
+ packet.getModifier().write(0, uuid);
+ InternalStructure internalStructure = packet.getStructures().read(1);
+ internalStructure.getModifier().write(0, AdventureManagerImpl.getInstance().getIChatComponentFromMiniMessage(latestMiniMessage));
+ internalStructure.getFloat().write(0,1F);
+ internalStructure.getEnumModifier(BarColor.class, 2).write(0, barColor);
+ internalStructure.getEnumModifier(Overlay.class, 3).write(0, overlay);
+ internalStructure.getModifier().write(4, false);
+ internalStructure.getModifier().write(5, false);
+ internalStructure.getModifier().write(6, false);
+ return packet;
+ }
+
+ private PacketContainer getUpdatePacket() {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS);
+ packet.getModifier().write(0, uuid);
+ try {
+ Object chatComponent = AdventureManagerImpl.getInstance().getIChatComponentFromMiniMessage(latestMiniMessage);
+ Object updatePacket = ReflectionUtils.updateConstructor.newInstance(chatComponent);
+ packet.getModifier().write(1, updatePacket);
+ } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
+ throw new RuntimeException(e);
+ }
+ return packet;
+ }
+
+ private PacketContainer getRemovePacket() {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS);
+ packet.getModifier().write(0, uuid);
+ packet.getModifier().write(1, ReflectionUtils.removeBossBarPacket);
+ return packet;
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBarConfig.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBarConfig.java
new file mode 100644
index 0000000..6d47a59
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBarConfig.java
@@ -0,0 +1,101 @@
+package net.momirealms.customnameplates.paper.mechanic.bossbar;
+
+import net.momirealms.customnameplates.api.requirement.Requirement;
+import net.momirealms.customnameplates.paper.mechanic.misc.TimeLimitText;
+
+public class BossBarConfig {
+
+ private Overlay overlay;
+ private BarColor barColor;
+ private int checkFrequency;
+ private Requirement[] requirements;
+ private TimeLimitText[] textDisplayOrder;
+
+ private BossBarConfig() {
+ overlay = Overlay.PROGRESS;
+ barColor = BarColor.YELLOW;
+ checkFrequency = 1;
+ requirements = new Requirement[0];
+ textDisplayOrder = new TimeLimitText[0];
+ }
+
+ public BossBarConfig(
+ Overlay overlay,
+ BarColor barColor,
+ int checkFrequency,
+ Requirement[] requirements,
+ TimeLimitText[] textDisplayOrder
+ ) {
+ this.overlay = overlay;
+ this.barColor = barColor;
+ this.checkFrequency = checkFrequency;
+ this.requirements = requirements;
+ this.textDisplayOrder = textDisplayOrder;
+ }
+
+ public Overlay getOverlay() {
+ return overlay;
+ }
+
+ public BarColor getBarColor() {
+ return barColor;
+ }
+
+ public int getCheckFrequency() {
+ return checkFrequency;
+ }
+
+ public Requirement[] getRequirements() {
+ return requirements;
+ }
+
+ public TimeLimitText[] getTextDisplayOrder() {
+ return textDisplayOrder;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final BossBarConfig config;
+
+ public Builder() {
+ this.config = new BossBarConfig();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder overlay(Overlay overlay) {
+ config.overlay = overlay;
+ return this;
+ }
+
+ public Builder barColor(BarColor barColor) {
+ config.barColor = barColor;
+ return this;
+ }
+
+ public Builder checkFrequency(int checkFrequency) {
+ config.checkFrequency = checkFrequency;
+ return this;
+ }
+
+ public Builder requirement(Requirement[] requirements) {
+ config.requirements = requirements;
+ return this;
+ }
+
+ public Builder displayOrder(TimeLimitText[] textDisplayOrder) {
+ config.textDisplayOrder = textDisplayOrder;
+ return this;
+ }
+
+ public BossBarConfig build() {
+ return config;
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBarManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBarManagerImpl.java
new file mode 100644
index 0000000..30c2fb9
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBarManagerImpl.java
@@ -0,0 +1,134 @@
+package net.momirealms.customnameplates.paper.mechanic.bossbar;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.common.Pair;
+import net.momirealms.customnameplates.api.manager.BossBarManager;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.mechanic.misc.DisplayController;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import net.momirealms.customnameplates.paper.util.ConfigUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+public class BossBarManagerImpl implements BossBarManager, Listener {
+
+ private final CustomNameplatesPlugin plugin;
+ private final ConcurrentHashMap receiverMap;
+ private BossBarConfig[] configs;
+
+ public void load() {
+ if (!CNConfig.bossBarModule) return;
+ this.loadConfigs();
+ Bukkit.getPluginManager().registerEvents(this, plugin);
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ createBossBarFor(player);
+ }
+ }
+
+ public void unload() {
+ HandlerList.unregisterAll(this);
+ for (BossBarReceiver receiver : receiverMap.values()) {
+ receiver.cancelTask();
+ receiver.destroy();
+ }
+ receiverMap.clear();
+ }
+
+ public void reload() {
+ unload();
+ load();
+ }
+
+ public BossBarManagerImpl(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ this.receiverMap = new ConcurrentHashMap<>();
+ this.configs = new BossBarConfig[0];
+ }
+
+ private void loadConfigs() {
+ ArrayList configs = new ArrayList<>();
+
+ YamlConfiguration config = plugin.getConfig("configs" + File.separator + "bossbar.yml");
+ for (Map.Entry barEntry : config.getValues(false).entrySet()) {
+ if (!(barEntry.getValue() instanceof ConfigurationSection section))
+ return;
+
+ var barConfig = BossBarConfig.Builder.of()
+ .barColor(BarColor.getColor(section.getString("color", "YELLOW")))
+ .overlay(Overlay.getOverlay(section.getString("overlay", "PROGRESS")))
+ .checkFrequency(section.getInt("check-frequency", 10))
+ .requirement(plugin.getRequirementManager().getRequirements(section.getConfigurationSection("conditions")))
+ .displayOrder(ConfigUtils.getTimeLimitTexts(section.getConfigurationSection("text-display-order")))
+ .build();
+
+ configs.add(barConfig);
+ }
+
+ this.configs = configs.toArray(new BossBarConfig[0]);
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ final Player player = event.getPlayer();
+ if (CNConfig.sendDelay == 0) {
+ createBossBarFor(player);
+ return;
+ }
+ this.plugin.getScheduler().runTaskAsyncLater(() -> {
+ createBossBarFor(player);
+ }, CNConfig.sendDelay * 50L, TimeUnit.MILLISECONDS);
+ }
+
+ private void createBossBarFor(Player player) {
+ if (player == null || !player.isOnline()) {
+ return;
+ }
+
+ Pair[] pairs = new Pair[configs.length];
+ for (int i = 0; i < configs.length; i++) {
+ var config = configs[i];
+ pairs[i] = Pair.of(
+ new DisplayController(player, config.getCheckFrequency(), config.getRequirements(), config.getTextDisplayOrder()),
+ new BossBar(player, config.getOverlay(), config.getBarColor())
+ );
+ }
+
+ BossBarReceiver bossBarReceiver = new BossBarReceiver(plugin, player, pairs);
+ bossBarReceiver.arrangeTask();
+ this.putReceiverToMap(player.getUniqueId(), bossBarReceiver);
+ }
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ BossBarReceiver receiver = receiverMap.remove(event.getPlayer().getUniqueId());
+ if (receiver != null) {
+ receiver.cancelTask();
+ }
+ }
+
+ private void putReceiverToMap(UUID uuid, BossBarReceiver bossBarReceiver) {
+ BossBarReceiver previous = this.receiverMap.put(uuid, bossBarReceiver);
+ if (previous != null) {
+ LogUtils.warn("Unexpected error: Duplicated bossbar created");
+ previous.cancelTask();
+ }
+ }
+
+ private BossBarReceiver getReceiver(UUID uuid) {
+ return this.receiverMap.get(uuid);
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBarReceiver.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBarReceiver.java
new file mode 100644
index 0000000..24fd79b
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/BossBarReceiver.java
@@ -0,0 +1,98 @@
+package net.momirealms.customnameplates.paper.mechanic.bossbar;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.common.Pair;
+import net.momirealms.customnameplates.api.requirement.Condition;
+import net.momirealms.customnameplates.api.scheduler.CancellableTask;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.mechanic.misc.DisplayController;
+import org.bukkit.entity.Player;
+
+import java.util.concurrent.TimeUnit;
+
+public class BossBarReceiver {
+
+ private final CustomNameplatesPlugin plugin;
+ private final Player player;
+ private final int bossBarSize;
+ private final Pair[] bossBars;
+ private CancellableTask bossBarTask;
+
+ public BossBarReceiver(CustomNameplatesPlugin plugin, Player player, Pair[] bossBars) {
+ this.player = player;
+ this.plugin = plugin;
+ this.bossBars = bossBars;
+ this.bossBarSize = bossBars.length;
+ }
+
+ public void arrangeTask() {
+ if (this.bossBarTask != null && !this.bossBarTask.isCancelled()) {
+ LogUtils.warn("There's already a BossBar task running");
+ return;
+ }
+ this.bossBarTask = this.plugin.getScheduler().runTaskAsyncTimer(() -> {
+ try {
+ timer();
+ } catch (Exception e) {
+ LogUtils.severe(
+ "Error occurred when sending BossBars. " +
+ "This might not be a bug in CustomNameplates. Please report " +
+ "to the Plugin on the top of the following " +
+ "stack trace."
+ );
+ e.printStackTrace();
+ }
+ }, 50, 50, TimeUnit.MILLISECONDS);
+ }
+
+ public void cancelTask() {
+ if (this.bossBarTask == null) {
+ LogUtils.warn("BossBar task has been already cancelled");
+ return;
+ }
+ this.bossBarTask.cancel();
+ this.bossBarTask = null;
+ }
+
+ public void destroy() {
+ for (Pair pair : bossBars) {
+ pair.right().hide();
+ }
+ }
+
+ public void timer() {
+ Condition condition = new Condition(this.player);
+ for (int i = 0; i < this.bossBarSize; i++) {
+ Pair pair = this.bossBars[i];
+ switch (pair.left().stateCheck(condition)) {
+ case KEEP -> {
+ if (!pair.left().isShown()) {
+ continue;
+ }
+ if (pair.left().updateText(condition)) {
+ pair.right().setMiniMessageText(pair.left().getLatestContent());
+ pair.right().update();
+ }
+ }
+ case UPDATE -> {
+ var controller = pair.left();
+ if (controller.isShown()) {
+ for (int j = i + 1; j < this.bossBarSize; j++)
+ if (this.bossBars[j].left().isShown())
+ this.bossBars[j].right().hide();
+
+ controller.initialize();
+ pair.right().setMiniMessageText(pair.left().getLatestContent());
+ pair.right().show();
+
+ for (int j = i + 1; j < this.bossBarSize; j++)
+ if (this.bossBars[j].left().isShown())
+ this.bossBars[j].right().show();
+ } else {
+ pair.right().hide();
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/momirealms/customnameplates/object/bossbar/Overlay.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/Overlay.java
similarity index 64%
rename from src/main/java/net/momirealms/customnameplates/object/bossbar/Overlay.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/Overlay.java
index 99cf7c0..a251da6 100644
--- a/src/main/java/net/momirealms/customnameplates/object/bossbar/Overlay.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/bossbar/Overlay.java
@@ -15,7 +15,11 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.object.bossbar;
+package net.momirealms.customnameplates.paper.mechanic.bossbar;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Locale;
public enum Overlay {
@@ -23,5 +27,13 @@ public enum Overlay {
NOTCHED_10,
NOTCHED_12,
NOTCHED_20,
- PROGRESS
+ PROGRESS;
+
+ public static Overlay getOverlay(@NotNull String name) {
+ try {
+ return Overlay.valueOf(name.toUpperCase(Locale.ENGLISH));
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException("Overlay: " + name + " doesn't exist");
+ }
+ }
}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/font/FontData.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/font/FontData.java
new file mode 100644
index 0000000..f4c72e0
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/font/FontData.java
@@ -0,0 +1,22 @@
+package net.momirealms.customnameplates.paper.mechanic.font;
+
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+
+import java.util.HashMap;
+
+public class FontData {
+
+ private final HashMap widthData;
+
+ public FontData() {
+ this.widthData = new HashMap<>();
+ }
+
+ public void registerCharWidth(char c, short width) {
+ widthData.put(c, width);
+ }
+
+ public short getWidth(char c) {
+ return widthData.getOrDefault(c, CNConfig.defaultCharWidth);
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/font/WidthManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/font/WidthManagerImpl.java
new file mode 100644
index 0000000..df0d8ea
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/font/WidthManagerImpl.java
@@ -0,0 +1,38 @@
+package net.momirealms.customnameplates.paper.mechanic.font;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.WidthManager;
+
+import java.util.HashMap;
+
+public class WidthManagerImpl implements WidthManager {
+
+ private final CustomNameplatesPlugin plugin;
+ private final HashMap fontDataMap;
+
+ private char TAG_START = '<';
+ private char TAG_END = '>';
+ private char TAG_CLOSE = '/';
+ private char TAG_ESCAPE = '\\';
+
+ public WidthManagerImpl(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ this.fontDataMap = new HashMap<>();
+ }
+
+ public void reload() {
+
+ }
+
+ public void load() {
+
+ }
+
+ public void unload() {
+ fontDataMap.clear();
+ }
+
+ public int getTextWidth(String textWithTags) {
+ return 0;
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/image/ImageManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/image/ImageManagerImpl.java
new file mode 100644
index 0000000..6da4e04
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/image/ImageManagerImpl.java
@@ -0,0 +1,100 @@
+package net.momirealms.customnameplates.paper.mechanic.image;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.ImageManager;
+import net.momirealms.customnameplates.api.mechanic.character.CharacterArranger;
+import net.momirealms.customnameplates.api.mechanic.character.ConfiguredChar;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+
+public class ImageManagerImpl implements ImageManager {
+
+ private final HashMap imageMap;
+ private final CustomNameplatesPlugin plugin;
+
+ public ImageManagerImpl(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ this.imageMap = new HashMap<>();
+ }
+
+ public void load() {
+ if (!CNConfig.imageModule) return;
+ loadConfigs();
+ }
+
+ public void unload() {
+ this.imageMap.clear();
+ }
+
+ public void reload() {
+ unload();
+ loadConfigs();
+ }
+
+ @Override
+ public ConfiguredChar getImage(@NotNull String key) {
+ return imageMap.get(key);
+ }
+
+ @Override
+ public Collection getImages() {
+ return imageMap.values();
+ }
+
+ private void loadConfigs() {
+ File imgFolder = new File(plugin.getDataFolder(), "contents" + File.separator + "images");
+ if (!imgFolder.exists() && imgFolder.mkdirs()) {
+ saveDefaultImages();
+ }
+ File[] configFiles = imgFolder.listFiles(file -> file.getName().endsWith(".yml"));
+ if (configFiles == null) return;
+ Arrays.sort(configFiles, Comparator.comparing(File::getName));
+ for (File configFile : configFiles) {
+
+ String key = configFile.getName().substring(0, configFile.getName().length() - 4);
+ YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
+
+ if (!registerImage(key,
+ ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("image", key))
+ .height(config.getInt("height", 8))
+ .width(config.getInt("width", 8))
+ .ascent(config.getInt("ascent", 8))
+ .build()
+ )) {
+ LogUtils.warn("Found duplicated image: " + key);
+ }
+ }
+ }
+
+ @Override
+ public boolean registerImage(@NotNull String key, @NotNull ConfiguredChar configuredChar) {
+ if (imageMap.containsKey(key)) return false;
+ imageMap.put(key, configuredChar);
+ return true;
+ }
+
+ @Override
+ public boolean unregisterImage(@NotNull String key) {
+ return this.imageMap.remove(key) != null;
+ }
+
+ private void saveDefaultImages() {
+ String[] png_list = new String[]{"bell", "bubble", "clock", "coin", "compass", "weather", "stamina_0", "stamina_1", "stamina_2"};
+ String[] part_list = new String[]{".png", ".yml"};
+ for (String name : png_list) {
+ for (String part : part_list) {
+ plugin.saveResource("contents" + File.separator + "images" + File.separator + name + part, false);
+ }
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/CoolDownManager.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/CoolDownManager.java
new file mode 100644
index 0000000..e32e513
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/CoolDownManager.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.mechanic.misc;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import org.bukkit.Bukkit;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+import java.util.HashMap;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Manages cooldowns for various actions or events.
+ * Keeps track of cooldown times for different keys associated with player UUIDs.
+ */
+public class CoolDownManager implements Listener {
+
+ private final ConcurrentHashMap dataMap;
+ private final CustomNameplatesPlugin plugin;
+
+ public CoolDownManager(CustomNameplatesPlugin plugin) {
+ this.dataMap = new ConcurrentHashMap<>();
+ this.plugin = plugin;
+ }
+
+ /**
+ * Checks if a player is currently in cooldown for a specific key.
+ *
+ * @param uuid The UUID of the player.
+ * @param key The key associated with the cooldown.
+ * @param time The cooldown time in milliseconds.
+ * @return True if the player is in cooldown, false otherwise.
+ */
+ public boolean isCoolDown(UUID uuid, String key, long time) {
+ Data data = this.dataMap.computeIfAbsent(uuid, k -> new Data());
+ return data.isCoolDown(key, time);
+ }
+
+ public void load() {
+ Bukkit.getPluginManager().registerEvents(this, plugin);
+ }
+
+ public void unload() {
+ HandlerList.unregisterAll(this);
+ }
+
+ public void disable() {
+ unload();
+ this.dataMap.clear();
+ }
+
+ /**
+ * Event handler for when a player quits the game. Removes their cooldown data.
+ *
+ * @param event The PlayerQuitEvent triggered when a player quits.
+ */
+ @EventHandler
+ public void onQuit(PlayerQuitEvent event) {
+ dataMap.remove(event.getPlayer().getUniqueId());
+ }
+
+ public static class Data {
+
+ private final HashMap coolDownMap;
+
+ public Data() {
+ this.coolDownMap = new HashMap<>();
+ }
+
+ /**
+ * Checks if the player is in cooldown for a specific key.
+ *
+ * @param key The key associated with the cooldown.
+ * @param delay The cooldown delay in milliseconds.
+ * @return True if the player is in cooldown, false otherwise.
+ */
+ public synchronized boolean isCoolDown(String key, long delay) {
+ long time = System.currentTimeMillis();
+ long last = coolDownMap.getOrDefault(key, time - delay);
+ if (last + delay > time) {
+ return true; // Player is in cooldown
+ } else {
+ coolDownMap.put(key, time);
+ return false; // Player is not in cooldown
+ }
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/DisplayController.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/DisplayController.java
new file mode 100644
index 0000000..17bb9f9
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/DisplayController.java
@@ -0,0 +1,116 @@
+package net.momirealms.customnameplates.paper.mechanic.misc;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.customnameplates.api.manager.RequirementManager;
+import net.momirealms.customnameplates.api.requirement.Condition;
+import net.momirealms.customnameplates.api.requirement.Requirement;
+import org.bukkit.entity.Player;
+
+public class DisplayController {
+
+ private final Player owner;
+ private final int checkFrequency;
+ private final Requirement[] requirements;
+ private boolean isShown;
+ private int checkTimer;
+ private int refreshTimer;
+ private String latestValue;
+ private int timeLeft;
+ private int index;
+ private final TimeLimitText[] texts;
+
+ public DisplayController(
+ Player player,
+ int checkFrequency,
+ Requirement[] requirements,
+ TimeLimitText[] texts
+ ) {
+ this.owner = player;
+ this.checkFrequency = checkFrequency;
+ this.checkTimer = 0;
+ this.refreshTimer = 0;
+ this.requirements = requirements;
+ this.texts = texts;
+ }
+
+ public NextStage stateCheck(Condition condition) {
+ this.checkTimer++;
+ if (this.checkTimer % checkFrequency != 0) {
+ return NextStage.KEEP;
+ }
+ boolean canShow = RequirementManager.isRequirementMet(condition, requirements);
+ if (canShow) {
+ if (this.isShown) {
+ return NextStage.KEEP;
+ } else {
+ this.isShown = true;
+ return NextStage.UPDATE;
+ }
+ } else {
+ if (!this.isShown) {
+ return NextStage.KEEP;
+ } else {
+ this.isShown = false;
+ return NextStage.UPDATE;
+ }
+ }
+ }
+
+ public boolean isShown() {
+ return isShown;
+ }
+
+ public boolean updateText(Condition condition) {
+ timeLeft--;
+ if (timeLeft <= 0) {
+ do {
+ index++;
+ if (index >= texts.length) {
+ index = 0;
+ }
+ } while (!RequirementManager.isRequirementMet(condition, texts[index].getRequirements()));
+
+ timeLeft = texts[index].getDuration();
+ refreshTimer = 0;
+ return updateText(texts[index].getText());
+ }
+
+ if (texts[index].getRefreshFrequency() <= 0) {
+ return false;
+ }
+
+ refreshTimer++;
+ if (refreshTimer >= texts[index].getRefreshFrequency()) {
+ refreshTimer = 0;
+ return updateText(texts[index].getText());
+ } else {
+ return false;
+ }
+ }
+
+ private boolean updateText(String text) {
+ var newText = PlaceholderAPI.setPlaceholders(owner, text);
+ if (newText.equals(latestValue)) {
+ return false;
+ }
+ latestValue = newText;
+ return true;
+ }
+
+ public void initialize() {
+ index = 0;
+ checkTimer = 0;
+ refreshTimer = 0;
+ timeLeft = texts[0].getDuration();
+ latestValue = PlaceholderAPI.setPlaceholders(owner, texts[0].getText());
+ }
+
+ public String getLatestContent() {
+ return latestValue;
+ }
+
+ public enum NextStage {
+ KEEP,
+ UPDATE
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/MessageManager.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/MessageManager.java
new file mode 100644
index 0000000..e716617
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/MessageManager.java
@@ -0,0 +1,9 @@
+package net.momirealms.customnameplates.paper.mechanic.misc;
+
+import org.bukkit.event.Listener;
+
+public class MessageManager implements Listener {
+
+
+
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/PacketManager.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/PacketManager.java
new file mode 100644
index 0000000..d468b4d
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/PacketManager.java
@@ -0,0 +1,32 @@
+package net.momirealms.customnameplates.paper.mechanic.misc;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.ProtocolManager;
+import com.comphenix.protocol.events.PacketContainer;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import org.bukkit.entity.Player;
+
+public class PacketManager {
+
+ private static PacketManager instance;
+ private final ProtocolManager protocolManager;
+ private final CustomNameplatesPlugin plugin;
+
+ public PacketManager(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ this.protocolManager = ProtocolLibrary.getProtocolManager();
+ instance = this;
+ }
+
+ public static PacketManager getInstance() {
+ return instance;
+ }
+
+ public void send(Player player, PacketContainer packet) {
+ if (!player.isOnline()) {
+ System.out.println(player.getName() + " not online");
+ }
+ this.plugin.debug("Packet sent: " + packet.getType() + " to " + player.getName());
+ this.protocolManager.sendServerPacket(player, packet);
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/TimeLimitText.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/TimeLimitText.java
new file mode 100644
index 0000000..c658112
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/TimeLimitText.java
@@ -0,0 +1,82 @@
+package net.momirealms.customnameplates.paper.mechanic.misc;
+
+import net.momirealms.customnameplates.api.requirement.Requirement;
+
+public class TimeLimitText {
+
+ private int duration;
+ private int refreshFrequency;
+ private String text;
+ private Requirement[] requirements;
+
+ private TimeLimitText() {
+ this.duration = 100;
+ this.refreshFrequency = -1;
+ this.text = "";
+ this.requirements = new Requirement[0];
+ }
+
+ public TimeLimitText(int duration, int refreshFrequency, String text, Requirement[] requirements) {
+ this.duration = duration;
+ this.text = text;
+ this.refreshFrequency = refreshFrequency;
+ this.requirements = requirements;
+ }
+
+ public int getDuration() {
+ return duration;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public Requirement[] getRequirements() {
+ return requirements;
+ }
+
+ public int getRefreshFrequency() {
+ return refreshFrequency;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final TimeLimitText text;
+
+ public Builder() {
+ this.text = new TimeLimitText();
+ }
+
+ public static Builder of() {
+ return new Builder();
+ }
+
+ public Builder duration(int duration) {
+ text.duration = duration;
+ return this;
+ }
+
+ public Builder refreshFrequency(int refreshFrequency) {
+ text.refreshFrequency = refreshFrequency;
+ return this;
+ }
+
+ public Builder text(String content) {
+ text.text = content;
+ return this;
+ }
+
+ public Builder requirement(Requirement[] requirements) {
+ text.requirements = requirements;
+ return this;
+ }
+
+ public TimeLimitText build() {
+ return text;
+ }
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/helper/VersionHelper.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/VersionManagerImpl.java
similarity index 59%
rename from src/main/java/net/momirealms/customnameplates/helper/VersionHelper.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/VersionManagerImpl.java
index bb59f3d..831a401 100644
--- a/src/main/java/net/momirealms/customnameplates/helper/VersionHelper.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/misc/VersionManagerImpl.java
@@ -15,120 +15,105 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.helper;
+package net.momirealms.customnameplates.paper.mechanic.misc;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.manager.ConfigManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.Bukkit;
+import net.momirealms.customnameplates.api.manager.VersionManager;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
import java.io.BufferedReader;
-import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
+import java.util.concurrent.CompletableFuture;
-public class VersionHelper {
+/**
+ * This class implements the VersionManager interface and is responsible for managing version-related information.
+ */
+public class VersionManagerImpl implements VersionManager {
- private boolean isNewerThan1_19_R2;
- private boolean isNewerThan1_20_R2;
- private boolean isNewerThan1_19;
- private boolean isNewerThan1_20;
- private String serverVersion;
- private final CustomNameplates plugin;
- private final String pluginVersion;
- private boolean isLatest;
- private final int pack_format;
+ private final boolean isNewerThan1_19_R2;
+ private final boolean isNewerThan1_20;
+ private final boolean isNewerThan1_19;
+ private final boolean isNewerThan1_20_R2;
+ private final String serverVersion;
+ private final CustomNameplatesPluginImpl plugin;
private boolean isFolia;
- private boolean hasGeyser;
+ private final String pluginVersion;
+ private boolean isLatest = true;
- public VersionHelper(CustomNameplates plugin) {
+ @SuppressWarnings("deprecation")
+ public VersionManagerImpl(CustomNameplatesPluginImpl plugin) {
this.plugin = plugin;
- this.pluginVersion = plugin.getDescription().getVersion();
- this.hasGeyser = Bukkit.getPluginManager().getPlugin("Geyser-Spigot") != null;
- this.initialize();
- this.pack_format = getPack_format(serverVersion);
+ // Get the server version
+ serverVersion = plugin.getServer().getClass().getPackage().getName().split("\\.")[3];
+ String[] split = serverVersion.split("_");
+ int main_ver = Integer.parseInt(split[1]);
+ // Determine if the server version is newer than 1_19_R2 and 1_20_R1
+ if (main_ver >= 20) {
+ isNewerThan1_20_R2 = Integer.parseInt(split[2].substring(1)) >= 2;
+ isNewerThan1_19_R2 = true;
+ isNewerThan1_20 = true;
+ isNewerThan1_19 = true;
+ } else if (main_ver == 19) {
+ isNewerThan1_19_R2 = Integer.parseInt(split[2].substring(1)) >= 2;
+ isNewerThan1_20 = false;
+ isNewerThan1_19 = true;
+ isNewerThan1_20_R2 = false;
+ } else {
+ isNewerThan1_19_R2 = false;
+ isNewerThan1_20 = false;
+ isNewerThan1_19 = false;
+ isNewerThan1_20_R2 = false;
+ }
+ // Check if the server is Folia
try {
Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler");
this.isFolia = true;
} catch (ClassNotFoundException ignored) {
- this.isFolia = false;
- }
- }
-
- public void initialize() {
- if (serverVersion == null) {
- this.serverVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
- String[] split = serverVersion.split("_");
- int main_ver = Integer.parseInt(split[1]);
- if (main_ver >= 20) {
- isNewerThan1_20 = true;
- isNewerThan1_19_R2 = true;
- isNewerThan1_19 = true;
- isNewerThan1_20_R2 = Integer.parseInt(split[2].substring(1)) >= 2;
- } else if (main_ver == 19) {
- isNewerThan1_19_R2 = Integer.parseInt(split[2].substring(1)) >= 2;
- isNewerThan1_19 = true;
- } else {
- isNewerThan1_19_R2 = false;
- isNewerThan1_19 = false;
- }
}
+ // Get the plugin version
+ this.pluginVersion = plugin.getDescription().getVersion();
}
+ @Override
public boolean isVersionNewerThan1_19_R2() {
return isNewerThan1_19_R2;
}
- public boolean isVersionNewerThan1_19() {
- return isNewerThan1_19;
- }
-
+ @Override
public boolean isVersionNewerThan1_20() {
return isNewerThan1_20;
}
+ @Override
public boolean isVersionNewerThan1_20_R2() {
return isNewerThan1_20_R2;
}
- public void checkUpdate() {
- plugin.getScheduler().runTaskAsync(() -> {
- try {
- URL url = new URL("https://api.polymart.org/v1/getResourceInfoSimple/?resource_id=2543&key=version");
- URLConnection conn = url.openConnection();
- conn.setConnectTimeout(3000);
- conn.setReadTimeout(3000);
- InputStream inputStream = conn.getInputStream();
- String newest = new BufferedReader(new InputStreamReader(inputStream)).readLine();
- String current = plugin.getDescription().getVersion();
- inputStream.close();
- isLatest = !compareVer(newest, current);
- if (isLatest) {
- AdventureUtils.consoleMessage(ConfigManager.lang.equalsIgnoreCase("chinese") ? "[CustomNameplates] 当前已是最新版本" : "[CustomNameplates] You are using the latest version.");
- return;
- }
- if (ConfigManager.lang.equalsIgnoreCase("chinese")) {
- AdventureUtils.consoleMessage("[CustomNameplates] 当前版本: " + current);
- AdventureUtils.consoleMessage("[CustomNameplates] 最新版本: " + newest);
- AdventureUtils.consoleMessage("[CustomNameplates] 请到 售后群 或 https://polymart.org/resource/customnameplates.2543 获取最新版本.");
- }
- else {
- AdventureUtils.consoleMessage("[CustomNameplates] Current version: " + current);
- AdventureUtils.consoleMessage("[CustomNameplates] Latest version: " + newest);
- AdventureUtils.consoleMessage("[CustomNameplates] Update is available: https://polymart.org/resource/customnameplates.2543");
- }
- } catch (IOException e) {
- isLatest = true;
- Log.warn("Error occurred when checking update");
- }
- });
+ @Override
+ public String getPluginVersion() {
+ return pluginVersion;
}
- private int getPack_format(String version) {
- switch (version) {
+ @Override
+ public boolean isLatest() {
+ return isLatest;
+ }
+
+ @Override
+ public boolean isVersionNewerThan1_19() {
+ return isNewerThan1_19;
+ }
+
+ @Override
+ public int getPackFormat() {
+ switch (serverVersion) {
+ case "v1_20_R3" -> {
+ return 22;
+ }
case "v1_20_R2" -> {
return 18;
}
@@ -153,6 +138,45 @@ public class VersionHelper {
}
}
+ @Override
+ public boolean isFolia() {
+ return isFolia;
+ }
+
+ @Override
+ public String getServerVersion() {
+ return serverVersion;
+ }
+
+ // Method to asynchronously check for plugin updates
+ public CompletableFuture checkUpdate() {
+ CompletableFuture updateFuture = new CompletableFuture<>();
+ plugin.getScheduler().runTaskAsync(() -> {
+ try {
+ URL url = new URL("https://api.polymart.org/v1/getResourceInfoSimple/?resource_id=2543&key=version");
+ URLConnection conn = url.openConnection();
+ conn.setConnectTimeout(3000);
+ conn.setReadTimeout(3000);
+ InputStream inputStream = conn.getInputStream();
+ String newest = new BufferedReader(new InputStreamReader(inputStream)).readLine();
+ String current = getPluginVersion();
+ inputStream.close();
+ if (!compareVer(newest, current)) {
+ updateFuture.complete(false);
+ return;
+ }
+ isLatest = false;
+ updateFuture.complete(true);
+ } catch (Exception exception) {
+ LogUtils.warn("Error occurred when checking update.");
+ updateFuture.completeExceptionally(exception);
+ }
+ });
+ return updateFuture;
+ }
+
+ // Method to compare two version strings
+ // return true when update is available
private boolean compareVer(String newV, String currentV) {
if (newV == null || currentV == null || newV.isEmpty() || currentV.isEmpty()) {
return false;
@@ -173,9 +197,7 @@ public class VersionHelper {
} else if (newPart.length > 1 && currentPart.length > 1) {
String[] newHotfix = newPart[1].split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
String[] currentHotfix = currentPart[1].split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
- // hotfix2 & hotfix
if (newHotfix.length == 2 && currentHotfix.length == 1) return true;
- // hotfix3 & hotfix2
else if (newHotfix.length > 1 && currentHotfix.length > 1) {
int newHotfixNum = Integer.parseInt(newHotfix[1]);
int currentHotfixNum = Integer.parseInt(currentHotfix[1]);
@@ -197,27 +219,6 @@ public class VersionHelper {
return false;
}
}
- // if common parts are the same, the longer is newer
return newVS.length > currentVS.length;
}
-
- public String getPluginVersion() {
- return pluginVersion;
- }
-
- public boolean isLatest() {
- return isLatest || !ConfigManager.checkUpdate;
- }
-
- public int getPack_format() {
- return pack_format;
- }
-
- public boolean isFolia() {
- return isFolia;
- }
-
- public boolean isGeyser() {
- return hasGeyser;
- }
-}
+}
\ No newline at end of file
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/NameplateManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/NameplateManagerImpl.java
new file mode 100644
index 0000000..e3b6804
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/NameplateManagerImpl.java
@@ -0,0 +1,565 @@
+package net.momirealms.customnameplates.paper.mechanic.nameplate;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.data.OnlineUser;
+import net.momirealms.customnameplates.api.event.NameplateDataLoadEvent;
+import net.momirealms.customnameplates.api.manager.NameplateManager;
+import net.momirealms.customnameplates.api.manager.TeamTagManager;
+import net.momirealms.customnameplates.api.manager.UnlimitedTagManager;
+import net.momirealms.customnameplates.api.mechanic.character.CharacterArranger;
+import net.momirealms.customnameplates.api.mechanic.character.ConfiguredChar;
+import net.momirealms.customnameplates.api.mechanic.nameplate.CachedNameplate;
+import net.momirealms.customnameplates.api.mechanic.nameplate.Nameplate;
+import net.momirealms.customnameplates.api.mechanic.nameplate.TagMode;
+import net.momirealms.customnameplates.api.mechanic.tag.NameplatePlayer;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.UnlimitedTagSetting;
+import net.momirealms.customnameplates.api.mechanic.team.TeamColor;
+import net.momirealms.customnameplates.api.scheduler.CancellableTask;
+import net.momirealms.customnameplates.api.util.FontUtils;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.tag.listener.*;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.tag.team.TeamTagManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.tag.unlimited.UnlimitedTagManagerImpl;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityPoseChangeEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.event.player.PlayerToggleSneakEvent;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+public class NameplateManagerImpl implements NameplateManager, Listener {
+
+ private final CustomNameplatesPlugin plugin;
+ /* A map for nameplate configs */
+ private final HashMap nameplateMap;
+ /* A map for cached nameplates */
+ private final ConcurrentHashMap cachedNameplateMap;
+ /* A map to quickly get Entity by its EntityID */
+ private final ConcurrentHashMap entityID2EntityMap;
+ /* A map to store players that have tags */
+ private final ConcurrentHashMap nameplatePlayerMap;
+ private CancellableTask nameplateRefreshTask;
+ private final TeamTagManagerImpl teamTagManager;
+ private final UnlimitedTagManagerImpl unlimitedTagManager;
+ private final EntityDestroyListener entityDestroyListener;
+ private final EntitySpawnListener entitySpawnListener;
+ private final EntityMoveListener entityMoveListener;
+ private final EntityLookListener entityLookListener;
+ private final EntityTeleportListener entityTeleportListener;
+
+ /**
+ * Configs
+ */
+ private long teamRefreshFrequency;
+ private String teamPrefix;
+ private String teamSuffix;
+ private boolean fixTab;
+ /* Is proxy server working */
+ private boolean proxyMode;
+ /* TEAM & UNLIMITED */
+ private TagMode tagMode;
+ private int previewDuration;
+ private String defaultNameplate;
+ private String playerName, prefix, suffix;
+ private long refreshFrequency;
+ private final List tagSettings;
+
+ public NameplateManagerImpl(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ this.nameplateMap = new HashMap<>();
+ this.tagSettings = new ArrayList<>();
+
+ this.cachedNameplateMap = new ConcurrentHashMap<>();
+ this.entityID2EntityMap = new ConcurrentHashMap<>();
+ this.nameplatePlayerMap = new ConcurrentHashMap<>();
+ this.teamTagManager = new TeamTagManagerImpl(this);
+ this.unlimitedTagManager = new UnlimitedTagManagerImpl(this);
+
+ this.entityTeleportListener = new EntityTeleportListener(this);
+ this.entityDestroyListener = new EntityDestroyListener(this);
+ this.entitySpawnListener = new EntitySpawnListener(this);
+ this.entityLookListener = new EntityLookListener(this);
+ this.entityMoveListener = new EntityMoveListener(this);
+ }
+
+ public void reload() {
+ unload();
+ load();
+ }
+
+ public void unload() {
+ if (this.nameplateRefreshTask != null && !this.nameplateRefreshTask.isCancelled()) {
+ this.nameplateRefreshTask.cancel();
+ }
+ this.nameplateMap.clear();
+ this.tagSettings.clear();
+ this.teamTagManager.unload();
+ this.unlimitedTagManager.unload();
+
+ HandlerList.unregisterAll(this);
+ ProtocolLibrary.getProtocolManager().removePacketListener(entityDestroyListener);
+ ProtocolLibrary.getProtocolManager().removePacketListener(entitySpawnListener);
+ ProtocolLibrary.getProtocolManager().removePacketListener(entityLookListener);
+ ProtocolLibrary.getProtocolManager().removePacketListener(entityMoveListener);
+ ProtocolLibrary.getProtocolManager().removePacketListener(entityTeleportListener);
+ }
+
+ public void load() {
+ if (!CNConfig.nameplateModule) return;
+ this.loadConfig();
+ this.loadNameplates();
+
+ this.teamTagManager.load(teamRefreshFrequency, fixTab);
+ this.unlimitedTagManager.load();
+
+ this.nameplateRefreshTask = plugin.getScheduler().runTaskAsyncTimer(() -> {
+ for (OnlineUser user : plugin.getStorageManager().getOnlineUsers()) {
+ updateCachedNameplate(user.getPlayer(), user.getNameplate());
+ }
+ }, refreshFrequency * 50, refreshFrequency * 50, TimeUnit.MILLISECONDS);
+
+ for (Player online : Bukkit.getOnlinePlayers()) {
+ createNameTag(online);
+ }
+
+ Bukkit.getPluginManager().registerEvents(this, plugin);
+ ProtocolLibrary.getProtocolManager().addPacketListener(entityDestroyListener);
+ ProtocolLibrary.getProtocolManager().addPacketListener(entitySpawnListener);
+ ProtocolLibrary.getProtocolManager().addPacketListener(entityLookListener);
+ ProtocolLibrary.getProtocolManager().addPacketListener(entityMoveListener);
+ ProtocolLibrary.getProtocolManager().addPacketListener(entityTeleportListener);
+ }
+
+ private void loadConfig() {
+ YamlConfiguration config = plugin.getConfig("configs" + File.separator + "nameplate.yml");
+
+ tagMode = TagMode.valueOf(config.getString("mode", "TEAM").toUpperCase(Locale.ENGLISH));
+ proxyMode = config.getBoolean("proxy", false);
+ previewDuration = config.getInt("preview-duration", 5);
+ defaultNameplate = config.getString("default-nameplate", "none");
+
+ playerName = config.getString("nameplate.player-name", "%player_name%");
+ prefix = config.getString("nameplate.prefix", "");
+ suffix = config.getString("nameplate.suffix", "");
+ refreshFrequency = config.getInt("nameplate.refresh-frequency", 10);
+
+ teamPrefix = config.getString("team.prefix", "");
+ teamSuffix = config.getString("team.suffix", "");
+ teamRefreshFrequency = config.getInt("team.refresh-frequency", 10);
+ fixTab = config.getBoolean("team.fix-Tab", true);
+
+ ConfigurationSection unlimitedSection = config.getConfigurationSection("unlimited");
+ if (unlimitedSection != null) {
+ for (Map.Entry entry : unlimitedSection.getValues(false).entrySet()) {
+ if (entry.getValue() instanceof ConfigurationSection innerSection) {
+ tagSettings.add(
+ UnlimitedTagSetting.builder()
+ .rawText(innerSection.getString("text", ""))
+ .refreshFrequency(innerSection.getInt("refresh-frequency", 20))
+ .checkFrequency(innerSection.getInt("check-frequency", 20))
+ .verticalOffset(innerSection.getDouble("vertical-offset", -1))
+ .ownerRequirements(plugin.getRequirementManager().getRequirements(innerSection.getConfigurationSection("owner-conditions")))
+ .viewerRequirements(plugin.getRequirementManager().getRequirements(innerSection.getConfigurationSection("viewer-conditions")))
+ .build()
+ );
+ }
+ }
+ }
+ }
+
+ private void loadNameplates() {
+ File npFolder = new File(plugin.getDataFolder(), "contents" + File.separator + "nameplates");
+ if (!npFolder.exists() && npFolder.mkdirs()) {
+ saveDefaultNameplates();
+ }
+ File[] npConfigFiles = npFolder.listFiles(file -> file.getName().endsWith(".yml"));
+ if (npConfigFiles == null) return;
+ Arrays.sort(npConfigFiles, Comparator.comparing(File::getName));
+ for (File npConfigFile : npConfigFiles) {
+
+ String key = npConfigFile.getName().substring(0, npConfigFile.getName().length() - 4);
+ if (key.equals("none")) {
+ LogUtils.severe("You can't use 'none' as nameplate's key");
+ continue;
+ }
+
+ YamlConfiguration config = YamlConfiguration.loadConfiguration(npConfigFile);
+ if (!registerNameplate(
+ key,
+ Nameplate.builder()
+ .displayName(config.getString("display-name", key))
+ .teamColor(TeamColor.valueOf(config.getString("name-color", "none").toUpperCase(Locale.ENGLISH)))
+ .namePrefix(config.getString("name-prefix", ""))
+ .nameSuffix(config.getString("name-suffix", ""))
+ .left(ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("left.image", key + "_left"))
+ .height(config.getInt("left.height", 16))
+ .ascent(config.getInt("left.ascent", 12))
+ .width(config.getInt("left.width", 16))
+ .build())
+ .right(ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("right.image", key + "_right"))
+ .height(config.getInt("right.height", 16))
+ .ascent(config.getInt("right.ascent", 12))
+ .width(config.getInt("right.width", 16))
+ .build())
+ .middle(ConfiguredChar.builder()
+ .character(CharacterArranger.getAndIncrease())
+ .png(config.getString("middle.image", key + "_middle"))
+ .height(config.getInt("middle.height", 16))
+ .ascent(config.getInt("middle.ascent", 12))
+ .width(config.getInt("middle.width", 16))
+ .build())
+ .build())
+ ) {
+ LogUtils.warn("Found duplicated nameplate: " + key);
+ }
+ }
+ }
+
+ @EventHandler (ignoreCancelled = true, priority = EventPriority.LOW)
+ public void onDataLoaded(NameplateDataLoadEvent event) {
+ OnlineUser data = event.getOnlineUser();
+ String nameplate = data.getNameplateKey();
+ if (nameplate.equals("none")) {
+ nameplate = defaultNameplate;
+ }
+
+ if (!nameplate.equals("none") && !containsNameplate(nameplate)) {
+ if (nameplate.equals(defaultNameplate)) {
+ LogUtils.severe("Default nameplate doesn't exist");
+ return;
+ }
+
+ LogUtils.severe("Nameplate " + nameplate + " doesn't exist. To prevent bugs, player " + event.getUUID() + " 's nameplate data is reset");
+ data.setNameplate("none");
+ plugin.getStorageManager().saveOnlinePlayerData(event.getUUID());
+ return;
+ }
+
+ Nameplate np = getNameplate(nameplate);
+ CachedNameplate cachedNameplate = new CachedNameplate();
+ putCachedNameplateToMap(event.getUUID(), cachedNameplate);
+ updateCachedNameplate(cachedNameplate, data.getPlayer(), np);
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ var player = event.getPlayer();
+ this.putEntityIDToMap(player.getEntityId(), player);
+ if (!CNConfig.isOtherTeamPluginHooked())
+ if (!proxyMode) plugin.getTeamManager().createTeam(player);
+ else plugin.getTeamManager().createProxyTeam(player);
+ this.createNameTag(player);
+ }
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ var player = event.getPlayer();
+ this.removeCachedNameplateFromMap(player.getUniqueId());
+ this.removeEntityIDFromMap(player.getEntityId());
+
+ this.teamTagManager.handlePlayerQuit(player);
+ this.unlimitedTagManager.handlePlayerQuit(player);
+
+ if (!proxyMode && !CNConfig.isOtherTeamPluginHooked()) {
+ plugin.getTeamManager().removeTeam(player);
+ }
+ }
+
+ @EventHandler (ignoreCancelled = true)
+ public void onPlayerToggleSneak(PlayerToggleSneakEvent event) {
+ var player = event.getPlayer();
+ unlimitedTagManager.handlePlayerSneak(player, event.isSneaking(), player.isFlying());
+ }
+
+ @EventHandler (ignoreCancelled = true)
+ public void onChangePose(EntityPoseChangeEvent event) {
+ if (event.getEntity() instanceof Player player) {
+ unlimitedTagManager.handlePlayerPose(player, event.getPose());
+ }
+ }
+
+ @Override
+ public boolean putEntityIDToMap(int entityID, Entity entity) {
+ if (this.entityID2EntityMap.containsKey(entityID))
+ return false;
+ this.entityID2EntityMap.put(entityID, entity);
+ return true;
+ }
+
+ @Override
+ public Entity removeEntityIDFromMap(int entityID) {
+ return this.entityID2EntityMap.remove(entityID);
+ }
+
+ @Override
+ public boolean putCachedNameplateToMap(UUID uuid, CachedNameplate nameplate) {
+ if (this.cachedNameplateMap.containsKey(uuid)) {
+ return false;
+ }
+ this.cachedNameplateMap.put(uuid, nameplate);
+ return true;
+ }
+
+ @Override
+ public CachedNameplate removeCachedNameplateFromMap(UUID uuid) {
+ return this.cachedNameplateMap.remove(uuid);
+ }
+
+ @Override
+ public Player getPlayerByEntityID(int id) {
+ Entity entity = entityID2EntityMap.get(id);
+ if (entity instanceof Player player) {
+ return player;
+ }
+ return null;
+ }
+
+ @Override
+ public Entity getEntityByEntityID(int id) {
+ return entityID2EntityMap.get(id);
+ }
+
+ @Override
+ public boolean updateCachedNameplate(Player player) {
+ Optional onlineUser = plugin.getStorageManager().getOnlineUser(player.getUniqueId());
+ if (onlineUser.isEmpty()) return false;
+ Nameplate nameplate = onlineUser.get().getNameplate();
+ return updateCachedNameplate(player, nameplate);
+ }
+
+ @Override
+ public boolean updateCachedNameplate(Player player, Nameplate nameplate) {
+ CachedNameplate cachedNameplate = cachedNameplateMap.get(player.getUniqueId());
+ if (cachedNameplate == null) return false;
+ return updateCachedNameplate(cachedNameplate, player, nameplate);
+ }
+
+ @Override
+ public CachedNameplate getCacheNameplate(Player player) {
+ return cachedNameplateMap.get(player.getUniqueId());
+ }
+
+ @Override
+ public void createNameTag(Player player) {
+ if (tagMode == TagMode.TEAM) {
+ putNameplatePlayerToMap(this.teamTagManager.createTagForPlayer(player, teamPrefix, teamSuffix));
+ } else if (tagMode == TagMode.UNLIMITED) {
+ putNameplatePlayerToMap(this.unlimitedTagManager.createTagForPlayer(player, tagSettings));
+ }
+ }
+
+ @Override
+ public void putNameplatePlayerToMap(NameplatePlayer player) {
+ this.nameplatePlayerMap.put(player.getOwner().getUniqueId(), player);
+ }
+
+ @Override
+ public NameplatePlayer removeNameplatePlayerFromMap(UUID uuid) {
+ return this.nameplatePlayerMap.remove(uuid);
+ }
+
+ private boolean updateCachedNameplate(CachedNameplate cachedNameplate, Player player, Nameplate nameplate) {
+ String parsePrefix = PlaceholderAPI.setPlaceholders(player, prefix);
+ String parseName = PlaceholderAPI.setPlaceholders(player, playerName);
+ String parseSuffix = PlaceholderAPI.setPlaceholders(player, suffix);
+ if (nameplate != null) {
+ int width= FontUtils.getTextWidth(
+ parsePrefix
+ + nameplate.getNamePrefix()
+ + parseName
+ + nameplate.getNameSuffix()
+ + parseSuffix
+ );
+
+ cachedNameplate.setTeamColor(nameplate.getTeamColor());
+ cachedNameplate.setNamePrefix(nameplate.getNamePrefix());
+ cachedNameplate.setNameSuffix(nameplate.getNameSuffix());
+ cachedNameplate.setTagSuffix(parseSuffix + nameplate.getSuffixWithFont(width));
+ cachedNameplate.setTagPrefix(nameplate.getPrefixWithFont(width) + parsePrefix);
+ } else {
+ cachedNameplate.setTeamColor(TeamColor.NONE);
+ cachedNameplate.setNamePrefix("");
+ cachedNameplate.setNameSuffix("");
+ cachedNameplate.setTagPrefix(parsePrefix);
+ cachedNameplate.setTagSuffix(parseSuffix);
+ }
+ cachedNameplate.setPlayerName(parseName);
+ return true;
+ }
+
+ @Override
+ public String getNameplatePrefix(Player player) {
+ CachedNameplate cachedNameplate = cachedNameplateMap.get(player.getUniqueId());
+ if (cachedNameplate == null) return "";
+ return cachedNameplate.getTagPrefix();
+ }
+
+ @Override
+ public String getNameplateSuffix(Player player) {
+ CachedNameplate cachedNameplate = cachedNameplateMap.get(player.getUniqueId());
+ if (cachedNameplate == null) return "";
+ return cachedNameplate.getTagSuffix();
+ }
+
+ @Override
+ public String getFullNameTag(Player player) {
+ CachedNameplate cachedNameplate = cachedNameplateMap.get(player.getUniqueId());
+ if (cachedNameplate == null) {
+ return player.getName();
+ }
+
+ return cachedNameplate.getTagPrefix()
+ + cachedNameplate.getNamePrefix()
+ + cachedNameplate.getPlayerName()
+ + cachedNameplate.getNameSuffix()
+ + cachedNameplate.getTagSuffix();
+ }
+
+ @Override
+ public boolean registerNameplate(String key, Nameplate nameplate) {
+ if (this.nameplateMap.containsKey(key)) return false;
+ this.nameplateMap.put(key, nameplate);
+ return true;
+ }
+
+ @Override
+ public List getAvailableNameplates(Player player) {
+ List nameplates = new ArrayList<>();
+ for (String nameplate : nameplateMap.keySet()) {
+ if (hasNameplate(player, nameplate)) {
+ nameplates.add(nameplate);
+ }
+ }
+ return nameplates;
+ }
+
+ @Override
+ public boolean equipNameplate(Player player, String nameplateKey) {
+ Nameplate nameplate = getNameplate(nameplateKey);
+ if (nameplate == null) {
+ return false;
+ }
+ plugin.getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(it -> {
+ it.setNameplate(nameplateKey);
+ plugin.getStorageManager().saveOnlinePlayerData(player.getUniqueId());
+ }, () -> {
+ LogUtils.severe("Player " + player.getName() + "'s data is not loaded.");
+ });
+ return true;
+ }
+
+ @Override
+ public void unEquipNameplate(Player player) {
+ plugin.getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(it -> {
+ it.setNameplate("none");
+ plugin.getStorageManager().saveOnlinePlayerData(player.getUniqueId());
+ }, () -> {
+ LogUtils.severe("Player " + player.getName() + "'s data is not loaded.");
+ });
+ }
+
+ private void saveDefaultNameplates() {
+ String[] png_list = new String[]{"cat", "egg", "cheems", "wither", "xmas", "halloween", "hutao", "starsky", "trident", "rabbit"};
+ String[] part_list = new String[]{"_left.png", "_middle.png", "_right.png", ".yml"};
+ for (String name : png_list) {
+ for (String part : part_list) {
+ plugin.saveResource("contents" + File.separator + "nameplates" + File.separator + name + part, false);
+ }
+ }
+ }
+
+ @Override
+ public boolean unregisterNameplate(String key) {
+ return this.nameplateMap.remove(key) != null;
+ }
+
+ @Override
+ public boolean isProxyMode() {
+ return proxyMode;
+ }
+
+ @Override
+ public TagMode getTagMode() {
+ return tagMode;
+ }
+
+ @Override
+ @Nullable
+ public Nameplate getNameplate(String key) {
+ return nameplateMap.get(key);
+ }
+
+ @Override
+ public Collection getNameplates() {
+ return nameplateMap.values();
+ }
+
+ @Override
+ public boolean containsNameplate(String key) {
+ return nameplateMap.containsKey(key);
+ }
+
+ @Override
+ public boolean hasNameplate(Player player, String nameplate) {
+ return player.hasPermission("nameplates.equip." + nameplate);
+ }
+
+ @Override
+ public TeamColor getTeamColor(Player player) {
+ CachedNameplate nameplate = getCacheNameplate(player);
+ return nameplate == null ? TeamColor.WHITE : nameplate.getTeamColor();
+ }
+
+ @Override
+ public String getDefaultNameplate() {
+ return defaultNameplate;
+ }
+
+ @Override
+ public TeamTagManager getTeamTagManager() {
+ return teamTagManager;
+ }
+
+ @Override
+ public UnlimitedTagManager getUnlimitedTagManager() {
+ return unlimitedTagManager;
+ }
+
+ public void onEntityMove(Player receiver, int entityID, short x, short y, short z, boolean onGround) {
+ unlimitedTagManager.handleEntityMovePacket(receiver, entityID, x, y, z, onGround);
+ }
+
+ public void onEntityDestroy(Player receiver, List list) {
+ teamTagManager.handleEntityDestroyPacket(receiver, list);
+ unlimitedTagManager.handleEntityDestroyPacket(receiver, list);
+ }
+
+ public void onEntitySpawn(Player receiver, int entityID) {
+ teamTagManager.handleEntitySpawnPacket(receiver, entityID);
+ unlimitedTagManager.handleEntitySpawnPacket(receiver, entityID);
+ }
+
+ public void onEntityTeleport(Player receiver, int entityID, double x, double y, double z, boolean onGround) {
+ unlimitedTagManager.handleEntityTeleportPacket(receiver, entityID, x, y, z, onGround);
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/listener/packet/EntityDestroyListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityDestroyListener.java
similarity index 67%
rename from src/main/java/net/momirealms/customnameplates/listener/packet/EntityDestroyListener.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityDestroyListener.java
index 9936175..13f1bb5 100644
--- a/src/main/java/net/momirealms/customnameplates/listener/packet/EntityDestroyListener.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityDestroyListener.java
@@ -15,27 +15,27 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.listener.packet;
+package net.momirealms.customnameplates.paper.mechanic.nameplate.tag.listener;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.object.carrier.AbstractPacketsHandler;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.NameplateManagerImpl;
public class EntityDestroyListener extends PacketAdapter {
- private final AbstractPacketsHandler handler;
+ private final NameplateManagerImpl manager;
- public EntityDestroyListener(AbstractPacketsHandler handler) {
- super(CustomNameplates.getInstance(), ListenerPriority.HIGHEST, PacketType.Play.Server.ENTITY_DESTROY);
- this.handler = handler;
+ public EntityDestroyListener(NameplateManagerImpl manager) {
+ super(CustomNameplatesPlugin.getInstance(), ListenerPriority.HIGH, PacketType.Play.Server.ENTITY_DESTROY);
+ this.manager = manager;
}
public void onPacketSending(PacketEvent event) {
PacketContainer packet = event.getPacket();
- handler.onEntityDestroy(event.getPlayer(), packet.getIntLists().read(0));
+ manager.onEntityDestroy(event.getPlayer(), packet.getIntLists().read(0));
}
}
diff --git a/src/main/java/net/momirealms/customnameplates/listener/packet/EntityLookListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityLookListener.java
similarity index 71%
rename from src/main/java/net/momirealms/customnameplates/listener/packet/EntityLookListener.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityLookListener.java
index e353ce0..c51f2f9 100644
--- a/src/main/java/net/momirealms/customnameplates/listener/packet/EntityLookListener.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityLookListener.java
@@ -15,28 +15,28 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.listener.packet;
+package net.momirealms.customnameplates.paper.mechanic.nameplate.tag.listener;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.object.carrier.AbstractPacketsHandler;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.NameplateManagerImpl;
public class EntityLookListener extends PacketAdapter {
- private final AbstractPacketsHandler handler;
+ private final NameplateManagerImpl manager;
- public EntityLookListener(AbstractPacketsHandler handler) {
- super(CustomNameplates.getInstance(), ListenerPriority.HIGHEST, PacketType.Play.Server.REL_ENTITY_MOVE_LOOK);
- this.handler = handler;
+ public EntityLookListener(NameplateManagerImpl manager) {
+ super(CustomNameplatesPlugin.getInstance(), ListenerPriority.HIGHEST, PacketType.Play.Server.REL_ENTITY_MOVE_LOOK);
+ this.manager = manager;
}
public void onPacketSending(PacketEvent event) {
PacketContainer packet = event.getPacket();
- handler.onEntityMove(event.getPlayer(),
+ manager.onEntityMove(event.getPlayer(),
packet.getIntegers().read(0),
packet.getShorts().read(0),
packet.getShorts().read(1),
diff --git a/src/main/java/net/momirealms/customnameplates/listener/packet/EntityMoveListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityMoveListener.java
similarity index 71%
rename from src/main/java/net/momirealms/customnameplates/listener/packet/EntityMoveListener.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityMoveListener.java
index e1801fa..86af826 100644
--- a/src/main/java/net/momirealms/customnameplates/listener/packet/EntityMoveListener.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityMoveListener.java
@@ -15,28 +15,28 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.listener.packet;
+package net.momirealms.customnameplates.paper.mechanic.nameplate.tag.listener;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.object.carrier.AbstractPacketsHandler;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.NameplateManagerImpl;
public class EntityMoveListener extends PacketAdapter {
- private final AbstractPacketsHandler handler;
+ private final NameplateManagerImpl manager;
- public EntityMoveListener(AbstractPacketsHandler handler) {
- super(CustomNameplates.getInstance(), ListenerPriority.NORMAL, PacketType.Play.Server.REL_ENTITY_MOVE);
- this.handler = handler;
+ public EntityMoveListener(NameplateManagerImpl manager) {
+ super(CustomNameplatesPlugin.getInstance(), ListenerPriority.HIGHEST, PacketType.Play.Server.REL_ENTITY_MOVE);
+ this.manager = manager;
}
public void onPacketSending(PacketEvent event) {
PacketContainer packet = event.getPacket();
- handler.onEntityMove(event.getPlayer(),
+ manager.onEntityMove(event.getPlayer(),
packet.getIntegers().read(0),
packet.getShorts().read(0),
packet.getShorts().read(1),
diff --git a/src/main/java/net/momirealms/customnameplates/listener/packet/EntitySpawnListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntitySpawnListener.java
similarity index 59%
rename from src/main/java/net/momirealms/customnameplates/listener/packet/EntitySpawnListener.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntitySpawnListener.java
index 890da94..8c1323e 100644
--- a/src/main/java/net/momirealms/customnameplates/listener/packet/EntitySpawnListener.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntitySpawnListener.java
@@ -15,25 +15,25 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.listener.packet;
+package net.momirealms.customnameplates.paper.mechanic.nameplate.tag.listener;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.object.carrier.AbstractPacketsHandler;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.NameplateManagerImpl;
public class EntitySpawnListener extends PacketAdapter {
- private final AbstractPacketsHandler handler;
+ private final NameplateManagerImpl manager;
- public EntitySpawnListener(AbstractPacketsHandler handler) {
- super(CustomNameplates.getInstance(), ListenerPriority.HIGHEST, CustomNameplates.getInstance().getVersionHelper().isVersionNewerThan1_20_R2() ? PacketType.Play.Server.SPAWN_ENTITY : PacketType.Play.Server.NAMED_ENTITY_SPAWN);
- this.handler = handler;
+ public EntitySpawnListener(NameplateManagerImpl manager) {
+ super(CustomNameplatesPlugin.getInstance(), ListenerPriority.HIGH, CustomNameplatesPlugin.getInstance().getVersionManager().isVersionNewerThan1_20_R2() ? PacketType.Play.Server.SPAWN_ENTITY : PacketType.Play.Server.NAMED_ENTITY_SPAWN);
+ this.manager = manager;
}
public synchronized void onPacketSending(PacketEvent event) {
- handler.onEntitySpawn(event.getPlayer(), event.getPacket().getIntegers().read(0), event);
+ manager.onEntitySpawn(event.getPlayer(), event.getPacket().getIntegers().read(0));
}
}
diff --git a/src/main/java/net/momirealms/customnameplates/listener/packet/EntityTeleportListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityTeleportListener.java
similarity index 58%
rename from src/main/java/net/momirealms/customnameplates/listener/packet/EntityTeleportListener.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityTeleportListener.java
index cdc0473..59d035d 100644
--- a/src/main/java/net/momirealms/customnameplates/listener/packet/EntityTeleportListener.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/EntityTeleportListener.java
@@ -15,27 +15,34 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.listener.packet;
+package net.momirealms.customnameplates.paper.mechanic.nameplate.tag.listener;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.object.carrier.AbstractPacketsHandler;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.NameplateManagerImpl;
public class EntityTeleportListener extends PacketAdapter {
- private final AbstractPacketsHandler handler;
+ private final NameplateManagerImpl manager;
- public EntityTeleportListener(AbstractPacketsHandler handler) {
- super(CustomNameplates.getInstance(), ListenerPriority.HIGHEST, PacketType.Play.Server.ENTITY_TELEPORT);
- this.handler = handler;
+ public EntityTeleportListener(NameplateManagerImpl manager) {
+ super(CustomNameplatesPlugin.getInstance(), ListenerPriority.HIGHEST, PacketType.Play.Server.ENTITY_TELEPORT);
+ this.manager = manager;
}
public void onPacketSending(PacketEvent event) {
PacketContainer packet = event.getPacket();
- handler.onEntityTeleport(event.getPlayer(), packet.getIntegers().read(0));
+ manager.onEntityTeleport(
+ event.getPlayer(),
+ packet.getIntegers().read(0),
+ packet.getDoubles().read(0),
+ packet.getDoubles().read(1),
+ packet.getDoubles().read(2),
+ packet.getBooleans().read(0)
+ );
}
}
diff --git a/src/main/java/net/momirealms/customnameplates/listener/compatibility/MagicCosmeticsListener.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/MagicCosmeticsListener.java
similarity index 54%
rename from src/main/java/net/momirealms/customnameplates/listener/compatibility/MagicCosmeticsListener.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/MagicCosmeticsListener.java
index 8e5f10c..52466ec 100644
--- a/src/main/java/net/momirealms/customnameplates/listener/compatibility/MagicCosmeticsListener.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/listener/MagicCosmeticsListener.java
@@ -1,24 +1,22 @@
-package net.momirealms.customnameplates.listener.compatibility;
+package net.momirealms.customnameplates.paper.mechanic.nameplate.tag.listener;
import com.francobm.magicosmetics.api.Cosmetic;
import com.francobm.magicosmetics.api.CosmeticType;
-import com.francobm.magicosmetics.api.MagicAPI;
import com.francobm.magicosmetics.cache.PlayerData;
import com.francobm.magicosmetics.cache.cosmetics.Hat;
import com.francobm.magicosmetics.events.*;
-import net.momirealms.customnameplates.object.carrier.NamedEntityCarrier;
-import net.momirealms.customnameplates.object.carrier.NamedEntityManager;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.UnlimitedPlayer;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.tag.unlimited.UnlimitedTagManagerImpl;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerChangedWorldEvent;
public class MagicCosmeticsListener implements Listener {
- private final NamedEntityCarrier namedEntityCarrier;
+ private final UnlimitedTagManagerImpl unlimitedTagManager;
- public MagicCosmeticsListener(NamedEntityCarrier namedEntityCarrier) {
- this.namedEntityCarrier = namedEntityCarrier;
+ public MagicCosmeticsListener(UnlimitedTagManagerImpl unlimitedTagManager) {
+ this.unlimitedTagManager = unlimitedTagManager;
}
@EventHandler
@@ -26,9 +24,8 @@ public class MagicCosmeticsListener implements Listener {
final Cosmetic cosmetic = event.getNewCosmetic();
final Player player = event.getPlayer();
if (cosmetic instanceof Hat hat) {
- NamedEntityManager nem = namedEntityCarrier.getNamedEntityManager(player);
- if (nem != null) {
- nem.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY());
+ if (unlimitedTagManager.getUnlimitedObject(player.getUniqueId()) instanceof UnlimitedPlayer unlimitedPlayer) {
+ unlimitedPlayer.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY());
}
}
}
@@ -36,16 +33,15 @@ public class MagicCosmeticsListener implements Listener {
@EventHandler
public void onEnterBlackListWorld(PlayerChangeBlacklistEvent event) {
var player = event.getPlayer();
- NamedEntityManager nem = namedEntityCarrier.getNamedEntityManager(player);
- if (nem != null) {
+ if (unlimitedTagManager.getUnlimitedObject(player.getUniqueId()) instanceof UnlimitedPlayer unlimitedPlayer) {
if (event.isInWorldBlacklist()) {
- nem.setHatOffset(0);
+ unlimitedPlayer.setHatOffset(0);
} else {
PlayerData playerData = PlayerData.getPlayer(player);
if (playerData != null) {
final Cosmetic cosmetic = playerData.getHat();
if (cosmetic instanceof Hat hat) {
- nem.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY());
+ unlimitedPlayer.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY());
}
}
}
@@ -57,9 +53,8 @@ public class MagicCosmeticsListener implements Listener {
final Cosmetic cosmetic = event.getCosmetic();
final Player player = event.getPlayer();
if (cosmetic instanceof Hat hat) {
- NamedEntityManager nem = namedEntityCarrier.getNamedEntityManager(player);
- if (nem != null) {
- nem.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY());
+ if (unlimitedTagManager.getUnlimitedObject(player.getUniqueId()) instanceof UnlimitedPlayer unlimitedPlayer) {
+ unlimitedPlayer.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY());
}
}
}
@@ -68,9 +63,8 @@ public class MagicCosmeticsListener implements Listener {
public void onUnEquip(CosmeticUnEquipEvent event) {
final Player player = event.getPlayer();
if (event.getCosmeticType() == CosmeticType.HAT) {
- NamedEntityManager nem = namedEntityCarrier.getNamedEntityManager(player);
- if (nem != null) {
- nem.setHatOffset(0);
+ if (unlimitedTagManager.getUnlimitedObject(player.getUniqueId()) instanceof UnlimitedPlayer unlimitedPlayer) {
+ unlimitedPlayer.setHatOffset(0);
}
}
}
@@ -79,9 +73,8 @@ public class MagicCosmeticsListener implements Listener {
public void onDataLoaded(PlayerDataLoadEvent event) {
for (Cosmetic cosmetic : event.getEquippedCosmetics()) {
if (cosmetic instanceof Hat hat) {
- NamedEntityManager nem = namedEntityCarrier.getNamedEntityManager(event.getPlayerData().getOfflinePlayer().getPlayer());
- if (nem != null) {
- nem.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY());
+ if (unlimitedTagManager.getUnlimitedObject(event.getPlayerData().getUniqueId()) instanceof UnlimitedPlayer unlimitedPlayer) {
+ unlimitedPlayer.setHatOffset(hat.isHideCosmetic() ? 0 : hat.getOffSetY());
}
}
}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/team/TeamTagManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/team/TeamTagManagerImpl.java
new file mode 100644
index 0000000..dd9495f
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/team/TeamTagManagerImpl.java
@@ -0,0 +1,128 @@
+package net.momirealms.customnameplates.paper.mechanic.nameplate.tag.team;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.NameplateManager;
+import net.momirealms.customnameplates.api.manager.TeamTagManager;
+import net.momirealms.customnameplates.api.mechanic.tag.team.TeamPlayer;
+import net.momirealms.customnameplates.api.scheduler.CancellableTask;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.util.LocationUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+public class TeamTagManagerImpl implements TeamTagManager {
+
+ private final NameplateManager manager;
+ private final ConcurrentHashMap teamPlayerMap;
+ private CancellableTask refreshTask;
+
+ public TeamTagManagerImpl(NameplateManager manager) {
+ this.manager = manager;
+ this.teamPlayerMap = new ConcurrentHashMap<>();
+ }
+
+ public void load(long refreshFrequency, boolean fixTab) {
+ this.refreshTask = CustomNameplatesPlugin.get().getScheduler().runTaskAsyncTimer(
+ () -> {
+ try {
+ for (TeamPlayer teamPlayer : teamPlayerMap.values()) {
+ teamPlayer.updateForNearbyPlayers(false);
+ }
+ } catch (Exception e) {
+ LogUtils.severe(
+ "Error occurred when updating team tags. " +
+ "This might not be a bug in CustomNameplates. Please report " +
+ "to the Plugin on the top of the following " +
+ "stack trace."
+ );
+ e.printStackTrace();
+ }
+ },
+ refreshFrequency * 50L,
+ refreshFrequency * 50L,
+ TimeUnit.MILLISECONDS
+ );
+ if (fixTab) {
+ //TODO
+ }
+ }
+
+ public void unload() {
+ if (this.refreshTask != null && !this.refreshTask.isCancelled()) {
+ this.refreshTask.cancel();
+ }
+ for (TeamPlayer entry : teamPlayerMap.values()) {
+ entry.destroy();
+ }
+ }
+
+ @Override
+ @SuppressWarnings("DuplicatedCode")
+ public TeamPlayer createTagForPlayer(Player player, String prefix, String suffix) {
+ if (this.teamPlayerMap.containsKey(player.getUniqueId())) {
+ return null;
+ }
+
+ var teamPlayer = new TeamPlayer(this, player, prefix, suffix);
+ this.teamPlayerMap.put(
+ player.getUniqueId(),
+ teamPlayer
+ );
+ for (Player online : Bukkit.getOnlinePlayers()) {
+ if ( online == player
+ || !online.canSee(player)
+ || LocationUtils.getDistance(online, player) > 48
+ || online.getWorld() != player.getWorld()
+ || online.isDead()
+ ) continue;
+ teamPlayer.addNearbyPlayer(online);
+ }
+ return teamPlayer;
+ }
+
+ @Override
+ public TeamPlayer removeTeamPlayerFromMap(UUID uuid) {
+ return teamPlayerMap.remove(uuid);
+ }
+
+ @Nullable
+ public TeamPlayer getTeamPlayer(UUID uuid) {
+ return teamPlayerMap.get(uuid);
+ }
+
+ public void handleEntitySpawnPacket(Player receiver, int entityId) {
+ Entity spawned = manager.getEntityByEntityID(entityId);
+ if (spawned == null) return;
+ TeamPlayer teamPlayer = getTeamPlayer(spawned.getUniqueId());
+ if (teamPlayer == null) return;
+ teamPlayer.addNearbyPlayer(receiver);
+ }
+
+ public void handleEntityDestroyPacket(Player receiver, List list) {
+ for (int id : list) {
+ handleSingleEntityDestroy(receiver, id);
+ }
+ }
+
+ public void handlePlayerQuit(Player quit) {
+ teamPlayerMap.remove(quit.getUniqueId());
+ for (TeamPlayer teamPlayer : teamPlayerMap.values()) {
+ teamPlayer.removeNearbyPlayer(quit);
+ }
+ }
+
+ private void handleSingleEntityDestroy(Player receiver, int entityID) {
+ Entity deSpawned = manager.getEntityByEntityID(entityID);
+ if (deSpawned == null) return;
+ TeamPlayer teamPlayer = getTeamPlayer(deSpawned.getUniqueId());
+ if (teamPlayer == null) return;
+ teamPlayer.removeNearbyPlayer(receiver);
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/unlimited/NamedEntityImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/unlimited/NamedEntityImpl.java
new file mode 100644
index 0000000..0e94eb2
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/unlimited/NamedEntityImpl.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.mechanic.nameplate.tag.unlimited;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.wrappers.WrappedChatComponent;
+import com.comphenix.protocol.wrappers.WrappedDataValue;
+import com.comphenix.protocol.wrappers.WrappedDataWatcher;
+import com.google.common.collect.Lists;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.RequirementManager;
+import net.momirealms.customnameplates.api.mechanic.misc.ViewerText;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.NamedEntity;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.UnlimitedPlayer;
+import net.momirealms.customnameplates.api.requirement.Condition;
+import net.momirealms.customnameplates.api.requirement.Requirement;
+import net.momirealms.customnameplates.paper.adventure.AdventureManagerImpl;
+import net.momirealms.customnameplates.paper.mechanic.misc.PacketManager;
+import org.bukkit.Location;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Pose;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+public class NamedEntityImpl implements NamedEntity {
+
+ private final UUID uuid = UUID.randomUUID();
+ private final UnlimitedPlayer owner;
+ private double yOffset;
+ private final int entityId;
+ private boolean sneaking;
+ private final ViewerText viewerText;
+ private final Requirement[] ownerRequirements;
+ private final Requirement[] viewerRequirements;
+ private final Vector viewers;
+ private final int refreshFrequency;
+ private final int checkFrequency;
+ private int checkTimer;
+ private int refreshTimer;
+ private boolean ownerCanShow;
+
+ public NamedEntityImpl(
+ UnlimitedPlayer unlimitedPlayer,
+ ViewerText text,
+ int refreshFrequency,
+ int checkFrequency,
+ double yOffset,
+ Requirement[] ownerRequirements,
+ Requirement[] viewerRequirements
+ ) {
+ this.entityId = new Random().nextInt(Integer.MAX_VALUE);
+ this.owner = unlimitedPlayer;
+ this.yOffset = yOffset;
+ this.viewerText = text;
+ this.sneaking = unlimitedPlayer.getOwner().isSneaking();
+ this.ownerRequirements = ownerRequirements;
+ this.viewerRequirements = viewerRequirements;
+ this.checkFrequency = checkFrequency;
+ this.refreshFrequency = refreshFrequency;
+ this.viewers = new Vector<>();
+ this.ownerCanShow = RequirementManager.isRequirementMet(new Condition(owner.getOwner()), ownerRequirements);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NamedEntityImpl that = (NamedEntityImpl) o;
+ return uuid.equals(that.uuid);
+ }
+
+ @Override
+ public int hashCode() {
+ return uuid.hashCode();
+ }
+
+ @Override
+ public boolean canShow() {
+ return ownerCanShow;
+ }
+
+ @Override
+ public boolean isShownTo(Player viewer) {
+ return viewers.contains(viewer);
+ }
+
+ @Override
+ public boolean canSee(Player viewer) {
+ Condition condition = new Condition(viewer);
+ return RequirementManager.isRequirementMet(condition, viewerRequirements);
+ }
+
+ @Override
+ public void timer() {
+ checkTimer++;
+ if (checkTimer >= checkFrequency) {
+ checkTimer = 0;
+ if (!RequirementManager.isRequirementMet(new Condition(owner.getOwner()), ownerRequirements)) {
+ ownerCanShow = false;
+ for (Player all : owner.getNearbyPlayers()) {
+ removePlayerFromViewers(all);
+ }
+ } else {
+ ownerCanShow = true;
+ for (Player all : owner.getNearbyPlayers()) {
+ if (canSee(all)) {
+ addPlayerToViewers(all);
+ } else {
+ removePlayerFromViewers(all);
+ }
+ }
+ }
+ }
+
+ refreshTimer++;
+ if (refreshTimer >= refreshFrequency) {
+ refreshTimer = 0;
+ viewerText.updateForOwner();
+ updateText();
+ }
+ }
+
+ @Override
+ public void removePlayerFromViewers(Player player) {
+ if (!viewers.contains(player)) {
+ return;
+ }
+ this.viewers.remove(player);
+ destroy(player);
+
+ }
+
+ @Override
+ public void addPlayerToViewers(Player player) {
+ if (viewers.contains(player)) {
+ return;
+ }
+ this.viewers.add(player);
+ spawn(player, owner.getOwner().getPose());
+ }
+
+ @Override
+ public double getOffset() {
+ return yOffset;
+ }
+
+ @Override
+ public void setOffset(double offset) {
+ if (yOffset == offset) return;
+ yOffset = offset;
+ for (Player all : viewers) {
+ PacketManager.getInstance().send(all, getTeleportPacket(0));
+ }
+ }
+
+ @Override
+ public ViewerText getViewerText() {
+ return viewerText;
+ }
+
+ @Override
+ public void spawn(Player viewer, Pose pose) {
+ if (viewerText.updateForViewer(viewer)) {
+ for (PacketContainer packet : getSpawnPackets(viewerText.getLatestValue(viewer), pose)) {
+ PacketManager.getInstance().send(viewer, packet);
+ }
+ }
+ }
+
+ @Override
+ public void spawn(Pose pose) {
+ for (Player all : viewers) {
+ spawn(all, pose);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ PacketContainer destroyPacket = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY);
+ destroyPacket.getIntLists().write(0, List.of(entityId));
+ for (Player all : viewers) {
+ PacketManager.getInstance().send(all, destroyPacket);
+ viewerText.removeViewer(all);
+ }
+ }
+
+ @Override
+ public void destroy(Player viewer) {
+ PacketContainer destroyPacket = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY);
+ destroyPacket.getIntLists().write(0, List.of(entityId));
+ PacketManager.getInstance().send(viewer, destroyPacket);
+ viewerText.removeViewer(viewer);
+ }
+
+ @Override
+ public void teleport(double x, double y, double z, boolean onGround) {
+ PacketContainer packet = getTeleportPacket(x, y, z, onGround);
+ for (Player all : viewers) {
+ PacketManager.getInstance().send(all, packet);
+ }
+ }
+
+ @Override
+ public void teleport(Player viewer, double x, double y, double z, boolean onGround) {
+ if (viewers.contains(viewer)) {
+ PacketManager.getInstance().send(viewer, getTeleportPacket(x, y, z, onGround));
+ }
+ }
+
+ @Override
+ public void setSneak(boolean isSneaking, boolean onGround) {
+ this.sneaking = isSneaking;
+ if (!onGround) {
+ for (Player viewer : viewers) {
+ PacketManager.getInstance().send(viewer, getMetaPacket(viewerText.getLatestValue(viewer)));
+ }
+ }
+ }
+
+ @Override
+ public int getEntityId() {
+ return entityId;
+ }
+
+ @Override
+ public void move(short x, short y, short z, boolean onGround) {
+ PacketContainer packet = getMovePacket(x, y, z, onGround);
+ for (Player viewer : viewers) {
+ PacketManager.getInstance().send(viewer, packet);
+ }
+ }
+
+ @Override
+ public void move(Player viewer, short x, short y, short z, boolean onGround) {
+ if (viewers.contains(viewer)) {
+ PacketContainer packet = getMovePacket(x, y, z, onGround);
+ PacketManager.getInstance().send(viewer, packet);
+ }
+ }
+
+ @Override
+ public void respawn(Player viewer, Pose pose) {
+ destroy(viewer);
+ spawn(viewer, pose);
+ }
+
+ @Override
+ public void respawn(Pose pose) {
+ for (Player viewer : viewers) {
+ respawn(viewer, pose);
+ }
+ }
+
+ @Override
+ public void updateText() {
+ for (Player viewer : viewers) {
+ updateText(viewer);
+ }
+ }
+
+ @Override
+ public void updateText(Player viewer) {
+ if (viewerText.updateForViewer(viewer)) {
+ PacketManager.getInstance().send(viewer, getMetaPacket(viewerText.getLatestValue(viewer)));
+ }
+ }
+
+ @Override
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public void handlePose(Pose previous, Pose pose) {
+ // Add delay to prevent the tag from appearing earlier
+ CustomNameplatesPlugin.get().getScheduler().runTaskAsyncLater(() -> {
+ for (Player viewer : viewers) {
+ respawn(viewer, pose);
+ }
+ }, 20, TimeUnit.MILLISECONDS);
+ }
+
+ public PacketContainer getMovePacket(short x, short y, short z, boolean onGround) {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.REL_ENTITY_MOVE);
+ packet.getIntegers().write(0, entityId);
+ packet.getShorts().write(0, x);
+ packet.getShorts().write(1, y);
+ packet.getShorts().write(2, z);
+ packet.getBooleans().write(0, onGround);
+ return packet;
+ }
+
+ public PacketContainer getTeleportPacket(double x, double y, double z, boolean onGround) {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT);
+ packet.getIntegers().write(0, entityId);
+ packet.getDoubles().write(0, x);
+ packet.getDoubles().write(1, y + owner.getHatOffset() + getCorrection(owner.getOwner().getPose()) + yOffset);
+ packet.getDoubles().write(2, z);
+ packet.getBooleans().write(0, onGround);
+ return packet;
+ }
+
+ public PacketContainer getTeleportPacket(double correction) {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT);
+ packet.getIntegers().write(0, entityId);
+ Location location = getEntityLocation(correction);
+ packet.getDoubles().write(0, location.getX());
+ packet.getDoubles().write(1, location.getY());
+ packet.getDoubles().write(2, location.getZ());
+ return packet;
+ }
+
+ protected PacketContainer getMetaPacket(String text) {
+ PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
+ metaPacket.getIntegers().write(0, entityId);
+ String json = AdventureManagerImpl.getInstance().componentToJson(AdventureManagerImpl.getInstance().getComponentFromMiniMessage(text));
+ if (CustomNameplatesPlugin.getInstance().getVersionManager().isVersionNewerThan1_19_R2()) {
+ WrappedDataWatcher wrappedDataWatcher = createArmorStandDataWatcher(json);
+ List wrappedDataValueList = Lists.newArrayList();
+ wrappedDataWatcher.getWatchableObjects().stream().filter(Objects::nonNull).forEach(entry -> wrappedDataValueList.add(new WrappedDataValue(entry.getWatcherObject().getIndex(), entry.getWatcherObject().getSerializer(), entry.getRawValue())));
+ metaPacket.getDataValueCollectionModifier().write(0, wrappedDataValueList);
+ } else {
+ metaPacket.getWatchableCollectionModifier().write(0, createArmorStandDataWatcher(json).getWatchableObjects());
+ }
+ return metaPacket;
+ }
+
+ private Location getEntityLocation(double correction) {
+ var player = owner.getOwner();
+ double x = player.getLocation().getX();
+ double y = player.getLocation().getY();
+ double z = player.getLocation().getZ();
+ y += yOffset;
+ y += owner.getHatOffset();
+ y += correction;
+ return new Location(null, x, y, z);
+ }
+
+ private WrappedDataWatcher createArmorStandDataWatcher(String json) {
+ WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher();
+ WrappedDataWatcher.Serializer serializer1 = WrappedDataWatcher.Registry.get(Boolean.class);
+ WrappedDataWatcher.Serializer serializer2 = WrappedDataWatcher.Registry.get(Byte.class);
+ wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(2, WrappedDataWatcher.Registry.getChatComponentSerializer(true)), Optional.of(WrappedChatComponent.fromJson(json).getHandle()));
+ wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(3, serializer1), true);
+ byte flag = 0x20;
+ if (sneaking) flag += (byte) 0x02;
+ wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(0, serializer2), flag);
+ wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(15, serializer2), (byte) 0x01);
+ wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(3, serializer1), true);
+ return wrappedDataWatcher;
+ }
+
+ private PacketContainer[] getSpawnPackets(String text, Pose pose) {
+ PacketContainer entityPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY);
+ entityPacket.getModifier().write(0, entityId);
+ entityPacket.getModifier().write(1, uuid);
+ entityPacket.getEntityTypeModifier().write(0, EntityType.ARMOR_STAND);
+ Location location = getEntityLocation(getCorrection(pose));
+ entityPacket.getDoubles().write(0, location.getX());
+ entityPacket.getDoubles().write(1, location.getY());
+ entityPacket.getDoubles().write(2, location.getZ());
+ PacketContainer metaPacket = getMetaPacket(text);
+ return new PacketContainer[] {entityPacket, metaPacket};
+ }
+
+ private double getCorrection(Pose pose) {
+ return switch (pose) {
+ case STANDING -> 1.8;
+ case SNEAKING -> 1.5;
+ case SLEEPING -> 0.2;
+ case SWIMMING, FALL_FLYING, SPIN_ATTACK -> 0.55;
+ default -> 0;
+ };
+ }
+}
\ No newline at end of file
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/unlimited/UnlimitedTagManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/unlimited/UnlimitedTagManagerImpl.java
new file mode 100644
index 0000000..3600da8
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/nameplate/tag/unlimited/UnlimitedTagManagerImpl.java
@@ -0,0 +1,199 @@
+package net.momirealms.customnameplates.paper.mechanic.nameplate.tag.unlimited;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.NameplateManager;
+import net.momirealms.customnameplates.api.manager.UnlimitedTagManager;
+import net.momirealms.customnameplates.api.mechanic.misc.ViewerText;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.NamedEntity;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.UnlimitedObject;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.UnlimitedPlayer;
+import net.momirealms.customnameplates.api.mechanic.tag.unlimited.UnlimitedTagSetting;
+import net.momirealms.customnameplates.api.scheduler.CancellableTask;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.mechanic.nameplate.tag.listener.MagicCosmeticsListener;
+import net.momirealms.customnameplates.paper.util.LocationUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Pose;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+public class UnlimitedTagManagerImpl implements UnlimitedTagManager {
+
+ private final NameplateManager manager;
+ private final ConcurrentHashMap unlimitedEntryMap;
+ private CancellableTask refreshTask;
+ private MagicCosmeticsListener magicCosmeticsListener;
+
+ public UnlimitedTagManagerImpl(NameplateManager nameplateManager) {
+ this.manager = nameplateManager;
+ this.unlimitedEntryMap = new ConcurrentHashMap<>();
+ if (Bukkit.getPluginManager().getPlugin("MagicCosmetics") != null) {
+ this.magicCosmeticsListener = new MagicCosmeticsListener(this);
+ }
+ }
+
+ public void load() {
+ this.refreshTask = CustomNameplatesPlugin.get().getScheduler().runTaskAsyncTimer(
+ () -> {
+ try {
+ for (UnlimitedObject unlimitedObject : unlimitedEntryMap.values()) {
+ if (unlimitedObject instanceof UnlimitedPlayer unlimitedPlayer) {
+ unlimitedPlayer.timer();
+ }
+ }
+ } catch (Exception e) {
+ LogUtils.severe(
+ "Error occurred when updating unlimited tags. " +
+ "This might not be a bug in CustomNameplates. Please report " +
+ "to the Plugin on the top of the following " +
+ "stack trace."
+ );
+ e.printStackTrace();
+ }
+ },
+ 50L,
+ 50L,
+ TimeUnit.MILLISECONDS
+ );
+ if (this.magicCosmeticsListener != null) {
+ Bukkit.getPluginManager().registerEvents(magicCosmeticsListener, CustomNameplatesPlugin.get());
+ }
+ }
+
+ public void unload() {
+ if (this.refreshTask != null && !this.refreshTask.isCancelled()) {
+ this.refreshTask.cancel();
+ }
+ for (UnlimitedObject entry : unlimitedEntryMap.values()) {
+ entry.destroy();
+ }
+ if (this.magicCosmeticsListener != null) {
+ HandlerList.unregisterAll(magicCosmeticsListener);
+ }
+ }
+
+ @NotNull
+ @Override
+ public NamedEntity createNamedEntity(UnlimitedPlayer player, UnlimitedTagSetting setting) {
+ return new NamedEntityImpl(
+ player,
+ new ViewerText(player.getOwner(), setting.getRawText()),
+ setting.getRefreshFrequency(),
+ setting.getCheckFrequency(),
+ setting.getVerticalOffset(),
+ setting.getOwnerRequirements(),
+ setting.getViewerRequirements()
+ );
+ }
+
+ @Override
+ @SuppressWarnings("DuplicatedCode")
+ public UnlimitedPlayer createTagForPlayer(Player player, List settings) {
+ if (this.unlimitedEntryMap.containsKey(player.getUniqueId())) {
+ return null;
+ }
+
+ var unlimitedPlayer = new UnlimitedPlayer(this, player);
+ this.unlimitedEntryMap.put(
+ player.getUniqueId(),
+ unlimitedPlayer
+ );
+
+ for (UnlimitedTagSetting setting : settings) {
+ unlimitedPlayer.addTag(
+ createNamedEntity(unlimitedPlayer, setting)
+ );
+ }
+
+ for (Player online : Bukkit.getOnlinePlayers()) {
+ if ( online == player
+ || !online.canSee(player)
+ || LocationUtils.getDistance(online, player) > 48
+ || online.getWorld() != player.getWorld()
+ || online.isDead()
+ ) continue;
+ unlimitedPlayer.addNearbyPlayer(online);
+ }
+ return unlimitedPlayer;
+ }
+
+ @Override
+ public UnlimitedObject removeUnlimitedObjectFromMap(UUID uuid) {
+ return unlimitedEntryMap.remove(uuid);
+ }
+
+ @Nullable
+ @Override
+ public UnlimitedObject getUnlimitedObject(UUID uuid) {
+ return unlimitedEntryMap.get(uuid);
+ }
+
+ public void handleEntitySpawnPacket(Player receiver, int entityId) {
+ Player spawned = manager.getPlayerByEntityID(entityId);
+ if (spawned == null) return;
+ UnlimitedObject unlimitedObject = getUnlimitedObject(spawned.getUniqueId());
+ if (unlimitedObject == null) return;
+ unlimitedObject.addNearbyPlayer(receiver);
+ }
+
+ public void handlePlayerPose(Player player, Pose pose) {
+ UnlimitedObject unlimitedObject = getUnlimitedObject(player.getUniqueId());
+ if (unlimitedObject != null) {
+ unlimitedObject.handlePose(player.getPose(), pose);
+ }
+ }
+
+ public void handlePlayerQuit(Player quit) {
+ UnlimitedObject unlimitedObject = getUnlimitedObject(quit.getUniqueId());
+ if (unlimitedObject != null) {
+ unlimitedObject.destroy();
+ }
+ for (UnlimitedObject entry : unlimitedEntryMap.values()) {
+ entry.removeNearbyPlayer(quit);
+ }
+ }
+
+ public void handleEntityMovePacket(Player receiver, int entityID, short x, short y, short z, boolean onGround) {
+ Entity mover = manager.getEntityByEntityID(entityID);
+ if (mover == null) return;
+ UnlimitedObject unlimitedObject = getUnlimitedObject(mover.getUniqueId());
+ if (unlimitedObject == null) return;
+ unlimitedObject.move(receiver, x, y, z, onGround);
+ }
+
+ public void handleEntityTeleportPacket(Player receiver, int entityID, double x, double y, double z, boolean onGround) {
+ Entity tp = manager.getEntityByEntityID(entityID);
+ if (tp == null) return;
+ UnlimitedObject unlimitedObject = getUnlimitedObject(tp.getUniqueId());
+ if (unlimitedObject == null) return;
+ unlimitedObject.teleport(receiver, x, y, z, onGround);
+ }
+
+ public void handleEntityDestroyPacket(Player receiver, List list) {
+ for (int id : list) {
+ handleSingleEntityDestroy(receiver, id);
+ }
+ }
+
+ private void handleSingleEntityDestroy(Player receiver, int entityID) {
+ Entity deSpawned = manager.getEntityByEntityID(entityID);
+ if (deSpawned == null) return;
+ UnlimitedObject unlimitedObject = getUnlimitedObject(deSpawned.getUniqueId());
+ if (unlimitedObject == null) return;
+ unlimitedObject.removeNearbyPlayer(receiver);
+ }
+
+ public void handlePlayerSneak(Player sneaker, boolean sneaking, boolean flying) {
+ UnlimitedObject unlimitedObject = getUnlimitedObject(sneaker.getUniqueId());
+ if (unlimitedObject == null) return;
+ unlimitedObject.sneak(sneaking, flying);
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/pack/ResourcePackManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/pack/ResourcePackManagerImpl.java
new file mode 100644
index 0000000..df14f1b
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/pack/ResourcePackManagerImpl.java
@@ -0,0 +1,285 @@
+package net.momirealms.customnameplates.paper.mechanic.pack;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.ResourcePackManager;
+import net.momirealms.customnameplates.api.mechanic.background.BackGround;
+import net.momirealms.customnameplates.api.mechanic.character.ConfiguredChar;
+import net.momirealms.customnameplates.api.mechanic.font.OffsetFont;
+import net.momirealms.customnameplates.api.mechanic.nameplate.Nameplate;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import org.bukkit.Bukkit;
+import org.codehaus.plexus.util.FileUtils;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class ResourcePackManagerImpl implements ResourcePackManager {
+
+ private final CustomNameplatesPlugin plugin;
+
+ public ResourcePackManagerImpl(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public void reload() {
+ unload();
+ load();
+ }
+
+ public void load() {
+
+ }
+
+ public void unload() {
+
+ }
+
+ @Override
+ public void generateResourcePack() {
+ // delete the old one
+ File resourcePackFolder = new File(plugin.getDataFolder() + File.separator + "ResourcePack");
+ this.deleteDirectory(resourcePackFolder);
+
+ // create folders
+ File fontFolder = new File(plugin.getDataFolder(), "ResourcePack" + File.separator + "assets" + File.separator + CNConfig.namespace + File.separatorChar + "font");
+ File texturesFolder = new File(plugin.getDataFolder(), "ResourcePack" + File.separator+ "assets" + File.separator + CNConfig.namespace + File.separatorChar + "textures");
+ if (!fontFolder.mkdirs() || !texturesFolder.mkdirs()) {
+ LogUtils.severe("Failed to generate resource pack folders");
+ return;
+ }
+
+ // create json object
+ JsonObject fontJson = new JsonObject();
+ JsonArray providers = new JsonArray();
+ fontJson.add("providers", providers);
+
+ // add offset characters
+ this.getOffsets(texturesFolder).forEach(providers::add);
+ // add nameplate characters
+ this.getNameplates(texturesFolder).forEach(providers::add);
+ // add bubble characters
+ this.getBubbles(texturesFolder).forEach(providers::add);
+ // add background characters
+ this.getBackgrounds(texturesFolder).forEach(providers::add);
+ // add image characters
+ this.getImages(texturesFolder).forEach(providers::add);
+ // set pack.mcmeta
+ this.setPackFormat();
+ // save json object to file
+ this.saveFont(fontJson);
+ // copy the resource pack to hooked plugins
+ this.copyResourcePackToHookedPlugins(resourcePackFolder);
+ }
+
+ private void saveFont(JsonObject fontJson) {
+ try (FileWriter fileWriter = new FileWriter(
+ plugin.getDataFolder() +
+ File.separator + "ResourcePack" +
+ File.separator + "assets" +
+ File.separator + CNConfig.namespace +
+ File.separator + "font" +
+ File.separator + CNConfig.font + ".json")
+ ) {
+ fileWriter.write(fontJson.toString().replace("\\\\", "\\"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void deleteDirectory(File file){
+ if (file.exists()) {
+ try {
+ FileUtils.deleteDirectory(file);
+ } catch (IOException e){
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public String native2ascii(char c) {
+ if (c > '\u007f') {
+ StringBuilder stringBuilder_1 = new StringBuilder("\\u");
+ StringBuilder stringBuilder_2 = new StringBuilder(Integer.toHexString(c));
+ 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(c);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private void saveSplit(File texturesFolder) {
+ try {
+ plugin.saveResource("space_split.png", false);
+ FileUtils.copyFile(new File(plugin.getDataFolder(),"space_split.png"), new File(texturesFolder, CNConfig.folderSplit.replace("\\", File.separator) + "space_split.png"));
+ File file = new File(plugin.getDataFolder(),"space_split.png");
+ if (file.exists()) {
+ file.delete();
+ }
+ } catch (IOException e){
+ e.printStackTrace();
+ }
+ }
+
+ private void copyResourcePackToHookedPlugins(File resourcePackFolder) {
+ if (CNConfig.copyPackIA) {
+ try {
+ FileUtils.copyDirectory(new File(resourcePackFolder, "assets"), new File(Objects.requireNonNull(Bukkit.getPluginManager().getPlugin("ItemsAdder")).getDataFolder() + File.separator + "contents" + File.separator + "nameplates" + File.separator + "resourcepack" + File.separator + "assets") );
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (CNConfig.copyPackOraxen) {
+ try {
+ FileUtils.copyDirectory(new File(resourcePackFolder, "assets"), new File(Objects.requireNonNull(Bukkit.getPluginManager().getPlugin("Oraxen")).getDataFolder() + File.separator + "pack" + File.separator + "assets"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private List getNameplates(File texturesFolder) {
+ ArrayList list = new ArrayList<>();
+ if (!CNConfig.nameplateModule) return list;
+ for (Nameplate nameplate : plugin.getNameplateManager().getNameplates()) {
+ for (ConfiguredChar configuredChar : new ConfiguredChar[]{nameplate.getLeft(), nameplate.getMiddle(), nameplate.getRight()}) {
+ JsonObject jo = new JsonObject();
+ jo.add("type", new JsonPrimitive("bitmap"));
+ jo.add("file", new JsonPrimitive(CNConfig.namespace + ":" + CNConfig.folderNameplate.replaceAll("\\\\", "/") + configuredChar.getFile()));
+ jo.add("ascent", new JsonPrimitive(configuredChar.getAscent()));
+ jo.add("height", new JsonPrimitive(configuredChar.getHeight()));
+ JsonArray ja = new JsonArray();
+ ja.add(native2ascii(configuredChar.getCharacter()));
+ jo.add("chars", ja);
+ list.add(jo);
+ list.add(jo);
+ try {
+ FileUtils.copyFile(
+ new File(plugin.getDataFolder(),
+ "contents" + File.separator + "nameplates" + File.separator + configuredChar.getFile()),
+ new File(texturesFolder,
+ CNConfig.folderNameplate.replace("\\", File.separator) + configuredChar.getFile()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return list;
+ }
+
+
+ private List getBackgrounds(File texturesFolder) {
+ ArrayList list = new ArrayList<>();
+ if (!CNConfig.backgroundModule) return list;
+ for (BackGround backGround : plugin.getBackGroundManager().getBackGrounds()) {
+ for (ConfiguredChar configuredChar : new ConfiguredChar[]{
+ backGround.getLeft(), backGround.getOffset_1(),
+ backGround.getOffset_2(), backGround.getOffset_4(),
+ backGround.getOffset_8(), backGround.getOffset_16(),
+ backGround.getOffset_32(), backGround.getOffset_64(),
+ backGround.getOffset_128(), backGround.getRight()}
+ ) {
+ JsonObject jo = new JsonObject();
+ jo.add("type", new JsonPrimitive("bitmap"));
+ jo.add("file", new JsonPrimitive(CNConfig.namespace + ":" + CNConfig.folderNameplate.replaceAll("\\\\", "/") + configuredChar.getFile()));
+ jo.add("ascent", new JsonPrimitive(configuredChar.getAscent()));
+ jo.add("height", new JsonPrimitive(configuredChar.getHeight()));
+ JsonArray ja = new JsonArray();
+ ja.add(native2ascii(configuredChar.getCharacter()));
+ jo.add("chars", ja);
+ list.add(jo);
+ try {
+ FileUtils.copyFile(
+ new File(plugin.getDataFolder(),
+ "contents" + File.separator + "backgrounds" + File.separator + configuredChar.getFile()),
+ new File(texturesFolder,
+ CNConfig.folderBackground.replace("\\", File.separator) + configuredChar.getFile()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return list;
+ }
+
+ private List getBubbles(File texturesFolder) {
+ ArrayList list = new ArrayList<>();
+ if (!CNConfig.bubbleModule) return list;
+ return list;
+ }
+
+ private List getImages(File texturesFolder) {
+ ArrayList list = new ArrayList<>();
+ if (!CNConfig.imageModule) return list;
+ for (ConfiguredChar configuredChar : plugin.getImageManager().getImages()) {
+ JsonObject jo = new JsonObject();
+ jo.add("type", new JsonPrimitive("bitmap"));
+ jo.add("file", new JsonPrimitive(CNConfig.namespace + ":" + CNConfig.folderNameplate.replaceAll("\\\\", "/") + configuredChar.getFile()));
+ jo.add("ascent", new JsonPrimitive(configuredChar.getAscent()));
+ jo.add("height", new JsonPrimitive(configuredChar.getHeight()));
+ JsonArray ja = new JsonArray();
+ ja.add(native2ascii(configuredChar.getCharacter()));
+ jo.add("chars", ja);
+ list.add(jo);
+ list.add(jo);
+ try {
+ FileUtils.copyFile(
+ new File(plugin.getDataFolder(),
+ "contents" + File.separator + "images" + File.separator + configuredChar.getFile()),
+ new File(texturesFolder,
+ CNConfig.folderImage.replace("\\", File.separator) + configuredChar.getFile()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return list;
+ }
+
+ private List getOffsets(File texturesFolder) {
+ this.saveSplit(texturesFolder);
+ ArrayList list = new ArrayList<>();
+ for (OffsetFont offsetFont : OffsetFont.values()) {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.add("type", new JsonPrimitive("bitmap"));
+ jsonObject.add("file", new JsonPrimitive(CNConfig.namespace + ":" + CNConfig.folderSplit.replaceAll("\\\\","/") + "space_split.png"));
+ jsonObject.add("ascent", new JsonPrimitive(-5000));
+ jsonObject.add("height", new JsonPrimitive(offsetFont.getHeight()));
+ final JsonArray jsonArray = new JsonArray();
+ jsonArray.add(native2ascii(offsetFont.getCharacter()));
+ jsonObject.add("chars", jsonArray);
+ list.add(jsonObject);
+ }
+ return list;
+ }
+
+ private void setPackFormat() {
+ plugin.saveResource("ResourcePack" + File.separator + "pack.mcmeta", false);
+ File format_file = new File(plugin.getDataFolder(), "ResourcePack" + File.separator + "pack.mcmeta");
+ String line;
+ StringBuilder sb = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(format_file), StandardCharsets.UTF_8))) {
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append(System.lineSeparator());
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try (BufferedWriter writer = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(new File(plugin.getDataFolder(),
+ "ResourcePack" + File.separator + "pack.mcmeta")), StandardCharsets.UTF_8))) {
+ writer.write(sb.toString().replace("%version%", String.valueOf(plugin.getVersionManager().getPackFormat())));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/placeholder/PlaceholderManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/placeholder/PlaceholderManagerImpl.java
new file mode 100644
index 0000000..deb25d4
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/placeholder/PlaceholderManagerImpl.java
@@ -0,0 +1,254 @@
+package net.momirealms.customnameplates.paper.mechanic.placeholder;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.common.Pair;
+import net.momirealms.customnameplates.api.manager.PlaceholderManager;
+import net.momirealms.customnameplates.api.mechanic.placeholder.*;
+import net.momirealms.customnameplates.api.requirement.Requirement;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PlaceholderManagerImpl implements PlaceholderManager {
+
+ private final Pattern placeholderPattern = Pattern.compile("%([^%]*)%");
+ private final PluginPlaceholders pluginPlaceholders;
+ private final HashMap staticTextMap;
+ private final HashMap switchTextMap;
+ private final HashMap descentTextMap;
+ private final HashMap conditionalTextMap;
+ private final HashMap nameplateTextMap;
+ private final HashMap backGroundTextMap;
+ private final CustomNameplatesPlugin plugin;
+
+ public PlaceholderManagerImpl(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ this.pluginPlaceholders = new PluginPlaceholders(plugin, this);
+ this.staticTextMap = new HashMap<>();
+ this.switchTextMap = new HashMap<>();
+ this.descentTextMap = new HashMap<>();
+ this.conditionalTextMap = new HashMap<>();
+ this.nameplateTextMap = new HashMap<>();
+ this.backGroundTextMap = new HashMap<>();
+ }
+
+ @NotNull
+ @Override
+ public List detectPlaceholders(String text){
+ List placeholders = new ArrayList<>();
+ Matcher matcher = placeholderPattern.matcher(text);
+ while (matcher.find()) placeholders.add(matcher.group());
+ return placeholders;
+ }
+
+ public void reload() {
+ unload();
+ load();
+ }
+
+ public void load() {
+ this.loadConfigs();
+
+ if (!pluginPlaceholders.isRegistered())
+ pluginPlaceholders.register();
+ }
+
+ public void unload() {
+ if (pluginPlaceholders.isRegistered())
+ pluginPlaceholders.unregister();
+
+ this.staticTextMap.clear();
+ this.switchTextMap.clear();
+ this.descentTextMap.clear();
+ this.conditionalTextMap.clear();
+ }
+
+ @Override
+ public StaticText getStaticText(String key) {
+ return staticTextMap.get(key);
+ }
+
+ @Override
+ public SwitchText getSwitchText(String key) {
+ return switchTextMap.get(key);
+ }
+
+ @Override
+ public DescentText getDescentText(String key) {
+ return descentTextMap.get(key);
+ }
+
+ @Override
+ public ConditionalText getConditionalText(String key) {
+ return conditionalTextMap.get(key);
+ }
+
+ @Override
+ public NameplateText getNameplateText(String key) {
+ return nameplateTextMap.get(key);
+ }
+
+ @Override
+ public BackGroundText getBackGroundText(String key) {
+ return backGroundTextMap.get(key);
+ }
+
+ public void loadConfigs() {
+ YamlConfiguration config = plugin.getConfig("configs" + File.separator + "custom-placeholders.yml");
+ ConfigurationSection staticSection = config.getConfigurationSection("static-text");
+ if (staticSection != null) {
+ loadStaticTexts(staticSection);
+ }
+
+ ConfigurationSection switchSection = config.getConfigurationSection("switch-text");
+ if (switchSection != null) {
+ loadSwitchTexts(switchSection);
+ }
+
+ ConfigurationSection descentSection = config.getConfigurationSection("descent-text");
+ if (descentSection != null) {
+ loadDescentTexts(descentSection, false);
+ }
+
+ ConfigurationSection unicodeSection = config.getConfigurationSection("descent-unicode");
+ if (unicodeSection != null) {
+ loadDescentTexts(unicodeSection, true);
+ }
+
+ ConfigurationSection conditionalSection = config.getConfigurationSection("conditional-text");
+ if (conditionalSection != null) {
+ loadConditionalTexts(conditionalSection);
+ }
+
+ ConfigurationSection nameplateSection = config.getConfigurationSection("nameplate-text");
+ if (nameplateSection != null) {
+ loadNameplateTexts(nameplateSection);
+ }
+
+ ConfigurationSection backgroundSection = config.getConfigurationSection("background-text");
+ if (backgroundSection != null) {
+ loadBackGroundTexts(backgroundSection);
+ }
+ }
+
+ private void loadBackGroundTexts(ConfigurationSection section) {
+ for (Map.Entry entry : section.getValues(false).entrySet()) {
+ if (!(entry.getValue() instanceof ConfigurationSection innerSection))
+ continue;
+
+ backGroundTextMap.put(entry.getKey(),
+ BackGroundText.builder()
+ .background(Objects.requireNonNull(plugin.getBackGroundManager().getBackGround(innerSection.getString("background"))))
+ .text(innerSection.getString("text", ""))
+ .build()
+ );
+ }
+ }
+
+ private void loadNameplateTexts(ConfigurationSection section) {
+ for (Map.Entry entry : section.getValues(false).entrySet()) {
+ if (!(entry.getValue() instanceof ConfigurationSection innerSection))
+ continue;
+
+ var nameplate = plugin.getNameplateManager().getNameplate(innerSection.getString("nameplate"));
+ if (nameplate == null) {
+ LogUtils.warn("Nameplate: " + innerSection.getString("nameplate") + " doesn't exist. nameplate-text: " + entry.getKey() + " would not take effect");
+ continue;
+ }
+
+ nameplateTextMap.put(entry.getKey(),
+ NameplateText.builder()
+ .nameplate(nameplate)
+ .text(innerSection.getString("text", ""))
+ .build()
+ );
+ }
+ }
+
+ private void loadDescentTexts(ConfigurationSection section, boolean unicode) {
+ for (Map.Entry entry : section.getValues(false).entrySet()) {
+ if (!(entry.getValue() instanceof ConfigurationSection innerSection))
+ continue;
+
+ descentTextMap.put(
+ entry.getKey(),
+ DescentText.builder()
+ .descent(innerSection.getInt("descent", 0))
+ .text(innerSection.getString("text", ""))
+ .unicode(unicode)
+ .build()
+ );
+ }
+ }
+
+ private void loadStaticTexts(ConfigurationSection section) {
+ for (Map.Entry entry : section.getValues(false).entrySet()) {
+ if (!(entry.getValue() instanceof ConfigurationSection innerSection))
+ continue;
+
+ staticTextMap.put(
+ entry.getKey(),
+ StaticText.builder()
+ .value(innerSection.getInt("value"))
+ .text(innerSection.getString("text"))
+ .state(StaticText.StaticState.valueOf(innerSection.getString("position", "middle").toUpperCase(Locale.ENGLISH)))
+ .build()
+ );
+ }
+ }
+
+ private void loadSwitchTexts(ConfigurationSection section) {
+ for (Map.Entry entry : section.getValues(false).entrySet()) {
+ if (!(entry.getValue() instanceof ConfigurationSection innerSection))
+ continue;
+
+ HashMap valueMap = new HashMap<>();
+ ConfigurationSection valueSection = innerSection.getConfigurationSection("case");
+ if (valueSection != null) {
+ for (String key : valueSection.getKeys(false)) {
+ valueMap.put(key, valueSection.getString(key));
+ }
+ }
+
+ switchTextMap.put(
+ entry.getKey(),
+ SwitchText.builder()
+ .toParse(Objects.requireNonNull(innerSection.getString("switch")))
+ .defaultValue(innerSection.getString("default"))
+ .valueMap(valueMap)
+ .build()
+ );
+ }
+ }
+
+ private void loadConditionalTexts(ConfigurationSection section) {
+ for (Map.Entry entry : section.getValues(false).entrySet()) {
+ if (!(entry.getValue() instanceof ConfigurationSection innerSection))
+ continue;
+
+ ArrayList> list = new ArrayList<>();
+ for (Map.Entry innerEntry : innerSection.getValues(false).entrySet()) {
+ if (!(innerEntry.getValue() instanceof ConfigurationSection prioritySection)) {
+ continue;
+ }
+ list.add(Pair.of(
+ prioritySection.getString("text"),
+ plugin.getRequirementManager().getRequirements(prioritySection.getConfigurationSection("conditions"))
+ ));
+ }
+
+ conditionalTextMap.put(
+ entry.getKey(),
+ ConditionalText.builder()
+ .textList(list)
+ .build()
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/placeholder/PluginPlaceholders.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/placeholder/PluginPlaceholders.java
new file mode 100644
index 0000000..a7270fa
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/placeholder/PluginPlaceholders.java
@@ -0,0 +1,141 @@
+package net.momirealms.customnameplates.paper.mechanic.placeholder;
+
+import me.clip.placeholderapi.expansion.PlaceholderExpansion;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.PlaceholderManager;
+import net.momirealms.customnameplates.api.mechanic.character.ConfiguredChar;
+import net.momirealms.customnameplates.api.mechanic.font.OffsetFont;
+import net.momirealms.customnameplates.api.mechanic.placeholder.*;
+import net.momirealms.customnameplates.api.util.FontUtils;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class PluginPlaceholders extends PlaceholderExpansion {
+
+ private final CustomNameplatesPlugin plugin;
+ private final PlaceholderManager placeholderManager;
+
+ public PluginPlaceholders(CustomNameplatesPlugin plugin, PlaceholderManager placeholderManager) {
+ this.plugin = plugin;
+ this.placeholderManager = placeholderManager;
+ }
+
+ @Override
+ public @NotNull String getIdentifier() {
+ return "nameplates";
+ }
+
+ @Override
+ public @NotNull String getAuthor() {
+ return "XiaoMoMi";
+ }
+
+ @Override
+ public @NotNull String getVersion() {
+ return "2.3";
+ }
+
+ @Override
+ public boolean persist() {
+ return true;
+ }
+
+ @Override
+ public @Nullable String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) {
+ String[] mainSplit = params.split("_", 2);
+ String mainPara = mainSplit[0];
+ String mainArg = mainSplit.length == 1 ? "" : mainSplit[1];
+ switch (mainPara) {
+ case "image" -> {
+ ConfiguredChar configuredChar = plugin.getImageManager().getImage(mainArg);
+ if (configuredChar == null) return "Image not exists";
+ return FontUtils.surroundNameplateFont(String.valueOf(configuredChar.getCharacter()));
+ }
+ case "image-char" -> {
+ ConfiguredChar configuredChar = plugin.getImageManager().getImage(mainArg);
+ if (configuredChar == null) return "Image not exists";
+ return String.valueOf(configuredChar.getCharacter());
+ }
+ case "offset" -> {
+ return FontUtils.surroundNameplateFont(OffsetFont.getOffsetChars(Integer.parseInt(mainArg)));
+ }
+ case "offset-char" -> {
+ return OffsetFont.getOffsetChars(Integer.parseInt(mainArg));
+ }
+ case "checkupdate" -> {
+ return String.valueOf(!plugin.getVersionManager().isLatest());
+ }
+ case "is-latest" -> {
+ return String.valueOf(plugin.getVersionManager().isLatest());
+ }
+ case "static" -> {
+ StaticText text = placeholderManager.getStaticText(mainArg);
+ if (text == null) return "Static text not exists";
+ return text.getValue(offlinePlayer);
+ }
+ case "unicode", "descent" -> {
+ DescentText descentText = placeholderManager.getDescentText(mainArg);
+ if (descentText == null) return "Descent text not exists";
+ return descentText.getValue(offlinePlayer);
+ }
+ case "conditional" -> {
+ ConditionalText conditionalText = placeholderManager.getConditionalText(mainArg);
+ if (conditionalText == null) return "Conditional text not exists";
+ return conditionalText.getValue(offlinePlayer);
+ }
+ case "nameplate" -> {
+ NameplateText nameplateText = placeholderManager.getNameplateText(mainArg);
+ if (nameplateText == null) return "Nameplate text not exists";
+ return nameplateText.getValue(offlinePlayer);
+ }
+ case "background" -> {
+ BackGroundText backGroundText = placeholderManager.getBackGroundText(mainArg);
+ if (backGroundText == null) return "Background text not exists";
+ return backGroundText.getValue(offlinePlayer);
+ }
+ }
+
+ Player onlinePlayer = offlinePlayer.getPlayer();
+ if (onlinePlayer == null) return null;
+ switch (mainPara) {
+ case "time" -> {
+ long time = onlinePlayer.getWorld().getTime();
+ String ap = time >= 6000 && time < 18000 ? " PM" : " AM";
+ int hours = (int) (time / 1000) ;
+ int minutes = (int) ((time - hours * 1000 ) * 0.06);
+ hours += 6;
+ while (hours >= 12) hours -= 12;
+ if (minutes < 10) return hours + ":0" + minutes + ap;
+ else return hours + ":" + minutes + ap;
+ }
+ case "actionbar" -> {
+ return plugin.getActionBarManager().getOtherPluginActionBar(onlinePlayer);
+ }
+ case "prefix" -> {
+ return plugin.getNameplateManager().getNameplatePrefix(onlinePlayer);
+ }
+ case "suffix" -> {
+ return plugin.getNameplateManager().getNameplateSuffix(onlinePlayer);
+ }
+ case "nametag" -> {
+ return plugin.getNameplateManager().getFullNameTag(onlinePlayer);
+ }
+ case "equipped" -> {
+ var optPlayer = plugin.getStorageManager().getOnlineUser(onlinePlayer.getUniqueId());
+ if (optPlayer.isEmpty()) return "Data not loaded";
+ switch (mainArg) {
+ case "nameplate" -> {
+ return optPlayer.get().getNameplateKey();
+ }
+ case "bubble" -> {
+ return optPlayer.get().getBubbleKey();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/net/momirealms/customnameplates/object/requirements/papi/PapiEquals.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/requirement/EmptyRequirement.java
similarity index 58%
rename from src/main/java/net/momirealms/customnameplates/object/requirements/papi/PapiEquals.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/requirement/EmptyRequirement.java
index 257d75b..0a7ea13 100644
--- a/src/main/java/net/momirealms/customnameplates/object/requirements/papi/PapiEquals.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/requirement/EmptyRequirement.java
@@ -15,18 +15,20 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.object.requirements.papi;
+package net.momirealms.customnameplates.paper.mechanic.requirement;
-import me.clip.placeholderapi.PlaceholderAPI;
-import org.bukkit.entity.Player;
+import net.momirealms.customnameplates.api.requirement.Condition;
+import net.momirealms.customnameplates.api.requirement.Requirement;
-import java.util.Objects;
+/**
+ * Represents an empty requirement that always returns true when checking conditions.
+ */
+public class EmptyRequirement implements Requirement {
-public record PapiEquals(String papi, String requirement) implements PapiRequirement{
+ public static EmptyRequirement instance = new EmptyRequirement();
@Override
- public boolean isMet(Player player) {
- String value = PlaceholderAPI.setPlaceholders(player, papi);
- return Objects.equals(value, PlaceholderAPI.setPlaceholders(player, requirement));
+ public boolean isConditionMet(Condition condition) {
+ return true;
}
}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/requirement/RequirementManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/requirement/RequirementManagerImpl.java
new file mode 100644
index 0000000..fd62c6c
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/requirement/RequirementManagerImpl.java
@@ -0,0 +1,706 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.mechanic.requirement;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import net.momirealms.biomeapi.BiomeAPI;
+import net.momirealms.customnameplates.api.common.Pair;
+import net.momirealms.customnameplates.api.manager.RequirementManager;
+import net.momirealms.customnameplates.api.requirement.Requirement;
+import net.momirealms.customnameplates.api.requirement.RequirementExpansion;
+import net.momirealms.customnameplates.api.requirement.RequirementFactory;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import net.momirealms.customnameplates.paper.util.ClassUtils;
+import net.momirealms.customnameplates.paper.util.ConfigUtils;
+import org.bukkit.World;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+
+public class RequirementManagerImpl implements RequirementManager {
+
+ private final CustomNameplatesPluginImpl plugin;
+ private final String EXPANSION_FOLDER = "expansions/requirement";
+ private final HashMap requirementBuilderMap;
+
+ public RequirementManagerImpl(CustomNameplatesPluginImpl plugin) {
+ this.plugin = plugin;
+ this.requirementBuilderMap = new HashMap<>(64);
+ this.registerInbuiltRequirements();
+ }
+
+ public void load() {
+ this.loadExpansions();
+ }
+
+ public void unload() {
+
+ }
+
+ public void reload() {
+ unload();
+ load();
+ }
+
+ public void disable() {
+ this.requirementBuilderMap.clear();
+ }
+
+ /**
+ * Registers a custom requirement type with its corresponding factory.
+ *
+ * @param type The type identifier of the requirement.
+ * @param requirementFactory The factory responsible for creating instances of the requirement.
+ * @return True if registration was successful, false if the type is already registered.
+ */
+ @Override
+ public boolean registerRequirement(String type, RequirementFactory requirementFactory) {
+ if (this.requirementBuilderMap.containsKey(type)) return false;
+ this.requirementBuilderMap.put(type, requirementFactory);
+ return true;
+ }
+
+ /**
+ * Unregisters a custom requirement type.
+ *
+ * @param type The type identifier of the requirement to unregister.
+ * @return True if unregistration was successful, false if the type is not registered.
+ */
+ @Override
+ public boolean unregisterRequirement(String type) {
+ return this.requirementBuilderMap.remove(type) != null;
+ }
+
+ private void registerInbuiltRequirements() {
+ this.registerTimeRequirement();
+ this.registerYRequirement();
+ this.registerContainRequirement();
+ this.registerStartWithRequirement();
+ this.registerEndWithRequirement();
+ this.registerEqualsRequirement();
+ this.registerBiomeRequirement();
+ this.registerDateRequirement();
+ this.registerPermissionRequirement();
+ this.registerWorldRequirement();
+ this.registerWeatherRequirement();
+ this.registerGreaterThanRequirement();
+ this.registerAndRequirement();
+ this.registerOrRequirement();
+ this.registerLevelRequirement();
+ this.registerRandomRequirement();
+ this.registerCoolDownRequirement();
+ this.registerLessThanRequirement();
+ this.registerNumberEqualRequirement();
+ this.registerRegexRequirement();
+ this.registerEnvironmentRequirement();
+ this.registerPotionEffectRequirement();
+ }
+
+ /**
+ * Retrieves an array of requirements based on a configuration section.
+ *
+ * @param section The configuration section containing requirement definitions.
+ * @return An array of Requirement objects based on the configuration section
+ */
+ @NotNull
+ @Override
+ public Requirement[] getRequirements(ConfigurationSection section) {
+ List requirements = new ArrayList<>();
+ if (section == null) {
+ return requirements.toArray(new Requirement[0]);
+ }
+ for (Map.Entry entry : section.getValues(false).entrySet()) {
+ String typeOrName = entry.getKey();
+ if (hasRequirement(typeOrName)) {
+ requirements.add(getRequirement(typeOrName, entry.getValue()));
+ } else {
+ requirements.add(getRequirement(section.getConfigurationSection(typeOrName)));
+ }
+ }
+ return requirements.toArray(new Requirement[0]);
+ }
+
+ public boolean hasRequirement(String type) {
+ return requirementBuilderMap.containsKey(type);
+ }
+
+ /**
+ * Retrieves a Requirement object based on a configuration section and advanced flag.
+ *
+ * @param section The configuration section containing requirement definitions.
+ * @return A Requirement object based on the configuration section, or an EmptyRequirement if the section is null or invalid.
+ */
+ @NotNull
+ @Override
+ public Requirement getRequirement(ConfigurationSection section) {
+ if (section == null) return EmptyRequirement.instance;
+ String type = section.getString("type");
+ if (type == null) {
+ LogUtils.warn("No requirement type found at " + section.getCurrentPath());
+ return EmptyRequirement.instance;
+ }
+ var builder = getRequirementFactory(type);
+ if (builder == null) {
+ return EmptyRequirement.instance;
+ }
+ return builder.build(section.get("value"));
+ }
+
+ /**
+ * Gets a requirement based on the provided key and value.
+ * If a valid RequirementFactory is found for the key, it is used to create the requirement.
+ * If no factory is found, a warning is logged, and an empty requirement instance is returned.
+ *
+ * @param type The key representing the requirement type.
+ * @param value The value associated with the requirement.
+ * @return A Requirement instance based on the key and value, or an empty requirement if not found.
+ */
+ @Override
+ @NotNull
+ public Requirement getRequirement(String type, Object value) {
+ RequirementFactory factory = getRequirementFactory(type);
+ if (factory == null) {
+ LogUtils.warn("Requirement type: " + type + " doesn't exist.");
+ return EmptyRequirement.instance;
+ }
+ return factory.build(value);
+ }
+
+ /**
+ * Retrieves a RequirementFactory based on the specified requirement type.
+ *
+ * @param type The requirement type for which to retrieve a factory.
+ * @return A RequirementFactory for the specified type, or null if no factory is found.
+ */
+ @Override
+ @Nullable
+ public RequirementFactory getRequirementFactory(String type) {
+ return requirementBuilderMap.get(type);
+ }
+
+ private void registerTimeRequirement() {
+ registerRequirement("time", (args) -> {
+ List> timePairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList();
+ return condition -> {
+ long time = Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).getWorld().getTime();
+ for (Pair pair : timePairs)
+ if (time >= pair.left() && time <= pair.right())
+ return true;
+ return false;
+ };
+ });
+ }
+
+ private void registerYRequirement() {
+ registerRequirement("ypos", (args) -> {
+ List> timePairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList();
+ return condition -> {
+ int y = Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).getLocation().getBlockY();
+ for (Pair pair : timePairs)
+ if (y >= pair.left() && y <= pair.right())
+ return true;
+ return false;
+ };
+ });
+ }
+
+ private void registerOrRequirement() {
+ registerRequirement("||", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ Requirement[] requirements = getRequirements(section);
+ return condition -> {
+ for (Requirement requirement : requirements) {
+ if (requirement.isConditionMet(condition)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at || requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerAndRequirement() {
+ registerRequirement("&&", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ Requirement[] requirements = getRequirements(section);
+ return condition -> {
+ outer: {
+ for (Requirement requirement : requirements) {
+ if (!requirement.isConditionMet(condition)) {
+ break outer;
+ }
+ }
+ return true;
+ }
+ return false;
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at && requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerLevelRequirement() {
+ registerRequirement("level", (args) -> {
+ int level = (int) args;
+ return condition -> {
+ int current = Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).getLevel();
+ return current >= level;
+ };
+ });
+ }
+
+ private void registerRandomRequirement() {
+ registerRequirement("random", (args) -> {
+ double random = ConfigUtils.getDoubleValue(args);
+ return condition -> Math.random() < random;
+ });
+ }
+
+ private void registerBiomeRequirement() {
+ registerRequirement("biome", (args) -> {
+ HashSet biomes = new HashSet<>(ConfigUtils.stringListArgs(args));
+ return condition -> {
+ String currentBiome = BiomeAPI.getBiome(Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).getLocation());
+ return biomes.contains(currentBiome);
+ };
+ });
+ registerRequirement("!biome", (args) -> {
+ HashSet biomes = new HashSet<>(ConfigUtils.stringListArgs(args));
+ return condition -> {
+ String currentBiome = BiomeAPI.getBiome(Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).getLocation());
+ return !biomes.contains(currentBiome);
+ };
+ });
+ }
+
+ private void registerWorldRequirement() {
+ registerRequirement("world", (args) -> {
+ HashSet worlds = new HashSet<>(ConfigUtils.stringListArgs(args));
+ return condition -> worlds.contains(Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).getWorld().getName());
+ });
+ registerRequirement("!world", (args) -> {
+ HashSet worlds = new HashSet<>(ConfigUtils.stringListArgs(args));
+ return condition -> !worlds.contains(Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).getWorld().getName());
+ });
+ }
+
+ private void registerWeatherRequirement() {
+ registerRequirement("weather", (args) -> {
+ List weathers = ConfigUtils.stringListArgs(args);
+ return condition -> {
+ String currentWeather;
+ World world = Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).getWorld();
+ if (world.isThundering()) currentWeather = "thunder";
+ else if (world.isClearWeather()) currentWeather = "clear";
+ else currentWeather = "rain";
+ for (String weather : weathers)
+ if (weather.equalsIgnoreCase(currentWeather))
+ return true;
+ return false;
+ };
+ });
+ }
+
+ private void registerCoolDownRequirement() {
+ registerRequirement("cooldown", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String key = section.getString("key");
+ int time = section.getInt("time");
+ return condition -> !plugin.getCoolDownManager().isCoolDown(condition.getOfflinePlayer().getUniqueId(), key, time);
+ } else {
+ LogUtils.warn("Wrong value format found at cooldown requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerDateRequirement() {
+ registerRequirement("date", (args) -> {
+ HashSet dates = new HashSet<>(ConfigUtils.stringListArgs(args));
+ return condition -> {
+ Calendar calendar = Calendar.getInstance();
+ String current = (calendar.get(Calendar.MONTH) + 1) + "/" + calendar.get(Calendar.DATE);
+ return dates.contains(current);
+ };
+ });
+ }
+
+ private void registerPermissionRequirement() {
+ registerRequirement("permission", (args) -> {
+ List perms = ConfigUtils.stringListArgs(args);
+ return condition -> {
+ for (String perm : perms)
+ if (Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).hasPermission(perm))
+ return true;
+ return false;
+ };
+ });
+ registerRequirement("!permission", (args) -> {
+ List perms = ConfigUtils.stringListArgs(args);
+ return condition -> {
+ for (String perm : perms)
+ if (Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).hasPermission(perm))
+ return false;
+ return true;
+ };
+ });
+ }
+
+ @SuppressWarnings("DuplicatedCode")
+ private void registerGreaterThanRequirement() {
+ registerRequirement(">=", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return Double.parseDouble(p1) >= Double.parseDouble(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at >= requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ registerRequirement(">", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return Double.parseDouble(p1) > Double.parseDouble(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at > requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerRegexRequirement() {
+ registerRequirement("regex", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("papi", "");
+ String v2 = section.getString("regex", "");
+ return condition -> PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1).matches(v2);
+ } else {
+ LogUtils.warn("Wrong value format found at regex requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerNumberEqualRequirement() {
+ registerRequirement("==", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return Double.parseDouble(p1) == Double.parseDouble(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at !startsWith requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ registerRequirement("!=", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return Double.parseDouble(p1) != Double.parseDouble(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at !startsWith requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ @SuppressWarnings("DuplicatedCode")
+ private void registerLessThanRequirement() {
+ registerRequirement("<", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return Double.parseDouble(p1) < Double.parseDouble(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at < requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ registerRequirement("<=", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return Double.parseDouble(p1) <= Double.parseDouble(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at <= requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerStartWithRequirement() {
+ registerRequirement("startsWith", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return p1.startsWith(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at startsWith requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ registerRequirement("!startsWith", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return !p1.startsWith(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at !startsWith requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerEndWithRequirement() {
+ registerRequirement("endsWith", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return p1.endsWith(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at endsWith requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ registerRequirement("!endsWith", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return !p1.endsWith(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at !endsWith requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerContainRequirement() {
+ registerRequirement("contains", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return p1.contains(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at contains requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ registerRequirement("!contains", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return !p1.contains(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at !contains requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerEqualsRequirement() {
+ registerRequirement("equals", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return p1.equals(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at equals requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ registerRequirement("!equals", (args) -> {
+ if (args instanceof ConfigurationSection section) {
+ String v1 = section.getString("value1", "");
+ String v2 = section.getString("value2", "");
+ return condition -> {
+ String p1 = v1.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v1) : v1;
+ String p2 = v2.startsWith("%") ? PlaceholderAPI.setPlaceholders(condition.getOfflinePlayer(), v2) : v2;
+ return !p1.equals(p2);
+ };
+ } else {
+ LogUtils.warn("Wrong value format found at !equals requirement.");
+ return EmptyRequirement.instance;
+ }
+ });
+ }
+
+ private void registerEnvironmentRequirement() {
+ registerRequirement("environment", (args) -> {
+ List environments = ConfigUtils.stringListArgs(args);
+ return condition -> {
+ var name = condition.getOfflinePlayer().getPlayer().getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH);
+ return environments.contains(name);
+ };
+ });
+ registerRequirement("!environment", (args) -> {
+ List environments = ConfigUtils.stringListArgs(args);
+ return condition -> {
+ var name = condition.getOfflinePlayer().getPlayer().getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH);
+ return !environments.contains(name);
+ };
+ });
+ }
+
+ private void registerPotionEffectRequirement() {
+ registerRequirement("potion-effect", (args) -> {
+ String potions = (String) args;
+ String[] split = potions.split("(<=|>=|<|>|==)", 2);
+ PotionEffectType type = PotionEffectType.getByName(split[0]);
+ if (type == null) {
+ LogUtils.warn("Potion effect doesn't exist: " + split[0]);
+ return EmptyRequirement.instance;
+ }
+ int required = Integer.parseInt(split[1]);
+ String operator = potions.substring(split[0].length(), potions.length() - split[1].length());
+ return condition -> {
+ int level = -1;
+ PotionEffect potionEffect = Objects.requireNonNull(condition.getOfflinePlayer().getPlayer()).getPotionEffect(type);
+ if (potionEffect != null) {
+ level = potionEffect.getAmplifier();
+ }
+ boolean result = false;
+ switch (operator) {
+ case ">=" -> {
+ if (level >= required) result = true;
+ }
+ case ">" -> {
+ if (level > required) result = true;
+ }
+ case "==" -> {
+ if (level == required) result = true;
+ }
+ case "!=" -> {
+ if (level != required) result = true;
+ }
+ case "<=" -> {
+ if (level <= required) result = true;
+ }
+ case "<" -> {
+ if (level < required) result = true;
+ }
+ }
+ return result;
+ };
+ });
+ }
+
+ /**
+ * Loads requirement expansions from external JAR files located in the expansion folder.
+ * Each expansion JAR should contain classes that extends the RequirementExpansion class.
+ * Expansions are registered and used to create custom requirements.
+ * If an error occurs while loading or initializing an expansion, a warning message is logged.
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private void loadExpansions() {
+ File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER);
+ if (!expansionFolder.exists())
+ expansionFolder.mkdirs();
+
+ List> classes = new ArrayList<>();
+ File[] expansionJars = expansionFolder.listFiles();
+ if (expansionJars == null) return;
+ for (File expansionJar : expansionJars) {
+ if (expansionJar.getName().endsWith(".jar")) {
+ try {
+ Class extends RequirementExpansion> expansionClass = ClassUtils.findClass(expansionJar, RequirementExpansion.class);
+ classes.add(expansionClass);
+ } catch (IOException | ClassNotFoundException e) {
+ LogUtils.warn("Failed to load expansion: " + expansionJar.getName(), e);
+ }
+ }
+ }
+ try {
+ for (Class extends RequirementExpansion> expansionClass : classes) {
+ RequirementExpansion expansion = expansionClass.getDeclaredConstructor().newInstance();
+ unregisterRequirement(expansion.getRequirementType());
+ registerRequirement(expansion.getRequirementType(), expansion.getRequirementFactory());
+ LogUtils.info("Loaded requirement expansion: " + expansion.getRequirementType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor());
+ }
+ } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
+ LogUtils.warn("Error occurred when creating expansion instance.", e);
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/Team.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/Team.java
new file mode 100644
index 0000000..9edc622
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/Team.java
@@ -0,0 +1,6 @@
+package net.momirealms.customnameplates.paper.mechanic.team;
+
+public interface Team {
+
+
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/TeamManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/TeamManagerImpl.java
new file mode 100644
index 0000000..a0179d0
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/TeamManagerImpl.java
@@ -0,0 +1,175 @@
+package net.momirealms.customnameplates.paper.mechanic.team;
+
+import com.comphenix.protocol.events.PacketContainer;
+import com.google.common.io.ByteArrayDataInput;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import net.kyori.adventure.text.Component;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.manager.TeamManager;
+import net.momirealms.customnameplates.api.mechanic.team.*;
+import net.momirealms.customnameplates.paper.mechanic.misc.PacketManager;
+import net.momirealms.customnameplates.paper.mechanic.team.packet.TeamPacketAdaptor;
+import net.momirealms.customnameplates.paper.mechanic.team.packet.TeamPacket_1_17;
+import net.momirealms.customnameplates.paper.mechanic.team.provider.CMIProvider;
+import net.momirealms.customnameplates.paper.mechanic.team.provider.DefaultProvider;
+import net.momirealms.customnameplates.paper.mechanic.team.provider.TABProvider;
+import net.momirealms.customnameplates.paper.mechanic.team.provider.TeamProvider;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.messaging.PluginMessageListener;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.Objects;
+
+public class TeamManagerImpl implements TeamManager, PluginMessageListener {
+
+ private final CustomNameplatesPlugin plugin;
+ private final TeamPacketAdaptor teamPacketAdaptor;
+ private final TeamProvider teamProvider;
+ private static final String CHANNEL = "customnameplates:cnp";
+
+ public TeamManagerImpl(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ this.teamPacketAdaptor = new TeamPacket_1_17();
+ if (CNConfig.tabTeam) {
+ teamProvider = new TABProvider();
+ } else if (CNConfig.cmiTeam) {
+ teamProvider = new CMIProvider();
+ } else {
+ teamProvider = new DefaultProvider();
+ }
+ }
+
+ @Override
+ public void createTeam(Player player) {
+ if (CNConfig.isOtherTeamPluginHooked()) {
+ return;
+ }
+ PacketContainer createOwner = teamPacketAdaptor.getTeamCreatePacket(
+ TeamCreatePacket.builder()
+ .teamName(teamProvider.getTeam(player))
+ .color(TeamColor.WHITE)
+ .prefix(Component.text(""))
+ .suffix(Component.text(""))
+ .members(Collections.singletonList(player.getName()))
+ .build()
+ );
+ for (Player online : Bukkit.getOnlinePlayers()) {
+ PacketManager.getInstance().send(online, createOwner);
+ if (online == player) continue;
+ PacketContainer createOther = teamPacketAdaptor.getTeamCreatePacket(
+ TeamCreatePacket.builder()
+ .teamName(teamProvider.getTeam(online))
+ .color(TeamColor.WHITE)
+ .prefix(Component.text(""))
+ .suffix(Component.text(""))
+ .members(Collections.singletonList(online.getName()))
+ .build()
+ );
+ PacketManager.getInstance().send(player, createOther);
+ }
+ }
+
+ @Override
+ public void createProxyTeam(Player player) {
+
+ }
+
+ @Override
+ public void removeTeam(Player player) {
+ if (CNConfig.isOtherTeamPluginHooked()) return;
+ PacketContainer packet = teamPacketAdaptor.getTeamRemovePacket(
+ TeamRemovePacket.builder()
+ .teamName(teamProvider.getTeam(player))
+ .build()
+ );
+ for (Player online : Bukkit.getOnlinePlayers()) {
+ if (player == online) continue;
+ PacketManager.getInstance().send(online, packet);
+ }
+ }
+
+ @Override
+ public void removeProxyTeam(Player player) {
+ this.sendPluginMessage(MessageType.REMOVE,
+ player.getName()
+ );
+ }
+
+ @Override
+ public void updateTeam(Player owner, Player viewer, Component prefix, Component suffix, TeamColor color, TeamTagVisibility visibility) {
+ if (color == TeamColor.NONE || color == TeamColor.CUSTOM)
+ color = TeamColor.WHITE;
+ PacketContainer packet = teamPacketAdaptor.getTeamUpdatePacket(
+ TeamUpdatePacket.builder()
+ .teamName(teamProvider.getTeam(owner))
+ .color(color)
+ .prefix(prefix)
+ .suffix(suffix)
+ .tagVisibility(visibility)
+ .build()
+ );
+ PacketManager.getInstance().send(viewer, packet);
+ }
+
+ public void reload() {
+ unload();
+ load();
+ }
+
+ public void unload() {
+ Bukkit.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL);
+ Bukkit.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CHANNEL);
+ }
+
+ public void load() {
+ Bukkit.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CHANNEL);
+ Bukkit.getServer().getMessenger().registerIncomingPluginChannel(plugin, CHANNEL, this);
+ }
+
+ @Override
+ public String getTeamName(Player player) {
+ return null;
+ }
+
+ private void handleMessage(String... message) {
+
+ }
+
+ @Override
+ @SuppressWarnings("UnstableApiUsage")
+ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
+ if (!Objects.equals(CHANNEL, channel)) {
+ return;
+ }
+ ByteArrayDataInput dataInput = ByteStreams.newDataInput(message);
+ byte args = dataInput.readByte();
+ String[] messages = new String[args];
+ for (int i = 0; i < args; i++) {
+ messages[i] = dataInput.readUTF();
+ }
+ handleMessage(messages);
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ public void sendPluginMessage(String... messages) {
+ ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
+ dataOutput.writeByte(messages.length);
+ for (String message : messages) {
+ dataOutput.writeUTF(message);
+ }
+ Bukkit.getOnlinePlayers().stream().findAny().ifPresent(player -> {
+ player.sendPluginMessage(plugin, CHANNEL, dataOutput.toByteArray());
+ });
+ }
+
+ public static class MessageType {
+
+ public static final String CREATE = "create";
+ public static final String REMOVE = "remove";
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/packet/TeamPacketAdaptor.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/packet/TeamPacketAdaptor.java
new file mode 100644
index 0000000..8e38a1b
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/packet/TeamPacketAdaptor.java
@@ -0,0 +1,15 @@
+package net.momirealms.customnameplates.paper.mechanic.team.packet;
+
+import com.comphenix.protocol.events.PacketContainer;
+import net.momirealms.customnameplates.api.mechanic.team.TeamCreatePacket;
+import net.momirealms.customnameplates.api.mechanic.team.TeamRemovePacket;
+import net.momirealms.customnameplates.api.mechanic.team.TeamUpdatePacket;
+
+public interface TeamPacketAdaptor {
+
+ PacketContainer getTeamCreatePacket(TeamCreatePacket teamCreatePacket);
+
+ PacketContainer getTeamUpdatePacket(TeamUpdatePacket teamUpdatePacket);
+
+ PacketContainer getTeamRemovePacket(TeamRemovePacket teamRemovePacket);
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/packet/TeamPacket_1_17.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/packet/TeamPacket_1_17.java
new file mode 100644
index 0000000..6ea3e38
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/packet/TeamPacket_1_17.java
@@ -0,0 +1,69 @@
+package net.momirealms.customnameplates.paper.mechanic.team.packet;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.InternalStructure;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.utility.MinecraftReflection;
+import com.comphenix.protocol.wrappers.WrappedChatComponent;
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.momirealms.customnameplates.api.mechanic.team.TeamColor;
+import net.momirealms.customnameplates.api.mechanic.team.TeamCreatePacket;
+import net.momirealms.customnameplates.api.mechanic.team.TeamRemovePacket;
+import net.momirealms.customnameplates.api.mechanic.team.TeamUpdatePacket;
+
+import java.util.Optional;
+
+@SuppressWarnings("DuplicatedCode")
+public class TeamPacket_1_17 implements TeamPacketAdaptor {
+
+ @Override
+ public PacketContainer getTeamCreatePacket(TeamCreatePacket teamCreatePacket) {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM);
+ // 0 = create team
+ packet.getModifier().write(0,0);
+ packet.getModifier().write(1, teamCreatePacket.getTeamName());
+ packet.getModifier().write(2, teamCreatePacket.getMembers());
+ Optional optionalInternalStructure = packet.getOptionalStructures().read(0);
+ if (optionalInternalStructure.isPresent()) {
+ InternalStructure is = optionalInternalStructure.get();
+ // Team
+ is.getChatComponents().write(0, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(teamCreatePacket.getTeamDisplay())));
+ is.getChatComponents().write(1, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(teamCreatePacket.getTeamPrefix())));
+ is.getChatComponents().write(2, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(teamCreatePacket.getTeamSuffix())));
+ is.getModifier().write(3, teamCreatePacket.getTagVisibility().getId());
+ is.getModifier().write(4, teamCreatePacket.getCollisionRule().getId());
+ is.getEnumModifier(TeamColor.class, MinecraftReflection.getMinecraftClass("EnumChatFormat")).write(0, teamCreatePacket.getTeamColor());
+ is.getModifier().write(6, teamCreatePacket.getMembers().size());
+ }
+ return packet;
+ }
+
+ @Override
+ public PacketContainer getTeamRemovePacket(TeamRemovePacket teamRemovePacket) {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM);
+ // 1 = remove team
+ packet.getModifier().write(0,1);
+ packet.getStrings().write(0, teamRemovePacket.getTeamName());
+ return packet;
+ }
+
+ @Override
+ public PacketContainer getTeamUpdatePacket(TeamUpdatePacket teamUpdatePacket) {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM);
+ // 2 = update team
+ packet.getModifier().write(0,2);
+ packet.getModifier().write(1, teamUpdatePacket.getTeamName());
+ Optional optionalInternalStructure = packet.getOptionalStructures().read(0);
+ if (optionalInternalStructure.isPresent()) {
+ InternalStructure is = optionalInternalStructure.get();
+ // Team
+ is.getChatComponents().write(0, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(teamUpdatePacket.getTeamDisplay())));
+ is.getChatComponents().write(1, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(teamUpdatePacket.getTeamPrefix())));
+ is.getChatComponents().write(2, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(teamUpdatePacket.getTeamSuffix())));
+ is.getModifier().write(3, teamUpdatePacket.getTagVisibility().getId());
+ is.getModifier().write(4, teamUpdatePacket.getCollisionRule().getId());
+ is.getEnumModifier(TeamColor.class, MinecraftReflection.getMinecraftClass("EnumChatFormat")).write(0, teamUpdatePacket.getTeamColor());
+ }
+ return packet;
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/CMIProvider.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/CMIProvider.java
new file mode 100644
index 0000000..fbadf01
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/CMIProvider.java
@@ -0,0 +1,15 @@
+package net.momirealms.customnameplates.paper.mechanic.team.provider;
+
+import com.Zrips.CMI.CMI;
+import org.bukkit.entity.Player;
+import org.bukkit.scoreboard.Team;
+
+public class CMIProvider implements TeamProvider {
+
+ @Override
+ public String getTeam(Player player) {
+ Team team = CMI.getInstance().getSB().getPlayerTeam(player);
+ if (team == null) return player.getName();
+ return team.getName();
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/DefaultProvider.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/DefaultProvider.java
new file mode 100644
index 0000000..1823509
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/DefaultProvider.java
@@ -0,0 +1,11 @@
+package net.momirealms.customnameplates.paper.mechanic.team.provider;
+
+import org.bukkit.entity.Player;
+
+public class DefaultProvider implements TeamProvider {
+
+ @Override
+ public String getTeam(Player player) {
+ return player.getName();
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/TABProvider.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/TABProvider.java
new file mode 100644
index 0000000..9619e52
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/TABProvider.java
@@ -0,0 +1,26 @@
+package net.momirealms.customnameplates.paper.mechanic.team.provider;
+
+import me.neznamy.tab.api.TabAPI;
+import me.neznamy.tab.api.TabPlayer;
+import me.neznamy.tab.api.tablist.SortingManager;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import org.bukkit.entity.Player;
+
+public class TABProvider implements TeamProvider {
+
+ private final SortingManager sortingManager;
+
+ public TABProvider() {
+ this.sortingManager = TabAPI.getInstance().getSortingManager();
+ if (sortingManager == null) {
+ LogUtils.warn("Detected that team management is disabled in TAB. Using player name as team name.");
+ }
+ }
+
+ @Override
+ public String getTeam(Player player) {
+ TabPlayer tabPlayer = TabAPI.getInstance().getPlayer(player.getUniqueId());
+ if (tabPlayer == null || sortingManager == null) return player.getName();
+ return sortingManager.getOriginalTeamName(tabPlayer);
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/TeamProvider.java b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/TeamProvider.java
new file mode 100644
index 0000000..fe14d56
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/mechanic/team/provider/TeamProvider.java
@@ -0,0 +1,8 @@
+package net.momirealms.customnameplates.paper.mechanic.team.provider;
+
+import org.bukkit.entity.Player;
+
+public interface TeamProvider {
+
+ String getTeam(Player player);
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/BukkitSchedulerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/BukkitSchedulerImpl.java
new file mode 100644
index 0000000..bce03bf
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/BukkitSchedulerImpl.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.scheduler;
+
+import net.momirealms.customnameplates.api.scheduler.CancellableTask;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.scheduler.BukkitTask;
+
+/**
+ * A scheduler implementation for synchronous tasks using Bukkit's Scheduler.
+ */
+public class BukkitSchedulerImpl implements SyncScheduler {
+
+ private final CustomNameplatesPluginImpl plugin;
+
+ public BukkitSchedulerImpl(CustomNameplatesPluginImpl plugin) {
+ this.plugin = plugin;
+ }
+
+ /**
+ * Runs a synchronous task on the main server thread using Bukkit's Scheduler.
+ * If already on the main thread, the task is executed immediately.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ */
+ @Override
+ public void runSyncTask(Runnable runnable, Location location) {
+ if (Bukkit.isPrimaryThread())
+ runnable.run();
+ else
+ Bukkit.getScheduler().runTask(plugin, runnable);
+ }
+
+ /**
+ * Runs a synchronous task repeatedly with a specified delay and period using Bukkit's Scheduler.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delay The delay in ticks before the first execution.
+ * @param period The period between subsequent executions in ticks.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ @Override
+ public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) {
+ return new BukkitCancellableTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period));
+ }
+
+ /**
+ * Runs a synchronous task with a specified delay using Bukkit's Scheduler.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delay The delay in ticks before the task execution.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ @Override
+ public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) {
+ return new BukkitCancellableTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay));
+ }
+
+ /**
+ * Represents a scheduled task using Bukkit's Scheduler that can be cancelled.
+ */
+ public static class BukkitCancellableTask implements CancellableTask {
+
+ private final BukkitTask bukkitTask;
+
+ public BukkitCancellableTask(BukkitTask bukkitTask) {
+ this.bukkitTask = bukkitTask;
+ }
+
+ @Override
+ public void cancel() {
+ this.bukkitTask.cancel();
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.bukkitTask.isCancelled();
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/FoliaSchedulerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/FoliaSchedulerImpl.java
new file mode 100644
index 0000000..15946dd
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/FoliaSchedulerImpl.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.scheduler;
+
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
+import net.momirealms.customnameplates.api.scheduler.CancellableTask;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+
+/**
+ * A scheduler implementation for "synchronous" tasks using Folia's RegionScheduler.
+ */
+public class FoliaSchedulerImpl implements SyncScheduler {
+
+ private final CustomNameplatesPluginImpl plugin;
+
+ public FoliaSchedulerImpl(CustomNameplatesPluginImpl plugin) {
+ this.plugin = plugin;
+ }
+
+ /**
+ * Runs a "synchronous" task on the region thread using Folia's RegionScheduler.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ */
+ @Override
+ public void runSyncTask(Runnable runnable, Location location) {
+ Bukkit.getRegionScheduler().execute(plugin, location, runnable);
+ }
+
+ /**
+ * Runs a "synchronous" task repeatedly with a specified delay and period using Folia's RegionScheduler.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delay The delay in ticks before the first execution.
+ * @param period The period between subsequent executions in ticks.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ @Override
+ public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) {
+ return new FoliaCancellableTask(Bukkit.getRegionScheduler().runAtFixedRate(plugin, location, (scheduledTask -> runnable.run()), delay, period));
+ }
+
+ /**
+ * Runs a "synchronous" task with a specified delay using Folia's RegionScheduler.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delay The delay in ticks before the task execution.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ @Override
+ public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) {
+ return new FoliaCancellableTask(Bukkit.getRegionScheduler().runDelayed(plugin, location, (scheduledTask -> runnable.run()), delay));
+ }
+
+ /**
+ * Represents a scheduled task using Folia's RegionScheduler that can be cancelled.
+ */
+ public static class FoliaCancellableTask implements CancellableTask {
+
+ private final ScheduledTask scheduledTask;
+
+ public FoliaCancellableTask(ScheduledTask scheduledTask) {
+ this.scheduledTask = scheduledTask;
+ }
+
+ @Override
+ public void cancel() {
+ this.scheduledTask.cancel();
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.scheduledTask.isCancelled();
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SchedulerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SchedulerImpl.java
new file mode 100644
index 0000000..77a849a
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SchedulerImpl.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.scheduler;
+
+import net.momirealms.customnameplates.api.scheduler.CancellableTask;
+import net.momirealms.customnameplates.api.scheduler.Scheduler;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import net.momirealms.customnameplates.paper.setting.CNConfig;
+import org.bukkit.Location;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A scheduler implementation responsible for scheduling and managing tasks in a multi-threaded environment.
+ */
+public class SchedulerImpl implements Scheduler {
+
+ private final SyncScheduler syncScheduler;
+ private final ScheduledThreadPoolExecutor schedule;
+ private final CustomNameplatesPluginImpl plugin;
+
+ public SchedulerImpl(CustomNameplatesPluginImpl plugin) {
+ this.plugin = plugin;
+ this.syncScheduler = plugin.getVersionManager().isFolia() ?
+ new FoliaSchedulerImpl(plugin) : new BukkitSchedulerImpl(plugin);
+ this.schedule = new ScheduledThreadPoolExecutor(1);
+ this.schedule.setMaximumPoolSize(1);
+ this.schedule.setKeepAliveTime(30, TimeUnit.SECONDS);
+ this.schedule.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
+ }
+
+ /**
+ * Reloads the scheduler configuration based on CustomFishingPlugin settings.
+ */
+ public void reload() {
+ try {
+ this.schedule.setMaximumPoolSize(CNConfig.maximumPoolSize);
+ this.schedule.setCorePoolSize(CNConfig.corePoolSize);
+ this.schedule.setKeepAliveTime(CNConfig.keepAliveTime, TimeUnit.SECONDS);
+ } catch (IllegalArgumentException e) {
+ plugin.getLogger().severe("Failed to create thread pool. Please lower the corePoolSize in config.yml.");
+ }
+ }
+
+ /**
+ * Shuts down the scheduler.
+ */
+ public void shutdown() {
+ if (this.schedule != null && !this.schedule.isShutdown())
+ this.schedule.shutdown();
+ }
+
+ /**
+ * Runs a task synchronously on the main server thread or region thread.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ */
+ @Override
+ public void runTaskSync(Runnable runnable, Location location) {
+ this.syncScheduler.runSyncTask(runnable, location);
+ }
+
+ /**
+ * Runs a task asynchronously.
+ *
+ * @param runnable The task to run.
+ */
+ @Override
+ public void runTaskAsync(Runnable runnable) {
+ this.schedule.execute(runnable);
+ }
+
+ /**
+ * Runs a task synchronously with a specified delay and period.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delayTicks The delay in ticks before the first execution.
+ * @param periodTicks The period between subsequent executions in ticks.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ @Override
+ public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks) {
+ return this.syncScheduler.runTaskSyncTimer(runnable, location, delayTicks, periodTicks);
+ }
+
+ /**
+ * Runs a task asynchronously with a specified delay.
+ *
+ * @param runnable The task to run.
+ * @param delay The delay before the task execution.
+ * @param timeUnit The time unit for the delay.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ @Override
+ public CancellableTask runTaskAsyncLater(Runnable runnable, long delay, TimeUnit timeUnit) {
+ return new ScheduledTask(schedule.schedule(runnable, delay, timeUnit));
+ }
+
+ /**
+ * Runs a task synchronously with a specified delay.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delay The delay before the task execution.
+ * @param timeUnit The time unit for the delay.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ @Override
+ public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay, TimeUnit timeUnit) {
+ return new ScheduledTask(schedule.schedule(() -> {
+ runTaskSync(runnable, location);
+ }, delay, timeUnit));
+ }
+
+ /**
+ * Runs a task synchronously with a specified delay in ticks.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delayTicks The delay in ticks before the task execution.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ @Override
+ public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks) {
+ return this.syncScheduler.runTaskSyncLater(runnable, location, delayTicks);
+ }
+
+ /**
+ * Runs a task asynchronously with a specified delay and period.
+ *
+ * @param runnable The task to run.
+ * @param delay The delay before the first execution.
+ * @param period The period between subsequent executions.
+ * @param timeUnit The time unit for the delay and period.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ @Override
+ public CancellableTask runTaskAsyncTimer(Runnable runnable, long delay, long period, TimeUnit timeUnit) {
+ return new ScheduledTask(schedule.scheduleAtFixedRate(runnable, delay, period, timeUnit));
+ }
+
+ /**
+ * Represents a thread-pool task that can be cancelled.
+ */
+ public static class ScheduledTask implements CancellableTask {
+
+ private final ScheduledFuture> scheduledFuture;
+
+ public ScheduledTask(ScheduledFuture> scheduledFuture) {
+ this.scheduledFuture = scheduledFuture;
+ }
+
+ @Override
+ public void cancel() {
+ this.scheduledFuture.cancel(false);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.scheduledFuture.isCancelled();
+ }
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SyncScheduler.java b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SyncScheduler.java
new file mode 100644
index 0000000..b6a5864
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/scheduler/SyncScheduler.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.scheduler;
+
+import net.momirealms.customnameplates.api.scheduler.CancellableTask;
+import org.bukkit.Location;
+
+public interface SyncScheduler {
+
+ /**
+ * Runs a task synchronously on the main server thread or region thread.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ */
+ void runSyncTask(Runnable runnable, Location location);
+
+ /**
+ * Runs a task synchronously with a specified delay and period.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delayTicks The delay in ticks before the first execution.
+ * @param periodTicks The period between subsequent executions in ticks.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks);
+
+ /**
+ * Runs a task synchronously with a specified delay in ticks.
+ *
+ * @param runnable The task to run.
+ * @param location The location associated with the task.
+ * @param delayTicks The delay in ticks before the task execution.
+ * @return A CancellableTask for managing the scheduled task.
+ */
+ CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks);
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/setting/CNConfig.java b/paper/src/main/java/net/momirealms/customnameplates/paper/setting/CNConfig.java
new file mode 100644
index 0000000..e3f794e
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/setting/CNConfig.java
@@ -0,0 +1,132 @@
+package net.momirealms.customnameplates.paper.setting;
+
+import dev.dejvokep.boostedyaml.YamlDocument;
+import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning;
+import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings;
+import dev.dejvokep.boostedyaml.settings.general.GeneralSettings;
+import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings;
+import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.mechanic.character.CharacterArranger;
+import net.momirealms.customnameplates.api.util.FontUtils;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Objects;
+
+public class CNConfig {
+
+ public static String configVersion = "22";
+ public static int corePoolSize;
+ public static long keepAliveTime;
+ public static int maximumPoolSize;
+ public static boolean debug;
+ public static String language;
+ public static boolean updateChecker;
+ public static boolean metrics;
+ public static boolean legacyColorSupport;
+ public static boolean generatePackOnStart;
+ public static int sendDelay;
+ public static String namespace;
+ public static String font;
+ public static char initChar;
+ public static boolean copyPackIA;
+ public static boolean copyPackOraxen;
+ public static boolean trChatChannel;
+ public static boolean ventureChatChannel;
+ public static boolean nameplateModule;
+ public static boolean bossBarModule;
+ public static boolean actionBarModule;
+ public static boolean bubbleModule;
+ public static boolean backgroundModule;
+ public static boolean imageModule;
+ public static boolean tabTeam;
+ public static boolean cmiTeam;
+ public static String folderNameplate;
+ public static String folderImage;
+ public static String folderBubble;
+ public static String folderBackground;
+ public static String folderSplit;
+ public static boolean add_1_20_Unicodes;
+ public static short defaultCharWidth;
+
+ public static void load() {
+ try {
+ YamlDocument.create(
+ new File(CustomNameplatesPlugin.getInstance().getDataFolder(), "config.yml"),
+ Objects.requireNonNull(CustomNameplatesPlugin.getInstance().getResource("config.yml")),
+ GeneralSettings.DEFAULT,
+ LoaderSettings
+ .builder()
+ .setAutoUpdate(true)
+ .build(),
+ DumperSettings.DEFAULT,
+ UpdaterSettings
+ .builder()
+ .setVersioning(new BasicVersioning("config-version"))
+ .build()
+ );
+ loadSettings(CustomNameplatesPlugin.getInstance().getConfig("config.yml"));
+ } catch (IOException e) {
+ LogUtils.warn(e.getMessage());
+ }
+ }
+
+ private static void loadSettings(YamlConfiguration config) {
+ debug = config.getBoolean("debug", false);
+
+ language = config.getString("lang", "english");
+ updateChecker = config.getBoolean("update-checker", true);
+ metrics = config.getBoolean("metrics");
+
+ ConfigurationSection moduleSection = config.getConfigurationSection("modules");
+ if (moduleSection != null) {
+ nameplateModule = moduleSection.getBoolean("nameplates");
+ bossBarModule = moduleSection.getBoolean("bossbars");
+ actionBarModule = moduleSection.getBoolean("actionbars");
+ bubbleModule = moduleSection.getBoolean("bubbles");
+ backgroundModule = moduleSection.getBoolean("backgrounds");
+ imageModule = moduleSection.getBoolean("images");
+ }
+
+ ConfigurationSection integrationSection = config.getConfigurationSection("integrations");
+ if (integrationSection != null) {
+ copyPackIA = integrationSection.getBoolean("resource-pack.ItemsAdder", false);
+ copyPackOraxen = integrationSection.getBoolean("resource-pack.Oraxen", false);
+ trChatChannel = integrationSection.getBoolean("chat.TrChat", false);
+ ventureChatChannel = integrationSection.getBoolean("chat.VentureChat", false);
+ tabTeam = integrationSection.getBoolean("team.TAB", false);
+ cmiTeam = integrationSection.getBoolean("team.CMI", false);
+ }
+
+ ConfigurationSection packSection = config.getConfigurationSection("resource-pack");
+ if (packSection != null) {
+ generatePackOnStart = !packSection.getBoolean("disable-generation-on-start", false);
+ namespace = packSection.getString("namespace", "nameplates");
+ font = packSection.getString("font", "default");
+ FontUtils.setNameSpaceAndFont(namespace, font);
+
+ initChar = packSection.getString("initial-char", "뀁").charAt(0);
+ CharacterArranger.reset(initChar);
+
+ folderNameplate = packSection.getString("image-path.nameplates","font\\nameplates\\");
+ folderBubble = packSection.getString("image-path.bubbles","font\\bubbles\\");
+ folderBackground = packSection.getString("image-path.backgrounds","font\\backgrounds\\");
+ folderImage = packSection.getString("image-path.images","font\\images\\");
+ folderSplit = packSection.getString("image-path.space-split","font\\base\\");
+
+ add_1_20_Unicodes = packSection.getBoolean("support-1_20-unicodes", true);
+ }
+
+ corePoolSize = config.getInt("other-settings.thread-pool-settings.corePoolSize", 10);
+ maximumPoolSize = config.getInt("other-settings.thread-pool-settings.maximumPoolSize", 10);
+ keepAliveTime = config.getInt("other-settings.thread-pool-settings.keepAliveTime", 30);
+ }
+
+ public static boolean isOtherTeamPluginHooked() {
+ return tabTeam || cmiTeam;
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/setting/CNLocale.java b/paper/src/main/java/net/momirealms/customnameplates/paper/setting/CNLocale.java
new file mode 100644
index 0000000..ebd1168
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/setting/CNLocale.java
@@ -0,0 +1,95 @@
+package net.momirealms.customnameplates.paper.setting;
+
+import dev.dejvokep.boostedyaml.YamlDocument;
+import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning;
+import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings;
+import dev.dejvokep.boostedyaml.settings.general.GeneralSettings;
+import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings;
+import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Objects;
+
+public class CNLocale {
+
+ public static String MSG_RELOAD;
+ public static String MSG_PREFIX;
+ public static String MSG_PREVIEW_COOLDOWN;
+ public static String MSG_PREVIEW_START;
+ public static String generate;
+ public static String noNameplate;
+ public static String MSG_PACK_GENERATION;
+ public static String MSG_EQUIP_NAMEPLATE;
+ public static String MSG_UNEQUIP_NAMEPLATE;
+ public static String MSG_FORCE_EQUIP_NAMEPLATE;
+ public static String np_force_unEquip;
+ public static String MSG_NAMEPLATE_NOT_EXISTS;
+ public static String MSG_NAMEPLATE_NOT_AVAILABLE;
+ public static String np_available;
+ public static String np_haveNone;
+ public static String bb_equip;
+ public static String bb_unEquip;
+ public static String bb_force_equip;
+ public static String bb_force_unEquip;
+ public static String bb_not_exist;
+ public static String bb_notAvailable;
+ public static String bb_available;
+ public static String bb_haveNone;
+
+ public static void load() {
+ try {
+ YamlDocument.create(
+ new File(CustomNameplatesPlugin.getInstance().getDataFolder(), "messages/" + CNConfig.language + ".yml"),
+ Objects.requireNonNull(CustomNameplatesPlugin.getInstance().getResource("messages/" + CNConfig.language + ".yml")),
+ GeneralSettings.DEFAULT,
+ LoaderSettings
+ .builder()
+ .setAutoUpdate(true)
+ .build(),
+ DumperSettings.DEFAULT,
+ UpdaterSettings
+ .builder()
+ .setVersioning(new BasicVersioning("config-version"))
+ .build()
+ );
+ } catch (IOException e) {
+ LogUtils.warn(e.getMessage());
+ }
+ loadSettings(CustomNameplatesPlugin.get().getConfig("messages/" + CNConfig.language + ".yml"));
+ }
+
+ public static void loadSettings(YamlConfiguration config) {
+ ConfigurationSection section = config.getConfigurationSection("messages");
+ if (section != null) {
+ MSG_RELOAD = section.getString("reload");
+ MSG_PREFIX = section.getString("prefix");
+ MSG_PREVIEW_COOLDOWN = config.getString("messages.cooldown");
+ MSG_PREVIEW_START = config.getString("messages.preview");
+ generate = config.getString("messages.generate");
+ MSG_PACK_GENERATION = config.getString("messages.generate-done");
+ noNameplate = config.getString("messages.no-nameplate");
+ MSG_EQUIP_NAMEPLATE = config.getString("messages.equip-nameplates");
+ MSG_UNEQUIP_NAMEPLATE = config.getString("messages.unequip-nameplates");
+ MSG_FORCE_EQUIP_NAMEPLATE = config.getString("messages.force-equip-nameplates");
+ np_force_unEquip = config.getString("messages.force-unequip-nameplates");
+ MSG_NAMEPLATE_NOT_EXISTS = config.getString("messages.not-exist-nameplates");
+ MSG_NAMEPLATE_NOT_AVAILABLE = config.getString("messages.not-available-nameplates");
+ np_available = config.getString("messages.available-nameplates");
+ np_haveNone = config.getString("messages.have-no-nameplates");
+ bb_equip = config.getString("messages.equip-bubbles");
+ bb_unEquip = config.getString("messages.unequip-bubbles");
+ bb_force_equip = config.getString("messages.force-equip-bubbles");
+ bb_force_unEquip = config.getString("messages.force-unequip-bubbles");
+ bb_not_exist = config.getString("messages.not-exist-bubbles");
+ bb_notAvailable = config.getString("messages.not-available-bubbles");
+ bb_available = config.getString("messages.available-bubbles");
+ bb_haveNone = config.getString("messages.have-no-bubbles");
+ }
+
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/data/DataStorageInterface.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/DataStorageInterface.java
similarity index 60%
rename from src/main/java/net/momirealms/customnameplates/data/DataStorageInterface.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/storage/DataStorageInterface.java
index ec0d8ba..9aed2f8 100644
--- a/src/main/java/net/momirealms/customnameplates/data/DataStorageInterface.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/DataStorageInterface.java
@@ -15,14 +15,34 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.data;
+package net.momirealms.customnameplates.paper.storage;
+import net.momirealms.customnameplates.api.data.PlayerData;
+
+import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
public interface DataStorageInterface {
+
+ /**
+ * Initialize the data resource
+ */
void initialize();
+
+ /**
+ * Close the data resource
+ */
void disable();
- PlayerData loadData(UUID uuid);
- void saveData(PlayerData playerData);
+
+ /**
+ * Get the storage data source type
+ *
+ * @return {@link StorageType}
+ */
StorageType getStorageType();
+
+ CompletableFuture> getPlayerData(UUID uuid);
+
+ CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData);
}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/StorageManagerImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/StorageManagerImpl.java
new file mode 100644
index 0000000..26c9bd0
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/StorageManagerImpl.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+import net.momirealms.customnameplates.api.data.OnlineUser;
+import net.momirealms.customnameplates.api.data.PlayerData;
+import net.momirealms.customnameplates.api.event.NameplateDataLoadEvent;
+import net.momirealms.customnameplates.api.manager.StorageManager;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import net.momirealms.customnameplates.paper.storage.method.database.nosql.MongoDBImpl;
+import net.momirealms.customnameplates.paper.storage.method.database.nosql.RedisManager;
+import net.momirealms.customnameplates.paper.storage.method.database.sql.H2Impl;
+import net.momirealms.customnameplates.paper.storage.method.database.sql.MariaDBImpl;
+import net.momirealms.customnameplates.paper.storage.method.database.sql.MySQLImpl;
+import net.momirealms.customnameplates.paper.storage.method.database.sql.SQLiteImpl;
+import net.momirealms.customnameplates.paper.storage.method.file.JsonImpl;
+import net.momirealms.customnameplates.paper.storage.method.file.YAMLImpl;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This class implements the StorageManager interface and is responsible for managing player data storage.
+ * It includes methods to handle player data retrieval, storage, and serialization.
+ */
+public class StorageManagerImpl implements Listener, StorageManager {
+
+ private final CustomNameplatesPluginImpl plugin;
+ private DataStorageInterface dataSource;
+ private StorageType previousType;
+ private boolean hasRedis;
+ private RedisManager redisManager;
+ private final Gson gson;
+ private final ConcurrentHashMap onlineUserMap;
+
+ public StorageManagerImpl(CustomNameplatesPluginImpl plugin) {
+ this.plugin = plugin;
+ this.gson = new GsonBuilder().create();
+ this.onlineUserMap = new ConcurrentHashMap<>(64);
+ Bukkit.getPluginManager().registerEvents(this, plugin);
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ var uuid = event.getPlayer().getUniqueId();
+ this.getPlayerData(uuid).thenAccept(optData -> {
+ if (optData.isPresent()) {
+ var playerData = optData.get();
+ var player = Bukkit.getPlayer(uuid);
+ if (player == null || !player.isOnline()) return;
+ var onlineUser = new OnlineUser(player, playerData);
+ this.putOnlineUserInMap(onlineUser);
+ NameplateDataLoadEvent syncEvent = new NameplateDataLoadEvent(uuid, onlineUser);
+ plugin.getServer().getPluginManager().callEvent(syncEvent);
+ }
+ });
+ }
+
+ @Override
+ public Collection getOnlineUsers() {
+ return onlineUserMap.values();
+ }
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ this.removeOnlineUserFromMap(event.getPlayer().getUniqueId());
+ }
+
+ public void putOnlineUserInMap(OnlineUser onlineUser) {
+ onlineUserMap.put(onlineUser.getUUID(), onlineUser);
+ }
+
+ public void removeOnlineUserFromMap(UUID uuid) {
+ onlineUserMap.remove(uuid);
+ }
+
+ @Override
+ public CompletableFuture> getPlayerData(UUID uuid) {
+ OnlineUser onlineUser = onlineUserMap.get(uuid);
+ if (onlineUser != null) {
+ return CompletableFuture.completedFuture(Optional.of(onlineUser.toPlayerData()));
+ }
+ return dataSource.getPlayerData(uuid);
+ }
+
+ @Override
+ public CompletableFuture saveOnlinePlayerData(UUID uuid) {
+ OnlineUser onlineUser = onlineUserMap.get(uuid);
+ if (onlineUser == null) {
+ return CompletableFuture.completedFuture(false);
+ }
+ return dataSource.updatePlayerData(uuid, onlineUser.toPlayerData());
+ }
+
+ @Override
+ public CompletableFuture savePlayerData(UUID uuid, PlayerData playerData) {
+ return dataSource.updatePlayerData(uuid, playerData);
+ }
+
+ @Override
+ public Optional getOnlineUser(UUID uuid) {
+ return Optional.ofNullable(onlineUserMap.get(uuid));
+ }
+
+ /**
+ * Reloads the storage manager configuration.
+ */
+ public void reload() {
+ YamlConfiguration config = plugin.getConfig("database.yml");
+
+ // Check if storage type has changed and reinitialize if necessary
+ StorageType storageType = StorageType.valueOf(config.getString("data-storage-method", "H2"));
+ if (storageType != previousType) {
+ if (this.dataSource != null) this.dataSource.disable();
+ this.previousType = storageType;
+ switch (storageType) {
+ case H2 -> this.dataSource = new H2Impl(plugin);
+ case JSON -> this.dataSource = new JsonImpl(plugin);
+ case YAML -> this.dataSource = new YAMLImpl(plugin);
+ case SQLite -> this.dataSource = new SQLiteImpl(plugin);
+ case MySQL -> this.dataSource = new MySQLImpl(plugin);
+ case MariaDB -> this.dataSource = new MariaDBImpl(plugin);
+ case MongoDB -> this.dataSource = new MongoDBImpl(plugin);
+ }
+ if (this.dataSource != null) this.dataSource.initialize();
+ else LogUtils.severe("No storage type is set.");
+ }
+
+ // Handle Redis configuration
+ if (!this.hasRedis && config.getBoolean("Redis.enable", false)) {
+ this.hasRedis = true;
+ this.redisManager = new RedisManager(plugin);
+ this.redisManager.initialize();
+ }
+
+ // Disable Redis if it was enabled but is now disabled
+ if (this.hasRedis && !config.getBoolean("Redis.enable", false) && this.redisManager != null) {
+ this.redisManager.disable();
+ this.redisManager = null;
+ }
+ }
+
+ /**
+ * Disables the storage manager and cleans up resources.
+ */
+ public void disable() {
+ HandlerList.unregisterAll(this);
+ if (this.dataSource != null)
+ this.dataSource.disable();
+ if (this.redisManager != null)
+ this.redisManager.disable();
+ }
+
+ /**
+ * Checks if Redis is enabled.
+ *
+ * @return True if Redis is enabled; otherwise, false.
+ */
+ public boolean isRedisEnabled() {
+ return hasRedis;
+ }
+
+ /**
+ * Gets the RedisManager instance.
+ *
+ * @return The RedisManager instance.
+ */
+ @Nullable
+ public RedisManager getRedisManager() {
+ return redisManager;
+ }
+
+ @NotNull
+ @Override
+ public PlayerData fromJson(String json) {
+ return gson.fromJson(json, PlayerData.class);
+ }
+
+ @Override
+ public PlayerData fromBytes(byte[] data) {
+ try {
+ return gson.fromJson(new String(data, StandardCharsets.UTF_8), PlayerData.class);
+ } catch (JsonSyntaxException e) {
+ throw new DataSerializationException("Failed to get PlayerData from bytes", e);
+ }
+ }
+
+ @Override
+ public byte[] toBytes(PlayerData playerData) {
+ return toJson(playerData).getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ @NotNull
+ public String toJson(@NotNull PlayerData playerData) {
+ return gson.toJson(playerData);
+ }
+
+ /**
+ * Custom exception class for data serialization errors.
+ */
+ public static class DataSerializationException extends RuntimeException {
+ protected DataSerializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/data/StorageType.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/StorageType.java
similarity index 83%
rename from src/main/java/net/momirealms/customnameplates/data/StorageType.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/storage/StorageType.java
index 70b929e..1490a1e 100644
--- a/src/main/java/net/momirealms/customnameplates/data/StorageType.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/StorageType.java
@@ -15,9 +15,16 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.data;
+package net.momirealms.customnameplates.paper.storage;
public enum StorageType {
- SQL,
- YAML
+
+ JSON,
+ YAML,
+ H2,
+ SQLite,
+ MySQL,
+ MariaDB,
+ MongoDB,
+ Redis
}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/AbstractStorage.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/AbstractStorage.java
new file mode 100644
index 0000000..ef04144
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/AbstractStorage.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage.method;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.storage.DataStorageInterface;
+
+/**
+ * An abstract class that implements the DataStorageInterface and provides common functionality for data storage.
+ */
+public abstract class AbstractStorage implements DataStorageInterface {
+
+ protected CustomNameplatesPlugin plugin;
+
+ public AbstractStorage(CustomNameplatesPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void initialize() {
+ // This method can be overridden in subclasses to perform initialization tasks specific to the storage type.
+ }
+
+ @Override
+ public void disable() {
+ // This method can be overridden in subclasses to perform cleanup or shutdown tasks specific to the storage type.
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/nosql/MongoDBImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/nosql/MongoDBImpl.java
new file mode 100644
index 0000000..7de6d2e
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/nosql/MongoDBImpl.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage.method.database.nosql;
+
+import com.mongodb.*;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.UpdateOptions;
+import com.mongodb.client.model.Updates;
+import com.mongodb.client.result.UpdateResult;
+import net.momirealms.customnameplates.api.data.PlayerData;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import net.momirealms.customnameplates.paper.storage.StorageType;
+import net.momirealms.customnameplates.paper.storage.method.AbstractStorage;
+import org.bson.Document;
+import org.bson.UuidRepresentation;
+import org.bson.conversions.Bson;
+import org.bson.types.Binary;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * An implementation of AbstractStorage that uses MongoDB for player data storage.
+ */
+public class MongoDBImpl extends AbstractStorage {
+
+ private MongoClient mongoClient;
+ private MongoDatabase database;
+ private String collectionPrefix;
+
+ public MongoDBImpl(CustomNameplatesPluginImpl plugin) {
+ super(plugin);
+ }
+
+ /**
+ * Initialize the MongoDB connection and configuration based on the plugin's YAML configuration.
+ */
+ @Override
+ public void initialize() {
+ YamlConfiguration config = plugin.getConfig("database.yml");
+ ConfigurationSection section = config.getConfigurationSection("MongoDB");
+ if (section == null) {
+ LogUtils.warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one.");
+ return;
+ }
+
+ collectionPrefix = section.getString("collection-prefix", "nameplates");
+ var settings = MongoClientSettings.builder().uuidRepresentation(UuidRepresentation.STANDARD);
+ if (!section.getString("connection-uri", "").equals("")) {
+ settings.applyConnectionString(new ConnectionString(section.getString("connection-uri", "")));
+ mongoClient = MongoClients.create(settings.build());
+ return;
+ }
+
+ if (section.contains("user")) {
+ MongoCredential credential = MongoCredential.createCredential(
+ section.getString("user", "root"),
+ section.getString("database", "minecraft"),
+ section.getString("password", "password").toCharArray()
+ );
+ settings.credential(credential);
+ }
+
+ settings.applyToClusterSettings(builder -> builder.hosts(Collections.singletonList(new ServerAddress(
+ section.getString("host", "localhost"),
+ section.getInt("port", 27017)
+ ))));
+ this.mongoClient = MongoClients.create(settings.build());
+ this.database = mongoClient.getDatabase(section.getString("database", "minecraft"));
+ }
+
+ /**
+ * Disable the MongoDB connection by closing the MongoClient.
+ */
+ @Override
+ public void disable() {
+ if (this.mongoClient != null) {
+ this.mongoClient.close();
+ }
+ }
+
+ @Override
+ public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData) {
+ var future = new CompletableFuture();
+ plugin.getScheduler().runTaskAsync(() -> {
+ MongoCollection collection = database.getCollection(getCollectionName("data"));
+ try {
+ Document query = new Document("uuid", uuid);
+ Bson updates = Updates.combine(
+ Updates.set(
+ "data",
+ new Binary(plugin.getStorageManager().toBytes(playerData))
+ )
+ );
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ UpdateResult result = collection.updateOne(query, updates, options);
+ future.complete(result.wasAcknowledged());
+ } catch (MongoException e) {
+ future.completeExceptionally(e);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public CompletableFuture> getPlayerData(UUID uuid) {
+ var future = new CompletableFuture>();
+ plugin.getScheduler().runTaskAsync(() -> {
+ MongoCollection collection = database.getCollection(getCollectionName("data"));
+ Document doc = collection.find(Filters.eq("uuid", uuid)).first();
+ if (doc == null) {
+ future.complete(Optional.empty());
+ } else if (Bukkit.getPlayer(uuid) != null) {
+ Binary binary = (Binary) doc.get("data");
+ future.complete(Optional.of(plugin.getStorageManager().fromBytes(binary.getData())));
+ } else {
+ future.complete(Optional.empty());
+ }
+ });
+ return future;
+ }
+
+ /**
+ * Get the collection name for a specific subcategory of data.
+ *
+ * @param value The subcategory identifier.
+ * @return The full collection name including the prefix.
+ */
+ public String getCollectionName(String value) {
+ return getCollectionPrefix() + "_" + value;
+ }
+
+ /**
+ * Get the collection prefix used for MongoDB collections.
+ *
+ * @return The collection prefix.
+ */
+ public String getCollectionPrefix() {
+ return collectionPrefix;
+ }
+
+ @Override
+ public StorageType getStorageType() {
+ return StorageType.MongoDB;
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/nosql/RedisManager.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/nosql/RedisManager.java
new file mode 100644
index 0000000..80cb8ee
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/nosql/RedisManager.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage.method.database.nosql;
+
+import net.momirealms.customnameplates.api.data.PlayerData;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import net.momirealms.customnameplates.paper.storage.StorageType;
+import net.momirealms.customnameplates.paper.storage.method.AbstractStorage;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.jetbrains.annotations.NotNull;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+import redis.clients.jedis.exceptions.JedisException;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A RedisManager class responsible for managing interactions with a Redis server for data storage.
+ */
+public class RedisManager extends AbstractStorage {
+
+ private static RedisManager instance;
+ private JedisPool jedisPool;
+ private String password;
+ private int port;
+ private String host;
+ private boolean useSSL;
+
+ public RedisManager(CustomNameplatesPluginImpl plugin) {
+ super(plugin);
+ instance = this;
+ }
+
+ /**
+ * Get the singleton instance of the RedisManager.
+ *
+ * @return The RedisManager instance.
+ */
+ public static RedisManager getInstance() {
+ return instance;
+ }
+
+ /**
+ * Get a Jedis resource for interacting with the Redis server.
+ *
+ * @return A Jedis resource.
+ */
+ public Jedis getJedis() {
+ return jedisPool.getResource();
+ }
+
+ /**
+ * Initialize the Redis connection and configuration based on the plugin's YAML configuration.
+ */
+ @Override
+ public void initialize() {
+ YamlConfiguration config = plugin.getConfig("database.yml");
+ ConfigurationSection section = config.getConfigurationSection("Redis");
+ if (section == null) {
+ LogUtils.warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one.");
+ return;
+ }
+
+ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
+ jedisPoolConfig.setTestWhileIdle(true);
+ jedisPoolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(30000));
+ jedisPoolConfig.setNumTestsPerEvictionRun(-1);
+ jedisPoolConfig.setMinEvictableIdleTime(Duration.ofMillis(section.getInt("MinEvictableIdleTimeMillis",1800000)));
+ jedisPoolConfig.setMaxTotal(section.getInt("MaxTotal",8));
+ jedisPoolConfig.setMaxIdle(section.getInt("MaxIdle",8));
+ jedisPoolConfig.setMinIdle(section.getInt("MinIdle",1));
+ jedisPoolConfig.setMaxWait(Duration.ofMillis(section.getInt("MaxWaitMillis")));
+
+ password = section.getString("password", "");
+ port = section.getInt("port", 6379);
+ host = section.getString("host", "localhost");
+ useSSL = section.getBoolean("use-ssl", false);
+
+ if (password.isBlank()) {
+ jedisPool = new JedisPool(jedisPoolConfig, host, port, 0, useSSL);
+ } else {
+ jedisPool = new JedisPool(jedisPoolConfig, host, port, 0, password, useSSL);
+ }
+ try (Jedis jedis = jedisPool.getResource()) {
+ jedis.ping();
+ LogUtils.info("Redis server connected.");
+ } catch (JedisException e) {
+ LogUtils.warn("Failed to connect redis.");
+ }
+ }
+
+ /**
+ * Disable the Redis connection by closing the JedisPool.
+ */
+ @Override
+ public void disable() {
+ if (jedisPool != null && !jedisPool.isClosed())
+ jedisPool.close();
+ }
+
+ /**
+ * Send a message to Redis on a specified channel.
+ *
+ * @param channel The Redis channel to send the message to.
+ * @param message The message to send.
+ */
+ public void sendRedisMessage(@NotNull String channel, @NotNull String message) {
+ try (Jedis jedis = jedisPool.getResource()) {
+ jedis.publish(channel, message);
+ plugin.debug("Sent Redis message: " + message);
+ }
+ }
+
+ @Override
+ public StorageType getStorageType() {
+ return StorageType.Redis;
+ }
+
+ @Override
+ public CompletableFuture> getPlayerData(UUID uuid) {
+ var future = new CompletableFuture>();
+ plugin.getScheduler().runTaskAsync(() -> {
+ try (Jedis jedis = jedisPool.getResource()) {
+ byte[] key = getRedisKey("cn_data", uuid);
+ byte[] data = jedis.get(key);
+ jedis.del(key);
+ if (data != null) {
+ future.complete(Optional.of(plugin.getStorageManager().fromBytes(data)));
+ plugin.debug("Redis data retrieved for " + uuid + "; normal data");
+ } else {
+ future.complete(Optional.empty());
+ plugin.debug("Redis data retrieved for " + uuid + "; empty data");
+ }
+ } catch (Exception e) {
+ future.complete(Optional.empty());
+ LogUtils.warn("Failed to get redis data for " + uuid, e);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData) {
+ var future = new CompletableFuture();
+ plugin.getScheduler().runTaskAsync(() -> {
+ try (Jedis jedis = jedisPool.getResource()) {
+ jedis.setex(
+ getRedisKey("cn_data", uuid),
+ 300,
+ plugin.getStorageManager().toBytes(playerData)
+ );
+ future.complete(true);
+ plugin.debug("Redis data set for " + uuid);
+ } catch (Exception e) {
+ future.complete(false);
+ LogUtils.warn("Failed to set redis data for player " + uuid, e);
+ }
+ });
+ return future;
+ }
+
+ /**
+ * Generate a Redis key for a specified key and UUID.
+ *
+ * @param key The key identifier.
+ * @param uuid The UUID to include in the key.
+ * @return A byte array representing the Redis key.
+ */
+ private byte[] getRedisKey(String key, @NotNull UUID uuid) {
+ return (key + ":" + uuid).getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractHikariDatabase.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractHikariDatabase.java
new file mode 100644
index 0000000..c6433b9
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractHikariDatabase.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage.method.database.sql;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.storage.StorageType;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * An abstract base class for SQL databases using the HikariCP connection pool, which handles player data storage.
+ */
+public abstract class AbstractHikariDatabase extends AbstractSQLDatabase {
+
+ private HikariDataSource dataSource;
+ private final String driverClass;
+ private final String sqlBrand;
+
+ public AbstractHikariDatabase(CustomNameplatesPlugin plugin) {
+ super(plugin);
+ this.driverClass = getStorageType() == StorageType.MariaDB ? "org.mariadb.jdbc.Driver" : "com.mysql.cj.jdbc.Driver";
+ this.sqlBrand = getStorageType() == StorageType.MariaDB ? "MariaDB" : "MySQL";
+ try {
+ Class.forName(this.driverClass);
+ } catch (ClassNotFoundException e1) {
+ if (getStorageType() == StorageType.MariaDB) {
+ LogUtils.warn("No MariaDB driver is found");
+ } else if (getStorageType() == StorageType.MySQL) {
+ try {
+ Class.forName("com.mysql.jdbc.Driver");
+ LogUtils.warn("It seems that you are not using MySQL 8.0+. It's recommended to update.");
+ } catch (ClassNotFoundException e2) {
+ LogUtils.warn("No MySQL driver is found");
+ }
+ }
+ }
+ }
+
+ /**
+ * Initialize the database connection pool and create tables if they don't exist.
+ */
+ @Override
+ public void initialize() {
+ YamlConfiguration config = plugin.getConfig("database.yml");
+ ConfigurationSection section = config.getConfigurationSection(sqlBrand);
+
+ if (section == null) {
+ LogUtils.warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one.");
+ return;
+ }
+
+ super.tablePrefix = section.getString("table-prefix", "customfishing");
+ HikariConfig hikariConfig = new HikariConfig();
+ hikariConfig.setUsername(section.getString("user", "root"));
+ hikariConfig.setPassword(section.getString("password", "pa55w0rd"));
+ hikariConfig.setJdbcUrl(String.format("jdbc:%s://%s:%s/%s%s",
+ sqlBrand.toLowerCase(Locale.ENGLISH),
+ section.getString("host", "localhost"),
+ section.getString("port", "3306"),
+ section.getString("database", "minecraft"),
+ section.getString("connection-parameters")
+ ));
+ hikariConfig.setDriverClassName(driverClass);
+ hikariConfig.setMaximumPoolSize(section.getInt("Pool-Settings.max-pool-size", 10));
+ hikariConfig.setMinimumIdle(section.getInt("Pool-Settings.min-idle", 10));
+ hikariConfig.setMaxLifetime(section.getLong("Pool-Settings.max-lifetime", 180000L));
+ hikariConfig.setConnectionTimeout(section.getLong("Pool-Settings.time-out", 20000L));
+ hikariConfig.setPoolName("CustomNameplatesHikariPool");
+ try {
+ hikariConfig.setKeepaliveTime(section.getLong("Pool-Settings.keep-alive-time", 60000L));
+ } catch (NoSuchMethodError ignored) {
+ }
+
+ final Properties properties = new Properties();
+ properties.putAll(
+ Map.of("cachePrepStmts", "true",
+ "prepStmtCacheSize", "250",
+ "prepStmtCacheSqlLimit", "2048",
+ "useServerPrepStmts", "true",
+ "useLocalSessionState", "true",
+ "useLocalTransactionState", "true"
+ ));
+ properties.putAll(
+ Map.of(
+ "rewriteBatchedStatements", "true",
+ "cacheResultSetMetadata", "true",
+ "cacheServerConfiguration", "true",
+ "elideSetAutoCommits", "true",
+ "maintainTimeStats", "false")
+ );
+ hikariConfig.setDataSourceProperties(properties);
+ dataSource = new HikariDataSource(hikariConfig);
+ super.createTableIfNotExist();
+ }
+
+ /**
+ * Disable the database by closing the connection pool.
+ */
+ @Override
+ public void disable() {
+ if (dataSource != null && !dataSource.isClosed())
+ dataSource.close();
+ }
+
+ /**
+ * Get a connection to the SQL database from the connection pool.
+ *
+ * @return A database connection.
+ * @throws SQLException If there is an error establishing a connection.
+ */
+ @Override
+ public Connection getConnection() throws SQLException {
+ return dataSource.getConnection();
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractSQLDatabase.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractSQLDatabase.java
new file mode 100644
index 0000000..2678619
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/AbstractSQLDatabase.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage.method.database.sql;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.data.PlayerData;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.storage.method.AbstractStorage;
+import org.bukkit.Bukkit;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.sql.*;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * An abstract base class for SQL database implementations that handle player data storage.
+ */
+public abstract class AbstractSQLDatabase extends AbstractStorage {
+
+ protected String tablePrefix;
+
+ public AbstractSQLDatabase(CustomNameplatesPlugin plugin) {
+ super(plugin);
+ }
+
+ /**
+ * Get a connection to the SQL database.
+ *
+ * @return A database connection.
+ * @throws SQLException If there is an error establishing a connection.
+ */
+ public abstract Connection getConnection() throws SQLException;
+
+ /**
+ * Create tables for storing data if they don't exist in the database.
+ */
+ public void createTableIfNotExist() {
+ try (Connection connection = getConnection()) {
+ final String[] databaseSchema = getSchema(getStorageType().name().toLowerCase(Locale.ENGLISH));
+ try (Statement statement = connection.createStatement()) {
+ for (String tableCreationStatement : databaseSchema) {
+ statement.execute(tableCreationStatement);
+ }
+ } catch (SQLException e) {
+ LogUtils.warn("Failed to create tables");
+ }
+ } catch (SQLException e) {
+ LogUtils.warn("Failed to get sql connection");
+ } catch (IOException e) {
+ LogUtils.warn("Failed to get schema resource");
+ }
+ }
+
+ /**
+ * Get the SQL schema from a resource file.
+ *
+ * @param fileName The name of the schema file.
+ * @return An array of SQL statements to create tables.
+ * @throws IOException If there is an error reading the schema resource.
+ */
+ private String[] getSchema(@NotNull String fileName) throws IOException {
+ return replaceSchemaPlaceholder(new String(Objects.requireNonNull(plugin.getResource("schema/" + fileName + ".sql"))
+ .readAllBytes(), StandardCharsets.UTF_8)).split(";");
+ }
+
+ /**
+ * Replace placeholder values in SQL schema with the table prefix.
+ *
+ * @param sql The SQL schema string.
+ * @return The SQL schema string with placeholders replaced.
+ */
+ private String replaceSchemaPlaceholder(@NotNull String sql) {
+ return sql.replace("{prefix}", tablePrefix);
+ }
+
+ /**
+ * Get the name of a database table based on a sub-table name and the table prefix.
+ *
+ * @param sub The sub-table name.
+ * @return The full table name.
+ */
+ public String getTableName(String sub) {
+ return getTablePrefix() + "_" + sub;
+ }
+
+ /**
+ * Get the current table prefix.
+ *
+ * @return The table prefix.
+ */
+ public String getTablePrefix() {
+ return tablePrefix;
+ }
+
+ @Override
+ public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData) {
+ var future = new CompletableFuture();
+ plugin.getScheduler().runTaskAsync(() -> {
+ try (
+ Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data")))
+ ) {
+ statement.setBlob(1, new ByteArrayInputStream(plugin.getStorageManager().toBytes(playerData)));
+ statement.setString(2, uuid.toString());
+ statement.executeUpdate();
+ future.complete(true);
+ } catch (SQLException e) {
+ LogUtils.warn("Failed to update " + uuid + "'s data.", e);
+ future.completeExceptionally(e);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public CompletableFuture> getPlayerData(UUID uuid) {
+ var future = new CompletableFuture>();
+ plugin.getScheduler().runTaskAsync(() -> {
+ try (
+ Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("data")))
+ ) {
+ statement.setString(1, uuid.toString());
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ Blob blob = rs.getBlob("data");
+ byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
+ blob.free();
+ future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray)));
+ } else if (Bukkit.getPlayer(uuid) != null) {
+ var data = PlayerData.empty();
+ this.insertPlayerData(uuid, data);
+ future.complete(Optional.of(data));
+ } else {
+ future.complete(Optional.empty());
+ }
+ } catch (SQLException e) {
+ LogUtils.warn("Failed to get " + uuid + "'s data.", e);
+ future.completeExceptionally(e);
+ }
+ });
+ return future;
+ }
+
+ public void insertPlayerData(UUID uuid, PlayerData playerData) {
+ try (
+ Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_INSERT_DATA_BY_UUID, getTableName("data")))
+ ) {
+ statement.setString(1, uuid.toString());
+ statement.setBlob(2, new ByteArrayInputStream(plugin.getStorageManager().toBytes(playerData)));
+ statement.execute();
+ } catch (SQLException e) {
+ LogUtils.warn("Failed to insert " + uuid + "'s data.", e);
+ }
+ }
+
+ /**
+ * Constants defining SQL statements used for database operations.
+ */
+ public static class SqlConstants {
+ public static final String SQL_SELECT_BY_UUID = "SELECT * FROM `%s` WHERE `uuid` = ?";
+ public static final String SQL_SELECT_ALL_UUID = "SELECT uuid FROM `%s`";
+ public static final String SQL_UPDATE_BY_UUID = "UPDATE `%s` SET `data` = ? WHERE `uuid` = ?";
+ public static final String SQL_INSERT_DATA_BY_UUID = "INSERT INTO `%s`(`uuid`, `data`) VALUES(?, ?)";
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/H2Impl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/H2Impl.java
new file mode 100644
index 0000000..e03c46d
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/H2Impl.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage.method.database.sql;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.storage.StorageType;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.h2.jdbcx.JdbcConnectionPool;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * An implementation of AbstractSQLDatabase that uses the H2 embedded database for player data storage.
+ */
+public class H2Impl extends AbstractSQLDatabase {
+
+ private JdbcConnectionPool connectionPool;
+
+ public H2Impl(CustomNameplatesPlugin plugin) {
+ super(plugin);
+ }
+
+ /**
+ * Initialize the H2 database and connection pool based on the configuration.
+ */
+ @Override
+ public void initialize() {
+ YamlConfiguration config = plugin.getConfig("database.yml");
+ File databaseFile = new File(plugin.getDataFolder(), config.getString("H2.file", "data.db"));
+ super.tablePrefix = config.getString("H2.table-prefix", "customnameplates");
+
+ final String url = String.format("jdbc:h2:%s", databaseFile.getAbsolutePath());
+ this.connectionPool = JdbcConnectionPool.create(url, "sa", "");
+ super.createTableIfNotExist();
+ }
+
+ /**
+ * Disable the H2 database by disposing of the connection pool.
+ */
+ @Override
+ public void disable() {
+ if (connectionPool != null) {
+ connectionPool.dispose();
+ }
+ }
+
+ @Override
+ public StorageType getStorageType() {
+ return StorageType.H2;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ return connectionPool.getConnection();
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/object/requirements/papi/ExpressionAnd.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/MariaDBImpl.java
similarity index 62%
rename from src/main/java/net/momirealms/customnameplates/object/requirements/papi/ExpressionAnd.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/MariaDBImpl.java
index 047169b..e28e728 100644
--- a/src/main/java/net/momirealms/customnameplates/object/requirements/papi/ExpressionAnd.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/MariaDBImpl.java
@@ -15,19 +15,19 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.object.requirements.papi;
+package net.momirealms.customnameplates.paper.storage.method.database.sql;
-import org.bukkit.entity.Player;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.storage.StorageType;
-import java.util.List;
+public class MariaDBImpl extends AbstractHikariDatabase {
-public record ExpressionAnd(List requirements) implements PapiRequirement {
+ public MariaDBImpl(CustomNameplatesPlugin plugin) {
+ super(plugin);
+ }
@Override
- public boolean isMet(Player player) {
- for (PapiRequirement requirement : requirements) {
- if (!requirement.isMet(player)) return false;
- }
- return true;
+ public StorageType getStorageType() {
+ return StorageType.MariaDB;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/net/momirealms/customnameplates/object/requirements/papi/ExpressionOr.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/MySQLImpl.java
similarity index 62%
rename from src/main/java/net/momirealms/customnameplates/object/requirements/papi/ExpressionOr.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/MySQLImpl.java
index b95e436..cadb247 100644
--- a/src/main/java/net/momirealms/customnameplates/object/requirements/papi/ExpressionOr.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/MySQLImpl.java
@@ -15,19 +15,19 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.object.requirements.papi;
+package net.momirealms.customnameplates.paper.storage.method.database.sql;
-import org.bukkit.entity.Player;
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.paper.storage.StorageType;
-import java.util.List;
+public class MySQLImpl extends AbstractHikariDatabase {
-public record ExpressionOr(List requirements) implements PapiRequirement{
+ public MySQLImpl(CustomNameplatesPlugin plugin) {
+ super(plugin);
+ }
@Override
- public boolean isMet(Player player) {
- for (PapiRequirement requirement : requirements) {
- if (requirement.isMet(player)) return true;
- }
- return false;
+ public StorageType getStorageType() {
+ return StorageType.MySQL;
}
-}
\ No newline at end of file
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/SQLiteImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/SQLiteImpl.java
new file mode 100644
index 0000000..b79df41
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/database/sql/SQLiteImpl.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage.method.database.sql;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.data.PlayerData;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.storage.StorageType;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.sqlite.SQLiteConfig;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.*;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * An implementation of AbstractSQLDatabase that uses the SQLite database for player data storage.
+ */
+public class SQLiteImpl extends AbstractSQLDatabase {
+
+ private Connection connection;
+ private File databaseFile;
+
+ public SQLiteImpl(CustomNameplatesPlugin plugin) {
+ super(plugin);
+ }
+
+ /**
+ * Initialize the SQLite database and connection based on the configuration.
+ */
+ @Override
+ public void initialize() {
+ YamlConfiguration config = plugin.getConfig("database.yml");
+ this.databaseFile = new File(plugin.getDataFolder(), config.getString("SQLite.file", "data") + ".db");
+ super.tablePrefix = config.getString("SQLite.table-prefix", "customfishing");
+ super.createTableIfNotExist();
+ }
+
+ /**
+ * Disable the SQLite database by closing the connection.
+ */
+ @Override
+ public void disable() {
+ try {
+ if (connection != null && !connection.isClosed())
+ connection.close();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData) {
+ var future = new CompletableFuture();
+ plugin.getScheduler().runTaskAsync(() -> {
+ try (
+ Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data")))
+ ) {
+ statement.setBytes(1, plugin.getStorageManager().toBytes(playerData));
+ statement.setString(2, uuid.toString());
+ statement.executeUpdate();
+ future.complete(true);
+ } catch (SQLException e) {
+ LogUtils.warn("Failed to update " + uuid + "'s data.", e);
+ future.completeExceptionally(e);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public CompletableFuture> getPlayerData(UUID uuid) {
+ var future = new CompletableFuture>();
+ plugin.getScheduler().runTaskAsync(() -> {
+ try (
+ Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("data")))
+ ) {
+ statement.setString(1, uuid.toString());
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ byte[] dataByteArray = rs.getBytes("data");
+ future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray)));
+ } else if (Bukkit.getPlayer(uuid) != null) {
+ var data = PlayerData.empty();
+ this.insertPlayerData(uuid, data);
+ future.complete(Optional.of(data));
+ } else {
+ future.complete(Optional.empty());
+ }
+ } catch (SQLException e) {
+ LogUtils.warn("Failed to get " + uuid + "'s data.", e);
+ future.completeExceptionally(e);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public void insertPlayerData(UUID uuid, PlayerData playerData) {
+ try (
+ Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_INSERT_DATA_BY_UUID, getTableName("data")))
+ ) {
+ statement.setString(1, uuid.toString());
+ statement.setBytes(2, plugin.getStorageManager().toBytes(playerData));
+ statement.execute();
+ } catch (SQLException e) {
+ LogUtils.warn("Failed to insert " + uuid + "'s data.", e);
+ }
+ }
+
+ @Override
+ public StorageType getStorageType() {
+ return StorageType.SQLite;
+ }
+
+ /**
+ * Get a connection to the SQLite database.
+ *
+ * @return A database connection.
+ * @throws SQLException If there is an error establishing a connection.
+ */
+ @Override
+ public Connection getConnection() throws SQLException {
+ if (connection == null || connection.isClosed()) {
+ setConnection();
+ }
+ return connection;
+ }
+
+ /**
+ * Set up the connection to the SQLite database.
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private void setConnection() {
+ try {
+ if (!databaseFile.exists()) databaseFile.createNewFile();
+ Class.forName("org.sqlite.JDBC");
+ SQLiteConfig config = new SQLiteConfig();
+ config.enforceForeignKeys(true);
+ config.setEncoding(SQLiteConfig.Encoding.UTF8);
+ config.setSynchronous(SQLiteConfig.SynchronousMode.FULL);
+ connection = DriverManager.getConnection(
+ String.format("jdbc:sqlite:%s", databaseFile.getAbsolutePath()),
+ config.toProperties()
+ );
+ } catch (IOException e) {
+ LogUtils.warn("Failed to create the SQLite database.");
+ } catch (SQLException e) {
+ LogUtils.warn("Failed to initialize SQLite database.");
+ } catch (ClassNotFoundException e) {
+ LogUtils.warn("Failed to find SQLite driver.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/file/JsonImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/file/JsonImpl.java
new file mode 100644
index 0000000..3a7fd0b
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/file/JsonImpl.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage.method.file;
+
+import com.google.gson.Gson;
+import net.momirealms.customnameplates.api.data.PlayerData;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import net.momirealms.customnameplates.paper.storage.StorageType;
+import net.momirealms.customnameplates.paper.storage.method.AbstractStorage;
+import org.bukkit.Bukkit;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A data storage implementation that uses JSON files to store player data.
+ */
+public class JsonImpl extends AbstractStorage {
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public JsonImpl(CustomNameplatesPluginImpl plugin) {
+ super(plugin);
+ File folder = new File(plugin.getDataFolder(), "data");
+ if (!folder.exists()) folder.mkdirs();
+ }
+
+ @Override
+ public StorageType getStorageType() {
+ return StorageType.JSON;
+ }
+
+ @Override
+ public CompletableFuture> getPlayerData(UUID uuid) {
+ File file = getPlayerDataFile(uuid);
+ PlayerData playerData;
+ if (file.exists()) {
+ playerData = readFromJsonFile(file, PlayerData.class);
+ } else if (Bukkit.getPlayer(uuid) != null) {
+ playerData = PlayerData.empty();
+ } else {
+ playerData = null;
+ }
+ return CompletableFuture.completedFuture(Optional.ofNullable(playerData));
+ }
+
+ /**
+ * Get the file associated with a player's UUID for storing JSON data.
+ *
+ * @param uuid The UUID of the player.
+ * @return The file for the player's data.
+ */
+ public File getPlayerDataFile(UUID uuid) {
+ return new File(plugin.getDataFolder(), "data" + File.separator + uuid + ".json");
+ }
+
+ /**
+ * Save an object to a JSON file.
+ *
+ * @param obj The object to be saved as JSON.
+ * @param filepath The file path where the JSON file should be saved.
+ */
+ public void saveToJsonFile(Object obj, File filepath) {
+ Gson gson = new Gson();
+ try (FileWriter file = new FileWriter(filepath)) {
+ gson.toJson(obj, file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Read JSON content from a file and parse it into an object of the specified class.
+ *
+ * @param file The JSON file to read.
+ * @param classOfT The class of the object to parse the JSON into.
+ * @param The type of the object.
+ * @return The parsed object.
+ */
+ public T readFromJsonFile(File file, Class classOfT) {
+ Gson gson = new Gson();
+ String jsonContent = new String(readFileToByteArray(file), StandardCharsets.UTF_8);
+ return gson.fromJson(jsonContent, classOfT);
+ }
+
+ /**
+ * Read the contents of a file and return them as a byte array.
+ *
+ * @param file The file to read.
+ * @return The byte array representing the file's content.
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public byte[] readFileToByteArray(File file) {
+ byte[] fileBytes = new byte[(int) file.length()];
+ try (FileInputStream fis = new FileInputStream(file)) {
+ fis.read(fileBytes);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return fileBytes;
+ }
+
+ @Override
+ public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData) {
+ this.saveToJsonFile(playerData, getPlayerDataFile(uuid));
+ return CompletableFuture.completedFuture(true);
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/file/YAMLImpl.java b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/file/YAMLImpl.java
new file mode 100644
index 0000000..762d053
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/storage/method/file/YAMLImpl.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.storage.method.file;
+
+import net.momirealms.customnameplates.api.data.PlayerData;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl;
+import net.momirealms.customnameplates.paper.storage.StorageType;
+import net.momirealms.customnameplates.paper.storage.method.AbstractStorage;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A data storage implementation that uses YAML files to store player data, with support for legacy data.
+ */
+public class YAMLImpl extends AbstractStorage {
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public YAMLImpl(CustomNameplatesPluginImpl plugin) {
+ super(plugin);
+ File folder = new File(plugin.getDataFolder(), "data");
+ if (!folder.exists()) folder.mkdirs();
+ }
+
+ @Override
+ public StorageType getStorageType() {
+ return StorageType.YAML;
+ }
+
+ @Override
+ public CompletableFuture> getPlayerData(UUID uuid) {
+ File dataFile = getPlayerDataFile(uuid);
+ if (!dataFile.exists()) {
+ if (Bukkit.getPlayer(uuid) != null) {
+ return CompletableFuture.completedFuture(Optional.of(PlayerData.empty()));
+ } else {
+ return CompletableFuture.completedFuture(Optional.empty());
+ }
+ }
+ YamlConfiguration data = readData(dataFile);
+ PlayerData playerData = new PlayerData.Builder()
+ .setBubble(data.getString("bubble", ""))
+ .setNameplate(data.getString("nameplate", ""))
+ .build();
+ return CompletableFuture.completedFuture(Optional.of(playerData));
+ }
+
+ /**
+ * Get the file associated with a player's UUID for storing YAML data.
+ *
+ * @param uuid The UUID of the player.
+ * @return The file for the player's data.
+ */
+ public File getPlayerDataFile(UUID uuid) {
+ return new File(plugin.getDataFolder(), "data" + File.separator + uuid + ".yml");
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public YamlConfiguration readData(File file) {
+ if (!file.exists()) {
+ try {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ LogUtils.warn("Failed to generate data files!");
+ }
+ }
+ return YamlConfiguration.loadConfiguration(file);
+ }
+
+ @Override
+ public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData) {
+ YamlConfiguration data = new YamlConfiguration();
+ data.set("bubble", playerData.getBubble());
+ data.set("nameplate", playerData.getNameplate());
+ try {
+ data.save(getPlayerDataFile(uuid));
+ } catch (IOException e) {
+ LogUtils.warn("Failed to save player data", e);
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+}
diff --git a/src/main/java/net/momirealms/customnameplates/utils/ClassUtils.java b/paper/src/main/java/net/momirealms/customnameplates/paper/util/ClassUtils.java
similarity index 98%
rename from src/main/java/net/momirealms/customnameplates/utils/ClassUtils.java
rename to paper/src/main/java/net/momirealms/customnameplates/paper/util/ClassUtils.java
index 74a4646..ba36931 100644
--- a/src/main/java/net/momirealms/customnameplates/utils/ClassUtils.java
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/util/ClassUtils.java
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package net.momirealms.customnameplates.utils;
+package net.momirealms.customnameplates.paper.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/util/ConfigUtils.java b/paper/src/main/java/net/momirealms/customnameplates/paper/util/ConfigUtils.java
new file mode 100644
index 0000000..de68cbd
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/util/ConfigUtils.java
@@ -0,0 +1,98 @@
+package net.momirealms.customnameplates.paper.util;
+
+import net.momirealms.customnameplates.api.CustomNameplatesPlugin;
+import net.momirealms.customnameplates.api.common.Pair;
+import net.momirealms.customnameplates.api.requirement.Requirement;
+import net.momirealms.customnameplates.api.util.LogUtils;
+import net.momirealms.customnameplates.paper.mechanic.misc.TimeLimitText;
+import org.bukkit.configuration.ConfigurationSection;
+
+import java.util.*;
+
+public class ConfigUtils {
+
+ private ConfigUtils() {}
+
+ /**
+ * Converts an object into an ArrayList of strings.
+ *
+ * @param object The input object
+ * @return An ArrayList of strings
+ */
+ @SuppressWarnings("unchecked")
+ public static ArrayList stringListArgs(Object object) {
+ ArrayList list = new ArrayList<>();
+ if (object instanceof String member) {
+ list.add(member);
+ } else if (object instanceof List> members) {
+ list.addAll((Collection extends String>) members);
+ } else if (object instanceof String[] strings) {
+ list.addAll(List.of(strings));
+ }
+ return list;
+ }
+
+ /**
+ * Splits a string into a pair of integers using the "~" delimiter.
+ *
+ * @param value The input string
+ * @return A Pair of integers
+ */
+ public static Pair splitStringIntegerArgs(String value, String regex) {
+ String[] split = value.split(regex);
+ return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
+ }
+
+ /**
+ * Converts an object into a double value.
+ *
+ * @param arg The input object
+ * @return A double value
+ */
+ public static double getDoubleValue(Object arg) {
+ if (arg instanceof Double d) {
+ return d;
+ } else if (arg instanceof Integer i) {
+ return Double.valueOf(i);
+ }
+ return 0;
+ }
+
+ public static TimeLimitText[] getTimeLimitTexts(ConfigurationSection section) {
+
+ TreeMap map = new TreeMap<>();
+ if (section == null) {
+ LogUtils.warn("No text display order set, this might cause bugs!");
+ map.put(1, new TimeLimitText(100, -1, "", new Requirement[0]));
+ return getOrderedTexts(map);
+ }
+
+ for (Map.Entry entry : section.getValues(false).entrySet()) {
+ if (!(entry.getValue() instanceof ConfigurationSection textSection))
+ continue;
+
+ var text = TimeLimitText.Builder.of()
+ .text(textSection.getString("text", ""))
+ .refreshFrequency(textSection.getInt("refresh-frequency", -1))
+ .duration(textSection.getInt("duration", 200))
+ .requirement(CustomNameplatesPlugin.get().getRequirementManager().getRequirements(textSection.getConfigurationSection("conditions")))
+ .build();
+
+ int order;
+ try {
+ order = Integer.parseInt(entry.getKey());
+ } catch (NumberFormatException e) {
+ LogUtils.warn("Invalid order format: " + entry.getKey());
+ continue;
+ }
+
+ map.put(order, text);
+ }
+
+ return getOrderedTexts(map);
+ }
+
+ private static TimeLimitText[] getOrderedTexts(TreeMap map) {
+ return map.values().toArray(new TimeLimitText[0]);
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/util/LocationUtils.java b/paper/src/main/java/net/momirealms/customnameplates/paper/util/LocationUtils.java
new file mode 100644
index 0000000..e7884c7
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/util/LocationUtils.java
@@ -0,0 +1,13 @@
+package net.momirealms.customnameplates.paper.util;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+
+public class LocationUtils {
+
+ public static double getDistance(Entity e1, Entity e2) {
+ Location loc1 = e1.getLocation();
+ Location loc2 = e2.getLocation();
+ return Math.sqrt(Math.pow(loc1.getX()-loc2.getX(), 2) + Math.pow(loc1.getZ()-loc2.getZ(), 2));
+ }
+}
diff --git a/paper/src/main/java/net/momirealms/customnameplates/paper/util/ReflectionUtils.java b/paper/src/main/java/net/momirealms/customnameplates/paper/util/ReflectionUtils.java
new file mode 100644
index 0000000..a4b2695
--- /dev/null
+++ b/paper/src/main/java/net/momirealms/customnameplates/paper/util/ReflectionUtils.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) <2022>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customnameplates.paper.util;
+
+import com.comphenix.protocol.utility.MinecraftReflection;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.momirealms.customnameplates.api.util.LogUtils;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class ReflectionUtils {
+
+ private ReflectionUtils() {}
+
+ public static Object removeBossBarPacket;
+ public static Constructor> progressConstructor;
+ public static Constructor> updateConstructor;
+ public static Method iChatComponentMethod;
+ public static Method gsonDeserializeMethod;
+ public static Object gsonInstance;
+ public static Object emptyComponent;
+
+ public static void load() {
+ try {
+ Class> bar = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutBoss");
+ Field remove = bar.getDeclaredField("f");
+ remove.setAccessible(true);
+ removeBossBarPacket = remove.get(null);
+ Class> packetBossClassF = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutBoss$f");
+ progressConstructor = packetBossClassF.getDeclaredConstructor(float.class);
+ progressConstructor.setAccessible(true);
+ Class> packetBossClassE = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutBoss$e");
+ updateConstructor = packetBossClassE.getDeclaredConstructor(MinecraftReflection.getIChatBaseComponentClass());
+ updateConstructor.setAccessible(true);
+ iChatComponentMethod = MinecraftReflection.getChatSerializerClass().getMethod("a", String.class);
+ iChatComponentMethod.setAccessible(true);
+ emptyComponent = iChatComponentMethod.invoke(null, "{\"text\":\"\"}");
+ } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException exception) {
+ LogUtils.severe("Error occurred when loading reflections", exception);
+ exception.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.fsh b/paper/src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.fsh
similarity index 100%
rename from src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.fsh
rename to paper/src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.fsh
diff --git a/src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.json b/paper/src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.json
similarity index 100%
rename from src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.json
rename to paper/src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.json
diff --git a/src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.vsh b/paper/src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.vsh
similarity index 100%
rename from src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.vsh
rename to paper/src/main/resources/ResourcePack/assets/minecraft/shaders/core/rendertype_text.vsh
diff --git a/src/main/resources/ResourcePack/assets/minecraft/textures/gui/bars.png b/paper/src/main/resources/ResourcePack/assets/minecraft/textures/gui/bars.png
similarity index 100%
rename from src/main/resources/ResourcePack/assets/minecraft/textures/gui/bars.png
rename to paper/src/main/resources/ResourcePack/assets/minecraft/textures/gui/bars.png
diff --git a/src/main/resources/ResourcePack/assets/minecraft/textures/gui/sprites/boss_bar/yellow_background.png b/paper/src/main/resources/ResourcePack/assets/minecraft/textures/gui/sprites/boss_bar/yellow_background.png
similarity index 100%
rename from src/main/resources/ResourcePack/assets/minecraft/textures/gui/sprites/boss_bar/yellow_background.png
rename to paper/src/main/resources/ResourcePack/assets/minecraft/textures/gui/sprites/boss_bar/yellow_background.png
diff --git a/src/main/resources/ResourcePack/assets/minecraft/textures/gui/sprites/boss_bar/yellow_progress.png b/paper/src/main/resources/ResourcePack/assets/minecraft/textures/gui/sprites/boss_bar/yellow_progress.png
similarity index 100%
rename from src/main/resources/ResourcePack/assets/minecraft/textures/gui/sprites/boss_bar/yellow_progress.png
rename to paper/src/main/resources/ResourcePack/assets/minecraft/textures/gui/sprites/boss_bar/yellow_progress.png
diff --git a/src/main/resources/ResourcePack/pack.mcmeta b/paper/src/main/resources/ResourcePack/pack.mcmeta
similarity index 100%
rename from src/main/resources/ResourcePack/pack.mcmeta
rename to paper/src/main/resources/ResourcePack/pack.mcmeta
diff --git a/paper/src/main/resources/config.yml b/paper/src/main/resources/config.yml
new file mode 100644
index 0000000..8f9675c
--- /dev/null
+++ b/paper/src/main/resources/config.yml
@@ -0,0 +1,103 @@
+# Do not change
+config-version: '22'
+
+# Debug mode
+debug: false
+
+# bStats
+metrics: true
+
+# Check update
+update-checker: true
+
+# Language
+# english/chinese/spanish/russian/turkish/french
+lang: english
+
+# Modules
+modules:
+ nameplates: true
+ backgrounds: true
+ bubbles: true
+ bossbars: true
+ actionbars: true
+ images: true
+
+# Integrations
+integrations:
+ resource-pack:
+ # If enabled, CustomNameplates will automatically place the generated resource pack into ItemsAdder/Oraxen.
+ # To apply this change, run "/nameplates reload" & "/iazip" or "/oraxen reload all"
+ ItemsAdder: false
+ Oraxen: false
+ # Require a restart to apply this
+ team:
+ # When enabled, CustomNameplates will hook into TAB's team management
+ TAB: false
+ # When enabled, CustomNameplates will hook into CMI's team management
+ CMI: false
+ chat:
+ # TrChat channels
+ TrChat: false
+ # VentureChat channels
+ VentureChat: false
+
+resource-pack:
+ # disable resource pack generation on server start
+ disable-generation-on-start: false
+ # Namespace
+ namespace: "nameplates"
+ # Font name
+ font: "default"
+ # The initial character of the custom font
+ # Although Korean is used here, it does not affect the normal use of Korean in chatting, as they are not in the same font
+ initial-char: '뀁'
+ # Customize the folder where png files should be generated.
+ # This is useful for those who want to keep their resource pack structure in order.
+ image-path:
+ nameplates: 'font\nameplates\'
+ backgrounds: 'font\backgrounds\'
+ images: 'font\images\'
+ bubbles: 'font\bubbles\'
+ space-split: 'font\base\'
+
+ shader:
+ enable: true
+ # Hide scoreboard numbers
+ hide-scoreboard-number: false
+ # Add Animated images support
+ animated-image: false
+ # Add ItemsAdder text effect support
+ ItemsAdder-text-effect: false
+
+ transparent-bossbar:
+ # Make a certain bossbar transparent
+ color: YELLOW
+ # Generate the transparent bossbar for 1.20.2+
+ 1_20_2+: true
+ # Generate the transparent bossbar for legacy versions
+ 1_17-1_20_1: true
+
+ # Unicode Support for 1.20+ clients
+ # This would make your resource pack about 900KB bigger
+ support-1_20-unicodes: true
+
+
+other-settings:
+ # It's recommended to use MiniMessage format. If you insist on using legacy color code "&", enable the support below.
+ legacy-color-code-support: false
+
+ # Thread pool settings
+ thread-pool-settings:
+ # The size of the core Thread pool, that is, the size of the Thread pool when there is no task to execute
+ # Increase the size of corePoolSize when you are running a large server with many players fishing at the same time
+ corePoolSize: 10
+ # The maximum number of threads allowed to be created in the Thread pool. The current number of threads in the Thread pool will not exceed this value
+ maximumPoolSize: 10
+ # If a thread is idle for more than this attribute value, it will exit due to timeout
+ keepAliveTime: 30
+
+ # default width
+ default-character-width: 8
+ # delay x ticks before actionbar/bossbar is sent to players
+ send-delay: 0
\ No newline at end of file
diff --git a/paper/src/main/resources/configs/actionbar.yml b/paper/src/main/resources/configs/actionbar.yml
new file mode 100644
index 0000000..6910ca5
--- /dev/null
+++ b/paper/src/main/resources/configs/actionbar.yml
@@ -0,0 +1,14 @@
+# You can only create "actionbar" section in this config file
+actionbar:
+ check-frequency: 10
+ conditions:
+ permission: "actionbar.show"
+ text-display-order:
+ 1:
+ # Measured in ticks
+ duration: 200
+ # Text content
+ text: 'Welcome to my server! %player_name%'
+ # The frequency of refreshing the text, the lower the value is, the faster the placeholders are updated
+ # measured in ticks, -1 = disable refreshing
+ refresh-frequency: 1
\ No newline at end of file
diff --git a/paper/src/main/resources/configs/bossbar.yml b/paper/src/main/resources/configs/bossbar.yml
new file mode 100644
index 0000000..60f733f
--- /dev/null
+++ b/paper/src/main/resources/configs/bossbar.yml
@@ -0,0 +1,34 @@
+# You can create as many sections as you want for more bossbars
+bossbar_1:
+ # Color (BLUE, GREEN, PINK, PURPLE, RED, WHITE, YELLOW)
+ color: YELLOW
+ # Overlay (It can be either "progress", "notched_6", "notched_10", "notched_12" or "notched_20")
+ overlay: PROGRESS
+ # The frequency of checking conditions for a bar, measured in ticks
+ check-frequency: 10
+ conditions:
+ permission: "bossbar.show"
+ # Bar's content would be shown as the following order
+ text-display-order:
+ 1:
+ # Measured in ticks
+ duration: 200
+ # Text content
+ text: 'Welcome to my server! %player_name%'
+ # The frequency of refreshing the text, the lower the value is, the faster the placeholders are updated
+ # measured in ticks, -1 = disable refreshing
+ refresh-frequency: -1
+ 2:
+ duration: 100
+ text: 'Your location: [X: %player_x%, Y: %player_y%, Z: %player_z%]'
+ refresh-frequency: 1
+ 3:
+ duration: 100
+ text: 'This is a message only visible to admins'
+ refresh-frequency: -1
+ # Optional
+ # When enabling conditions, make sure that players would see at least one in the order,
+ # otherwise it will fall into a dead cycle
+ conditions:
+ # If your player doesn't meet the conditions, it would be skipped
+ permission: nameplates.admin
\ No newline at end of file
diff --git a/src/main/resources/configs/custom-placeholders.yml b/paper/src/main/resources/configs/custom-placeholders.yml
similarity index 90%
rename from src/main/resources/configs/custom-placeholders.yml
rename to paper/src/main/resources/configs/custom-placeholders.yml
index 901b309..c24e90c 100644
--- a/src/main/resources/configs/custom-placeholders.yml
+++ b/paper/src/main/resources/configs/custom-placeholders.yml
@@ -163,23 +163,39 @@ descent-text:
# %nameplates_unicode_{0}%
descent-unicode:
- normal:
- text: "Just for registering"
- descent: 0
hello:
text: "Hello 여보세요 你好 こんにちは"
descent: 5
+# %nameplates_switch_{0}%
+switch-text:
+ season:
+ switch: '%customcrops_season%'
+ case:
+ 'Spring': 'Spring'
+ 'Summer': 'Summer'
+ 'Autumn': 'Autumn'
+ 'Winter': 'Winter'
+ default: 'Invalid season'
+
# %nameplates_vanilla_{0}%
# This is useful for creating a vanilla like hud
vanilla-hud:
stamina_hud:
reverse: true
images:
- empty: "%nameplates_image_stamina_0%"
- half: "%nameplates_image_stamina_1%"
- full: "%nameplates_image_stamina_2%"
+ empty: stamina_0
+ half: stamina_1
+ full: stamina_2
placeholder:
# value/max-value can be placeholders
value: '1.1'
- max-value: '2'
\ No newline at end of file
+ max-value: '2'
+
+# %nameplates_cached_{0}%
+cached-text:
+ cached:
+ # The text to cache
+ text: '%super_complex_javascript%'
+ # How often is the value refreshed, measured in ms
+ refresh-interval: 10000
\ No newline at end of file
diff --git a/paper/src/main/resources/configs/image-width.yml b/paper/src/main/resources/configs/image-width.yml
new file mode 100644
index 0000000..06abcce
--- /dev/null
+++ b/paper/src/main/resources/configs/image-width.yml
@@ -0,0 +1,5 @@
+minecraft:default:
+ '%img_xxx%': 15
+ 默: 8
+
+customcrops:default:
diff --git a/paper/src/main/resources/configs/nameplate.yml b/paper/src/main/resources/configs/nameplate.yml
new file mode 100644
index 0000000..ff9f083
--- /dev/null
+++ b/paper/src/main/resources/configs/nameplate.yml
@@ -0,0 +1,46 @@
+# Team / Unlimited / Disable
+mode: TEAM
+
+# If you are using BungeeCord/Velocity, it's advised to install CustomNameplates on proxy too and enable this option.
+proxy: false
+
+# The duration (in seconds) that the nameplate preview will last for.
+preview-duration: 5
+
+# Default nameplate to display if player doesn't equip any
+default-nameplate: 'none'
+
+# This decides what %nameplates_prefix/suffix% would return
+# Check xxx for examples
+nameplate:
+ refresh-frequency: 10
+ prefix: ''
+ player-name: '%player_name%'
+ suffix: ''
+ # Disable this if you are using a custom Tab plugin
+ fix-Tab: true
+
+# Settings for Team mode
+team:
+ # measured in ticks
+ refresh-frequency: 10
+ prefix: '%nameplates_prefix%'
+ suffix: '%nameplates_suffix%'
+
+# Settings for Unlimited mode
+unlimited:
+ tag_1:
+ text: '%nameplates_nametag%'
+ vertical-offset: -1
+ owner-conditions: { }
+ viewer-conditions: { }
+ tag_2:
+ text: "IP: %player_ip% My IP: %viewer_player_ip%"
+ vertical-offset: -0.7
+ refresh-frequency: 10
+ check-frequency: 20
+ owner-conditions: { }
+ viewer-conditions:
+ condition_1:
+ type: permission
+ value: 'nameplates.admin'
\ No newline at end of file
diff --git a/src/main/resources/contents/backgrounds/b0.png b/paper/src/main/resources/contents/backgrounds/b0.png
similarity index 100%
rename from src/main/resources/contents/backgrounds/b0.png
rename to paper/src/main/resources/contents/backgrounds/b0.png
diff --git a/src/main/resources/contents/backgrounds/b1.png b/paper/src/main/resources/contents/backgrounds/b1.png
similarity index 100%
rename from src/main/resources/contents/backgrounds/b1.png
rename to paper/src/main/resources/contents/backgrounds/b1.png
diff --git a/src/main/resources/contents/backgrounds/b128.png b/paper/src/main/resources/contents/backgrounds/b128.png
similarity index 100%
rename from src/main/resources/contents/backgrounds/b128.png
rename to paper/src/main/resources/contents/backgrounds/b128.png
diff --git a/src/main/resources/contents/backgrounds/b16.png b/paper/src/main/resources/contents/backgrounds/b16.png
similarity index 100%
rename from src/main/resources/contents/backgrounds/b16.png
rename to paper/src/main/resources/contents/backgrounds/b16.png
diff --git a/src/main/resources/contents/backgrounds/b2.png b/paper/src/main/resources/contents/backgrounds/b2.png
similarity index 100%
rename from src/main/resources/contents/backgrounds/b2.png
rename to paper/src/main/resources/contents/backgrounds/b2.png
diff --git a/src/main/resources/contents/backgrounds/b32.png b/paper/src/main/resources/contents/backgrounds/b32.png
similarity index 100%
rename from src/main/resources/contents/backgrounds/b32.png
rename to paper/src/main/resources/contents/backgrounds/b32.png
diff --git a/src/main/resources/contents/backgrounds/b4.png b/paper/src/main/resources/contents/backgrounds/b4.png
similarity index 100%
rename from src/main/resources/contents/backgrounds/b4.png
rename to paper/src/main/resources/contents/backgrounds/b4.png
diff --git a/src/main/resources/contents/backgrounds/b64.png b/paper/src/main/resources/contents/backgrounds/b64.png
similarity index 100%
rename from src/main/resources/contents/backgrounds/b64.png
rename to paper/src/main/resources/contents/backgrounds/b64.png
diff --git a/src/main/resources/contents/backgrounds/b8.png b/paper/src/main/resources/contents/backgrounds/b8.png
similarity index 100%
rename from src/main/resources/contents/backgrounds/b8.png
rename to paper/src/main/resources/contents/backgrounds/b8.png
diff --git a/src/main/resources/contents/backgrounds/bedrock_1.yml b/paper/src/main/resources/contents/backgrounds/bedrock_1.yml
similarity index 100%
rename from src/main/resources/contents/backgrounds/bedrock_1.yml
rename to paper/src/main/resources/contents/backgrounds/bedrock_1.yml
diff --git a/src/main/resources/contents/backgrounds/bedrock_2.yml b/paper/src/main/resources/contents/backgrounds/bedrock_2.yml
similarity index 100%
rename from src/main/resources/contents/backgrounds/bedrock_2.yml
rename to paper/src/main/resources/contents/backgrounds/bedrock_2.yml
diff --git a/src/main/resources/contents/backgrounds/bedrock_3.yml b/paper/src/main/resources/contents/backgrounds/bedrock_3.yml
similarity index 100%
rename from src/main/resources/contents/backgrounds/bedrock_3.yml
rename to paper/src/main/resources/contents/backgrounds/bedrock_3.yml
diff --git a/src/main/resources/contents/bubbles/chat.yml b/paper/src/main/resources/contents/bubbles/chat.yml
similarity index 100%
rename from src/main/resources/contents/bubbles/chat.yml
rename to paper/src/main/resources/contents/bubbles/chat.yml
diff --git a/src/main/resources/contents/bubbles/chat_left.png b/paper/src/main/resources/contents/bubbles/chat_left.png
similarity index 100%
rename from src/main/resources/contents/bubbles/chat_left.png
rename to paper/src/main/resources/contents/bubbles/chat_left.png
diff --git a/src/main/resources/contents/bubbles/chat_middle.png b/paper/src/main/resources/contents/bubbles/chat_middle.png
similarity index 100%
rename from src/main/resources/contents/bubbles/chat_middle.png
rename to paper/src/main/resources/contents/bubbles/chat_middle.png
diff --git a/src/main/resources/contents/bubbles/chat_right.png b/paper/src/main/resources/contents/bubbles/chat_right.png
similarity index 100%
rename from src/main/resources/contents/bubbles/chat_right.png
rename to paper/src/main/resources/contents/bubbles/chat_right.png
diff --git a/src/main/resources/contents/bubbles/chat_tail.png b/paper/src/main/resources/contents/bubbles/chat_tail.png
similarity index 100%
rename from src/main/resources/contents/bubbles/chat_tail.png
rename to paper/src/main/resources/contents/bubbles/chat_tail.png
diff --git a/src/main/resources/contents/images/bell.png b/paper/src/main/resources/contents/images/bell.png
similarity index 100%
rename from src/main/resources/contents/images/bell.png
rename to paper/src/main/resources/contents/images/bell.png
diff --git a/src/main/resources/contents/images/bell.yml b/paper/src/main/resources/contents/images/bell.yml
similarity index 100%
rename from src/main/resources/contents/images/bell.yml
rename to paper/src/main/resources/contents/images/bell.yml
diff --git a/src/main/resources/contents/images/bubble.png b/paper/src/main/resources/contents/images/bubble.png
similarity index 100%
rename from src/main/resources/contents/images/bubble.png
rename to paper/src/main/resources/contents/images/bubble.png
diff --git a/src/main/resources/contents/images/bubble.yml b/paper/src/main/resources/contents/images/bubble.yml
similarity index 100%
rename from src/main/resources/contents/images/bubble.yml
rename to paper/src/main/resources/contents/images/bubble.yml
diff --git a/src/main/resources/contents/images/clock.png b/paper/src/main/resources/contents/images/clock.png
similarity index 100%
rename from src/main/resources/contents/images/clock.png
rename to paper/src/main/resources/contents/images/clock.png
diff --git a/src/main/resources/contents/images/clock.yml b/paper/src/main/resources/contents/images/clock.yml
similarity index 100%
rename from src/main/resources/contents/images/clock.yml
rename to paper/src/main/resources/contents/images/clock.yml
diff --git a/src/main/resources/contents/images/coin.png b/paper/src/main/resources/contents/images/coin.png
similarity index 100%
rename from src/main/resources/contents/images/coin.png
rename to paper/src/main/resources/contents/images/coin.png
diff --git a/src/main/resources/contents/images/coin.yml b/paper/src/main/resources/contents/images/coin.yml
similarity index 100%
rename from src/main/resources/contents/images/coin.yml
rename to paper/src/main/resources/contents/images/coin.yml
diff --git a/src/main/resources/contents/images/compass.png b/paper/src/main/resources/contents/images/compass.png
similarity index 100%
rename from src/main/resources/contents/images/compass.png
rename to paper/src/main/resources/contents/images/compass.png
diff --git a/src/main/resources/contents/images/compass.yml b/paper/src/main/resources/contents/images/compass.yml
similarity index 100%
rename from src/main/resources/contents/images/compass.yml
rename to paper/src/main/resources/contents/images/compass.yml
diff --git a/src/main/resources/contents/images/stamina_0.png b/paper/src/main/resources/contents/images/stamina_0.png
similarity index 100%
rename from src/main/resources/contents/images/stamina_0.png
rename to paper/src/main/resources/contents/images/stamina_0.png
diff --git a/src/main/resources/contents/images/stamina_0.yml b/paper/src/main/resources/contents/images/stamina_0.yml
similarity index 100%
rename from src/main/resources/contents/images/stamina_0.yml
rename to paper/src/main/resources/contents/images/stamina_0.yml
diff --git a/src/main/resources/contents/images/stamina_1.png b/paper/src/main/resources/contents/images/stamina_1.png
similarity index 100%
rename from src/main/resources/contents/images/stamina_1.png
rename to paper/src/main/resources/contents/images/stamina_1.png
diff --git a/src/main/resources/contents/images/stamina_1.yml b/paper/src/main/resources/contents/images/stamina_1.yml
similarity index 100%
rename from src/main/resources/contents/images/stamina_1.yml
rename to paper/src/main/resources/contents/images/stamina_1.yml
diff --git a/src/main/resources/contents/images/stamina_2.png b/paper/src/main/resources/contents/images/stamina_2.png
similarity index 100%
rename from src/main/resources/contents/images/stamina_2.png
rename to paper/src/main/resources/contents/images/stamina_2.png
diff --git a/src/main/resources/contents/images/stamina_2.yml b/paper/src/main/resources/contents/images/stamina_2.yml
similarity index 100%
rename from src/main/resources/contents/images/stamina_2.yml
rename to paper/src/main/resources/contents/images/stamina_2.yml
diff --git a/src/main/resources/contents/images/weather.png b/paper/src/main/resources/contents/images/weather.png
similarity index 100%
rename from src/main/resources/contents/images/weather.png
rename to paper/src/main/resources/contents/images/weather.png
diff --git a/src/main/resources/contents/images/weather.yml b/paper/src/main/resources/contents/images/weather.yml
similarity index 100%
rename from src/main/resources/contents/images/weather.yml
rename to paper/src/main/resources/contents/images/weather.yml
diff --git a/src/main/resources/contents/nameplates/cat.yml b/paper/src/main/resources/contents/nameplates/cat.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/cat.yml
rename to paper/src/main/resources/contents/nameplates/cat.yml
diff --git a/src/main/resources/contents/nameplates/cat_left.png b/paper/src/main/resources/contents/nameplates/cat_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/cat_left.png
rename to paper/src/main/resources/contents/nameplates/cat_left.png
diff --git a/src/main/resources/contents/nameplates/cat_middle.png b/paper/src/main/resources/contents/nameplates/cat_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/cat_middle.png
rename to paper/src/main/resources/contents/nameplates/cat_middle.png
diff --git a/src/main/resources/contents/nameplates/cat_right.png b/paper/src/main/resources/contents/nameplates/cat_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/cat_right.png
rename to paper/src/main/resources/contents/nameplates/cat_right.png
diff --git a/src/main/resources/contents/nameplates/cheems.yml b/paper/src/main/resources/contents/nameplates/cheems.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/cheems.yml
rename to paper/src/main/resources/contents/nameplates/cheems.yml
diff --git a/src/main/resources/contents/nameplates/cheems_left.png b/paper/src/main/resources/contents/nameplates/cheems_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/cheems_left.png
rename to paper/src/main/resources/contents/nameplates/cheems_left.png
diff --git a/src/main/resources/contents/nameplates/cheems_middle.png b/paper/src/main/resources/contents/nameplates/cheems_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/cheems_middle.png
rename to paper/src/main/resources/contents/nameplates/cheems_middle.png
diff --git a/src/main/resources/contents/nameplates/cheems_right.png b/paper/src/main/resources/contents/nameplates/cheems_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/cheems_right.png
rename to paper/src/main/resources/contents/nameplates/cheems_right.png
diff --git a/src/main/resources/contents/nameplates/egg.yml b/paper/src/main/resources/contents/nameplates/egg.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/egg.yml
rename to paper/src/main/resources/contents/nameplates/egg.yml
diff --git a/src/main/resources/contents/nameplates/egg_left.png b/paper/src/main/resources/contents/nameplates/egg_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/egg_left.png
rename to paper/src/main/resources/contents/nameplates/egg_left.png
diff --git a/src/main/resources/contents/nameplates/egg_middle.png b/paper/src/main/resources/contents/nameplates/egg_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/egg_middle.png
rename to paper/src/main/resources/contents/nameplates/egg_middle.png
diff --git a/src/main/resources/contents/nameplates/egg_right.png b/paper/src/main/resources/contents/nameplates/egg_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/egg_right.png
rename to paper/src/main/resources/contents/nameplates/egg_right.png
diff --git a/src/main/resources/contents/nameplates/halloween.yml b/paper/src/main/resources/contents/nameplates/halloween.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/halloween.yml
rename to paper/src/main/resources/contents/nameplates/halloween.yml
diff --git a/src/main/resources/contents/nameplates/halloween_left.png b/paper/src/main/resources/contents/nameplates/halloween_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/halloween_left.png
rename to paper/src/main/resources/contents/nameplates/halloween_left.png
diff --git a/src/main/resources/contents/nameplates/halloween_middle.png b/paper/src/main/resources/contents/nameplates/halloween_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/halloween_middle.png
rename to paper/src/main/resources/contents/nameplates/halloween_middle.png
diff --git a/src/main/resources/contents/nameplates/halloween_right.png b/paper/src/main/resources/contents/nameplates/halloween_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/halloween_right.png
rename to paper/src/main/resources/contents/nameplates/halloween_right.png
diff --git a/src/main/resources/contents/nameplates/hutao.yml b/paper/src/main/resources/contents/nameplates/hutao.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/hutao.yml
rename to paper/src/main/resources/contents/nameplates/hutao.yml
diff --git a/src/main/resources/contents/nameplates/hutao_left.png b/paper/src/main/resources/contents/nameplates/hutao_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/hutao_left.png
rename to paper/src/main/resources/contents/nameplates/hutao_left.png
diff --git a/src/main/resources/contents/nameplates/hutao_middle.png b/paper/src/main/resources/contents/nameplates/hutao_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/hutao_middle.png
rename to paper/src/main/resources/contents/nameplates/hutao_middle.png
diff --git a/src/main/resources/contents/nameplates/hutao_right.png b/paper/src/main/resources/contents/nameplates/hutao_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/hutao_right.png
rename to paper/src/main/resources/contents/nameplates/hutao_right.png
diff --git a/src/main/resources/contents/nameplates/rabbit.yml b/paper/src/main/resources/contents/nameplates/rabbit.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/rabbit.yml
rename to paper/src/main/resources/contents/nameplates/rabbit.yml
diff --git a/src/main/resources/contents/nameplates/rabbit_left.png b/paper/src/main/resources/contents/nameplates/rabbit_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/rabbit_left.png
rename to paper/src/main/resources/contents/nameplates/rabbit_left.png
diff --git a/src/main/resources/contents/nameplates/rabbit_middle.png b/paper/src/main/resources/contents/nameplates/rabbit_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/rabbit_middle.png
rename to paper/src/main/resources/contents/nameplates/rabbit_middle.png
diff --git a/src/main/resources/contents/nameplates/rabbit_right.png b/paper/src/main/resources/contents/nameplates/rabbit_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/rabbit_right.png
rename to paper/src/main/resources/contents/nameplates/rabbit_right.png
diff --git a/src/main/resources/contents/nameplates/starsky.yml b/paper/src/main/resources/contents/nameplates/starsky.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/starsky.yml
rename to paper/src/main/resources/contents/nameplates/starsky.yml
diff --git a/src/main/resources/contents/nameplates/starsky_left.png b/paper/src/main/resources/contents/nameplates/starsky_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/starsky_left.png
rename to paper/src/main/resources/contents/nameplates/starsky_left.png
diff --git a/src/main/resources/contents/nameplates/starsky_middle.png b/paper/src/main/resources/contents/nameplates/starsky_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/starsky_middle.png
rename to paper/src/main/resources/contents/nameplates/starsky_middle.png
diff --git a/src/main/resources/contents/nameplates/starsky_right.png b/paper/src/main/resources/contents/nameplates/starsky_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/starsky_right.png
rename to paper/src/main/resources/contents/nameplates/starsky_right.png
diff --git a/src/main/resources/contents/nameplates/trident.yml b/paper/src/main/resources/contents/nameplates/trident.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/trident.yml
rename to paper/src/main/resources/contents/nameplates/trident.yml
diff --git a/src/main/resources/contents/nameplates/trident_left.png b/paper/src/main/resources/contents/nameplates/trident_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/trident_left.png
rename to paper/src/main/resources/contents/nameplates/trident_left.png
diff --git a/src/main/resources/contents/nameplates/trident_middle.png b/paper/src/main/resources/contents/nameplates/trident_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/trident_middle.png
rename to paper/src/main/resources/contents/nameplates/trident_middle.png
diff --git a/src/main/resources/contents/nameplates/trident_right.png b/paper/src/main/resources/contents/nameplates/trident_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/trident_right.png
rename to paper/src/main/resources/contents/nameplates/trident_right.png
diff --git a/src/main/resources/contents/nameplates/wither.yml b/paper/src/main/resources/contents/nameplates/wither.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/wither.yml
rename to paper/src/main/resources/contents/nameplates/wither.yml
diff --git a/src/main/resources/contents/nameplates/wither_left.png b/paper/src/main/resources/contents/nameplates/wither_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/wither_left.png
rename to paper/src/main/resources/contents/nameplates/wither_left.png
diff --git a/src/main/resources/contents/nameplates/wither_middle.png b/paper/src/main/resources/contents/nameplates/wither_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/wither_middle.png
rename to paper/src/main/resources/contents/nameplates/wither_middle.png
diff --git a/src/main/resources/contents/nameplates/wither_right.png b/paper/src/main/resources/contents/nameplates/wither_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/wither_right.png
rename to paper/src/main/resources/contents/nameplates/wither_right.png
diff --git a/src/main/resources/contents/nameplates/xmas.yml b/paper/src/main/resources/contents/nameplates/xmas.yml
similarity index 100%
rename from src/main/resources/contents/nameplates/xmas.yml
rename to paper/src/main/resources/contents/nameplates/xmas.yml
diff --git a/src/main/resources/contents/nameplates/xmas_left.png b/paper/src/main/resources/contents/nameplates/xmas_left.png
similarity index 100%
rename from src/main/resources/contents/nameplates/xmas_left.png
rename to paper/src/main/resources/contents/nameplates/xmas_left.png
diff --git a/src/main/resources/contents/nameplates/xmas_middle.png b/paper/src/main/resources/contents/nameplates/xmas_middle.png
similarity index 100%
rename from src/main/resources/contents/nameplates/xmas_middle.png
rename to paper/src/main/resources/contents/nameplates/xmas_middle.png
diff --git a/src/main/resources/contents/nameplates/xmas_right.png b/paper/src/main/resources/contents/nameplates/xmas_right.png
similarity index 100%
rename from src/main/resources/contents/nameplates/xmas_right.png
rename to paper/src/main/resources/contents/nameplates/xmas_right.png
diff --git a/paper/src/main/resources/database.yml b/paper/src/main/resources/database.yml
new file mode 100644
index 0000000..7b882a4
--- /dev/null
+++ b/paper/src/main/resources/database.yml
@@ -0,0 +1,77 @@
+# file:
+# JSON
+# YAML
+#
+# local database:
+# SQLite
+# H2 (preferred over SQLite)
+#
+# remote database:
+# MySQL
+# MariaDB (preferred over MySQL)
+# MongoDB
+#
+data-storage-method: H2
+
+SQLite:
+ file: 'sqlite'
+ table-prefix: nameplates
+
+H2:
+ file: 'h2'
+ table-prefix: nameplates
+
+MySQL:
+ host: 'localhost'
+ port: '3306'
+ user: 'root'
+ password: 'password'
+ database: 'minecraft'
+ connection-parameters: '?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8'
+ Pool-Settings:
+ max-pool-size: 10
+ min-idle: 10
+ max-lifetime: 180000
+ keep-alive-time: 60000
+ time-out: 20000
+ table-prefix: nameplates
+
+MariaDB:
+ host: 'localhost'
+ port: '3306'
+ user: 'root'
+ password: 'password'
+ database: 'minecraft'
+ connection-parameters: '?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8'
+ Pool-Settings:
+ max-pool-size: 10
+ min-idle: 10
+ max-lifetime: 180000
+ keep-alive-time: 60000
+ time-out: 20000
+ table-prefix: nameplates
+
+MongoDB:
+ host: 'localhost'
+ port: '27017'
+ #user: 'root'
+ #password: 'password'
+ database: 'minecraft'
+ # If this section is not empty, it would override the configs above
+ # https://www.mongodb.com/docs/manual/reference/connection-string/
+ connection-uri: ''
+ collection-prefix: nameplates
+
+# Redis is optional if you are using remote database
+# If you do not know how to use Redis, please do not enable it
+Redis:
+ enable: false
+ host: localhost
+ #password: 123456
+ port: 6379
+ use-ssl: false
+ MaxTotal: 10
+ MaxIdle: 10
+ MinIdle: 1
+ MaxWaitMillis: 30000
+ MinEvictableIdleTimeMillis: 1800000
\ No newline at end of file
diff --git a/src/main/resources/messages/chinese.yml b/paper/src/main/resources/messages/chinese.yml
similarity index 85%
rename from src/main/resources/messages/chinese.yml
rename to paper/src/main/resources/messages/chinese.yml
index 3742fe3..a68c7cc 100644
--- a/src/main/resources/messages/chinese.yml
+++ b/paper/src/main/resources/messages/chinese.yml
@@ -1,12 +1,8 @@
+config-version: "23"
+
messages:
prefix: '[CustomNameplates] '
- no-perm: '你没有权限!'
- lack-args: '参数不足!'
- none-args: '非空参数'
- invalid-args: '无效参数'
reload: '插件已重载! 请不要忘记重载你的资源包.'
- not-online: '玩家 {Player} 不在线!'
- no-console: '这个指令不能由控制台执行!'
cooldown: '上一个预览还没结束!'
generate: '正在生成资源包...'
generate-done: '资源包生成完毕...'
diff --git a/src/main/resources/messages/english.yml b/paper/src/main/resources/messages/english.yml
similarity index 85%
rename from src/main/resources/messages/english.yml
rename to paper/src/main/resources/messages/english.yml
index cab94f9..be66755 100644
--- a/src/main/resources/messages/english.yml
+++ b/paper/src/main/resources/messages/english.yml
@@ -1,12 +1,8 @@
+config-version: "23"
+
messages:
prefix: '[CustomNameplates] '
- no-perm: 'No Permission!'
- lack-args: 'Insufficient parameters!'
reload: 'Reloaded! Don''t forget to reinstall your resource pack.'
- none-args: 'Non arguments'
- invalid-args: 'Invalid arguments'
- not-online: 'Player {Player} is not online!'
- no-console: 'This command can be only executed by player!'
cooldown: 'Previewing is still Ongoing!'
generate: 'Resource Pack is generating..'
generate-done: 'Resource Pack has been generated!'
diff --git a/src/main/resources/messages/french.yml b/paper/src/main/resources/messages/french.yml
similarity index 85%
rename from src/main/resources/messages/french.yml
rename to paper/src/main/resources/messages/french.yml
index 0444843..981edc2 100644
--- a/src/main/resources/messages/french.yml
+++ b/paper/src/main/resources/messages/french.yml
@@ -1,12 +1,8 @@
+config-version: "23"
+
messages:
prefix: '[CustomNameplates] '
- no-perm: 'Pas de permission !'
- lack-args: 'Paramètres insuffisants !'
reload: 'Rechargé ! N'oubliez pas de réinstaller votre pack de ressources.'
- none-args: 'Aucun argument'
- invalid-args: 'Arguments invalides'
- not-online: 'Le joueur {Player} n'est pas en ligne !'
- no-console: 'Cette commande ne peut être exécutée que par un joueur !'
cooldown: 'La prévisualisation est toujours en cours !'
generate: 'Le pack de ressources est en cours de génération...'
generate-done: 'Le pack de ressources a été généré !'
diff --git a/src/main/resources/messages/russian.yml b/paper/src/main/resources/messages/russian.yml
similarity index 84%
rename from src/main/resources/messages/russian.yml
rename to paper/src/main/resources/messages/russian.yml
index 29e0cf7..54ee91d 100644
--- a/src/main/resources/messages/russian.yml
+++ b/paper/src/main/resources/messages/russian.yml
@@ -1,12 +1,8 @@
+config-version: "23"
+
messages:
prefix: '[CustomNameplates] '
- no-perm: 'Нет прав!'
- lack-args: 'Недостаточно параметров!'
reload: 'Перезагружено! Не забудьте переустановить свой ресурспак.'
- none-args: 'В команде обязательны аргументы.'
- invalid-args: 'Неверные аргументы'
- not-online: 'Игрок {Player} не в сети!'
- no-console: 'Эта команда может быть использована только игроком!'
cooldown: 'Предпросмотр до сих пор активен!'
generate: 'Ресурспак генерируется...'
generate-done: 'Ресурспак сгенерирован!'
diff --git a/src/main/resources/messages/spanish.yml b/paper/src/main/resources/messages/spanish.yml
similarity index 84%
rename from src/main/resources/messages/spanish.yml
rename to paper/src/main/resources/messages/spanish.yml
index 0ff4e30..9ff3662 100644
--- a/src/main/resources/messages/spanish.yml
+++ b/paper/src/main/resources/messages/spanish.yml
@@ -1,12 +1,8 @@
+config-version: "23"
+
messages:
prefix: '[CustomNameplates] '
- no-perm: 'No tienes permisos!'
- lack-args: '¡Parámetros insuficientes!'
reload: 'Recargado! No olvides reinstalar el paquete de recursos.'
- none-args: '¡Ningún argumento!'
- invalid-args: 'Argumentos no válidos'
- not-online: 'Jugador {Player} no está en línea!'
- no-console: 'Este comando sólo puede ser ejecutado por el jugador.'
cooldown: '¡El preestreno sigue en curso!'
generate: 'El paquete de recursos está generando..'
generate-done: 'Se ha generado un paquete de recursos.'
diff --git a/src/main/resources/messages/turkish.yml b/paper/src/main/resources/messages/turkish.yml
similarity index 85%
rename from src/main/resources/messages/turkish.yml
rename to paper/src/main/resources/messages/turkish.yml
index 0988729..ef37d12 100644
--- a/src/main/resources/messages/turkish.yml
+++ b/paper/src/main/resources/messages/turkish.yml
@@ -1,13 +1,9 @@
+config-version: "23"
+
#Turkish language file by WinTone01
messages:
prefix: '[CustomNameplates] '
- no-perm: 'İzin Yok!'
- lack-args: 'Yetersiz parametreler!'
reload: 'Yeniden Yüklendi! Kaynak paketinizi yeniden yüklemeyi unutmayın.'
- none-args: 'Argüman bulunmuyor'
- invalid-args: 'Geçersiz argümanlar'
- not-online: 'Oyuncu {Player} çevrimiçi değil!'
- no-console: 'Bu komut sadece oyuncular tarafından kullanılabilir!'
cooldown: 'Önizleme hala devam ediyor!'
generate: 'Kaynak paketi oluşturuluyor...'
generate-done: 'Kaynak paketi oluşturuldu!'
diff --git a/paper/src/main/resources/plugin.yml b/paper/src/main/resources/plugin.yml
new file mode 100644
index 0000000..f740ea6
--- /dev/null
+++ b/paper/src/main/resources/plugin.yml
@@ -0,0 +1,7 @@
+name: CustomNameplates
+version: '${version}'
+main: net.momirealms.customnameplates.paper.CustomNameplatesPluginImpl
+api-version: 1.17
+authors: [ XiaoMoMi ]
+folia-supported: true
+depend: [ ProtocolLib ,PlaceholderAPI ]
\ No newline at end of file
diff --git a/paper/src/main/resources/schema/h2.sql b/paper/src/main/resources/schema/h2.sql
new file mode 100644
index 0000000..578ec71
--- /dev/null
+++ b/paper/src/main/resources/schema/h2.sql
@@ -0,0 +1,6 @@
+CREATE TABLE IF NOT EXISTS `{prefix}_data`
+(
+ `uuid` char(36) NOT NULL UNIQUE,
+ `data` longblob NOT NULL,
+ PRIMARY KEY (`uuid`)
+);
\ No newline at end of file
diff --git a/paper/src/main/resources/schema/mariadb.sql b/paper/src/main/resources/schema/mariadb.sql
new file mode 100644
index 0000000..efef31d
--- /dev/null
+++ b/paper/src/main/resources/schema/mariadb.sql
@@ -0,0 +1,10 @@
+SET DEFAULT_STORAGE_ENGINE = INNODB;
+
+CREATE TABLE IF NOT EXISTS `{prefix}_data`
+(
+ `uuid` char(36) NOT NULL UNIQUE,
+ `data` longblob NOT NULL,
+ PRIMARY KEY (`uuid`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci;
\ No newline at end of file
diff --git a/paper/src/main/resources/schema/mysql.sql b/paper/src/main/resources/schema/mysql.sql
new file mode 100644
index 0000000..578ec71
--- /dev/null
+++ b/paper/src/main/resources/schema/mysql.sql
@@ -0,0 +1,6 @@
+CREATE TABLE IF NOT EXISTS `{prefix}_data`
+(
+ `uuid` char(36) NOT NULL UNIQUE,
+ `data` longblob NOT NULL,
+ PRIMARY KEY (`uuid`)
+);
\ No newline at end of file
diff --git a/paper/src/main/resources/schema/sqlite.sql b/paper/src/main/resources/schema/sqlite.sql
new file mode 100644
index 0000000..578ec71
--- /dev/null
+++ b/paper/src/main/resources/schema/sqlite.sql
@@ -0,0 +1,6 @@
+CREATE TABLE IF NOT EXISTS `{prefix}_data`
+(
+ `uuid` char(36) NOT NULL UNIQUE,
+ `data` longblob NOT NULL,
+ PRIMARY KEY (`uuid`)
+);
\ No newline at end of file
diff --git a/src/main/resources/space_split.png b/paper/src/main/resources/space_split.png
similarity index 100%
rename from src/main/resources/space_split.png
rename to paper/src/main/resources/space_split.png
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index 6529a22..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-rootProject.name = 'CustomNameplates'
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..1c3a374
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,5 @@
+rootProject.name = "CustomNameplates"
+include("api")
+include("bungeecord")
+include("velocity")
+include("paper")
diff --git a/src/main/java/net/momirealms/customnameplates/CustomNameplates.java b/src/main/java/net/momirealms/customnameplates/CustomNameplates.java
deleted file mode 100644
index ac3ec07..0000000
--- a/src/main/java/net/momirealms/customnameplates/CustomNameplates.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates;
-
-import com.comphenix.protocol.PacketType;
-import com.comphenix.protocol.ProtocolLibrary;
-import com.comphenix.protocol.ProtocolManager;
-import com.comphenix.protocol.events.PacketContainer;
-import net.kyori.adventure.platform.bukkit.BukkitAudiences;
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.command.BubblesCommand;
-import net.momirealms.customnameplates.command.NameplateCommand;
-import net.momirealms.customnameplates.helper.LibraryLoader;
-import net.momirealms.customnameplates.helper.VersionHelper;
-import net.momirealms.customnameplates.manager.*;
-import net.momirealms.customnameplates.object.scheduler.Scheduler;
-import net.momirealms.customnameplates.object.scheduler.SchedulerPlatform;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bstats.bukkit.Metrics;
-import org.bukkit.Bukkit;
-import org.bukkit.command.PluginCommand;
-import org.bukkit.plugin.java.JavaPlugin;
-
-import java.util.TimeZone;
-
-public final class CustomNameplates extends JavaPlugin {
-
- private static CustomNameplates plugin;
- private static BukkitAudiences adventure;
- private static ProtocolManager protocolManager;
- private ResourceManager resourceManager;
- private BossBarManager bossBarManager;
- private ActionBarManager actionBarManager;
- private PlaceholderManager placeholderManager;
- private NameplateManager nameplateManager;
- private ChatBubblesManager chatBubblesManager;
- private DataManager dataManager;
- private ConfigManager configManager;
- private MessageManager messageManager;
- private FontManager fontManager;
- private VersionHelper versionHelper;
- private TeamManager teamManager;
- private BackgroundManager backgroundManager;
- private ImageManager imageManager;
- private CustomNameplatesAPI customNameplatesAPI;
- private Scheduler scheduler;
-
- @Override
- public void onLoad(){
- plugin = this;
- loadLibs();
- }
-
- @Override
- public void onEnable() {
- adventure = BukkitAudiences.create(this);
- protocolManager = ProtocolLibrary.getProtocolManager();
- AdventureUtils.consoleMessage("[CustomNameplates] Running on " + Bukkit.getVersion());
- this.fix();
- this.versionHelper = new VersionHelper(this);
- this.scheduler = new Scheduler(this);
- this.configManager = new ConfigManager();
- this.messageManager = new MessageManager();
- this.teamManager = new TeamManager(this);
- this.placeholderManager = new PlaceholderManager(this);
- this.actionBarManager = new ActionBarManager(this);
- this.bossBarManager = new BossBarManager(this);
- this.resourceManager = new ResourceManager(this);
- this.dataManager = new DataManager(this);
- this.nameplateManager = new NameplateManager(this);
- this.backgroundManager = new BackgroundManager(this);
- this.fontManager = new FontManager(this);
- this.imageManager = new ImageManager(this);
- this.chatBubblesManager = new ChatBubblesManager(this);
- this.customNameplatesAPI = new CustomNameplatesAPI(this);
- this.customNameplatesAPI.init();
- this.registerCommands();
- this.reload(false);
- AdventureUtils.consoleMessage("[CustomNameplates] Plugin Enabled!");
- if (ConfigManager.enableBStats) new Metrics(this, 16649);
- if (ConfigManager.checkUpdate) this.versionHelper.checkUpdate();
- if (ConfigManager.generatePackOnStart) resourceManager.generateResourcePack();
- }
-
- @Override
- public void onDisable() {
- if (actionBarManager != null) actionBarManager.unload();
- if (nameplateManager != null) nameplateManager.unload();
- if (bossBarManager != null) bossBarManager.unload();
- if (chatBubblesManager != null) chatBubblesManager.unload();
- if (placeholderManager != null) placeholderManager.unload();
- if (fontManager != null) fontManager.unload();
- if (teamManager != null) teamManager.unload();
- if (imageManager != null) imageManager.unload();
- if (backgroundManager != null) backgroundManager.unload();
- if (dataManager != null) dataManager.disable();
- if (adventure != null) adventure.close();
- }
-
- private void loadLibs() {
- TimeZone timeZone = TimeZone.getDefault();
- String libRepo = timeZone.getID().startsWith("Asia") ? "https://maven.aliyun.com/repository/public/" : "https://repo.maven.apache.org/maven2/";
- LibraryLoader.load("commons-io","commons-io","2.13.0", libRepo);
- LibraryLoader.load("org.apache.commons","commons-lang3","3.13.0", libRepo);
- LibraryLoader.load("com.zaxxer","HikariCP","5.0.1", libRepo);
- LibraryLoader.load("dev.dejvokep","boosted-yaml","1.3.1", libRepo);
- LibraryLoader.load("org.mariadb.jdbc","mariadb-java-client","3.1.4", libRepo);
- LibraryLoader.load("com.mysql","mysql-connector-j","8.2.0", libRepo);
- }
-
- private void registerCommands() {
- NameplateCommand nameplateCommand = new NameplateCommand();
- PluginCommand main = Bukkit.getPluginCommand("customnameplates");
- if (main != null) {
- main.setExecutor(nameplateCommand);
- main.setTabCompleter(nameplateCommand);
- }
- BubblesCommand bubblesCommand = new BubblesCommand();
- PluginCommand bubble = Bukkit.getPluginCommand("bubbles");
- if (bubble != null) {
- bubble.setExecutor(bubblesCommand);
- bubble.setTabCompleter(bubblesCommand);
- }
- }
-
- private void fix() {
- //Don't delete this, a temp fix for a certain version of ProtocolLib
- new PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM);
- }
-
- public void reload(boolean generate) {
- configManager.unload();
- messageManager.unload();
- bossBarManager.unload();
- actionBarManager.unload();
- placeholderManager.unload();
- nameplateManager.unload();
- teamManager.unload();
- chatBubblesManager.unload();
- imageManager.unload();
- fontManager.unload();
- backgroundManager.unload();
- dataManager.unload();
- configManager.load();
- messageManager.load();
- dataManager.load();
- // image manager must load before font manager
- imageManager.load();
- fontManager.load();
- // team manager must load before nameplates manager
- teamManager.load();
- nameplateManager.load();
- chatBubblesManager.load();
- backgroundManager.load();
- bossBarManager.load();
- actionBarManager.load();
- placeholderManager.load();
-
- if (generate) resourceManager.generateResourcePack();
- }
-
- public static CustomNameplates getInstance() {
- return plugin;
- }
-
- public static BukkitAudiences getAdventure() {
- return adventure;
- }
-
- public static ProtocolManager getProtocolManager() {
- return protocolManager;
- }
-
- public ResourceManager getResourceManager() {
- return this.resourceManager;
- }
-
- public DataManager getDataManager() {
- return this.dataManager;
- }
-
- public PlaceholderManager getPlaceholderManager() {
- return placeholderManager;
- }
-
- public BossBarManager getBossBarManager() {
- return bossBarManager;
- }
-
- public ActionBarManager getActionBarManager() {
- return actionBarManager;
- }
-
- public NameplateManager getNameplateManager() {
- return nameplateManager;
- }
-
- public ChatBubblesManager getChatBubblesManager() {
- return chatBubblesManager;
- }
-
- public VersionHelper getVersionHelper() {
- return versionHelper;
- }
-
- public FontManager getFontManager() {
- return fontManager;
- }
-
- public TeamManager getTeamManager() {
- return teamManager;
- }
-
- public BackgroundManager getBackgroundManager() {
- return backgroundManager;
- }
-
- public ImageManager getImageManager() {
- return imageManager;
- }
-
- public CustomNameplatesAPI getAPI() {
- return customNameplatesAPI;
- }
-
- public SchedulerPlatform getScheduler() {
- return scheduler.getInstance();
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/api/CustomNameplatesAPI.java b/src/main/java/net/momirealms/customnameplates/api/CustomNameplatesAPI.java
deleted file mode 100644
index 70959d0..0000000
--- a/src/main/java/net/momirealms/customnameplates/api/CustomNameplatesAPI.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.api;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.object.nameplate.NameplatesTeam;
-import org.bukkit.entity.Player;
-
-public class CustomNameplatesAPI {
-
- private static CustomNameplatesAPI api;
- private final CustomNameplates plugin;
-
- public CustomNameplatesAPI(CustomNameplates plugin) {
- this.plugin = plugin;
- }
-
- public void init() {
- api = this;
- }
-
- public static CustomNameplatesAPI getInstance() {
- return api;
- }
-
- @Deprecated
- public static CustomNameplatesAPI getAPI() {
- return api;
- }
-
- public boolean doesNameplateExist(String nameplate) {
- return plugin.getNameplateManager().existNameplate(nameplate);
- }
-
- public boolean doesBubbleExist(String bubble) {
- return plugin.getChatBubblesManager().existBubble(bubble);
- }
-
- public void equipNameplate(Player player, String nameplate) {
- plugin.getDataManager().equipNameplate(player, nameplate);
- updateAndSave(player);
- }
-
- private void updateAndSave(Player player) {
- updateNameplateTeam(player);
- plugin.getScheduler().runTaskAsync(() -> {
- plugin.getDataManager().saveData(player);
- });
- }
-
- public void equipBubble(Player player, String bubble) {
- plugin.getDataManager().equipBubble(player, bubble);
- plugin.getScheduler().runTaskAsync(() -> {
- plugin.getDataManager().saveData(player);
- });
- }
-
- public void unEquipNameplate(Player player) {
- plugin.getDataManager().equipNameplate(player, "none");
- updateAndSave(player);
- }
-
- public void unEquipBubble(Player player) {
- plugin.getDataManager().equipBubble(player, "none");
- plugin.getScheduler().runTaskAsync(() -> {
- plugin.getDataManager().saveData(player);
- });
- }
-
- public void updateNameplateTeam(Player player) {
- NameplatesTeam nameplatesTeam = plugin.getTeamManager().getNameplateTeam(player.getUniqueId());
- if (nameplatesTeam != null) {
- nameplatesTeam.update(true);
- plugin.getTeamManager().sendUpdateToAll(player, true);
- }
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/api/events/BubblesEvent.java b/src/main/java/net/momirealms/customnameplates/api/events/BubblesEvent.java
deleted file mode 100644
index 8f658ae..0000000
--- a/src/main/java/net/momirealms/customnameplates/api/events/BubblesEvent.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.api.events;
-
-import org.bukkit.entity.Player;
-import org.bukkit.event.Cancellable;
-import org.bukkit.event.HandlerList;
-import org.bukkit.event.player.PlayerEvent;
-import org.jetbrains.annotations.NotNull;
-
-public class BubblesEvent extends PlayerEvent implements Cancellable {
-
- private boolean cancelled;
- private String bubble;
- private String text;
- private static final HandlerList handlerList = new HandlerList();
-
- public BubblesEvent(@NotNull Player who, String bubble, String text) {
- super(who);
- this.cancelled = false;
- this.bubble = bubble;
- this.text = text;
- }
-
- @Override
- public boolean isCancelled() {
- return cancelled;
- }
-
- @Override
- public void setCancelled(boolean cancel) {
- cancelled = cancel;
- }
-
- public static HandlerList getHandlerList() {
- return handlerList;
- }
-
- @NotNull
- @Override
- public HandlerList getHandlers() {
- return getHandlerList();
- }
-
- public String getBubble() {
- return bubble;
- }
-
- public void setBubble(String bubble) {
- this.bubble = bubble;
- }
-
- public String getText() {
- return text;
- }
-
- public void setText(String text) {
- this.text = text;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/bungeecord/BungeeConfigManager.java b/src/main/java/net/momirealms/customnameplates/bungeecord/BungeeConfigManager.java
deleted file mode 100644
index 7a9f1e0..0000000
--- a/src/main/java/net/momirealms/customnameplates/bungeecord/BungeeConfigManager.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.bungeecord;
-
-public class BungeeConfigManager {
-
- private final CustomNameplatesBC plugin;
-
- public BungeeConfigManager(CustomNameplatesBC plugin) {
- this.plugin = plugin;
- }
-
- private boolean tab;
-
- public void load() {
- tab = plugin.getProxy().getPluginManager().getPlugin("TAB") != null;
- if (tab) {
- plugin.getLogger().info("TAB hooked");
- }
- }
-
- public boolean isTab() {
- return tab;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/bungeecord/BungeeEventListener.java b/src/main/java/net/momirealms/customnameplates/bungeecord/BungeeEventListener.java
deleted file mode 100644
index 35d6cb6..0000000
--- a/src/main/java/net/momirealms/customnameplates/bungeecord/BungeeEventListener.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.bungeecord;
-
-import com.google.common.io.ByteArrayDataInput;
-import com.google.common.io.ByteArrayDataOutput;
-import com.google.common.io.ByteStreams;
-import me.neznamy.tab.api.TabAPI;
-import me.neznamy.tab.api.TabPlayer;
-import me.neznamy.tab.api.tablist.SortingManager;
-import net.md_5.bungee.api.connection.ProxiedPlayer;
-import net.md_5.bungee.api.event.PluginMessageEvent;
-import net.md_5.bungee.api.plugin.Listener;
-import net.md_5.bungee.event.EventHandler;
-
-import java.util.Objects;
-import java.util.Optional;
-
-public class BungeeEventListener implements Listener {
-
- private final CustomNameplatesBC plugin;
- private final SortingManager sortingManager;
-
- public BungeeEventListener (CustomNameplatesBC plugin) {
- this.plugin = plugin;
- this.sortingManager = TabAPI.getInstance().getSortingManager();
- }
-
- @EventHandler
- @SuppressWarnings("UnstableApiUsage")
- public void onReceived(PluginMessageEvent event) {
- String channel = event.getTag();
- if (event.isCancelled() || !Objects.equals("customnameplates:cnp", channel)) {
- return;
- }
- ByteArrayDataInput dataInput = ByteStreams.newDataInput(event.getData());
- parseMessage(dataInput);
- }
-
- @SuppressWarnings("UnstableApiUsage")
- private void parseMessage(ByteArrayDataInput dataInput) {
- String playerName = dataInput.readUTF();
- String teamName = playerName;
- if (plugin.getBungeeConfig().isTab()) {
- TabPlayer tabPlayer = TabAPI.getInstance().getPlayer(playerName);
- if (tabPlayer != null) {
- teamName = Optional.ofNullable(sortingManager.getOriginalTeamName(tabPlayer)).orElse(playerName);
- }
- }
- ProxiedPlayer proxiedPlayer = plugin.getProxy().getPlayer(playerName);
- ByteArrayDataOutput byteArrayDataOutput = ByteStreams.newDataOutput();
- byteArrayDataOutput.writeUTF(playerName);
- byteArrayDataOutput.writeUTF(teamName);
- proxiedPlayer.getServer().sendData("customnameplates:cnp", byteArrayDataOutput.toByteArray());
- }
-}
\ No newline at end of file
diff --git a/src/main/java/net/momirealms/customnameplates/bungeecord/CustomNameplatesBC.java b/src/main/java/net/momirealms/customnameplates/bungeecord/CustomNameplatesBC.java
deleted file mode 100644
index 23daf86..0000000
--- a/src/main/java/net/momirealms/customnameplates/bungeecord/CustomNameplatesBC.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.bungeecord;
-
-import net.md_5.bungee.api.plugin.Plugin;
-
-public class CustomNameplatesBC extends Plugin {
-
- public static CustomNameplatesBC bungeePlugin;
- private BungeeEventListener bungeeEventListener;
- private BungeeConfigManager bungeeConfigManager;
-
- @Override
- public void onEnable() {
- bungeePlugin = this;
- this.bungeeEventListener = new BungeeEventListener(this);
- this.getProxy().registerChannel("customnameplates:cnp");
- this.getProxy().getPluginManager().registerListener(this, bungeeEventListener);
- this.bungeeConfigManager = new BungeeConfigManager(this);
- this.bungeeConfigManager.load();
- }
-
- @Override
- public void onDisable() {
- this.getProxy().unregisterChannel("customnameplates:cnp");
- this.getProxy().getPluginManager().unregisterListener(bungeeEventListener);
- }
-
- public BungeeConfigManager getBungeeConfig() {
- return bungeeConfigManager;
- }
-
- public static CustomNameplatesBC getPlugin() {
- return bungeePlugin;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/AbstractMainCommand.java b/src/main/java/net/momirealms/customnameplates/command/AbstractMainCommand.java
deleted file mode 100644
index 8fb1b5d..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/AbstractMainCommand.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command;
-
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandSender;
-import org.bukkit.command.TabExecutor;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-
-public abstract class AbstractMainCommand implements TabExecutor {
-
- protected final Map subCommandMap;
- protected String name;
-
- public AbstractMainCommand(String name) {
- this.name = name;
- this.subCommandMap = new ConcurrentHashMap<>();
- }
-
- public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
- List argList = Arrays.asList(args);
- if (argList.size() < 1) {
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.nonArgs);
- return true;
- }
- AbstractSubCommand subCommand = subCommandMap.get(argList.get(0));
- if (subCommand != null) {
- if (!sender.hasPermission(name + "." + subCommand.getSubCommand())) {
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.noPerm);
- return true;
- }
- return subCommand.onCommand(sender, argList.subList(1, argList.size()));
- } else {
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.unavailableArgs);
- return true;
- }
- }
-
- @Override
- public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
- List argList = Arrays.asList(args);
- if (argList.size() <= 1) {
- List returnList = new ArrayList<>(subCommandMap.keySet());
- returnList.removeIf(str -> !str.startsWith(args[0]) || !sender.hasPermission(name + "." + str));
- return returnList;
- }
- AbstractSubCommand subCommand = subCommandMap.get(argList.get(0));
- if (subCommand != null)
- return subCommand.onTabComplete(sender, argList.subList(1, argList.size()));
- else
- return Collections.singletonList("");
- }
-
- public void regSubCommand(AbstractSubCommand executor) {
- subCommandMap.put(executor.getSubCommand(), executor);
- }
-
- public Map getSubCommandMap() {
- return subCommandMap;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/AbstractSubCommand.java b/src/main/java/net/momirealms/customnameplates/command/AbstractSubCommand.java
deleted file mode 100644
index c9a3c03..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/AbstractSubCommand.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.HumanEntity;
-import org.bukkit.entity.Player;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
-
-public abstract class AbstractSubCommand {
-
- private final String command;
- private Map subCommandMap;
-
- public AbstractSubCommand(String command) {
- this.command = command;
- }
-
- public boolean onCommand(CommandSender sender, List args) {
- if (subCommandMap == null || args.size() < 1) {
- return true;
- }
- AbstractSubCommand subCommand = subCommandMap.get(args.get(0));
- if (subCommand == null) {
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.unavailableArgs);
- } else {
- subCommand.onCommand(sender, args.subList(1, args.size()));
- }
- return true;
- }
-
- public List onTabComplete(CommandSender sender, List args) {
- if (subCommandMap == null)
- return Collections.singletonList("");
- if (args.size() <= 1) {
- List returnList = new ArrayList<>(subCommandMap.keySet());
- returnList.removeIf(str -> !str.startsWith(args.get(0)));
- return returnList;
- }
- AbstractSubCommand subCmd = subCommandMap.get(args.get(0));
- if (subCmd != null)
- return subCommandMap.get(args.get(0)).onTabComplete(sender, args.subList(1, args.size()));
- return Collections.singletonList("");
- }
-
- public String getSubCommand() {
- return command;
- }
-
- public Map getSubCommands() {
- return Collections.unmodifiableMap(subCommandMap);
- }
-
- public void regSubCommand(AbstractSubCommand command) {
- if (subCommandMap == null) {
- subCommandMap = new ConcurrentHashMap<>();
- }
- subCommandMap.put(command.getSubCommand(), command);
- }
-
- protected List online_players() {
- return Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName).collect(Collectors.toList());
- }
-
- protected List allNameplates() {
- return new ArrayList<>(CustomNameplates.getInstance().getNameplateManager().getNameplateConfigMap().keySet());
- }
-
- protected List allBubbles() {
- return new ArrayList<>(CustomNameplates.getInstance().getChatBubblesManager().getBubbleConfigMap().keySet());
- }
-
- protected boolean notExist(CommandSender commandSender, String type, String value) {
- if (type.equals("nameplate")) {
- if (!CustomNameplatesAPI.getInstance().doesNameplateExist(value)) {
- AdventureUtils.sendMessage(commandSender, MessageManager.prefix + MessageManager.np_not_exist);
- return true;
- }
- } else if (type.equals("bubble")) {
- if (!CustomNameplatesAPI.getInstance().doesBubbleExist(value)) {
- AdventureUtils.sendMessage(commandSender, MessageManager.prefix + MessageManager.bb_not_exist);
- return true;
- }
- }
- return false;
- }
-
- protected boolean noConsoleExecute(CommandSender commandSender) {
- if (!(commandSender instanceof Player)) {
- AdventureUtils.consoleMessage(MessageManager.prefix + MessageManager.no_console);
- return true;
- }
- return false;
- }
-
- protected boolean lackArgs(CommandSender commandSender, int required, int current) {
- if (required > current) {
- AdventureUtils.sendMessage(commandSender, MessageManager.prefix + MessageManager.lackArgs);
- return true;
- }
- return false;
- }
-
- protected boolean playerNotOnline(CommandSender commandSender, String player) {
- if (Bukkit.getPlayer(player) == null) {
- AdventureUtils.sendMessage(commandSender, MessageManager.prefix + MessageManager.not_online.replace("{Player}", player));
- return true;
- }
- return false;
- }
-
- protected List filterStartingWith(List list, String prefix) {
- return list.stream().filter(s -> s.startsWith(prefix)).collect(Collectors.toList());
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/BubblesCommand.java b/src/main/java/net/momirealms/customnameplates/command/BubblesCommand.java
deleted file mode 100644
index 09aab66..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/BubblesCommand.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command;
-
-import net.momirealms.customnameplates.command.subcmd.*;
-
-public class BubblesCommand extends AbstractMainCommand {
-
- public BubblesCommand() {
- super("bubbles");
- regDefaultSubCommands();
- }
-
- private void regDefaultSubCommands() {
- regSubCommand(BubblesEquipCommand.INSTANCE);
- regSubCommand(BubblesUnequipCommand.INSTANCE);
- regSubCommand(BubblesForceEquipCommand.INSTANCE);
- regSubCommand(BubblesForceUnequipCommand.INSTANCE);
- regSubCommand(BubblesListCommand.INSTANCE);
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/NameplateCommand.java b/src/main/java/net/momirealms/customnameplates/command/NameplateCommand.java
deleted file mode 100644
index d441142..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/NameplateCommand.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command;
-
-import net.momirealms.customnameplates.command.subcmd.*;
-
-public class NameplateCommand extends AbstractMainCommand {
-
-
- public NameplateCommand() {
- super("nameplates");
- regDefaultSubCommands();
- }
-
- private void regDefaultSubCommands() {
- regSubCommand(ReloadCommand.INSTANCE);
- regSubCommand(NameplatesEquipCommand.INSTANCE);
- regSubCommand(NameplatesForceEquipCommand.INSTANCE);
- regSubCommand(NameplatesUnequipCommand.INSTANCE);
- regSubCommand(NameplatesForceUnequipCommand.INSTANCE);
- regSubCommand(PreviewCommand.INSTANCE);
- regSubCommand(ForcePreviewCommand.INSTANCE);
- regSubCommand(NameplatesListCommand.INSTANCE);
- regSubCommand(HelpCommand.INSTANCE);
- regSubCommand(AboutCommand.INSTANCE);
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/AboutCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/AboutCommand.java
deleted file mode 100644
index ee8a3b5..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/AboutCommand.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.CommandSender;
-
-import java.util.List;
-
-public class AboutCommand extends AbstractSubCommand {
-
- public static final AboutCommand INSTANCE = new AboutCommand();
-
- public AboutCommand() {
- super("about");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- AdventureUtils.sendMessage(sender, "<#3CB371>⚓ CustomNameplates - <#98FB98>" + CustomNameplates.getInstance().getVersionHelper().getPluginVersion());
- AdventureUtils.sendMessage(sender, "<#7FFFAA>A plugin that provides adjustable images for texts");
- AdventureUtils.sendMessage(sender, "<#DA70D6>\uD83E\uDDEA Author: <#FFC0CB>XiaoMoMi");
- AdventureUtils.sendMessage(sender, "<#FF7F50>\uD83D\uDD25 Contributors: <#FFA07A>TopOrigin");
- AdventureUtils.sendMessage(sender, "<#FFD700>⭐ Document <#A9A9A9>| <#FAFAD2>⛏ Github <#A9A9A9>| <#48D1CC>\uD83D\uDD14 Polymart");
- return true;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesEquipCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesEquipCommand.java
deleted file mode 100644
index 953240d..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesEquipCommand.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class BubblesEquipCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new BubblesEquipCommand();
-
- public BubblesEquipCommand() {
- super("equip");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (noConsoleExecute(sender) || lackArgs(sender, 1, args.size()) || notExist(sender, "bubble", args.get(0)))
- return true;
- if (!sender.hasPermission("bubbles.equip." + args.get(0))) {
- AdventureUtils.playerMessage((Player) sender, MessageManager.prefix + MessageManager.bb_notAvailable);
- return true;
- }
- CustomNameplatesAPI.getInstance().equipBubble((Player) sender, args.get(0));
- AdventureUtils.playerMessage((Player) sender, MessageManager.prefix + MessageManager.bb_equip.replace("{Bubble}", CustomNameplates.getInstance().getChatBubblesManager().getBubbleConfig(args.get(0)).display_name()));
- return true;
- }
-
- @Override
- public List onTabComplete(CommandSender sender, List args) {
- if (args.size() == 1 && sender instanceof Player player) {
- return filterStartingWith(CustomNameplates.getInstance().getChatBubblesManager().getAvailableBubbles(player), args.get(0));
- }
- return null;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesForceEquipCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesForceEquipCommand.java
deleted file mode 100644
index e023295..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesForceEquipCommand.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class BubblesForceEquipCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new BubblesForceEquipCommand();
-
- public BubblesForceEquipCommand() {
- super("forceequip");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (lackArgs(sender, 2, args.size()) || playerNotOnline(sender, args.get(0)) || notExist(sender, "bubble", args.get(1)))
- return true;
-
- Player player = Bukkit.getPlayer(args.get(0));
- CustomNameplatesAPI.getInstance().equipBubble(player, args.get(1));
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.bb_force_equip.replace("{Bubble}", CustomNameplates.getInstance().getChatBubblesManager().getBubbleConfig(args.get(1)).display_name()).replace("{Player}", args.get(0)));
- return true;
- }
-
- @Override
- public List onTabComplete(CommandSender sender, List args) {
- if (args.size() == 1) {
- return filterStartingWith(online_players(), args.get(0));
- }
- if (args.size() == 2) {
- return filterStartingWith(allBubbles(), args.get(1));
- }
- return null;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesForceUnequipCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesForceUnequipCommand.java
deleted file mode 100644
index 24de2b9..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesForceUnequipCommand.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class BubblesForceUnequipCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new BubblesForceUnequipCommand();
-
- public BubblesForceUnequipCommand() {
- super("forceunequip");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (lackArgs(sender, 1, args.size()) || playerNotOnline(sender, args.get(0)))
- return true;
- Player player = Bukkit.getPlayer(args.get(0));
- CustomNameplatesAPI.getInstance().unEquipBubble(player);
- AdventureUtils.sendMessage(sender,MessageManager.prefix + MessageManager.bb_force_unEquip.replace("{Player}", args.get(0)));
- return true;
- }
-
- @Override
- public List onTabComplete(CommandSender sender, List args) {
- if (args.size() == 1) {
- return filterStartingWith(online_players(), args.get(0));
- }
- return null;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesListCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesListCommand.java
deleted file mode 100644
index 5d00018..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesListCommand.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-import java.util.StringJoiner;
-
-public class BubblesListCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new BubblesListCommand();
-
- public BubblesListCommand() {
- super("list");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (noConsoleExecute(sender)) return true;
- Player player = (Player) sender;
- List availableBubbles = CustomNameplates.getInstance().getChatBubblesManager().getAvailableBubbles(player);
- if (availableBubbles.size() != 0) {
- StringJoiner stringJoiner = new StringJoiner(", ");
- for (String availableBubble : availableBubbles) {
- stringJoiner.add(availableBubble);
- }
- AdventureUtils.playerMessage(player, MessageManager.prefix + MessageManager.bb_available.replace("{Bubbles}", stringJoiner.toString()));
- }
- else {
- AdventureUtils.playerMessage(player, MessageManager.prefix + MessageManager.bb_haveNone);
- }
- return true;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesUnequipCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesUnequipCommand.java
deleted file mode 100644
index d1d6e8c..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/BubblesUnequipCommand.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class BubblesUnequipCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new BubblesUnequipCommand();
-
- public BubblesUnequipCommand() {
- super("unequip");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (noConsoleExecute(sender)) return true;
- Player player = (Player) sender;
- CustomNameplatesAPI.getInstance().unEquipBubble(player);
- AdventureUtils.playerMessage(player, MessageManager.prefix + MessageManager.bb_unEquip);
- return true;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/ForcePreviewCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/ForcePreviewCommand.java
deleted file mode 100644
index 2034be1..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/ForcePreviewCommand.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.kyori.adventure.key.Key;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.TextColor;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.manager.NameplateManager;
-import net.momirealms.customnameplates.object.DisplayMode;
-import net.momirealms.customnameplates.object.nameplate.NameplateConfig;
-import net.momirealms.customnameplates.object.nameplate.NameplatesTeam;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import net.momirealms.customnameplates.utils.ArmorStandUtils;
-import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class ForcePreviewCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new ForcePreviewCommand();
-
- public ForcePreviewCommand() {
- super("forcepreview");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (lackArgs(sender, 1, args.size()) || playerNotOnline(sender, args.get(0))) return true;
- Player player = Bukkit.getPlayer(args.get(0));
- assert player != null;
- NameplateManager nameplateManager = CustomNameplates.getInstance().getNameplateManager();
- if (nameplateManager.isInCoolDown(player)) {
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.coolDown);
- return true;
- }
- String nameplate = args.size() >= 2 ? args.get(1) : nameplateManager.getEquippedNameplate(player);
- NameplateConfig nameplateConfig = nameplateManager.getNameplateConfig(nameplate);
- if (nameplateConfig == null) {
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.np_not_exist);
- return true;
- }
- if (nameplateManager.getMode() == DisplayMode.TEAM) {
- NameplatesTeam team = CustomNameplates.getInstance().getTeamManager().getNameplateTeam(player.getUniqueId());
- if (team != null) {
- Component full = team.getNameplatePrefixComponent()
- .append(Component.text(player.getName()).color(TextColor.color(AdventureUtils.colorToDecimal(team.getColor()))).font(Key.key("minecraft:default"))
- .append(team.getNameplateSuffixComponent()));
- ArmorStandUtils.preview(full, player, (int) nameplateManager.getPreview_time());
- }
- } else if (nameplateManager.getMode() == DisplayMode.ARMOR_STAND || nameplateManager.getMode() == DisplayMode.TEXT_DISPLAY) {
- nameplateManager.showPlayerArmorStandTags(player, nameplate);
- } else {
- AdventureUtils.sendMessage(sender, MessageManager.prefix + "Nameplate is disabled.");
- }
- return true;
- }
-
- @Override
- public List onTabComplete(CommandSender sender, List args) {
- if (args.size() == 1) {
- return filterStartingWith(online_players(), args.get(0));
- }
- if (args.size() == 2) {
- return filterStartingWith(allNameplates(), args.get(1));
- }
- return null;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/HelpCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/HelpCommand.java
deleted file mode 100644
index 5dcd920..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/HelpCommand.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.CommandSender;
-
-import java.util.List;
-
-public class HelpCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new HelpCommand();
-
- public HelpCommand() {
- super("help");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- AdventureUtils.sendMessage(sender, "<#3CB371>Command usage:");
- AdventureUtils.sendMessage(sender, " ├─<#FFFACD> ");
- AdventureUtils.sendMessage(sender, " └─<#FFFACD><#E1FFFF>[Optional Augument]");
- AdventureUtils.sendMessage(sender, "<#3CB371>/nameplates");
- AdventureUtils.sendMessage(sender, " ├─help");
- AdventureUtils.sendMessage(sender, " ├─about");
- AdventureUtils.sendMessage(sender, " ├─reload <#98FB98>Reload the plugin");
- AdventureUtils.sendMessage(sender, " ├─list <#98FB98>Show a list of available nameplates");
- AdventureUtils.sendMessage(sender, " ├─equip <#FFFACD> <#98FB98>Equip a nameplate");
- AdventureUtils.sendMessage(sender, " ├─forceequip <#FFFACD> <#98FB98>Force a player to equip a nameplate");
- AdventureUtils.sendMessage(sender, " ├─unequip <#98FB98>Unequip the current nameplate");
- AdventureUtils.sendMessage(sender, " ├─forceunequip <#FFFACD> <#98FB98>Force a player to unequip his nameplate");
- AdventureUtils.sendMessage(sender, " ├─preview <#98FB98>Preview your current nameplate");
- AdventureUtils.sendMessage(sender, " └─forcepreview <#E1FFFF>[nameplate] <#98FB98>Force a player to preview the nameplate");
- AdventureUtils.sendMessage(sender, "<#3CB371>/bubbles");
- AdventureUtils.sendMessage(sender, " ├─list <#98FB98>Show a list of available bubbles");
- AdventureUtils.sendMessage(sender, " ├─equip <#FFFACD><#98FB98>Equip a bubble");
- AdventureUtils.sendMessage(sender, " ├─forceequip <#FFFACD> <#98FB98>Force a player to equip a bubble");
- AdventureUtils.sendMessage(sender, " ├─unequip <#98FB98>Unequip the current bubble");
- AdventureUtils.sendMessage(sender, " └─forceunequip <#FFFACD> <#98FB98>Force a player to unequip his bubble");
- return true;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesEquipCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesEquipCommand.java
deleted file mode 100644
index ddcfc14..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesEquipCommand.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class NameplatesEquipCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new NameplatesEquipCommand();
-
- public NameplatesEquipCommand() {
- super("equip");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (noConsoleExecute(sender) || lackArgs(sender, 1, args.size()) || notExist(sender, "nameplate", args.get(0))) return true;
- if (!sender.hasPermission("nameplates.equip." + args.get(0))) {
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.np_notAvailable);
- return true;
- }
- Player player = (Player) sender;
- CustomNameplatesAPI.getInstance().equipNameplate(player, args.get(0));
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.np_equip.replace("{Nameplate}", CustomNameplates.getInstance().getNameplateManager().getNameplateConfig(args.get(0)).display_name()));
- return true;
- }
-
- @Override
- public List onTabComplete(CommandSender sender, List args) {
- if (args.size() == 1 && sender instanceof Player player) {
- return filterStartingWith(CustomNameplates.getInstance().getNameplateManager().getAvailableNameplates(player), args.get(0));
- }
- return null;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesForceEquipCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesForceEquipCommand.java
deleted file mode 100644
index 77bd195..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesForceEquipCommand.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class NameplatesForceEquipCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new NameplatesForceEquipCommand();
-
- public NameplatesForceEquipCommand() {
- super("forceequip");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (lackArgs(sender, 2, args.size()) || playerNotOnline(sender, args.get(0)) || notExist(sender, "nameplate", args.get(1))) return true;
- Player player = Bukkit.getPlayer(args.get(0));
- CustomNameplatesAPI.getInstance().equipNameplate(player, args.get(1));
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.np_force_equip.replace("{Nameplate}", CustomNameplates.getInstance().getNameplateManager().getNameplateConfig(args.get(1)).display_name()).replace("{Player}", args.get(0)));
- return true;
- }
-
- @Override
- public List onTabComplete(CommandSender sender, List args) {
- if (args.size() == 1) {
- return filterStartingWith(online_players(), args.get(0));
- }
- if (args.size() == 2) {
- return filterStartingWith(allNameplates(), args.get(1));
- }
- return null;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesForceUnequipCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesForceUnequipCommand.java
deleted file mode 100644
index c092cf4..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesForceUnequipCommand.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class NameplatesForceUnequipCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new NameplatesForceUnequipCommand();
-
- public NameplatesForceUnequipCommand() {
- super("forceunequip");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (lackArgs(sender, 1, args.size()) || playerNotOnline(sender, args.get(0)))
- return true;
- Player player = Bukkit.getPlayer(args.get(0));
- CustomNameplatesAPI.getInstance().unEquipNameplate(player);
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.np_force_unEquip.replace("{Player}", args.get(0)));
- return true;
- }
-
- @Override
- public List onTabComplete(CommandSender sender, List args) {
- if (args.size() == 1) {
- return filterStartingWith(online_players(), args.get(0));
- }
- return null;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesListCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesListCommand.java
deleted file mode 100644
index bce5484..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesListCommand.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-import java.util.StringJoiner;
-
-public class NameplatesListCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new NameplatesListCommand();
-
- public NameplatesListCommand() {
- super("list");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (noConsoleExecute(sender)) return true;
- Player player = (Player) sender;
- List availableNameplates = CustomNameplates.getInstance().getNameplateManager().getAvailableNameplates(player);
- if (availableNameplates.size() != 0) {
- StringJoiner stringJoiner = new StringJoiner(", ");
- for (String availableNameplate : availableNameplates) {
- stringJoiner.add(availableNameplate);
- }
- AdventureUtils.playerMessage(player, MessageManager.prefix + MessageManager.np_available.replace("{Nameplates}", stringJoiner.toString()));
- } else {
- AdventureUtils.playerMessage(player, MessageManager.prefix + MessageManager.np_haveNone);
- }
- return true;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesUnequipCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesUnequipCommand.java
deleted file mode 100644
index 7a580fa..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/NameplatesUnequipCommand.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.api.CustomNameplatesAPI;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class NameplatesUnequipCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new NameplatesUnequipCommand();
-
- public NameplatesUnequipCommand() {
- super("unequip");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (noConsoleExecute(sender)) return true;
- Player player = (Player) sender;
- CustomNameplatesAPI.getInstance().unEquipNameplate(player);
- AdventureUtils.playerMessage(player, MessageManager.prefix + MessageManager.np_unEquip);
- return true;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/PreviewCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/PreviewCommand.java
deleted file mode 100644
index c977df7..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/PreviewCommand.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.kyori.adventure.key.Key;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.TextColor;
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.manager.NameplateManager;
-import net.momirealms.customnameplates.object.DisplayMode;
-import net.momirealms.customnameplates.object.nameplate.NameplatesTeam;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import net.momirealms.customnameplates.utils.ArmorStandUtils;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-
-public class PreviewCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new PreviewCommand();
- public PreviewCommand() {
- super("preview");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- if (noConsoleExecute(sender)) return true;
- Player player = (Player) sender;
- NameplateManager nameplateManager = CustomNameplates.getInstance().getNameplateManager();
- if (nameplateManager.isInCoolDown(player)) {
- AdventureUtils.playerMessage(player, MessageManager.prefix + MessageManager.coolDown);
- return true;
- }
- if (nameplateManager.getMode() == DisplayMode.TEAM) {
- NameplatesTeam team = CustomNameplates.getInstance().getTeamManager().getNameplateTeam(player.getUniqueId());
- if (team != null) {
- Component full = team.getNameplatePrefixComponent()
- .append(Component.text(player.getName()).color(TextColor.color(AdventureUtils.colorToDecimal(team.getColor()))).font(Key.key("minecraft:default"))
- .append(team.getNameplateSuffixComponent()));
- ArmorStandUtils.preview(full, player, (int) nameplateManager.getPreview_time());
- }
- } else if (nameplateManager.getMode() == DisplayMode.ARMOR_STAND || nameplateManager.getMode() == DisplayMode.TEXT_DISPLAY) {
- nameplateManager.showPlayerArmorStandTags(player);
- } else {
- AdventureUtils.playerMessage(player, MessageManager.prefix + "Nameplate is disabled.");
- return true;
- }
- AdventureUtils.playerMessage(player, MessageManager.prefix + MessageManager.preview);
- return true;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/command/subcmd/ReloadCommand.java b/src/main/java/net/momirealms/customnameplates/command/subcmd/ReloadCommand.java
deleted file mode 100644
index 264d1ce..0000000
--- a/src/main/java/net/momirealms/customnameplates/command/subcmd/ReloadCommand.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) <2022>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package net.momirealms.customnameplates.command.subcmd;
-
-import net.momirealms.customnameplates.CustomNameplates;
-import net.momirealms.customnameplates.command.AbstractSubCommand;
-import net.momirealms.customnameplates.manager.MessageManager;
-import net.momirealms.customnameplates.utils.AdventureUtils;
-import org.bukkit.command.CommandSender;
-
-import java.util.List;
-
-public class ReloadCommand extends AbstractSubCommand {
-
- public static final AbstractSubCommand INSTANCE = new ReloadCommand();
-
- private ReloadCommand() {
- super("reload");
- }
-
- @Override
- public boolean onCommand(CommandSender sender, List args) {
- long time1 = System.currentTimeMillis();
- CustomNameplates.getInstance().reload(true);
- AdventureUtils.sendMessage(sender, MessageManager.prefix + MessageManager.reload.replace("{time}", String.valueOf(System.currentTimeMillis() - time1)));
- return true;
- }
-}
diff --git a/src/main/java/net/momirealms/customnameplates/data/FileStorageImpl.java b/src/main/java/net/momirealms/customnameplates/data/FileStorageImpl.java
deleted file mode 100644
index 24f106b..0000000
--- a/src/main/java/net/momirealms/customnameplates/data/FileStorageImpl.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) <2022>