changes, long delay) {
+ super(manager);
+ this.changes = changes;
+ this.delay = delay;
+ this.current = "";
+ this.ticks = System.currentTimeMillis();
+ this.index = 0;
+ }
+
+ public void tick() {
+ if (ticks < System.currentTimeMillis()) {
+ ticks = System.currentTimeMillis() + delay;
+
+ if (index == changes.size()) {
+ index = 0; // Reset the index
+ current = changes.get(0);
+ return;
+ }
+
+ String nextTitle = changes.get(index);
+ index++;
+ current = nextTitle;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/board/fastboard/FastBoard.java b/src/java/me/keano/azurite/modules/board/fastboard/FastBoard.java
new file mode 100644
index 0000000..ce245bc
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/board/fastboard/FastBoard.java
@@ -0,0 +1,596 @@
+/*
+ * This file is part of FastBoard, licensed under the MIT License.
+ *
+ * Copyright (c) 2019-2021 MrMicky
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package me.keano.azurite.modules.board.fastboard;
+
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Lightweight packet-based scoreboard API for Bukkit plugins.
+ * It can be safely used asynchronously as everything is at packet level.
+ *
+ * The project is on GitHub.
+ *
+ * @author MrMicky
+ * @version 1.2.1
+ */
+public class FastBoard {
+
+ private static final Map, Field[]> PACKETS = new HashMap<>(8);
+ private static final String[] COLOR_CODES = Arrays.stream(ChatColor.values())
+ .map(Object::toString)
+ .toArray(String[]::new);
+ private static final VersionType VERSION_TYPE;
+ // Packets and components
+ private static final Class> CHAT_COMPONENT_CLASS;
+ private static final Class> CHAT_FORMAT_ENUM;
+ private static final Object EMPTY_MESSAGE;
+ private static final Object RESET_FORMATTING;
+ private static final MethodHandle MESSAGE_FROM_STRING;
+ private static final MethodHandle PLAYER_CONNECTION;
+ private static final MethodHandle SEND_PACKET;
+ private static final MethodHandle PLAYER_GET_HANDLE;
+ // Scoreboard packets
+ private static final FastReflection.PacketConstructor PACKET_SB_OBJ;
+ private static final FastReflection.PacketConstructor PACKET_SB_DISPLAY_OBJ;
+ private static final FastReflection.PacketConstructor PACKET_SB_SCORE;
+ private static final FastReflection.PacketConstructor PACKET_SB_TEAM;
+ private static final FastReflection.PacketConstructor PACKET_SB_SERIALIZABLE_TEAM;
+ // Scoreboard enums
+ private static final Class> ENUM_SB_HEALTH_DISPLAY;
+ private static final Class> ENUM_SB_ACTION;
+ private static final Object ENUM_SB_HEALTH_DISPLAY_INTEGER;
+ private static final Object ENUM_SB_ACTION_CHANGE;
+ private static final Object ENUM_SB_ACTION_REMOVE;
+
+ static {
+ try {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+ if (FastReflection.isRepackaged()) {
+ VERSION_TYPE = VersionType.V1_17;
+ } else if (FastReflection.nmsOptionalClass(null, "ScoreboardServer$Action").isPresent()) {
+ VERSION_TYPE = VersionType.V1_13;
+ } else if (FastReflection.nmsOptionalClass(null, "IScoreboardCriteria$EnumScoreboardHealthDisplay").isPresent()) {
+ VERSION_TYPE = VersionType.V1_8;
+ } else {
+ VERSION_TYPE = VersionType.V1_7;
+ }
+
+ String gameProtocolPackage = "network.protocol.game";
+ Class> craftPlayerClass = FastReflection.obcClass("entity.CraftPlayer");
+ Class> craftChatMessageClass = FastReflection.obcClass("util.CraftChatMessage");
+ Class> entityPlayerClass = FastReflection.nmsClass("server.level", "EntityPlayer");
+ Class> playerConnectionClass = FastReflection.nmsClass("server.network", "PlayerConnection");
+ Class> packetClass = FastReflection.nmsClass("network.protocol", "Packet");
+ Class> packetSbObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardObjective");
+ Class> packetSbDisplayObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardDisplayObjective");
+ Class> packetSbScoreClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardScore");
+ Class> packetSbTeamClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardTeam");
+ Class> sbTeamClass = VersionType.V1_17.isHigherOrEqual()
+ ? FastReflection.innerClass(packetSbTeamClass, innerClass -> !innerClass.isEnum()) : null;
+ Field playerConnectionField = Arrays.stream(entityPlayerClass.getFields())
+ .filter(field -> field.getType().isAssignableFrom(playerConnectionClass))
+ .findFirst().orElseThrow(NoSuchFieldException::new);
+ Method sendPacketMethod = Arrays.stream(playerConnectionClass.getMethods())
+ .filter(m -> m.getParameterCount() == 1 && m.getParameterTypes()[0] == packetClass)
+ .findFirst().orElseThrow(NoSuchMethodException::new);
+
+ MESSAGE_FROM_STRING = lookup.unreflect(craftChatMessageClass.getMethod("fromString", String.class));
+ CHAT_COMPONENT_CLASS = FastReflection.nmsClass("network.chat", "IChatBaseComponent");
+ CHAT_FORMAT_ENUM = FastReflection.nmsClass(null, "EnumChatFormat");
+ EMPTY_MESSAGE = Array.get(MESSAGE_FROM_STRING.invoke(""), 0);
+ RESET_FORMATTING = FastReflection.enumValueOf(CHAT_FORMAT_ENUM, "RESET", 21);
+ PLAYER_GET_HANDLE = lookup.findVirtual(craftPlayerClass, "getHandle", MethodType.methodType(entityPlayerClass));
+ PLAYER_CONNECTION = lookup.unreflectGetter(playerConnectionField);
+ SEND_PACKET = lookup.unreflect(sendPacketMethod);
+ PACKET_SB_OBJ = FastReflection.findPacketConstructor(packetSbObjClass, lookup);
+ PACKET_SB_DISPLAY_OBJ = FastReflection.findPacketConstructor(packetSbDisplayObjClass, lookup);
+ PACKET_SB_SCORE = FastReflection.findPacketConstructor(packetSbScoreClass, lookup);
+ PACKET_SB_TEAM = FastReflection.findPacketConstructor(packetSbTeamClass, lookup);
+ PACKET_SB_SERIALIZABLE_TEAM = sbTeamClass == null ? null : FastReflection.findPacketConstructor(sbTeamClass, lookup);
+
+ for (Class> clazz : Arrays.asList(packetSbObjClass, packetSbDisplayObjClass, packetSbScoreClass, packetSbTeamClass, sbTeamClass)) {
+ if (clazz == null) {
+ continue;
+ }
+ Field[] fields = Arrays.stream(clazz.getDeclaredFields())
+ .filter(field -> !Modifier.isStatic(field.getModifiers()))
+ .toArray(Field[]::new);
+ for (Field field : fields) {
+ field.setAccessible(true);
+ }
+ PACKETS.put(clazz, fields);
+ }
+
+ if (VersionType.V1_8.isHigherOrEqual()) {
+ String enumSbActionClass = VersionType.V1_13.isHigherOrEqual()
+ ? "ScoreboardServer$Action"
+ : "PacketPlayOutScoreboardScore$EnumScoreboardAction";
+ ENUM_SB_HEALTH_DISPLAY = FastReflection.nmsClass("world.scores.criteria", "IScoreboardCriteria$EnumScoreboardHealthDisplay");
+ ENUM_SB_ACTION = FastReflection.nmsClass("server", enumSbActionClass);
+ ENUM_SB_HEALTH_DISPLAY_INTEGER = FastReflection.enumValueOf(ENUM_SB_HEALTH_DISPLAY, "INTEGER", 0);
+ ENUM_SB_ACTION_CHANGE = FastReflection.enumValueOf(ENUM_SB_ACTION, "CHANGE", 0);
+ ENUM_SB_ACTION_REMOVE = FastReflection.enumValueOf(ENUM_SB_ACTION, "REMOVE", 1);
+ } else {
+ ENUM_SB_HEALTH_DISPLAY = null;
+ ENUM_SB_ACTION = null;
+ ENUM_SB_HEALTH_DISPLAY_INTEGER = null;
+ ENUM_SB_ACTION_CHANGE = null;
+ ENUM_SB_ACTION_REMOVE = null;
+ }
+ } catch (Throwable t) {
+ throw new ExceptionInInitializerError(t);
+ }
+ }
+
+ private final Player player;
+ private final String id;
+
+ private final List lines = new ArrayList<>();
+ private final Map scores = new HashMap<>();
+ private String title = ChatColor.RESET.toString();
+
+ private boolean deleted = false;
+
+ /**
+ * Creates a new FastBoard.
+ *
+ * @param player the owner of the scoreboard
+ */
+ public FastBoard(Player player) {
+ this.player = Objects.requireNonNull(player, "player");
+ this.id = "fb-" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
+
+ try {
+ sendObjectivePacket(ObjectiveMode.CREATE);
+ sendDisplayObjectivePacket();
+ } catch (Throwable t) {
+ throw new RuntimeException("Unable to create scoreboard", t);
+ }
+ }
+
+ /**
+ * Get the scoreboard title.
+ *
+ * @return the scoreboard title
+ */
+ public String getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Update the scoreboard title.
+ *
+ * @param title the new scoreboard title
+ * @throws IllegalArgumentException if the title is longer than 32 chars on 1.12 or lower
+ * @throws IllegalStateException if {@link #delete()} was call before
+ */
+ public void setTitle(String title) {
+ if (this.title.equals(Objects.requireNonNull(title, "title"))) {
+ return;
+ }
+
+ if (!VersionType.V1_13.isHigherOrEqual() && title.length() > 32) {
+ throw new IllegalArgumentException("Title is longer than 32 chars");
+ }
+
+ this.title = title;
+
+ try {
+ sendObjectivePacket(ObjectiveMode.UPDATE);
+ } catch (Throwable t) {
+ throw new RuntimeException("Unable to update scoreboard title", t);
+ }
+ }
+
+ /**
+ * Get the scoreboard lines.
+ *
+ * @return the scoreboard lines
+ */
+ public List getLines() {
+ return new ArrayList<>(this.lines);
+ }
+
+ /**
+ * Update the lines of the scoreboard
+ *
+ * @param lines the new scoreboard lines
+ * @throws IllegalArgumentException if one line is longer than 48 chars on 1.12 or lower
+ * @throws IllegalStateException if {@link #delete()} was call before
+ */
+ public synchronized void setLines(List lines) {
+ if (lines.size() >= 21) {
+ lines = lines.subList(0, 21);
+ }
+
+ if (!VersionType.V1_13.isHigherOrEqual()) {
+ int lineCount = 0;
+ for (String s : lines) {
+ if (s != null && s.length() > 48) {
+ throw new IllegalArgumentException("Line " + lineCount + " is longer than 48 chars");
+ }
+ lineCount++;
+ }
+ }
+
+ List oldLines = new ArrayList<>(this.lines);
+ this.lines.clear();
+ this.lines.addAll(lines);
+
+ int linesSize = this.lines.size();
+
+ try {
+ if (oldLines.size() != linesSize) {
+ List oldLinesCopy = new ArrayList<>(oldLines);
+
+ if (oldLines.size() > linesSize) {
+ for (int i = oldLinesCopy.size(); i > linesSize; i--) {
+ String score = this.scores.remove(i - 1);
+
+ if (score == null) {
+ score = COLOR_CODES[i - 1];
+ }
+
+ sendTeamPacket(i - 1);
+ sendScorePacket(ScoreboardAction.REMOVE, score, 0);
+
+ oldLines.remove(0);
+ }
+ } else {
+ for (int i = oldLinesCopy.size(); i < linesSize; i++) {
+ sendScoreChange(i, null, null);
+ sendTeamPacket(i, TeamMode.CREATE, "", "");
+ }
+ }
+ }
+
+ for (int i = 0; i < linesSize; i++) {
+ if (!Objects.equals(getLineByScore(oldLines, i), getLineByScore(i))) {
+ sendLineChange(i);
+ }
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException("Unable to update scoreboard lines", t);
+ }
+ }
+
+ /**
+ * Get the player who has the scoreboard.
+ *
+ * @return current player for this FastBoard
+ */
+ public Player getPlayer() {
+ return this.player;
+ }
+
+ /**
+ * Get if the scoreboard is deleted.
+ *
+ * @return true if the scoreboard is deleted
+ */
+ public boolean isDeleted() {
+ return this.deleted;
+ }
+
+ /**
+ * Get the scoreboard size (the number of lines).
+ *
+ * @return the size
+ */
+ public int size() {
+ return this.lines.size();
+ }
+
+ /**
+ * Delete this FastBoard, and will remove the scoreboard for the associated player if he is online.
+ * After this, all uses of {@link #setLines(List)} and {@link #setTitle(String)} will throw an {@link IllegalStateException}
+ *
+ * @throws IllegalStateException if this was already call before
+ */
+ public void delete() {
+ try {
+ for (int i = 0; i < this.lines.size(); i++) {
+ sendTeamPacket(i);
+ }
+
+ sendObjectivePacket(ObjectiveMode.REMOVE);
+ } catch (Throwable t) {
+ throw new RuntimeException("Unable to delete scoreboard", t);
+ }
+
+ this.deleted = true;
+ }
+
+ /**
+ * Return if the player has a prefix/suffix characters limit.
+ * By default, it returns true only in 1.12 or lower.
+ * This method can be overridden to fix compatibility with some versions support plugin.
+ *
+ * @return max length
+ */
+ protected boolean hasLinesMaxLength() {
+ return !VersionType.V1_13.isHigherOrEqual();
+ }
+
+ private String getLineByScore(int score) {
+ return getLineByScore(this.lines, score);
+ }
+
+ private String getLineByScore(List lines, int score) {
+ return score < lines.size() ? lines.get(lines.size() - score - 1) : null;
+ }
+
+ private void sendObjectivePacket(ObjectiveMode mode) throws Throwable {
+ Object packet = PACKET_SB_OBJ.invoke();
+
+ setField(packet, String.class, this.id);
+ setField(packet, int.class, mode.ordinal());
+
+ if (mode != ObjectiveMode.REMOVE) {
+ setComponentField(packet, this.title, 1);
+
+ if (VersionType.V1_8.isHigherOrEqual()) {
+ setField(packet, ENUM_SB_HEALTH_DISPLAY, ENUM_SB_HEALTH_DISPLAY_INTEGER);
+ }
+ } else if (VERSION_TYPE == VersionType.V1_7) {
+ setField(packet, String.class, "", 1);
+ }
+
+ sendPacket(packet);
+ }
+
+ private void sendDisplayObjectivePacket() throws Throwable {
+ Object packet = PACKET_SB_DISPLAY_OBJ.invoke();
+
+ setField(packet, int.class, 1); // Position (1: sidebar)
+ setField(packet, String.class, this.id); // Score Name
+
+ sendPacket(packet);
+ }
+
+ private void sendScorePacket(ScoreboardAction action, String name, int score)
+ throws Throwable {
+ Object packet = PACKET_SB_SCORE.invoke();
+
+ Objects.requireNonNull(name, "name");
+
+ setField(packet, String.class, name, 0); // Player Name
+
+ if (VersionType.V1_8.isHigherOrEqual()) {
+ Object enumAction = action == ScoreboardAction.REMOVE
+ ? ENUM_SB_ACTION_REMOVE : ENUM_SB_ACTION_CHANGE;
+ setField(packet, ENUM_SB_ACTION, enumAction);
+ } else {
+ setField(packet, int.class, action.ordinal(), 1); // Action
+ }
+
+ if (action == ScoreboardAction.CHANGE) {
+ setField(packet, String.class, this.id, 1); // Objective Name
+ setField(packet, int.class, score); // Score
+ }
+
+ sendPacket(packet);
+ }
+
+ private void sendTeamPacket(int score) throws Throwable {
+ sendTeamPacket(score, TeamMode.REMOVE, null, null);
+ }
+
+ private void sendTeamPacket(int score, TeamMode mode, String prefix, String suffix)
+ throws Throwable {
+ if (mode == TeamMode.ADD_PLAYERS || mode == TeamMode.REMOVE_PLAYERS) {
+ throw new UnsupportedOperationException();
+ }
+
+ Object packet = PACKET_SB_TEAM.invoke();
+
+ setField(packet, String.class, this.id + ':' + score); // Team name
+ setField(packet, int.class, mode.ordinal(), VERSION_TYPE == VersionType.V1_8 ? 1 : 0); // Update mode
+
+ if (mode == TeamMode.CREATE || mode == TeamMode.UPDATE) {
+ Objects.requireNonNull(prefix, "prefix");
+ Objects.requireNonNull(suffix, "suffix");
+
+ if (VersionType.V1_17.isHigherOrEqual()) {
+ Object team = PACKET_SB_SERIALIZABLE_TEAM.invoke();
+ // Since the packet is initialized with null values, we need to change more things.
+ setComponentField(team, "", 0); // Display name
+ setField(team, CHAT_FORMAT_ENUM, RESET_FORMATTING); // Color
+ setComponentField(team, prefix, 1); // Prefix
+ setComponentField(team, suffix, 2); // Suffix
+ setField(team, String.class, "always", 0); // Visibility
+ setField(team, String.class, "always", 1); // Collisions
+ setField(packet, Optional.class, Optional.of(team));
+ } else {
+ setComponentField(packet, prefix, 2); // Prefix
+ setComponentField(packet, suffix, 3); // Suffix
+ setField(packet, String.class, "always", 4); // Visibility for 1.8+
+ setField(packet, String.class, "always", 5); // Collisions for 1.9+
+ }
+
+ if (mode == TeamMode.CREATE) {
+ String player = this.scores.get(score);
+
+ if (player == null) {
+ player = COLOR_CODES[score];
+ }
+
+ setField(packet, Collection.class, Collections.singletonList(player)); // Players in the team
+ }
+ }
+
+ sendPacket(packet);
+ }
+
+ private void sendLineChange(int score) throws Throwable {
+ int maxLength = hasLinesMaxLength() ? 16 : 1024;
+ String line = getLineByScore(score);
+ String prefix;
+ String name = null;
+ String suffix = null;
+
+ if (line == null || line.isEmpty()) {
+ prefix = COLOR_CODES[score] + ChatColor.RESET;
+ } else if (line.length() <= maxLength) {
+ prefix = line;
+ } else {
+ int i = line.charAt(maxLength - 1) == ChatColor.COLOR_CHAR
+ ? (maxLength - 1) : maxLength;
+ if (line.length() <= 28) {
+ // Prevent splitting color codes
+ prefix = line.substring(0, i);
+ String suffixTmp = line.substring(i);
+ ChatColor chatColor = null;
+
+ if (suffixTmp.length() >= 2 && suffixTmp.charAt(0) == ChatColor.COLOR_CHAR) {
+ chatColor = ChatColor.getByChar(suffixTmp.charAt(1));
+ }
+
+ String color = ChatColor.getLastColors(prefix);
+ boolean addColor = chatColor == null || chatColor.isFormat();
+
+ suffix = (addColor ? (color.isEmpty() ? ChatColor.RESET.toString() : color) : "") + suffixTmp;
+ } else {
+ int suffixLength = Math.min(maxLength, line.length() - i);
+ int index2 = line.charAt(line.length() - suffixLength - 1) == ChatColor.COLOR_CHAR
+ ? (line.length() - suffixLength - 1) : (line.length() - suffixLength);
+
+ prefix = line.substring(0, i);
+ name = line.substring(i, index2);
+ suffix = line.substring(index2);
+ }
+ }
+
+ if (prefix.length() > maxLength || (suffix != null && suffix.length() > maxLength)) {
+ // Something went wrong, just cut to prevent client crash/kick
+ prefix = prefix.substring(0, maxLength);
+ suffix = (suffix != null) ? suffix.substring(0, maxLength) : null;
+ }
+
+ if (sendScoreChange(score, name, prefix)) {
+ sendTeamPacket(score);
+ sendTeamPacket(score, TeamMode.CREATE, prefix, suffix != null ? suffix : "");
+ } else {
+ sendTeamPacket(score, TeamMode.UPDATE, prefix, suffix != null ? suffix : "");
+ }
+ }
+
+ private boolean sendScoreChange(int score, String value, String prefix) throws Throwable {
+ String oldValue = this.scores.get(score);
+ String newValue = value != null ? value : COLOR_CODES[score];
+
+ if (Objects.equals(oldValue, newValue)) {
+ return false;
+ }
+
+ if (this.scores.containsValue(newValue)) {
+ String colors = COLOR_CODES[score] + ChatColor.getLastColors(prefix + value);
+ newValue = newValue + colors;
+ }
+
+ if (Objects.equals(oldValue, newValue)) {
+ return false;
+ }
+
+ this.scores.put(score, newValue);
+
+ if (oldValue != null) {
+ sendScorePacket(ScoreboardAction.REMOVE, oldValue, score);
+ }
+
+ sendScorePacket(ScoreboardAction.CHANGE, newValue, score);
+ return true;
+ }
+
+ private void sendPacket(Object packet) throws Throwable {
+ if (this.deleted) {
+ throw new IllegalStateException("This FastBoard is deleted");
+ }
+
+ if (this.player.isOnline()) {
+ Object entityPlayer = PLAYER_GET_HANDLE.invoke(this.player);
+ Object playerConnection = PLAYER_CONNECTION.invoke(entityPlayer);
+ SEND_PACKET.invoke(playerConnection, packet);
+ }
+ }
+
+ private void setField(Object object, Class> fieldType, Object value) throws ReflectiveOperationException {
+ setField(object, fieldType, value, 0);
+ }
+
+ private void setField(Object packet, Class> fieldType, Object value, int count) throws ReflectiveOperationException {
+ int i = 0;
+ for (Field field : PACKETS.get(packet.getClass())) {
+ if (field.getType() == fieldType && count == i++) {
+ field.set(packet, value);
+ }
+ }
+ }
+
+ private void setComponentField(Object packet, String value, int count) throws Throwable {
+ if (!VersionType.V1_13.isHigherOrEqual()) {
+ setField(packet, String.class, value, count);
+ return;
+ }
+
+ int i = 0;
+ for (Field field : PACKETS.get(packet.getClass())) {
+ if ((field.getType() == String.class || field.getType() == CHAT_COMPONENT_CLASS) && count == i++) {
+ field.set(packet, value.isEmpty() ? EMPTY_MESSAGE : Array.get(MESSAGE_FROM_STRING.invoke(value), 0));
+ }
+ }
+ }
+
+ enum ObjectiveMode {
+ CREATE, REMOVE, UPDATE
+ }
+
+ enum TeamMode {
+ CREATE, REMOVE, UPDATE, ADD_PLAYERS, REMOVE_PLAYERS
+ }
+
+ enum ScoreboardAction {
+ CHANGE, REMOVE
+ }
+
+ enum VersionType {
+ V1_7, V1_8, V1_13, V1_17;
+
+ public boolean isHigherOrEqual() {
+ return VERSION_TYPE.ordinal() >= ordinal();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/board/fastboard/FastReflection.java b/src/java/me/keano/azurite/modules/board/fastboard/FastReflection.java
new file mode 100644
index 0000000..705a919
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/board/fastboard/FastReflection.java
@@ -0,0 +1,137 @@
+package me.keano.azurite.modules.board.fastboard;
+
+import org.bukkit.Bukkit;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+/**
+ * Small reflection utility class to use CraftBukkit and NMS.
+ *
+ * @author MrMicky
+ */
+public final class FastReflection {
+
+ public static final String OBC_PACKAGE = "org.bukkit.craftbukkit";
+ public static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().substring(OBC_PACKAGE.length() + 1);
+ private static final String NM_PACKAGE = "net.minecraft";
+ public static final String NMS_PACKAGE = NM_PACKAGE + ".server";
+ private static final MethodType VOID_METHOD_TYPE = MethodType.methodType(void.class);
+ private static final boolean NMS_REPACKAGED = optionalClass(NM_PACKAGE + ".network.protocol.Packet").isPresent();
+
+ private static volatile Object theUnsafe;
+
+ private FastReflection() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static boolean isRepackaged() {
+ return NMS_REPACKAGED;
+ }
+
+ public static String nmsClassName(String post1_17package, String className) {
+ if (NMS_REPACKAGED) {
+ String classPackage = post1_17package == null ? NM_PACKAGE : NM_PACKAGE + '.' + post1_17package;
+ return classPackage + '.' + className;
+ }
+ return NMS_PACKAGE + '.' + VERSION + '.' + className;
+ }
+
+ public static Class> nmsClass(String post1_17package, String className) throws ClassNotFoundException {
+ return Class.forName(nmsClassName(post1_17package, className));
+ }
+
+ public static Optional> nmsOptionalClass(String post1_17package, String className) {
+ return optionalClass(nmsClassName(post1_17package, className));
+ }
+
+ public static String obcClassName(String className) {
+ return OBC_PACKAGE + '.' + VERSION + '.' + className;
+ }
+
+ public static Class> obcClass(String className) throws ClassNotFoundException {
+ return Class.forName(obcClassName(className));
+ }
+
+ public static Optional> optionalClass(String className) {
+ try {
+ return Optional.of(Class.forName(className));
+ } catch (ClassNotFoundException e) {
+ return Optional.empty();
+ }
+ }
+
+ public static Object enumValueOf(Class> enumClass, String enumName) {
+ return Enum.valueOf(enumClass.asSubclass(Enum.class), enumName);
+ }
+
+ public static Object enumValueOf(Class> enumClass, String enumName, int fallbackOrdinal) {
+ try {
+ return enumValueOf(enumClass, enumName);
+ } catch (IllegalArgumentException e) {
+ Object[] constants = enumClass.getEnumConstants();
+ if (constants.length > fallbackOrdinal) {
+ return constants[fallbackOrdinal];
+ }
+ throw e;
+ }
+ }
+
+ public static String nmsClassName(String className) {
+ return NMS_PACKAGE + '.' + VERSION + '.' + className;
+ }
+
+ public static Class> nmsClass(String className) throws ClassNotFoundException {
+ return Class.forName(nmsClassName(className));
+ }
+
+ public static Optional> nmsOptionalClass(String className) {
+ return optionalClass(nmsClassName(className));
+ }
+
+ public static Optional> obcOptionalClass(String className) {
+ return optionalClass(obcClassName(className));
+ }
+
+ static Class> innerClass(Class> parentClass, Predicate> classPredicate) throws ClassNotFoundException {
+ for (Class> innerClass : parentClass.getDeclaredClasses()) {
+ if (classPredicate.test(innerClass)) {
+ return innerClass;
+ }
+ }
+ throw new ClassNotFoundException("No class in " + parentClass.getCanonicalName() + " matches the predicate.");
+ }
+
+ public static PacketConstructor findPacketConstructor(Class> packetClass, MethodHandles.Lookup lookup) throws Exception {
+ try {
+ MethodHandle constructor = lookup.findConstructor(packetClass, VOID_METHOD_TYPE);
+ return constructor::invoke;
+ } catch (NoSuchMethodException | IllegalAccessException e) {
+ // try below with Unsafe
+ }
+
+ if (theUnsafe == null) {
+ synchronized (FastReflection.class) {
+ if (theUnsafe == null) {
+ Class> unsafeClass = Class.forName("sun.misc.Unsafe");
+ Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
+ theUnsafeField.setAccessible(true);
+ theUnsafe = theUnsafeField.get(null);
+ }
+ }
+ }
+
+ MethodType allocateMethodType = MethodType.methodType(Object.class, Class.class);
+ MethodHandle allocateMethod = lookup.findVirtual(theUnsafe.getClass(), "allocateInstance", allocateMethodType);
+ return () -> allocateMethod.invoke(theUnsafe, packetClass);
+ }
+
+ @FunctionalInterface
+ interface PacketConstructor {
+ Object invoke() throws Throwable;
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/board/listener/BoardListener.java b/src/java/me/keano/azurite/modules/board/listener/BoardListener.java
new file mode 100644
index 0000000..cb2c185
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/board/listener/BoardListener.java
@@ -0,0 +1,33 @@
+package me.keano.azurite.modules.board.listener;
+
+import me.keano.azurite.modules.board.Board;
+import me.keano.azurite.modules.board.BoardManager;
+import me.keano.azurite.modules.framework.Module;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class BoardListener extends Module {
+
+ public BoardListener(BoardManager manager) {
+ super(manager);
+ }
+
+ @EventHandler
+ public void onJoin(PlayerJoinEvent e) {
+ Player player = e.getPlayer();
+ getManager().getBoards().put(player.getUniqueId(), new Board(getManager(), player));
+ }
+
+ @EventHandler
+ public void onQuit(PlayerQuitEvent e) {
+ Player player = e.getPlayer();
+ getManager().getBoards().remove(player.getUniqueId());
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/board/task/BoardTask.java b/src/java/me/keano/azurite/modules/board/task/BoardTask.java
new file mode 100644
index 0000000..b679c46
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/board/task/BoardTask.java
@@ -0,0 +1,53 @@
+package me.keano.azurite.modules.board.task;
+
+import me.keano.azurite.modules.board.Board;
+import me.keano.azurite.modules.board.BoardManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.Module;
+import me.keano.azurite.modules.timers.type.PlayerTimer;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class BoardTask extends Module implements Runnable {
+
+ public BoardTask(BoardManager manager) {
+ super(manager);
+ }
+
+ @Override
+ public void run() {
+ try {
+
+ // tick timers before ticking the board, this will allow it to be sync with expiring.
+ for (PlayerTimer timer : getInstance().getTimerManager().getPlayerTimers().values()) {
+ timer.tick();
+ }
+
+ // Tick sotw
+ getInstance().getSotwManager().getRemainingString();
+
+ if (Config.SCOREBOARD_ENABLED) {
+ // Tick title and footer change
+ getManager().getTitle().tick();
+ getManager().getFooter().tick();
+
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ Board board = getManager().getBoards().get(player.getUniqueId());
+
+ // not sure if this would happen but just in case.
+ if (board != null) {
+ board.update();
+ }
+ }
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/CommandManager.java b/src/java/me/keano/azurite/modules/commands/CommandManager.java
new file mode 100644
index 0000000..6be715f
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/CommandManager.java
@@ -0,0 +1,300 @@
+package me.keano.azurite.modules.commands;
+
+import me.keano.azurite.HCF;
+import me.keano.azurite.modules.ability.command.AbilitiesCommand;
+import me.keano.azurite.modules.ability.command.AbilityCommand;
+import me.keano.azurite.modules.commands.type.*;
+import me.keano.azurite.modules.commands.type.essential.*;
+import me.keano.azurite.modules.deathban.command.DeathbanCommand;
+import me.keano.azurite.modules.events.conquest.command.ConquestCommand;
+import me.keano.azurite.modules.events.eotw.command.EOTWCommand;
+import me.keano.azurite.modules.events.king.command.KingCommand;
+import me.keano.azurite.modules.events.koth.command.KothCommand;
+import me.keano.azurite.modules.events.purge.command.PurgeCommand;
+import me.keano.azurite.modules.events.sotw.command.SOTWCommand;
+import me.keano.azurite.modules.framework.Manager;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.killstreaks.command.KillstreakCommand;
+import me.keano.azurite.modules.killtag.command.KilltagCommand;
+import me.keano.azurite.modules.kits.commands.KitCommand;
+import me.keano.azurite.modules.reclaims.command.DailyCommand;
+import me.keano.azurite.modules.reclaims.command.ReclaimCommand;
+import me.keano.azurite.modules.reclaims.command.ResetDailyCommand;
+import me.keano.azurite.modules.reclaims.command.ResetReclaimCommand;
+import me.keano.azurite.modules.scheduler.command.SchedulesCommand;
+import me.keano.azurite.modules.spawners.command.SpawnerCommand;
+import me.keano.azurite.modules.staff.command.*;
+import me.keano.azurite.modules.teams.commands.citadel.CitadelCommand;
+import me.keano.azurite.modules.teams.commands.mountain.MountainCommand;
+import me.keano.azurite.modules.teams.commands.systeam.SysTeamCommand;
+import me.keano.azurite.modules.teams.commands.team.TeamCommand;
+import me.keano.azurite.modules.teams.commands.team.args.TeamCampArg;
+import me.keano.azurite.modules.teams.commands.team.args.TeamHQArg;
+import me.keano.azurite.modules.timers.command.customtimer.CTimerCommand;
+import me.keano.azurite.modules.timers.command.keyall.KeyAllCommand;
+import me.keano.azurite.modules.timers.command.timer.TimerCommand;
+import me.keano.azurite.utils.ReflectionUtils;
+import org.bukkit.command.CommandMap;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.SimpleCommandMap;
+import org.bukkit.command.defaults.BukkitCommand;
+
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+@SuppressWarnings("unchecked")
+public class CommandManager extends Manager {
+
+ private static final Field KNOWN_COMMANDS = ReflectionUtils.accessField(SimpleCommandMap.class, "knownCommands");
+
+ private final List commands;
+
+ public CommandManager(HCF instance) {
+ super(instance);
+
+ this.commands = new ArrayList<>();
+
+ this.load();
+ this.checkCommands(); // Check before registering
+
+ instance.getVersionManager().getVersion().getCommandMap().registerAll("azurite", commands
+ .stream()
+ .map(Command::asBukkitCommand)
+ .collect(Collectors.toList())
+ );
+ }
+
+ private void load() {
+ commands.addAll(Arrays.asList(
+ // Main Azurite
+ new AzuriteCommand(this),
+
+ // Team Commands
+ new TeamCommand(this),
+ new SysTeamCommand(this),
+ new MountainCommand(this),
+
+ // Commands (w/ arguments)
+ new TimerCommand(this),
+ new CTimerCommand(this),
+ new DeathbanCommand(this),
+ new KitCommand(this),
+ new KothCommand(this),
+ new KingCommand(this),
+ new CitadelCommand(this),
+ new KeyAllCommand(this),
+
+ // Essential commands
+ new PlaytimeCommand(this),
+ new CraftCommand(this),
+ new TopCommand(this),
+ new RenameCommand(this),
+ new RepairCommand(this),
+ new GappleCommand(this),
+ new GamemodeCommand(this),
+ new GMCCommand(this),
+ new GMSCommand(this),
+ new PvPCommand(this),
+ new BalanceCommand(this),
+ new EcoManageCommand(this),
+ new WorldCommand(this),
+ new TLCommand(this),
+ new SettingsCommand(this),
+ new LogoutCommand(this),
+ new TpCommand(this),
+ new TpHereCommand(this),
+ new TpRandomCommand(this),
+ new TpLocCommand(this),
+ new HealCommand(this),
+ new FeedCommand(this),
+ new BroadcastCommand(this),
+ new MessageCommand(this),
+ new ReplyCommand(this),
+ new IgnoreCommand(this),
+ new ClearCommand(this),
+ new ClearChatCommand(this),
+ new ToggleSoundsCommand(this),
+ new TogglePMCommand(this),
+ new LivesCommand(this),
+ new PayCommand(this),
+ new KillCommand(this),
+ new ReclaimCommand(this),
+ new ResetReclaimCommand(this),
+ new DailyCommand(this),
+ new ResetDailyCommand(this),
+ new SpawnerCommand(this),
+ new AbilityCommand(this),
+ new AbilitiesCommand(this),
+ new LivesManageCommand(this),
+ new SOTWCommand(this),
+ new SetEndCommand(this),
+ new TpAllCommand(this),
+ new PingCommand(this),
+ new SchedulesCommand(this),
+ new StaffCommand(this),
+ new StaffBuildCommand(this),
+ new VanishCommand(this),
+ new FreezeCommand(this),
+ new SpawnCommand(this),
+ new StatsCommand(this),
+ new LeaderboardsCommand(this),
+ new EOTWCommand(this),
+ new KillstreakCommand(this),
+ new FocusCommand(this),
+ new UnfocusCommand(this),
+ new CrowbarCommand(this),
+ new PurgeCommand(this),
+ new RedeemCommand(this),
+ new ResetRedeemCommand(this),
+ new FalltrapTokenCommand(this),
+ new BaseTokenCommand(this),
+ new SendFalltrapTokenCommand(this),
+ new SendBaseTokenCommand(this),
+ new ManageFalltrapTokenCommand(this),
+ new ManageBaseTokenCommand(this),
+ new KilltagCommand(this),
+ new ConquestCommand(this),
+ new RestoreCommand(this),
+ new CobbleCommand(this),
+ new EnchantCommand(this),
+ new InvseeCommand(this),
+ new LFFCommand(this),
+ new HelpCommand(this),
+ new StackCommand(this),
+ new EditMenuCommand(this),
+ new EndPlayersCommand(this),
+ new NetherPlayersCommand(this),
+ new NearCommand(this),
+ new StaffChatCommand(this),
+ new RequestCommand(this),
+ new RequestsMenuCommand(this),
+ new ReportCommand(this),
+ new ReportsMenuCommand(this),
+ new EChestCommand(this),
+ new CoordsCommand(this),
+ new CopyInvCommand(this),
+ new InvToCommand(this),
+ new CheckRedeemsCommand(this),
+ new HideStaffCommand(this),
+ new SetSpawnCommand(this),
+ new SetKillsCommand(this),
+ new SetDeathsCommand(this),
+ new LastKillsCommand(this),
+ new LastDeathsCommand(this),
+ new SetKillstreakCommand(this),
+ new SetRepairCommand(this)
+ ));
+
+ // Alias for /f home
+ commands.add(new Command(this, "hq") {
+ private final TeamHQArg hqArg = new TeamHQArg(this.getManager());
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return Collections.singletonList(hqArg.usage());
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ hqArg.execute(sender, args);
+ }
+
+ @Override
+ public List tabComplete(CommandSender sender, String[] args) {
+ return hqArg.tabComplete(sender, args);
+ }
+ });
+
+ // Alias for /f camp
+ commands.add(new Command(this, "camp") {
+ private final TeamCampArg campArg = new TeamCampArg(this.getManager());
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return Collections.singletonList(campArg.usage());
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ campArg.execute(sender, args);
+ }
+
+ @Override
+ public List tabComplete(CommandSender sender, String[] args) {
+ return campArg.tabComplete(sender, args);
+ }
+ });
+ }
+
+ // Used to check all the disabled commands and don't register them
+ private void checkCommands() {
+ List disabled = getConfig().getStringList("DISABLED_COMMANDS.MAIN_COMMANDS");
+ Iterator iterator = commands.iterator();
+
+ while (iterator.hasNext()) {
+ Command command = iterator.next();
+
+ if (command.getName().equalsIgnoreCase("AZURITE")) continue;
+
+ if (disabled.contains(command.getName().toLowerCase())) {
+ iterator.remove();
+ continue;
+ }
+
+ for (String alias : command.aliases()) {
+ if (!disabled.contains(alias.toLowerCase())) continue;
+ iterator.remove();
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void reload() {
+ CommandMap map = getInstance().getVersionManager().getVersion().getCommandMap();
+
+ for (Command command : commands) {
+ BukkitCommand bukkitCommand = command.asBukkitCommand();
+ boolean isAzuriteCommand = bukkitCommand.getLabel().split(":")[0].equalsIgnoreCase("azurite");
+
+ if (isAzuriteCommand) {
+ bukkitCommand.unregister(map);
+
+ try {
+
+ Map knownCommands = (Map) KNOWN_COMMANDS.get(map);
+ knownCommands.values().removeIf(cmd -> cmd.getName().equals(bukkitCommand.getName()));
+
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ commands.clear();
+ this.load();
+ this.checkCommands();
+
+ map.registerAll("azurite", commands
+ .stream()
+ .map(Command::asBukkitCommand)
+ .collect(Collectors.toList())
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/AzuriteCommand.java b/src/java/me/keano/azurite/modules/commands/type/AzuriteCommand.java
new file mode 100644
index 0000000..c1eff03
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/AzuriteCommand.java
@@ -0,0 +1,182 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.Manager;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.framework.commands.extra.TabCompletion;
+import me.keano.azurite.modules.storage.Storage;
+import me.keano.azurite.modules.teams.Team;
+import me.keano.azurite.modules.teams.TeamManager;
+import me.keano.azurite.modules.teams.enums.TeamType;
+import me.keano.azurite.modules.teams.type.PlayerTeam;
+import me.keano.azurite.modules.users.User;
+import me.keano.azurite.utils.CC;
+import me.keano.azurite.utils.configs.ConfigYML;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class AzuriteCommand extends Command {
+
+ public AzuriteCommand(CommandManager manager) {
+ super(
+ manager,
+ "azurite"
+ );
+ this.completions.add(new TabCompletion(Arrays.asList("reload", "deleteteams", "deleteusers", "version", "forcesave"), 0));
+ this.setPermissible("azurite.reload");
+ }
+
+ @Override
+ public List aliases() {
+ return Arrays.asList(
+ "hcf",
+ "hcfcore"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return Arrays.asList(
+ CC.LINE,
+ "&eThis server is running &dAzuriteHCF&e.",
+ "&eUse &d/azurite reload &eto reload configs.",
+ "&eUse &d/azurite version &eto check your current ver.",
+ "&eUse &d/azurite forcesave &eto save data.",
+ "&eUse &d/azurite deleteteams &eto delete teams.",
+ "&eUse &d/azurite deleteusers &eto delete users.",
+ CC.LINE
+ );
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!sender.hasPermission(permissible)) {
+ return;
+ }
+
+ if (args.length == 0) {
+ sendUsage(sender);
+ return;
+ }
+
+ switch (args[0].toLowerCase()) {
+ case "reload":
+ long now = System.currentTimeMillis();
+
+ for (ConfigYML config : getInstance().getConfigs()) {
+ config.reload(); // reload
+ config.reloadCache(); // then re-cache all the objects in the cache.
+ }
+
+ for (Manager manage : getInstance().getManagers()) {
+ manage.reload();
+ }
+
+ Config.load(getInstance().getConfigsObject(), true);
+
+ sendMessage(sender, "&dAzurite &ehas been reloaded in &a" + (System.currentTimeMillis() - now) + "ms&e.");
+ sendMessage(sender, "&cPLEASE NOTE THIS MIGHT NOT RELOAD SOME THINGS! - A RESTART IS REQUIRED.");
+ return;
+
+ case "version":
+ sendMessage(sender, "&dAzurite &eis currently on version &a" + getInstance().getDescription().getVersion() + "&e.");
+ return;
+
+ case "forcesave":
+ long now1 = System.currentTimeMillis();
+ Storage storage = getInstance().getStorageManager().getStorage();
+
+ storage.saveTimers();
+ storage.saveTeams();
+ storage.saveUsers();
+
+ sendMessage(sender, "&dAzurite &ehas been saved in &a" + (System.currentTimeMillis() - now1) + "ms&e.");
+ return;
+
+ case "deleteteams":
+ TeamManager teamManager = getInstance().getTeamManager();
+ int teams = 0;
+
+ for (Team team : getInstance().getTeamManager().getTeams().values()) {
+ if (team.getType() != TeamType.PLAYER) continue;
+ PlayerTeam playerTeam = (PlayerTeam) team;
+ playerTeam.disband(false);
+ }
+
+ // Do not modify the maps while looping otherwise CME
+ teamManager.getTeams().clear();
+ teamManager.getPlayerTeams().clear();
+ teamManager.getSystemTeams().clear();
+ teamManager.getStringTeams().clear();
+ teamManager.getClaimManager().getClaims().clear();
+
+ sendMessage(sender, "&eYou have deleted &d" + teams + " &eteams.");
+ return;
+
+ case "deleteusers":
+ int users = 0;
+
+ for (User user : getInstance().getUserManager().getUsers().values()) {
+ user.delete();
+ users++;
+ }
+
+ // Fix
+ getInstance().getUserManager().getUsers().clear();
+ getInstance().getUserManager().getUuidCache().clear();
+
+ // Create a user for all online players
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ User user = new User(getInstance().getUserManager(), player.getUniqueId(), player.getName());
+ user.save();
+ }
+
+ sendMessage(sender, "&eYou have deleted &d" + users + " &eusers.");
+ return;
+ }
+
+ sendUsage(sender);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onProcess(PlayerCommandPreprocessEvent e) {
+ Player sender = e.getPlayer();
+
+ if (sender.hasPermission("azurite.reload")) return;
+
+ // /azurite
+ if (e.getMessage().equals("/azurite:" + name) || e.getMessage().equals("/" + name)) {
+ sendMessage(sender, CC.LINE);
+ sendMessage(sender, "&eThis server is running &dAzuriteHCF&e.");
+ sendMessage(sender, "&eMade by &dKeqno_ &efor azurite.cc");
+ sendMessage(sender, "&ehttps://www.mc-market.org/resources/24593/");
+ sendMessage(sender, CC.LINE);
+ return;
+ }
+
+ // Aliases
+ for (String alias : aliases()) {
+ if (e.getMessage().equals("/azurite:" + alias) || e.getMessage().equals("/" + alias)) {
+ sendMessage(sender, CC.LINE);
+ sendMessage(sender, "&eThis server is running &dAzuriteHCF&e.");
+ sendMessage(sender, "&eMade by &dKeqno_ &efor azurite.cc");
+ sendMessage(sender, "&ehttps://www.mc-market.org/resources/24593/");
+ sendMessage(sender, CC.LINE);
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/BalanceCommand.java b/src/java/me/keano/azurite/modules/commands/type/BalanceCommand.java
new file mode 100644
index 0000000..618bb0f
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/BalanceCommand.java
@@ -0,0 +1,73 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.users.User;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class BalanceCommand extends Command {
+
+ public BalanceCommand(CommandManager manager) {
+ super(
+ manager,
+ "balance"
+ );
+ this.setAsync(true);
+ }
+
+ @Override
+ public List aliases() {
+ return Arrays.asList(
+ "eco",
+ "bal",
+ "$"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("BALANCE_COMMAND.USAGE");
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (args.length == 0) {
+ if (sender instanceof Player) {
+ Player player = (Player) sender;
+ int balance = getInstance().getBalanceManager().getBalance(player.getUniqueId());
+ sendMessage(sender, getLanguageConfig().getString("BALANCE_COMMAND.SELF_CHECK")
+ .replace("%balance%", String.valueOf(balance))
+ );
+ return;
+ }
+
+ sendUsage(sender);
+ return;
+ }
+
+ User target = getInstance().getUserManager().getByName(args[0]);
+
+ if (target == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[0])
+ );
+ return;
+ }
+
+ int targetBalance = getInstance().getBalanceManager().getBalance(target.getUniqueID());
+ sendMessage(sender, getLanguageConfig().getString("BALANCE_COMMAND.TARGET_CHECK")
+ .replace("%target%", target.getName())
+ .replace("%balance%", String.valueOf(targetBalance))
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/BaseTokenCommand.java b/src/java/me/keano/azurite/modules/commands/type/BaseTokenCommand.java
new file mode 100644
index 0000000..36fdb72
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/BaseTokenCommand.java
@@ -0,0 +1,71 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.users.User;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class BaseTokenCommand extends Command {
+
+ public BaseTokenCommand(CommandManager manager) {
+ super(
+ manager,
+ "basetoken"
+ );
+ }
+
+ @Override
+ public List aliases() {
+ return Arrays.asList(
+ "btoken",
+ "base"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("BASE_TOKEN_COMMAND.USAGE");
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (args.length == 0) {
+ if (sender instanceof Player) {
+ Player player = (Player) sender;
+ User user = getInstance().getUserManager().getByUUID(player.getUniqueId());
+ sendMessage(sender, getLanguageConfig().getString("BASE_TOKEN_COMMAND.SELF_CHECK")
+ .replace("%balance%", String.valueOf(user.getBaseTokens()))
+ );
+ return;
+ }
+
+ sendUsage(sender);
+ return;
+ }
+
+ User user = getInstance().getUserManager().getByName(args[0]);
+
+ if (user == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[0])
+ );
+ return;
+ }
+
+ int targetBalance = user.getBaseTokens();
+ sendMessage(sender, getLanguageConfig().getString("BASE_TOKEN_COMMAND.TARGET_CHECK")
+ .replace("%target%", user.getName())
+ .replace("%balance%", String.valueOf(targetBalance))
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/CheckRedeemsCommand.java b/src/java/me/keano/azurite/modules/commands/type/CheckRedeemsCommand.java
new file mode 100644
index 0000000..c2d3561
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/CheckRedeemsCommand.java
@@ -0,0 +1,67 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import org.bukkit.command.CommandSender;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class CheckRedeemsCommand extends Command {
+
+ public CheckRedeemsCommand(CommandManager manager) {
+ super(
+ manager,
+ "checkredeems"
+ );
+ this.setPermissible("azurite.checkredeems");
+ }
+
+ @Override
+ public List aliases() {
+ return Arrays.asList(
+ "redeemscount",
+ "redeemcount",
+ "countredeems"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ for (String s : getLanguageConfig().getStringList("CHECKREDEEMS_COMMAND.FORMAT")) {
+ if (!s.equalsIgnoreCase("%redeems%")) {
+ sendMessage(sender, s);
+ continue;
+ }
+
+ for (String string : getMiscConfig().getKeys(false)) {
+ if (!string.endsWith("_REDEEM")) continue;
+
+ String redeem = getMiscConfig().getString(string);
+ String[] split = redeem.split(", ");
+ String path = split[0];
+
+ sendMessage(sender, getLanguageConfig().getString("CHECKREDEEMS_COMMAND.REDEEM_FORMAT")
+ .replace("%name%", getConfig().getString(path + "NAME"))
+ .replace("%count%", String.valueOf(Integer.parseInt(split[1])))
+ );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/ClearLagCommand.java b/src/java/me/keano/azurite/modules/commands/type/ClearLagCommand.java
new file mode 100644
index 0000000..b4094b9
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/ClearLagCommand.java
@@ -0,0 +1,51 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Entity;
+
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class ClearLagCommand extends Command {
+
+ public ClearLagCommand(CommandManager manager) {
+ super(
+ manager,
+ "clearlag"
+ );
+ this.setPermissible("azurite.clearlag");
+ }
+
+ @Override
+ public List aliases() {
+ return null;
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ for (World world : Bukkit.getWorlds()) {
+ for (Entity entity : world.getEntities()) {
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/CobbleCommand.java b/src/java/me/keano/azurite/modules/commands/type/CobbleCommand.java
new file mode 100644
index 0000000..68672fb
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/CobbleCommand.java
@@ -0,0 +1,60 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.users.User;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class CobbleCommand extends Command {
+
+ public CobbleCommand(CommandManager manager) {
+ super(
+ manager,
+ "cobble"
+ );
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.singletonList(
+ "cobblestone"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player)) {
+ sendMessage(sender, Config.PLAYER_ONLY);
+ return;
+ }
+
+ Player player = (Player) sender;
+ User user = getInstance().getUserManager().getByUUID(player.getUniqueId());
+
+ if (user.isCobblePickup()) {
+ user.setCobblePickup(false);
+ user.save();
+ sendMessage(sender, getLanguageConfig().getString("COBBLE_COMMAND.TOGGLED_OFF"));
+
+ } else {
+ user.setCobblePickup(true);
+ user.save();
+ sendMessage(sender, getLanguageConfig().getString("COBBLE_COMMAND.TOGGLED_ON"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/CoordsCommand.java b/src/java/me/keano/azurite/modules/commands/type/CoordsCommand.java
new file mode 100644
index 0000000..f04ac40
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/CoordsCommand.java
@@ -0,0 +1,40 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.commands.Command;
+import org.bukkit.command.CommandSender;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class CoordsCommand extends Command {
+
+ public CoordsCommand(CommandManager manager) {
+ super(
+ manager,
+ "coords"
+ );
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ for (String s : getLanguageConfig().getStringList("COORDS_COMMAND.COORDS_MESSAGE")) {
+ sendMessage(sender, s);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/CopyInvCommand.java b/src/java/me/keano/azurite/modules/commands/type/CopyInvCommand.java
new file mode 100644
index 0000000..d645832
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/CopyInvCommand.java
@@ -0,0 +1,74 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.PlayerInventory;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class CopyInvCommand extends Command {
+
+ public CopyInvCommand(CommandManager manager) {
+ super(
+ manager,
+ "copyinv"
+ );
+ this.setPermissible("azurite.copyinv");
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("COPYINV_COMMAND.USAGE");
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player)) {
+ sendMessage(sender, Config.PLAYER_ONLY);
+ return;
+ }
+
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ if (args.length == 0) {
+ sendUsage(sender);
+ return;
+ }
+
+ Player player = (Player) sender;
+ Player target = Bukkit.getPlayer(args[0]);
+
+ if (target == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[0])
+ );
+ return;
+ }
+
+ PlayerInventory playerInventory = player.getInventory();
+ PlayerInventory targetInventory = target.getInventory();
+ playerInventory.setContents(targetInventory.getContents());
+ playerInventory.setArmorContents(targetInventory.getArmorContents());
+ sendMessage(sender, getLanguageConfig().getString("COPYINV_COMMAND.COPIED")
+ .replace("%player%", target.getName())
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/CrowbarCommand.java b/src/java/me/keano/azurite/modules/commands/type/CrowbarCommand.java
new file mode 100644
index 0000000..e7847df
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/CrowbarCommand.java
@@ -0,0 +1,70 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class CrowbarCommand extends Command {
+
+ public CrowbarCommand(CommandManager manager) {
+ super(manager,
+ "crowbar"
+ );
+ this.setPermissible("azurite.crowbar");
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ if (args.length == 0) {
+ if (!(sender instanceof Player)) {
+ sendMessage(sender, Config.PLAYER_ONLY);
+ return;
+ }
+
+ Player player = (Player) sender;
+ player.getInventory().addItem(Config.CROWBAR.clone());
+ player.updateInventory();
+ sendMessage(sender, getLanguageConfig().getString("CROWBAR_COMMAND.RECEIVED"));
+ return;
+ }
+
+ Player target = Bukkit.getPlayer(args[0]);
+
+ if (target == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[0])
+ );
+ return;
+ }
+
+ target.getInventory().addItem(Config.CROWBAR.clone());
+ target.updateInventory();
+ sendMessage(target, getLanguageConfig().getString("CROWBAR_COMMAND.RECEIVED"));
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/EcoManageCommand.java b/src/java/me/keano/azurite/modules/commands/type/EcoManageCommand.java
new file mode 100644
index 0000000..8d42567
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/EcoManageCommand.java
@@ -0,0 +1,102 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.framework.commands.extra.TabCompletion;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class EcoManageCommand extends Command {
+
+ public EcoManageCommand(CommandManager manager) {
+ super(
+ manager,
+ "ecomanage"
+ );
+ this.completions.add(new TabCompletion(Arrays.asList("set", "add", "plus", "remove", "take"), 0));
+ this.setPermissible("azurite.ecomanage");
+ }
+
+ @Override
+ public List aliases() {
+ return Arrays.asList(
+ "ecomanager",
+ "balmanager"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("ECOMANAGE_COMMAND.USAGE");
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ if (args.length < 3) {
+ sendUsage(sender);
+ return;
+ }
+
+ Player target = Bukkit.getPlayer(args[1]);
+ Integer amount = getInt(args[2]);
+
+ if (target == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[1])
+ );
+ return;
+ }
+
+ if (amount == null || amount < 0) {
+ sendMessage(sender, Config.NOT_VALID_NUMBER
+ .replace("%number%", args[2])
+ );
+ return;
+ }
+
+ switch (args[0].toLowerCase()) {
+ case "take":
+ case "remove":
+ getInstance().getBalanceManager().takeBalance(target, amount);
+ sendMessage(sender, getLanguageConfig().getString("ECOMANAGE_COMMAND.REMOVED_BAL")
+ .replace("%amount%", String.valueOf(amount))
+ .replace("%target%", target.getName())
+ );
+ return;
+
+ case "plus":
+ case "add":
+ getInstance().getBalanceManager().giveBalance(target.getUniqueId(), amount);
+ sendMessage(sender, getLanguageConfig().getString("ECOMANAGE_COMMAND.ADDED_BAL")
+ .replace("%amount%", String.valueOf(amount))
+ .replace("%target%", target.getName())
+ );
+ return;
+
+ case "set":
+ getInstance().getBalanceManager().setBalance(target, amount);
+ sendMessage(sender, getLanguageConfig().getString("ECOMANAGE_COMMAND.SET_BAL")
+ .replace("%amount%", String.valueOf(amount))
+ .replace("%target%", target.getName())
+ );
+ return;
+ }
+
+ sendUsage(sender);
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/EditMenuCommand.java b/src/java/me/keano/azurite/modules/commands/type/EditMenuCommand.java
new file mode 100644
index 0000000..d212ba9
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/EditMenuCommand.java
@@ -0,0 +1,156 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.framework.commands.extra.TabCompletion;
+import me.keano.azurite.modules.framework.menu.Menu;
+import me.keano.azurite.modules.framework.menu.MenuManager;
+import me.keano.azurite.modules.framework.menu.button.Button;
+import me.keano.azurite.utils.BukkitSerialization;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class EditMenuCommand extends Command {
+
+ public EditMenuCommand(CommandManager manager) {
+ super(
+ manager,
+ "editmenu"
+ );
+ this.completions.add(new TabCompletion(Arrays.stream(MenuType.values())
+ .map(MenuType::name).collect(Collectors.toList()), 0, "azurite.editmenu"));
+ this.setPermissible("azurite.editmenu");
+ }
+
+ @Override
+ public List aliases() {
+ return Arrays.asList(
+ "kitmenu",
+ "menu"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("EDIT_MENU_COMMAND.USAGE");
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player)) {
+ sendMessage(sender, Config.PLAYER_ONLY);
+ return;
+ }
+
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ if (args.length == 0) {
+ sendUsage(sender);
+ return;
+ }
+
+ Player player = (Player) sender;
+ MenuType type;
+
+ try {
+
+ type = MenuType.valueOf(args[0]);
+
+ } catch (IllegalArgumentException e) {
+ sendMessage(sender, getLanguageConfig().getString("EDIT_MENU_COMMAND.MENU_NOT_FOUND")
+ .replace("%menu%", args[0])
+ );
+ return;
+ }
+
+ new EditMenu(getInstance().getMenuManager(), player, type).open();
+ }
+
+ private enum MenuType {
+
+ QUICK_REFILL,
+ REFILL
+
+ }
+
+ private static class EditMenu extends Menu {
+
+ private final MenuType type;
+
+ public EditMenu(MenuManager manager, Player player, MenuType type) {
+ super(
+ manager,
+ player,
+ (type == MenuType.REFILL ?
+ manager.getConfig().getString("SIGNS_CONFIG.REFILL_SIGN.MENU_TITLE") :
+ manager.getConfig().getString("SIGNS_CONFIG.QUICK_REFILL_SIGN.MENU_TITLE")),
+ (type == MenuType.REFILL ?
+ manager.getConfig().getInt("SIGNS_CONFIG.REFILL_SIGN.MENU_SIZE") :
+ manager.getConfig().getInt("SIGNS_CONFIG.QUICK_REFILL_SIGN.MENU_SIZE")),
+ false
+ );
+ this.type = type;
+ this.setAllowInteract(true);
+ }
+
+ @Override
+ public Map getButtons(Player player) {
+ Map buttons = new HashMap<>();
+ ItemStack[] current = (type == MenuType.REFILL ? Config.REFILL_SIGN : Config.QUICK_REFILL_SIGN);
+
+ for (int i = 1; i <= current.length; i++) {
+ int copy = i;
+ buttons.put(i, new Button() {
+ @Override
+ public void onClick(InventoryClickEvent e) {
+ // Empty
+ }
+
+ @Override
+ public ItemStack getItemStack() {
+ return current[copy - 1];
+ }
+ });
+ }
+
+ return buttons;
+ }
+
+ @Override
+ public void onClose() {
+ String toSave = BukkitSerialization.itemStackArrayToBase64(Arrays.stream(getInventory().getContents())
+ .filter(Objects::nonNull)
+ .toArray(ItemStack[]::new));
+
+ switch (type) {
+ case REFILL:
+ getMiscConfig().set("REFILL_SIGN", toSave);
+ break;
+
+ case QUICK_REFILL:
+ getMiscConfig().set("QUICK_REFILL", toSave);
+ break;
+ }
+
+ getMiscConfig().save();
+ getMiscConfig().reload();
+ getMiscConfig().reloadCache();
+ Config.load(getInstance().getConfigsObject(), true); // We need to reload the config file / items cached in Config
+ getPlayer().sendMessage(getLanguageConfig().getString("EDIT_MENU_COMMAND.SAVED_MENU"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/EndPlayersCommand.java b/src/java/me/keano/azurite/modules/commands/type/EndPlayersCommand.java
new file mode 100644
index 0000000..53f93fb
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/EndPlayersCommand.java
@@ -0,0 +1,52 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class EndPlayersCommand extends Command {
+
+ public EndPlayersCommand(CommandManager manager) {
+ super(
+ manager,
+ "endplayers"
+ );
+ this.setPermissible("azurite.endplayers");
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ int endPlayers = Bukkit.getWorld("world_the_end").getPlayers().size();
+
+ for (String s : getLanguageConfig().getStringList("ENDPLAYERS_COMMAND.FORMAT")) {
+ sendMessage(sender, s
+ .replace("%endplayers%", String.valueOf(endPlayers))
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/FalltrapTokenCommand.java b/src/java/me/keano/azurite/modules/commands/type/FalltrapTokenCommand.java
new file mode 100644
index 0000000..18ea845
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/FalltrapTokenCommand.java
@@ -0,0 +1,71 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.users.User;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class FalltrapTokenCommand extends Command {
+
+ public FalltrapTokenCommand(CommandManager manager) {
+ super(
+ manager,
+ "falltraptoken"
+ );
+ }
+
+ @Override
+ public List aliases() {
+ return Arrays.asList(
+ "fttoken",
+ "falltrap"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("FALLTRAP_TOKEN_COMMAND.USAGE");
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (args.length == 0) {
+ if (sender instanceof Player) {
+ Player player = (Player) sender;
+ User user = getInstance().getUserManager().getByUUID(player.getUniqueId());
+ sendMessage(sender, getLanguageConfig().getString("FALLTRAP_TOKEN_COMMAND.SELF_CHECK")
+ .replace("%balance%", String.valueOf(user.getFalltrapTokens()))
+ );
+ return;
+ }
+
+ sendUsage(sender);
+ return;
+ }
+
+ User user = getInstance().getUserManager().getByName(args[0]);
+
+ if (user == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[0])
+ );
+ return;
+ }
+
+ int targetBalance = user.getFalltrapTokens();
+ sendMessage(sender, getLanguageConfig().getString("FALLTRAP_TOKEN_COMMAND.TARGET_CHECK")
+ .replace("%target%", user.getName())
+ .replace("%balance%", String.valueOf(targetBalance))
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/FocusCommand.java b/src/java/me/keano/azurite/modules/commands/type/FocusCommand.java
new file mode 100644
index 0000000..170cd24
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/FocusCommand.java
@@ -0,0 +1,87 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.teams.type.PlayerTeam;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class FocusCommand extends Command {
+
+ public FocusCommand(CommandManager manager) {
+ super(
+ manager,
+ "focus"
+ );
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("FOCUS_COMMAND.USAGE");
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player)) {
+ sendMessage(sender, Config.PLAYER_ONLY);
+ return;
+ }
+
+ if (args.length == 0) {
+ sendUsage(sender);
+ return;
+ }
+
+ Player player = (Player) sender;
+ Player target = Bukkit.getPlayer(args[0]);
+ PlayerTeam pt = getInstance().getTeamManager().getByPlayer(player.getUniqueId());
+
+ if (pt == null) {
+ sendMessage(sender, Config.NOT_IN_TEAM);
+ return;
+ }
+
+ if (target == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[0])
+ );
+ return;
+ }
+
+ if (pt.getSingularFocus().contains(target.getUniqueId())) {
+ sendMessage(sender, getLanguageConfig().getString("FOCUS_COMMAND.ALREADY_FOCUSED"));
+ return;
+ }
+
+ if (pt.getPlayers().contains(target.getUniqueId())) {
+ sendMessage(sender, getLanguageConfig().getString("FOCUS_COMMAND.CANNOT_FOCUS_TEAMMATE"));
+ return;
+ }
+
+ if (pt.isAlly(target)) {
+ sendMessage(sender, getLanguageConfig().getString("FOCUS_COMMAND.CANNOT_FOCUS_ALLY"));
+ return;
+ }
+
+ pt.getSingularFocus().add(target.getUniqueId());
+ pt.broadcast(getLanguageConfig().getString("FOCUS_COMMAND.FOCUSED")
+ .replace("%player%", player.getName())
+ .replace("%target%", target.getName())
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/GappleCommand.java b/src/java/me/keano/azurite/modules/commands/type/GappleCommand.java
new file mode 100644
index 0000000..2b1f2ad
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/GappleCommand.java
@@ -0,0 +1,58 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.timers.listeners.playertimers.GappleTimer;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class GappleCommand extends Command {
+
+ public GappleCommand(CommandManager manager) {
+ super(
+ manager,
+ "gapple"
+ );
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.singletonList(
+ "gopple"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player)) {
+ sendMessage(sender, Config.PLAYER_ONLY);
+ return;
+ }
+
+ Player player = (Player) sender;
+ GappleTimer timer = getInstance().getTimerManager().getGappleTimer();
+
+ if (!timer.hasTimer(player)) {
+ sendMessage(sender, getLanguageConfig().getString("GAPPLE_COMMAND.NO_TIMER"));
+ return;
+ }
+
+ sendMessage(sender, getLanguageConfig().getString("GAPPLE_COMMAND.FORMAT")
+ .replace("%remaining%", timer.getRemainingString(player))
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/HelpCommand.java b/src/java/me/keano/azurite/modules/commands/type/HelpCommand.java
new file mode 100644
index 0000000..123a66e
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/HelpCommand.java
@@ -0,0 +1,43 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.commands.Command;
+import org.bukkit.command.CommandSender;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class HelpCommand extends Command {
+
+ public HelpCommand(CommandManager manager) {
+ super(
+ manager,
+ "help"
+ );
+ }
+
+ @Override
+ public List aliases() {
+ return Arrays.asList(
+ "?",
+ "info"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ for (String s : getLanguageConfig().getStringList("HELP_COMMAND.HELP_MESSAGE")) {
+ sendMessage(sender, s);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/InvToCommand.java b/src/java/me/keano/azurite/modules/commands/type/InvToCommand.java
new file mode 100644
index 0000000..e590514
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/InvToCommand.java
@@ -0,0 +1,74 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.PlayerInventory;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class InvToCommand extends Command {
+
+ public InvToCommand(CommandManager manager) {
+ super(
+ manager,
+ "invto"
+ );
+ this.setPermissible("azurite.invto");
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("INVTO_COMMAND.USAGE");
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player)) {
+ sendMessage(sender, Config.PLAYER_ONLY);
+ return;
+ }
+
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ if (args.length == 0) {
+ sendUsage(sender);
+ return;
+ }
+
+ Player player = (Player) sender;
+ Player target = Bukkit.getPlayer(args[0]);
+
+ if (target == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[0])
+ );
+ return;
+ }
+
+ PlayerInventory playerInventory = player.getInventory();
+ PlayerInventory targetInventory = target.getInventory();
+ targetInventory.setContents(playerInventory.getContents());
+ targetInventory.setArmorContents(playerInventory.getArmorContents());
+ sendMessage(sender, getLanguageConfig().getString("INVTO_COMMAND.GAVE")
+ .replace("%player%", target.getName())
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/LFFCommand.java b/src/java/me/keano/azurite/modules/commands/type/LFFCommand.java
new file mode 100644
index 0000000..c6fecbe
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/LFFCommand.java
@@ -0,0 +1,199 @@
+package me.keano.azurite.modules.commands.type;
+
+import lombok.Getter;
+import lombok.Setter;
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.framework.menu.Menu;
+import me.keano.azurite.modules.framework.menu.MenuManager;
+import me.keano.azurite.modules.framework.menu.button.Button;
+import me.keano.azurite.modules.teams.type.PlayerTeam;
+import me.keano.azurite.utils.ItemBuilder;
+import me.keano.azurite.utils.ItemUtils;
+import me.keano.azurite.utils.Utils;
+import me.keano.azurite.utils.extra.Cooldown;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.*;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class LFFCommand extends Command {
+
+ private static Cooldown cooldown;
+
+ public LFFCommand(CommandManager manager) {
+ super(
+ manager,
+ "lff"
+ );
+ cooldown = new Cooldown(manager);
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player)) {
+ sendMessage(sender, Config.PLAYER_ONLY);
+ return;
+ }
+
+ Player player = (Player) sender;
+ PlayerTeam pt = getInstance().getTeamManager().getByPlayer(player.getUniqueId());
+
+ if (cooldown.hasCooldown(player)) {
+ sendMessage(sender, getLanguageConfig().getString("LFF_COMMAND.ON_COOLDOWN")
+ .replace("%seconds%", cooldown.getRemaining(player))
+ );
+ return;
+ }
+
+ if (pt != null) {
+ sendMessage(sender, getLanguageConfig().getString("LFF_COMMAND.ALREADY_IN_TEAM"));
+ return;
+ }
+
+ new LFFMenu(getInstance().getMenuManager(), player).open();
+ }
+
+ @Getter
+ @Setter
+ private static class LFFItem {
+
+ private String name;
+ private ItemStack disabled;
+ private ItemStack enabled;
+ private boolean toggled;
+ private int slot;
+
+ public LFFItem(String name, ItemStack disabled, ItemStack enabled, int slot) {
+ this.name = name;
+ this.disabled = disabled;
+ this.enabled = enabled;
+ this.slot = slot;
+ }
+ }
+
+ private static class LFFMenu extends Menu {
+
+ private final List items;
+
+ public LFFMenu(MenuManager manager, Player player) {
+ super(
+ manager,
+ player,
+ manager.getLanguageConfig().getString("LFF_COMMAND.LFF_MENU.TITLE"),
+ manager.getLanguageConfig().getInt("LFF_COMMAND.LFF_MENU.SIZE"),
+ false
+ );
+ this.items = new ArrayList<>();
+ this.load();
+ }
+
+ private void load() {
+ for (String key : getLanguageConfig().getConfigurationSection("LFF_COMMAND.LFF_MENU.CLASSES").getKeys(false)) {
+ String path = "LFF_COMMAND.LFF_MENU.CLASSES." + key + ".";
+ ItemStack enabled = new ItemBuilder(ItemUtils.getMat(getLanguageConfig().getString(path + "ENABLED_ITEM.MATERIAL")))
+ .setName(getLanguageConfig().getString(path + "ENABLED_ITEM.NAME"))
+ .data(getManager(), (short) getLanguageConfig().getInt(path + "ENABLED_ITEM.DATA"))
+ .setLore(getLanguageConfig().getStringList(path + "ENABLED_ITEM.LORE"))
+ .toItemStack();
+
+ ItemStack disabled = new ItemBuilder(ItemUtils.getMat(getLanguageConfig().getString(path + "DISABLED_ITEM.MATERIAL")))
+ .setName(getLanguageConfig().getString(path + "DISABLED_ITEM.NAME"))
+ .data(getManager(), (short) getLanguageConfig().getInt(path + "DISABLED_ITEM.DATA"))
+ .setLore(getLanguageConfig().getStringList(path + "DISABLED_ITEM.LORE"))
+ .toItemStack();
+
+ items.add(new LFFItem(
+ getLanguageConfig().getString(path + "NAME"),
+ Utils.splitEnchantAdd(getLanguageConfig().getString(path + "DISABLED_ITEM.ENCHANT"), disabled),
+ Utils.splitEnchantAdd(getLanguageConfig().getString(path + "ENABLED_ITEM.ENCHANT"), enabled),
+ getLanguageConfig().getInt(path + "SLOT"))
+ );
+ }
+ }
+
+ @Override
+ public Map getButtons(Player player) {
+ Map buttons = new HashMap<>();
+
+ for (LFFItem item : items) {
+ buttons.put(item.getSlot(), new Button() {
+ @Override
+ public void onClick(InventoryClickEvent e) {
+ e.setCancelled(true);
+ item.setToggled(!item.isToggled());
+ update();
+ }
+
+ @Override
+ public ItemStack getItemStack() {
+ if (item.isToggled()) {
+ return item.getEnabled();
+
+ } else return item.getDisabled();
+ }
+ });
+ }
+
+ buttons.put(getLanguageConfig().getInt("LFF_COMMAND.LFF_MENU.CONFIRM_BUTTON.SLOT"), new Button() {
+ @Override
+ public void onClick(InventoryClickEvent e) {
+ String[] array = items.stream()
+ .filter(LFFItem::isToggled)
+ .map(LFFItem::getName).toArray(String[]::new);
+
+ if (array.length == 0) {
+ getPlayer().sendMessage(getLanguageConfig().getString("LFF_COMMAND.NONE_CHOSEN"));
+ return;
+ }
+
+ cooldown.applyCooldown(getPlayer(), getConfig().getInt("TIMERS_COOLDOWN.LFF_COMMAND"));
+
+ for (String s : getLanguageConfig().getStringList("LFF_COMMAND.BROADCAST_MESSAGE")) {
+ Bukkit.broadcastMessage(s
+ .replace("%player%", getPlayer().getName())
+ .replace("%classes%", String.join(getLanguageConfig().getString("LFF_COMMAND.SEPARATOR"), array))
+ );
+ }
+
+ e.setCancelled(true);
+ player.closeInventory();
+ }
+
+ @Override
+ public ItemStack getItemStack() {
+ return new ItemBuilder(ItemUtils.getMat(getLanguageConfig().getString("LFF_COMMAND.LFF_MENU.CONFIRM_BUTTON.MATERIAL")))
+ .setName(getLanguageConfig().getString("LFF_COMMAND.LFF_MENU.CONFIRM_BUTTON.NAME"))
+ .setLore(getLanguageConfig().getStringList("LFF_COMMAND.LFF_MENU.CONFIRM_BUTTON.LORE"))
+ .data(getManager(), (short) getLanguageConfig().getInt("LFF_COMMAND.LFF_MENU.CONFIRM_BUTTON.DATA")).toItemStack();
+ }
+ });
+
+ return buttons;
+ }
+
+ @Override
+ public void onClose() {
+ items.clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/LastDeathsCommand.java b/src/java/me/keano/azurite/modules/commands/type/LastDeathsCommand.java
new file mode 100644
index 0000000..6960adb
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/LastDeathsCommand.java
@@ -0,0 +1,70 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.users.User;
+import org.bukkit.command.CommandSender;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class LastDeathsCommand extends Command {
+
+ public LastDeathsCommand(CommandManager manager) {
+ super(
+ manager,
+ "lastdeaths"
+ );
+ this.setPermissible("azurite.lastdeaths");
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("LAST_DEATHS_COMMAND.USAGE");
+ }
+
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ if (args.length == 0) {
+ sendUsage(sender);
+ return;
+ }
+
+ User target = getInstance().getUserManager().getByName(args[0]);
+
+ if (target == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[0])
+ );
+ return;
+ }
+
+ for (String s : getLanguageConfig().getStringList("LAST_DEATHS_COMMAND.FORMAT")) {
+ if (!s.equalsIgnoreCase("%lastdeaths%")) {
+ sendMessage(sender, s.replace("%player%", target.getName()));
+ continue;
+ }
+
+ for (String lastDeath : target.getLastDeaths()) {
+ sendMessage(sender, lastDeath);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/LastKillsCommand.java b/src/java/me/keano/azurite/modules/commands/type/LastKillsCommand.java
new file mode 100644
index 0000000..dfed38e
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/LastKillsCommand.java
@@ -0,0 +1,69 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.users.User;
+import org.bukkit.command.CommandSender;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class LastKillsCommand extends Command {
+
+ public LastKillsCommand(CommandManager manager) {
+ super(
+ manager,
+ "lastkills"
+ );
+ this.setPermissible("azurite.lastkills");
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List usage() {
+ return getLanguageConfig().getStringList("LAST_KILLS_COMMAND.USAGE");
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!sender.hasPermission(permissible)) {
+ sendMessage(sender, Config.INSUFFICIENT_PERM);
+ return;
+ }
+
+ if (args.length == 0) {
+ sendUsage(sender);
+ return;
+ }
+
+ User target = getInstance().getUserManager().getByName(args[0]);
+
+ if (target == null) {
+ sendMessage(sender, Config.PLAYER_NOT_FOUND
+ .replace("%player%", args[0])
+ );
+ return;
+ }
+
+ for (String s : getLanguageConfig().getStringList("LAST_KILLS_COMMAND.FORMAT")) {
+ if (!s.equalsIgnoreCase("%lastkills%")) {
+ sendMessage(sender, s.replace("%player%", target.getName()));
+ continue;
+ }
+
+ for (String lastKill : target.getLastKills()) {
+ sendMessage(sender, lastKill);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/me/keano/azurite/modules/commands/type/LeaderboardsCommand.java b/src/java/me/keano/azurite/modules/commands/type/LeaderboardsCommand.java
new file mode 100644
index 0000000..f1c12a8
--- /dev/null
+++ b/src/java/me/keano/azurite/modules/commands/type/LeaderboardsCommand.java
@@ -0,0 +1,166 @@
+package me.keano.azurite.modules.commands.type;
+
+import me.keano.azurite.modules.commands.CommandManager;
+import me.keano.azurite.modules.framework.Config;
+import me.keano.azurite.modules.framework.commands.Command;
+import me.keano.azurite.modules.framework.menu.Menu;
+import me.keano.azurite.modules.framework.menu.MenuManager;
+import me.keano.azurite.modules.framework.menu.button.Button;
+import me.keano.azurite.modules.teams.type.PlayerTeam;
+import me.keano.azurite.modules.users.User;
+import me.keano.azurite.utils.ItemBuilder;
+import me.keano.azurite.utils.ItemUtils;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Copyright (c) 2023. Keano
+ * Use or redistribution of source or file is
+ * only permitted if given explicit permission.
+ */
+public class LeaderboardsCommand extends Command {
+
+ public LeaderboardsCommand(CommandManager manager) {
+ super(
+ manager,
+ "leaderboards"
+ );
+ }
+
+ @Override
+ public List aliases() {
+ return Collections.singletonList(
+ "leaderboard"
+ );
+ }
+
+ @Override
+ public List usage() {
+ return null;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player)) {
+ sendMessage(sender, Config.PLAYER_ONLY);
+ return;
+ }
+
+ Player player = (Player) sender;
+ new LeaderboardsMenu(getInstance().getMenuManager(), player).open();
+ }
+
+ private static class LeaderboardsMenu extends Menu {
+
+ public LeaderboardsMenu(MenuManager manager, Player player) {
+ super(
+ manager,
+ player,
+ manager.getLanguageConfig().getString("LEADERBOARDS_COMMAND.TITLE"),
+ manager.getLanguageConfig().getInt("LEADERBOARDS_COMMAND.SIZE"),
+ false
+ );
+ }
+
+ @Override
+ public Map getButtons(Player player) {
+ Map