alive = new ArrayList<>();
+
+ this.teamPlayers.forEach(teamPlayer -> {
+ if (teamPlayer.isAlive()) {
+ alive.add(teamPlayer);
+ }
+ });
+
+ return alive;
+ }
+
+ /**
+ * Returns an integer that is incremented for each {@link TeamPlayer} element in the {@code teamPlayers} list whose
+ * {@link TeamPlayer#isAlive()} returns true.
+ *
+ * Use this method rather than calling {@link List#size()} on the result of {@code getAliveTeamPlayers}.
+ *
+ * @return The count of team players that are alive.
+ */
+ public int getAliveCount() {
+ if (this.teamPlayers.size() == 1) {
+ return this.leader.isAlive() ? 1 : 0;
+ } else {
+ int alive = 0;
+
+ for (TeamPlayer teamPlayer : this.teamPlayers) {
+ if (teamPlayer.isAlive()) {
+ alive++;
+ }
+ }
+
+ return alive;
+ }
+ }
+
+ /**
+ * Returns a list of objects that extend {@link TeamPlayer} whose {@link TeamPlayer#isAlive()} returns false.
+ *
+ * @return A list of team players that are dead.
+ */
+ public List getDeadTeamPlayers() {
+ List dead = new ArrayList<>();
+
+ this.teamPlayers.forEach(teamPlayer -> {
+ if (!teamPlayer.isAlive()) {
+ dead.add(teamPlayer);
+ }
+ });
+
+ return dead;
+ }
+
+ /**
+ * Subtracts the result of {@code getAliveCount} from the size of the {@code teamPlayers} list.
+ *
+ * @return The count of team players that are dead.
+ */
+ public int getDeadCount() {
+ return this.teamPlayers.size() - this.getAliveCount();
+ }
+
+ public void broadcast(String messages) {
+ this.getPlayers().forEach(player -> player.sendMessage(messages));
+ }
+
+ public void broadcast(List messages) {
+ this.getPlayers().forEach(player -> messages.forEach(player::sendMessage));
+ }
+
+ public void broadcastComponents(List components) {
+ this.getPlayers().forEach(player -> components.forEach(array -> player.spigot().sendMessage(array)));
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/team/TeamPlayer.java b/src/main/java/me/joeleoli/nucleus/team/TeamPlayer.java
new file mode 100644
index 0000000..50a562f
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/team/TeamPlayer.java
@@ -0,0 +1,18 @@
+package me.joeleoli.nucleus.team;
+
+import java.util.UUID;
+import lombok.Getter;
+import lombok.Setter;
+import me.joeleoli.nucleus.player.PlayerInfo;
+
+@Getter
+public class TeamPlayer extends PlayerInfo {
+
+ @Setter
+ private boolean alive;
+
+ public TeamPlayer(UUID uuid, String name) {
+ super(uuid, name);
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/AtomicString.java b/src/main/java/me/joeleoli/nucleus/util/AtomicString.java
new file mode 100644
index 0000000..c280054
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/AtomicString.java
@@ -0,0 +1,14 @@
+package me.joeleoli.nucleus.util;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class AtomicString {
+
+ private String string;
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/BlockUtil.java b/src/main/java/me/joeleoli/nucleus/util/BlockUtil.java
new file mode 100644
index 0000000..79497d4
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/BlockUtil.java
@@ -0,0 +1,432 @@
+package me.joeleoli.nucleus.util;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.entity.Player;
+
+public class BlockUtil {
+
+ public static List blocked = new ArrayList<>();
+ private static Set blockSolidPassSet;
+ private static Set blockStairsSet;
+ private static Set blockLiquidsSet;
+ private static Set blockWebsSet;
+ private static Set blockIceSet;
+ private static Set blockCarpetSet;
+
+ static {
+ BlockUtil.blockSolidPassSet = new HashSet<>();
+ BlockUtil.blockStairsSet = new HashSet<>();
+ BlockUtil.blockLiquidsSet = new HashSet<>();
+ BlockUtil.blockWebsSet = new HashSet<>();
+ BlockUtil.blockIceSet = new HashSet<>();
+ BlockUtil.blockCarpetSet = new HashSet<>();
+ BlockUtil.blockSolidPassSet.add((byte) 0);
+ BlockUtil.blockSolidPassSet.add((byte) 6);
+ BlockUtil.blockSolidPassSet.add((byte) 8);
+ BlockUtil.blockSolidPassSet.add((byte) 9);
+ BlockUtil.blockSolidPassSet.add((byte) 10);
+ BlockUtil.blockSolidPassSet.add((byte) 11);
+ BlockUtil.blockSolidPassSet.add((byte) 27);
+ BlockUtil.blockSolidPassSet.add((byte) 28);
+ BlockUtil.blockSolidPassSet.add((byte) 30);
+ BlockUtil.blockSolidPassSet.add((byte) 31);
+ BlockUtil.blockSolidPassSet.add((byte) 32);
+ BlockUtil.blockSolidPassSet.add((byte) 37);
+ BlockUtil.blockSolidPassSet.add((byte) 38);
+ BlockUtil.blockSolidPassSet.add((byte) 39);
+ BlockUtil.blockSolidPassSet.add((byte) 40);
+ BlockUtil.blockSolidPassSet.add((byte) 50);
+ BlockUtil.blockSolidPassSet.add((byte) 51);
+ BlockUtil.blockSolidPassSet.add((byte) 55);
+ BlockUtil.blockSolidPassSet.add((byte) 59);
+ BlockUtil.blockSolidPassSet.add((byte) 63);
+ BlockUtil.blockSolidPassSet.add((byte) 66);
+ BlockUtil.blockSolidPassSet.add((byte) 68);
+ BlockUtil.blockSolidPassSet.add((byte) 69);
+ BlockUtil.blockSolidPassSet.add((byte) 70);
+ BlockUtil.blockSolidPassSet.add((byte) 72);
+ BlockUtil.blockSolidPassSet.add((byte) 75);
+ BlockUtil.blockSolidPassSet.add((byte) 76);
+ BlockUtil.blockSolidPassSet.add((byte) 77);
+ BlockUtil.blockSolidPassSet.add((byte) 78);
+ BlockUtil.blockSolidPassSet.add((byte) 83);
+ BlockUtil.blockSolidPassSet.add((byte) 90);
+ BlockUtil.blockSolidPassSet.add((byte) 104);
+ BlockUtil.blockSolidPassSet.add((byte) 105);
+ BlockUtil.blockSolidPassSet.add((byte) 115);
+ BlockUtil.blockSolidPassSet.add((byte) 119);
+ BlockUtil.blockSolidPassSet.add((byte) (-124));
+ BlockUtil.blockSolidPassSet.add((byte) (-113));
+ BlockUtil.blockSolidPassSet.add((byte) (-81));
+ BlockUtil.blockStairsSet.add((byte) 53);
+ BlockUtil.blockStairsSet.add((byte) 67);
+ BlockUtil.blockStairsSet.add((byte) 108);
+ BlockUtil.blockStairsSet.add((byte) 109);
+ BlockUtil.blockStairsSet.add((byte) 114);
+ BlockUtil.blockStairsSet.add((byte) (-128));
+ BlockUtil.blockStairsSet.add((byte) (-122));
+ BlockUtil.blockStairsSet.add((byte) (-121));
+ BlockUtil.blockStairsSet.add((byte) (-120));
+ BlockUtil.blockStairsSet.add((byte) (-100));
+ BlockUtil.blockStairsSet.add((byte) (-93));
+ BlockUtil.blockStairsSet.add((byte) (-92));
+ BlockUtil.blockStairsSet.add((byte) (-76));
+ BlockUtil.blockStairsSet.add((byte) 126);
+ BlockUtil.blockStairsSet.add((byte) (-74));
+ BlockUtil.blockStairsSet.add((byte) 44);
+ BlockUtil.blockStairsSet.add((byte) 78);
+ BlockUtil.blockStairsSet.add((byte) 99);
+ BlockUtil.blockStairsSet.add((byte) (-112));
+ BlockUtil.blockStairsSet.add((byte) (-115));
+ BlockUtil.blockStairsSet.add((byte) (-116));
+ BlockUtil.blockStairsSet.add((byte) (-105));
+ BlockUtil.blockStairsSet.add((byte) (-108));
+ BlockUtil.blockStairsSet.add((byte) 100);
+ BlockUtil.blockLiquidsSet.add((byte) 8);
+ BlockUtil.blockLiquidsSet.add((byte) 9);
+ BlockUtil.blockLiquidsSet.add((byte) 10);
+ BlockUtil.blockLiquidsSet.add((byte) 11);
+ BlockUtil.blockWebsSet.add((byte) 30);
+ BlockUtil.blockIceSet.add((byte) 79);
+ BlockUtil.blockIceSet.add((byte) (-82));
+ BlockUtil.blockCarpetSet.add((byte) (-85));
+
+ blocked.add(Material.ACTIVATOR_RAIL);
+ blocked.add(Material.ANVIL);
+ blocked.add(Material.BED_BLOCK);
+ blocked.add(Material.POTATO);
+ blocked.add(Material.POTATO_ITEM);
+ blocked.add(Material.CARROT);
+ blocked.add(Material.CARROT_ITEM);
+ blocked.add(Material.BIRCH_WOOD_STAIRS);
+ blocked.add(Material.BREWING_STAND);
+ blocked.add(Material.BOAT);
+ blocked.add(Material.BRICK_STAIRS);
+ blocked.add(Material.BROWN_MUSHROOM);
+ blocked.add(Material.CAKE_BLOCK);
+ blocked.add(Material.CARPET);
+ blocked.add(Material.CAULDRON);
+ blocked.add(Material.COBBLESTONE_STAIRS);
+ blocked.add(Material.COBBLE_WALL);
+ blocked.add(Material.DARK_OAK_STAIRS);
+ blocked.add(Material.DIODE);
+ blocked.add(Material.DIODE_BLOCK_ON);
+ blocked.add(Material.DIODE_BLOCK_OFF);
+ blocked.add(Material.DEAD_BUSH);
+ blocked.add(Material.DETECTOR_RAIL);
+ blocked.add(Material.DOUBLE_PLANT);
+ blocked.add(Material.DOUBLE_STEP);
+ blocked.add(Material.DRAGON_EGG);
+ blocked.add(Material.PAINTING);
+ blocked.add(Material.FLOWER_POT);
+ blocked.add(Material.GOLD_PLATE);
+ blocked.add(Material.HOPPER);
+ blocked.add(Material.STONE_PLATE);
+ blocked.add(Material.IRON_PLATE);
+ blocked.add(Material.HUGE_MUSHROOM_1);
+ blocked.add(Material.HUGE_MUSHROOM_2);
+ blocked.add(Material.IRON_DOOR_BLOCK);
+ blocked.add(Material.IRON_DOOR);
+ blocked.add(Material.FENCE);
+ blocked.add(Material.IRON_FENCE);
+ blocked.add(Material.IRON_PLATE);
+ blocked.add(Material.ITEM_FRAME);
+ blocked.add(Material.JUKEBOX);
+ blocked.add(Material.JUNGLE_WOOD_STAIRS);
+ blocked.add(Material.LADDER);
+ blocked.add(Material.LEVER);
+ blocked.add(Material.LONG_GRASS);
+ blocked.add(Material.NETHER_FENCE);
+ blocked.add(Material.NETHER_STALK);
+ blocked.add(Material.NETHER_WARTS);
+ blocked.add(Material.MELON_STEM);
+ blocked.add(Material.PUMPKIN_STEM);
+ blocked.add(Material.QUARTZ_STAIRS);
+ blocked.add(Material.RAILS);
+ blocked.add(Material.RED_MUSHROOM);
+ blocked.add(Material.RED_ROSE);
+ blocked.add(Material.SAPLING);
+ blocked.add(Material.SEEDS);
+ blocked.add(Material.SIGN);
+ blocked.add(Material.SIGN_POST);
+ blocked.add(Material.SKULL);
+ blocked.add(Material.SMOOTH_STAIRS);
+ blocked.add(Material.NETHER_BRICK_STAIRS);
+ blocked.add(Material.SPRUCE_WOOD_STAIRS);
+ blocked.add(Material.STAINED_GLASS_PANE);
+ blocked.add(Material.REDSTONE_COMPARATOR);
+ blocked.add(Material.REDSTONE_COMPARATOR_OFF);
+ blocked.add(Material.REDSTONE_COMPARATOR_ON);
+ blocked.add(Material.REDSTONE_LAMP_OFF);
+ blocked.add(Material.REDSTONE_LAMP_ON);
+ blocked.add(Material.REDSTONE_TORCH_OFF);
+ blocked.add(Material.REDSTONE_TORCH_ON);
+ blocked.add(Material.REDSTONE_WIRE);
+ blocked.add(Material.SANDSTONE_STAIRS);
+ blocked.add(Material.STEP);
+ blocked.add(Material.ACACIA_STAIRS);
+ blocked.add(Material.SUGAR_CANE);
+ blocked.add(Material.SUGAR_CANE_BLOCK);
+ blocked.add(Material.ENCHANTMENT_TABLE);
+ blocked.add(Material.SOUL_SAND);
+ blocked.add(Material.TORCH);
+ blocked.add(Material.TRAP_DOOR);
+ blocked.add(Material.TRIPWIRE);
+ blocked.add(Material.TRIPWIRE_HOOK);
+ blocked.add(Material.WALL_SIGN);
+ blocked.add(Material.VINE);
+ blocked.add(Material.WATER_LILY);
+ blocked.add(Material.WEB);
+ blocked.add(Material.WOOD_DOOR);
+ blocked.add(Material.WOOD_DOUBLE_STEP);
+ blocked.add(Material.WOOD_PLATE);
+ blocked.add(Material.WOOD_STAIRS);
+ blocked.add(Material.WOOD_STEP);
+ blocked.add(Material.HOPPER);
+ blocked.add(Material.WOODEN_DOOR);
+ blocked.add(Material.YELLOW_FLOWER);
+ blocked.add(Material.LAVA);
+ blocked.add(Material.WATER);
+ blocked.add(Material.STATIONARY_WATER);
+ blocked.add(Material.STATIONARY_LAVA);
+ blocked.add(Material.CACTUS);
+ blocked.add(Material.CHEST);
+ blocked.add(Material.PISTON_BASE);
+ blocked.add(Material.PISTON_MOVING_PIECE);
+ blocked.add(Material.PISTON_EXTENSION);
+ blocked.add(Material.PISTON_STICKY_BASE);
+ blocked.add(Material.TRAPPED_CHEST);
+ blocked.add(Material.SNOW);
+ blocked.add(Material.ENDER_CHEST);
+ blocked.add(Material.THIN_GLASS);
+ blocked.add(Material.ENDER_PORTAL_FRAME);
+ }
+
+ public static boolean isOnStairs(final Location location, final int down) {
+ return isUnderBlock(location, BlockUtil.blockStairsSet, down);
+ }
+
+ public static boolean isOnLiquid(final Location location, final int down) {
+ return isUnderBlock(location, BlockUtil.blockLiquidsSet, down);
+ }
+
+ public static boolean isOnWeb(final Location location, final int down) {
+ return isUnderBlock(location, BlockUtil.blockWebsSet, down);
+ }
+
+ public static boolean isOnIce(final Location location, final int down) {
+ return isUnderBlock(location, BlockUtil.blockIceSet, down);
+ }
+
+ public static boolean isOnCarpet(final Location location, final int down) {
+ return isUnderBlock(location, BlockUtil.blockCarpetSet, down);
+ }
+
+ public static boolean isSlab(final Player player) {
+ return blocked.contains(player.getLocation().getBlock().getRelative(BlockFace.DOWN).getType());
+ }
+
+ public static boolean isBlockFaceAir(final Player player) {
+ final Block block = player.getLocation().getBlock().getRelative(BlockFace.DOWN);
+ return block.getType().equals(Material.AIR) && block.getRelative(BlockFace.WEST).getType().equals(Material
+ .AIR) && block.getRelative(BlockFace.NORTH).getType().equals(Material.AIR) && block.getRelative
+ (BlockFace.EAST).getType().equals(Material.AIR) && block.getRelative(BlockFace.SOUTH).getType()
+ .equals(Material.AIR);
+ }
+
+ private static boolean isUnderBlock(final Location location, final Set itemIDs, final int down) {
+ final double posX = location.getX();
+ final double posZ = location.getZ();
+ final double fracX = (posX % 1.0 > 0.0) ? Math.abs(posX % 1.0) : (1.0 - Math.abs(posX % 1.0));
+ final double fracZ = (posZ % 1.0 > 0.0) ? Math.abs(posZ % 1.0) : (1.0 - Math.abs(posZ % 1.0));
+ final int blockX = location.getBlockX();
+ final int blockY = location.getBlockY() - down;
+ final int blockZ = location.getBlockZ();
+ final World world = location.getWorld();
+
+ if (itemIDs.contains((byte) world.getBlockAt(blockX, blockY, blockZ).getTypeId())) {
+ return true;
+ }
+
+ if (fracX < 0.3) {
+ if (itemIDs.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ).getTypeId())) {
+ return true;
+ }
+
+ if (fracZ < 0.3) {
+ if (itemIDs.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ - 1).getTypeId())) {
+ return true;
+ }
+
+ if (itemIDs.contains((byte) world.getBlockAt(blockX, blockY, blockZ - 1).getTypeId())) {
+ return true;
+ }
+
+ if (itemIDs.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ - 1).getTypeId())) {
+ return true;
+ }
+ } else if (fracZ > 0.7) {
+ if (itemIDs.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ + 1).getTypeId())) {
+ return true;
+ }
+
+ if (itemIDs.contains((byte) world.getBlockAt(blockX, blockY, blockZ + 1).getTypeId())) {
+ return true;
+ }
+
+ if (itemIDs.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ + 1).getTypeId())) {
+ return true;
+ }
+ }
+ } else if (fracX > 0.7) {
+ if (itemIDs.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ).getTypeId())) {
+ return true;
+ }
+
+ if (fracZ < 0.3) {
+ if (itemIDs.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ - 1).getTypeId())) {
+ return true;
+ }
+
+ if (itemIDs.contains((byte) world.getBlockAt(blockX, blockY, blockZ - 1).getTypeId())) {
+ return true;
+ }
+
+ if (itemIDs.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ - 1).getTypeId())) {
+ return true;
+ }
+ } else if (fracZ > 0.7) {
+ if (itemIDs.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ + 1).getTypeId())) {
+ return true;
+ }
+
+ if (itemIDs.contains((byte) world.getBlockAt(blockX, blockY, blockZ + 1).getTypeId())) {
+ return true;
+ }
+
+ if (itemIDs.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ + 1).getTypeId())) {
+ return true;
+ }
+ }
+ } else if (fracZ < 0.3) {
+ if (itemIDs.contains((byte) world.getBlockAt(blockX, blockY, blockZ - 1).getTypeId())) {
+ return true;
+ }
+ } else if (fracZ > 0.7 && itemIDs.contains((byte) world.getBlockAt(blockX, blockY, blockZ + 1).getTypeId())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean isOnGround(final Location location, final int down) {
+ final double posX = location.getX();
+ final double posZ = location.getZ();
+ final double fracX = (posX % 1.0 > 0.0) ? Math.abs(posX % 1.0) : (1.0 - Math.abs(posX % 1.0));
+ final double fracZ = (posZ % 1.0 > 0.0) ? Math.abs(posZ % 1.0) : (1.0 - Math.abs(posZ % 1.0));
+ final int blockX = location.getBlockX();
+ final int blockY = location.getBlockY() - down;
+ final int blockZ = location.getBlockZ();
+ final World world = location.getWorld();
+
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX, blockY, blockZ).getTypeId())) {
+ return true;
+ }
+
+ if (fracX < 0.3) {
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ).getTypeId()
+ )) {
+ return true;
+ }
+
+ if (fracZ < 0.3) {
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ - 1)
+ .getTypeId())) {
+ return true;
+ }
+
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX, blockY, blockZ - 1)
+ .getTypeId())) {
+ return true;
+ }
+
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ - 1)
+ .getTypeId())) {
+ return true;
+ }
+ } else if (fracZ > 0.7) {
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ + 1)
+ .getTypeId())) {
+ return true;
+ }
+
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX, blockY, blockZ + 1)
+ .getTypeId())) {
+ return true;
+ }
+
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ + 1)
+ .getTypeId())) {
+ return true;
+ }
+ }
+ } else if (fracX > 0.7) {
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ).getTypeId()
+ )) {
+ return true;
+ }
+
+ if (fracZ < 0.3) {
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ - 1)
+ .getTypeId())) {
+ return true;
+ }
+
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX, blockY, blockZ - 1)
+ .getTypeId())) {
+ return true;
+ }
+
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ - 1)
+ .getTypeId())) {
+ return true;
+ }
+ } else if (fracZ > 0.7) {
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX - 1, blockY, blockZ + 1)
+ .getTypeId())) {
+ return true;
+ }
+
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX, blockY, blockZ + 1)
+ .getTypeId())) {
+ return true;
+ }
+
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX + 1, blockY, blockZ + 1)
+ .getTypeId())) {
+ return true;
+ }
+ }
+ } else if (fracZ < 0.3) {
+ if (!BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX, blockY, blockZ - 1).getTypeId()
+ )) {
+ return true;
+ }
+ } else if (fracZ > 0.7 && !BlockUtil.blockSolidPassSet.contains((byte) world.getBlockAt(blockX, blockY,
+ blockZ + 1
+ ).getTypeId())) {
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/BukkitUtil.java b/src/main/java/me/joeleoli/nucleus/util/BukkitUtil.java
new file mode 100644
index 0000000..89a5150
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/BukkitUtil.java
@@ -0,0 +1,36 @@
+package me.joeleoli.nucleus.util;
+
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Projectile;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.potion.PotionEffectType;
+
+public class BukkitUtil {
+
+ public static String getName(PotionEffectType potionEffectType) {
+ if (potionEffectType.getName().equalsIgnoreCase("fire_resistance")) {
+ return "Fire Resistance";
+ } else if (potionEffectType.getName().equalsIgnoreCase("speed")) {
+ return "Speed";
+ } else if (potionEffectType.getName().equalsIgnoreCase("weakness")) {
+ return "Weakness";
+ } else if (potionEffectType.getName().equalsIgnoreCase("slowness")) {
+ return "Slowness";
+ } else {
+ return "Unknown";
+ }
+ }
+
+ public static Player getDamager(EntityDamageByEntityEvent event) {
+ if (event.getDamager() instanceof Player) {
+ return (Player) event.getDamager();
+ } else if (event.getDamager() instanceof Projectile) {
+ if (((Projectile) event.getDamager()).getShooter() instanceof Player) {
+ return (Player) ((Projectile) event.getDamager()).getShooter();
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/BungeeUtil.java b/src/main/java/me/joeleoli/nucleus/util/BungeeUtil.java
new file mode 100644
index 0000000..e6d3ac7
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/BungeeUtil.java
@@ -0,0 +1,59 @@
+package me.joeleoli.nucleus.util;
+
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import me.joeleoli.nucleus.Nucleus;
+import org.apache.commons.lang3.Validate;
+import org.bukkit.entity.Player;
+
+public class BungeeUtil {
+
+ private BungeeUtil() {
+ throw new RuntimeException("Cannot instantiate a utility class.");
+ }
+
+ public static void sendMessage(Player source, String target, String message) {
+ Validate.notNull(source, target, message, "Input values cannot be null!");
+
+ try {
+ ByteArrayDataOutput out = ByteStreams.newDataOutput();
+ out.writeUTF("Message");
+ out.writeUTF(target);
+ out.writeUTF(message);
+
+ source.sendPluginMessage(Nucleus.getInstance(), "BungeeCord", out.toByteArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void kickPlayer(Player source, String target, String reason) {
+ Validate.notNull(source, target, reason, "Input values cannot be null!");
+
+ try {
+ ByteArrayDataOutput out = ByteStreams.newDataOutput();
+ out.writeUTF("KickPlayer");
+ out.writeUTF(target);
+ out.writeUTF(reason);
+
+ source.sendPluginMessage(Nucleus.getInstance(), "BungeeCord", out.toByteArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void sendToServer(Player player, String server) {
+ Validate.notNull(player, server, "Input values cannot be null!");
+
+ try {
+ ByteArrayDataOutput out = ByteStreams.newDataOutput();
+ out.writeUTF("Connect");
+ out.writeUTF(server);
+
+ player.sendPluginMessage(Nucleus.getInstance(), "BungeeCord", out.toByteArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/ClassUtil.java b/src/main/java/me/joeleoli/nucleus/util/ClassUtil.java
new file mode 100644
index 0000000..43e19f3
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/ClassUtil.java
@@ -0,0 +1,80 @@
+package me.joeleoli.nucleus.util;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.net.URL;
+import java.security.CodeSource;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import org.bukkit.plugin.Plugin;
+
+public final class ClassUtil {
+
+ private ClassUtil() {
+ throw new RuntimeException("Cannot instantiate a utility class.");
+ }
+
+ /**
+ * Gets all the classes in a the provided package.
+ *
+ * @param plugin The plugin who owns the package
+ * @param packageName The package to scan classes in.
+ *
+ * @return The classes in the package packageName.
+ */
+ public static Collection> getClassesInPackage(Plugin plugin, String packageName) {
+ Collection> classes = new ArrayList<>();
+
+ CodeSource codeSource = plugin.getClass().getProtectionDomain().getCodeSource();
+ URL resource = codeSource.getLocation();
+ String relPath = packageName.replace('.', '/');
+ String resPath = resource.getPath().replace("%20", " ");
+ String jarPath = resPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", "");
+ JarFile jarFile;
+
+ try {
+ jarFile = new JarFile(jarPath);
+ } catch (IOException e) {
+ throw (new RuntimeException("Unexpected IOException reading JAR File '" + jarPath + "'", e));
+ }
+
+ Enumeration entries = jarFile.entries();
+
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ String entryName = entry.getName();
+ String className = null;
+
+ if (entryName.endsWith(".class") && entryName.startsWith(relPath) &&
+ entryName.length() > (relPath.length() + "/".length())) {
+ className = entryName.replace('/', '.').replace('\\', '.').replace(".class", "");
+ }
+
+ if (className != null) {
+ Class> clazz = null;
+
+ try {
+ clazz = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ if (clazz != null) {
+ classes.add(clazz);
+ }
+ }
+ }
+
+ try {
+ jarFile.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return (ImmutableSet.copyOf(classes));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/me/joeleoli/nucleus/util/FontRenderer.java b/src/main/java/me/joeleoli/nucleus/util/FontRenderer.java
new file mode 100644
index 0000000..d161575
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/FontRenderer.java
@@ -0,0 +1,111 @@
+package me.joeleoli.nucleus.util;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+
+public final class FontRenderer {
+
+ private int[] charOffsets = new int[256];
+ private int[] charWidths = new int[256];
+
+ private int textureHeight;
+ private int textureWidth;
+
+ FontRenderer() {
+ BufferedImage fontTexture;
+
+ try {
+ fontTexture = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("default.png"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ int width = fontTexture.getWidth();
+ int height = fontTexture.getHeight();
+
+ textureWidth = width;
+ textureHeight = height;
+
+ calculateCharWidths(fontTexture, width, height);
+ }
+
+ private static boolean isColEmpty(int[] imgData, int offset, int imageWidth, int maxCharHeight) {
+ // Checks if a column of pixels contains any non-transparent pixels
+ for (int row = 0; row < maxCharHeight; row++) {
+ int rowOffset = offset + row * imageWidth;
+ if (((imgData[rowOffset] >> 24) & 0xFF) > 128) {
+ // Non-transparent pixel found in column!
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Calculates width and offset of every character, to space characters correctly.
+ private void calculateCharWidths(BufferedImage fontTexture, int width, int height) {
+ int[] fontData = new int[width * height];
+ fontTexture.getRGB(0, 0, width, height, fontData, 0, width);
+ int maxCharWidth = width / 16;
+ int maxCharHeight = height / 16;
+
+ for (int character = 0; character < 128; ++character) {
+ int col = character % 16;
+ int row = character / 16;
+ int offset = (col * maxCharWidth) + (row * maxCharHeight * width);
+
+ if (character == 32) {
+ // Space is always 33% width
+ charWidths[32] = maxCharWidth / 3;
+ } else {
+ // Other chars' width is determined by examining pixels
+ // First, find start of character (left-most non-empty column)
+ int chStart = 0;
+ for (int c = 0; c < maxCharWidth; c++) {
+ chStart = c;
+ if (!isColEmpty(fontData, offset + c, width, maxCharHeight)) {
+ break;
+ }
+ }
+ // Next, find end of character (right-most non-empty column)
+ int chEnd = maxCharWidth - 1;
+ for (int c = maxCharWidth - 1; c >= chStart; c--) {
+ chEnd = c;
+ if (!isColEmpty(fontData, offset + c, width, maxCharHeight)) {
+ break;
+ }
+ }
+
+ charOffsets[character] = chStart;
+ charWidths[character] = chEnd - chStart + 1;
+ }
+ }
+ }
+
+ public int getWidth(String text) {
+ if (text == null) {
+ return 0;
+ }
+
+ float charWidthScale = 128f / textureWidth;
+ float width = 0;
+
+ for (int j = 0; j < text.length(); j++) {
+ int k = text.charAt(j);
+
+ if (k == '&') {
+ j++;
+ } else {
+ width += charWidths[k] * charWidthScale + 1;
+ }
+ }
+
+ return (int) Math.ceil(width);
+ }
+
+ public int getHeight() {
+ return (int) Math.ceil(textureHeight);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/me/joeleoli/nucleus/util/InventoryUtil.java b/src/main/java/me/joeleoli/nucleus/util/InventoryUtil.java
new file mode 100644
index 0000000..cae29a6
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/InventoryUtil.java
@@ -0,0 +1,175 @@
+package me.joeleoli.nucleus.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.bukkit.Material;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+public class InventoryUtil {
+
+ public static ItemStack[] fixInventoryOrder(ItemStack[] source) {
+ ItemStack[] fixed = new ItemStack[36];
+
+ System.arraycopy(source, 0, fixed, 27, 9);
+ System.arraycopy(source, 9, fixed, 0, 27);
+
+ return fixed;
+ }
+
+ public static String serializeInventory(ItemStack[] source) {
+ StringBuilder builder = new StringBuilder();
+
+ for (ItemStack itemStack : source) {
+ builder.append(serializeItemStack(itemStack));
+ builder.append(";");
+ }
+
+ return builder.toString();
+ }
+
+ public static ItemStack[] deserializeInventory(String source) {
+ List items = new ArrayList<>();
+ String[] split = source.split(";");
+
+ for (String piece : split) {
+ items.add(deserializeItemStack(piece));
+ }
+
+ return items.toArray(new ItemStack[items.size()]);
+ }
+
+ public static String serializeItemStack(ItemStack item) {
+ StringBuilder builder = new StringBuilder();
+
+ if (item == null) {
+ return "null";
+ }
+
+ String isType = String.valueOf(item.getType().getId());
+ builder.append("t@").append(isType);
+
+ if (item.getDurability() != 0) {
+ String isDurability = String.valueOf(item.getDurability());
+ builder.append(":d@").append(isDurability);
+ }
+
+ if (item.getAmount() != 1) {
+ String isAmount = String.valueOf(item.getAmount());
+ builder.append(":a@").append(isAmount);
+ }
+
+ Map isEnch = item.getEnchantments();
+
+ if (isEnch.size() > 0) {
+ for (Map.Entry ench : isEnch.entrySet()) {
+ builder.append(":e@").append(ench.getKey().getId()).append("@").append(ench.getValue());
+ }
+ }
+
+ if (item.hasItemMeta()) {
+ ItemMeta imeta = item.getItemMeta();
+
+ if (imeta.hasDisplayName()) {
+ builder.append(":dn@").append(imeta.getDisplayName());
+ }
+
+ if (imeta.hasLore()) {
+ builder.append(":l@").append(imeta.getLore());
+ }
+ }
+
+ return builder.toString();
+ }
+
+ public static ItemStack deserializeItemStack(String in) {
+ ItemStack item = null;
+ ItemMeta meta = null;
+
+ if (in.equals("null")) {
+ return new ItemStack(Material.AIR);
+ }
+
+ String[] split = in.split(":");
+
+ for (String itemInfo : split) {
+ String[] itemAttribute = itemInfo.split("@");
+ String s2 = itemAttribute[0];
+
+ switch (s2) {
+ case "t": {
+ item = new ItemStack(Material.getMaterial(Integer.valueOf(itemAttribute[1])));
+ meta = item.getItemMeta();
+ break;
+ }
+ case "d": {
+ if (item != null) {
+ item.setDurability(Short.valueOf(itemAttribute[1]));
+ break;
+ }
+ break;
+ }
+ case "a": {
+ if (item != null) {
+ item.setAmount(Integer.valueOf(itemAttribute[1]));
+ break;
+ }
+ break;
+ }
+ case "e": {
+ if (item != null) {
+ item.addEnchantment(
+ Enchantment.getById(Integer.valueOf(itemAttribute[1])),
+ Integer.valueOf(itemAttribute[2])
+ );
+ break;
+ }
+ break;
+ }
+ case "dn": {
+ if (meta != null) {
+ meta.setDisplayName(itemAttribute[1]);
+ break;
+ }
+ break;
+ }
+ case "l": {
+ itemAttribute[1] = itemAttribute[1].replace("[", "");
+ itemAttribute[1] = itemAttribute[1].replace("]", "");
+ List lore = Arrays.asList(itemAttribute[1].split(","));
+
+ for (int x = 0; x < lore.size(); ++x) {
+ String s = lore.get(x);
+
+ if (s != null) {
+ if (s.toCharArray().length != 0) {
+ if (s.charAt(0) == ' ') {
+ s = s.replaceFirst(" ", "");
+ }
+
+ lore.set(x, s);
+ }
+ }
+ }
+
+ if (meta != null) {
+ meta.setLore(lore);
+ break;
+ }
+
+ break;
+ }
+ }
+ }
+
+ if (meta != null && (meta.hasDisplayName() || meta.hasLore())) {
+ item.setItemMeta(meta);
+ }
+
+ return item;
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/ItemBuilder.java b/src/main/java/me/joeleoli/nucleus/util/ItemBuilder.java
new file mode 100644
index 0000000..32009fc
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/ItemBuilder.java
@@ -0,0 +1,107 @@
+package me.joeleoli.nucleus.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.event.Listener;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+public class ItemBuilder implements Listener {
+
+ private final ItemStack is;
+
+ public ItemBuilder(final Material mat) {
+ is = new ItemStack(mat);
+ }
+
+ public ItemBuilder(final ItemStack is) {
+ this.is = is;
+ }
+
+ public ItemBuilder amount(final int amount) {
+ is.setAmount(amount);
+ return this;
+ }
+
+ public ItemBuilder name(final String name) {
+ final ItemMeta meta = is.getItemMeta();
+ meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name));
+ is.setItemMeta(meta);
+ return this;
+ }
+
+ public ItemBuilder lore(final String name) {
+ final ItemMeta meta = is.getItemMeta();
+ List lore = meta.getLore();
+
+ if (lore == null) {
+ lore = new ArrayList<>();
+ }
+
+ lore.add(name);
+ meta.setLore(lore);
+
+ is.setItemMeta(meta);
+
+ return this;
+ }
+
+ public ItemBuilder lore(final List lore) {
+ List toSet = new ArrayList<>();
+ ItemMeta meta = is.getItemMeta();
+
+ for (String string : lore) {
+ toSet.add(ChatColor.translateAlternateColorCodes('&', string));
+ }
+
+ meta.setLore(toSet);
+ is.setItemMeta(meta);
+
+ return this;
+ }
+
+ public ItemBuilder durability(final int durability) {
+ is.setDurability((short) durability);
+ return this;
+ }
+
+ public ItemBuilder enchantment(final Enchantment enchantment, final int level) {
+ is.addUnsafeEnchantment(enchantment, level);
+ return this;
+ }
+
+ public ItemBuilder enchantment(final Enchantment enchantment) {
+ is.addUnsafeEnchantment(enchantment, 1);
+ return this;
+ }
+
+ public ItemBuilder type(final Material material) {
+ is.setType(material);
+ return this;
+ }
+
+ public ItemBuilder clearLore() {
+ final ItemMeta meta = is.getItemMeta();
+
+ meta.setLore(new ArrayList<>());
+ is.setItemMeta(meta);
+
+ return this;
+ }
+
+ public ItemBuilder clearEnchantments() {
+ for (final Enchantment e : is.getEnchantments().keySet()) {
+ is.removeEnchantment(e);
+ }
+
+ return this;
+ }
+
+ public ItemStack build() {
+ return is;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/me/joeleoli/nucleus/util/ItemUtil.java b/src/main/java/me/joeleoli/nucleus/util/ItemUtil.java
new file mode 100644
index 0000000..453e785
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/ItemUtil.java
@@ -0,0 +1,194 @@
+package me.joeleoli.nucleus.util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import me.joeleoli.nucleus.Nucleus;
+import me.joeleoli.nucleus.reflection.BukkitReflection;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.WordUtils;
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+
+public class ItemUtil {
+
+ private static final Map NAME_MAP = new HashMap<>();
+
+ public static ItemData[] repeat(Material material, int times) {
+ return repeat(material, (byte) 0, times);
+ }
+
+ public static ItemData[] repeat(Material material, byte data, int times) {
+ ItemData[] itemData = new ItemData[times];
+
+ for (int i = 0; i < times; i++) {
+ itemData[i] = new ItemData(material, data);
+ }
+
+ return itemData;
+
+ }
+
+ public static ItemData[] armorOf(ArmorPart part) {
+ List data = new ArrayList<>();
+
+ for (ArmorType at : ArmorType.values()) {
+ data.add(new ItemData(Material.valueOf(at.name() + "_" + part.name()), (short) 0));
+ }
+
+ return data.toArray(new ItemData[data.size()]);
+ }
+
+ public static ItemData[] swords() {
+ List data = new ArrayList<>();
+
+ for (SwordType at : SwordType.values()) {
+ data.add(new ItemData(Material.valueOf(at.name() + "_SWORD"), (short) 0));
+ }
+
+ return data.toArray(new ItemData[data.size()]);
+ }
+
+ public static void load() {
+ NAME_MAP.clear();
+
+ List lines = readLines();
+
+ for (String line : lines) {
+ String[] parts = line.split(",");
+
+ NAME_MAP.put(
+ parts[0],
+ new ItemData(Material.getMaterial(Integer.parseInt(parts[1])), Short.parseShort(parts[2]))
+ );
+ }
+ }
+
+ public static ItemStack get(String input, int amount) {
+ ItemStack item = get(input);
+
+ if (item != null) {
+ item.setAmount(amount);
+ }
+
+ return item;
+ }
+
+ public static ItemStack get(String input) {
+ if (NumberUtil.isInteger(input)) {
+ return new ItemStack(Material.getMaterial(Integer.parseInt(input)));
+ }
+
+ if (input.contains(":")) {
+ if (NumberUtil.isShort(input.split(":")[1])) {
+ if (NumberUtil.isInteger(input.split(":")[0])) {
+ return new ItemStack(
+ Material.getMaterial(Integer.parseInt(input.split(":")[0])), 1,
+ Short.parseShort(input.split(":")[1])
+ );
+ } else {
+ if (!NAME_MAP.containsKey(input.split(":")[0].toLowerCase())) {
+ return null;
+ }
+
+ ItemData data = NAME_MAP.get(input.split(":")[0].toLowerCase());
+ return new ItemStack(data.getMaterial(), 1, Short.parseShort(input.split(":")[1]));
+ }
+ } else {
+ return null;
+ }
+ }
+
+ if (!NAME_MAP.containsKey(input)) {
+ return null;
+ }
+
+ return NAME_MAP.get(input).toItemStack();
+ }
+
+ public static String getName(ItemStack item) {
+ if (item.getDurability() != 0) {
+ String reflectedName = BukkitReflection.getItemStackName(item);
+
+ if (reflectedName != null) {
+ if (reflectedName.contains(".")) {
+ reflectedName = WordUtils.capitalize(item.getType().toString().toLowerCase().replace("_", " "));
+ }
+
+ return reflectedName;
+ }
+ }
+
+ String string = item.getType().toString().replace("_", " ");
+ char[] chars = string.toLowerCase().toCharArray();
+ boolean found = false;
+
+ for (int i = 0; i < chars.length; i++) {
+ if (!found && Character.isLetter(chars[i])) {
+ chars[i] = Character.toUpperCase(chars[i]);
+ found = true;
+ } else if (Character.isWhitespace(chars[i]) || chars[i] == '.' || chars[i] == '\'') {
+ found = false;
+ }
+ }
+
+ return String.valueOf(chars);
+ }
+
+ private static List readLines() {
+ try {
+ return IOUtils.readLines(Nucleus.class.getClassLoader().getResourceAsStream("items.csv"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public enum ArmorPart {
+ HELMET,
+ CHESTPLATE,
+ LEGGINGS,
+ BOOTS
+ }
+
+ public enum ArmorType {
+ DIAMOND,
+ IRON,
+ GOLD,
+ LEATHER
+ }
+
+ public enum SwordType {
+ DIAMOND,
+ IRON,
+ GOLD,
+ STONE
+ }
+
+ @Getter
+ @AllArgsConstructor
+ public static class ItemData {
+
+ private final Material material;
+ private final short data;
+
+ public String getName() {
+ return ItemUtil.getName(toItemStack());
+ }
+
+ public boolean matches(ItemStack item) {
+ return item != null && item.getType() == material && item.getDurability() == data;
+ }
+
+ public ItemStack toItemStack() {
+ return new ItemStack(material, 1, data);
+ }
+
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/LocationUtil.java b/src/main/java/me/joeleoli/nucleus/util/LocationUtil.java
new file mode 100644
index 0000000..a73f137
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/LocationUtil.java
@@ -0,0 +1,30 @@
+package me.joeleoli.nucleus.util;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+
+public class LocationUtil {
+
+ public static Location[] getFaces(Location start) {
+ Location[] faces = new Location[4];
+ faces[0] = new Location(start.getWorld(), start.getX() + 1, start.getY(), start.getZ());
+ faces[1] = new Location(start.getWorld(), start.getX() - 1, start.getY(), start.getZ());
+ faces[2] = new Location(start.getWorld(), start.getX(), start.getY() + 1, start.getZ());
+ faces[3] = new Location(start.getWorld(), start.getX(), start.getY() - 1, start.getZ());
+ return faces;
+ }
+
+ public static String serialize(final Location location) {
+ return location.getWorld().getName() + ":" + location.getX() + ":" + location.getY() + ":" + location.getZ() +
+ ":" + location.getYaw() + ":" + location.getPitch();
+ }
+
+ public static Location deserialize(final String source) {
+ final String[] split = source.split(":");
+ return new Location(
+ Bukkit.getServer().getWorld(split[0]), Double.parseDouble(split[1]), Double.parseDouble(split[2]),
+ Double.parseDouble(split[3]), Float.parseFloat(split[4]), Float.parseFloat(split[5])
+ );
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/NumberUtil.java b/src/main/java/me/joeleoli/nucleus/util/NumberUtil.java
new file mode 100644
index 0000000..1f2a2a7
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/NumberUtil.java
@@ -0,0 +1,80 @@
+package me.joeleoli.nucleus.util;
+
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class NumberUtil {
+
+ private static final NavigableMap SUFFIXES = new TreeMap<>();
+
+ static {
+ SUFFIXES.put(1_000L, "k");
+ SUFFIXES.put(1_000_000L, "M");
+ SUFFIXES.put(1_000_000_000L, "B");
+ SUFFIXES.put(1_000_000_000_000L, "T");
+ SUFFIXES.put(1_000_000_000_000L, "Q");
+ SUFFIXES.put(1_000_000_000_000_000L, "QT");
+ }
+
+ public static int getRandomRange(int min, int max) {
+ return ThreadLocalRandom.current().nextInt(min, max + 1);
+ }
+
+ public static float getRandomRange() {
+ return ThreadLocalRandom.current().nextFloat();
+ }
+
+ public static boolean isInteger(String input) {
+ try {
+ Integer.parseInt(input);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ public static boolean isShort(String input) {
+ try {
+ Short.parseShort(input);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ public static boolean isEven(int number) {
+ return number % 2 == 0;
+ }
+
+ public static String convertAbbreviated(long value) {
+ if (value == Long.MIN_VALUE) {
+ return convertAbbreviated(Long.MIN_VALUE + 1);
+ }
+
+ if (value < 0) {
+ return "-" + convertAbbreviated(-value);
+ }
+
+ if (value < 1000) {
+ return Long.toString(value);
+ }
+
+ Map.Entry e = SUFFIXES.floorEntry(value);
+ Long divideBy = e.getKey();
+ String suffix = e.getValue();
+
+ long truncated = value / (divideBy / 10);
+ boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
+
+ return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
+ }
+
+ public static String formatNumber(long value) {
+ return NumberFormat.getInstance(Locale.US).format(value);
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/ObjectUtil.java b/src/main/java/me/joeleoli/nucleus/util/ObjectUtil.java
new file mode 100644
index 0000000..837781c
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/ObjectUtil.java
@@ -0,0 +1,36 @@
+package me.joeleoli.nucleus.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class ObjectUtil {
+
+ public static Object transform(String value) {
+ if (value.equalsIgnoreCase("true")) {
+ return true;
+ } else if (value.equalsIgnoreCase("false")) {
+ return false;
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * @param e An {@link Enum}.
+ *
+ * @return The first {@link Enum} or next {@link Enum} according to the given {@link Enum}'s ordinal.
+ */
+ public static Object getNext(Enum e) {
+ Object[] objects = e.getClass().getEnumConstants();
+
+ if (e.ordinal() == objects.length - 1) {
+ return objects[0];
+ } else {
+ return objects[e.ordinal() + 1];
+ }
+ }
+
+ public static String toReadable(Enum e) {
+ return StringUtils.capitalize(e.name().toLowerCase());
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/PlayerUtil.java b/src/main/java/me/joeleoli/nucleus/util/PlayerUtil.java
new file mode 100644
index 0000000..8e6f873
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/PlayerUtil.java
@@ -0,0 +1,115 @@
+package me.joeleoli.nucleus.util;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import me.joeleoli.nucleus.Nucleus;
+import me.joeleoli.nucleus.NucleusAPI;
+import me.joeleoli.nucleus.player.NucleusPlayer;
+import org.bukkit.Bukkit;
+import org.bukkit.GameMode;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+
+public class PlayerUtil {
+
+ public static final Comparator VISIBLE_RANK_ORDER = (a, b) -> {
+ NucleusPlayer playerA = NucleusPlayer.getByUuid(a.getUniqueId());
+ NucleusPlayer playerB = NucleusPlayer.getByUuid(b.getUniqueId());
+
+ return -playerA.getActiveRank().compareTo(playerB.getActiveRank());
+ };
+
+ public static List getPlayersSortedByRank() {
+ final List list = new ArrayList<>(Bukkit.getOnlinePlayers());
+
+ list.sort(VISIBLE_RANK_ORDER);
+
+ return list;
+ }
+
+ public static void spawn(Player player) {
+ player.teleport(Bukkit.getWorlds().get(0).getSpawnLocation());
+ }
+
+ public static void reset(Player player) {
+ reset(player, true);
+ }
+
+ public static void reset(Player player, boolean resetHeldSlot) {
+ if (!NucleusAPI.isFrozen(player)) {
+ player.setWalkSpeed(0.2F);
+ player.setFlySpeed(0.0001F);
+ }
+
+ player.setHealth(20.0D);
+ player.setSaturation(20.0F);
+ player.setFallDistance(0.0F);
+ player.setFoodLevel(20);
+ player.setFireTicks(0);
+ player.setMaximumNoDamageTicks(20);
+ player.setExp(0.0F);
+ player.setLevel(0);
+ player.setAllowFlight(false);
+ player.setFlying(false);
+ player.setGameMode(GameMode.SURVIVAL);
+ player.getInventory().setArmorContents(new ItemStack[4]);
+ player.getInventory().setContents(new ItemStack[36]);
+ player.getActivePotionEffects().forEach(effect -> player.removePotionEffect(effect.getType()));
+
+ if (resetHeldSlot) {
+ player.getInventory().setHeldItemSlot(0);
+ }
+
+ player.updateInventory();
+ }
+
+ public static void denyMovement(Player player) {
+ player.setWalkSpeed(0.0F);
+ player.setFlySpeed(0.0F);
+ player.setFoodLevel(0);
+ player.setSprinting(false);
+ player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP, Integer.MAX_VALUE, 200));
+ }
+
+ public static void allowMovement(Player player) {
+ player.setWalkSpeed(0.2F);
+ player.setFlySpeed(0.0001F);
+ player.setFoodLevel(20);
+ player.setSprinting(true);
+ player.removePotionEffect(PotionEffectType.JUMP);
+ }
+
+ public static void messageAll(String message) {
+ for (Player player : Nucleus.getInstance().getServer().getOnlinePlayers()) {
+ player.sendMessage(message);
+ }
+ }
+
+ public static void messageStaff(String message) {
+ for (Player player : Nucleus.getInstance().getServer().getOnlinePlayers()) {
+ if (player.hasPermission("nucleus.staff")) {
+ player.sendMessage(message);
+ }
+ }
+ }
+
+ public static List convertUUIDListToPlayerList(List list) {
+ return list.stream().map(Bukkit::getPlayer).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
+ public List getPlayerList() {
+ return PlayerUtil.getPlayersSortedByRank().stream()
+ .map(Player::getUniqueId)
+ .map(NucleusPlayer::getByUuid)
+ .map(nucleusPlayer -> nucleusPlayer.getActiveRank().getColor() +
+ nucleusPlayer.getDisplayName() + Style.RESET)
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/Style.java b/src/main/java/me/joeleoli/nucleus/util/Style.java
new file mode 100644
index 0000000..d0a45de
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/Style.java
@@ -0,0 +1,213 @@
+package me.joeleoli.nucleus.util;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import me.joeleoli.nucleus.NucleusAPI;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+public final class Style {
+
+ public static final String API_FAILED =
+ ChatColor.RED.toString() + "The API failed to retrieve your information. Try again later.";
+ public static final String BLUE = ChatColor.BLUE.toString();
+ public static final String AQUA = ChatColor.AQUA.toString();
+ public static final String YELLOW = ChatColor.YELLOW.toString();
+ public static final String RED = ChatColor.RED.toString();
+ public static final String GRAY = ChatColor.GRAY.toString();
+ public static final String GOLD = ChatColor.GOLD.toString();
+ public static final String GREEN = ChatColor.GREEN.toString();
+ public static final String WHITE = ChatColor.WHITE.toString();
+ public static final String BLACK = ChatColor.BLACK.toString();
+ public static final String BOLD = ChatColor.BOLD.toString();
+ public static final String ITALIC = ChatColor.ITALIC.toString();
+ public static final String UNDER_LINE = ChatColor.UNDERLINE.toString();
+ public static final String STRIKE_THROUGH = ChatColor.STRIKETHROUGH.toString();
+ public static final String RESET = ChatColor.RESET.toString();
+ public static final String MAGIC = ChatColor.MAGIC.toString();
+ public static final String DARK_BLUE = ChatColor.DARK_BLUE.toString();
+ public static final String DARK_AQUA = ChatColor.DARK_AQUA.toString();
+ public static final String DARK_GRAY = ChatColor.DARK_GRAY.toString();
+ public static final String DARK_GREEN = ChatColor.DARK_GREEN.toString();
+ public static final String DARK_PURPLE = ChatColor.DARK_PURPLE.toString();
+ public static final String DARK_RED = ChatColor.DARK_RED.toString();
+ public static final String PINK = ChatColor.LIGHT_PURPLE.toString();
+ public static final String BLANK_LINE = "§a §b §c §d §e §f §0 §r";
+ public static final String BORDER_LINE_SCOREBOARD = Style.GRAY + Style.STRIKE_THROUGH + "----------------------";
+ public static final String UNICODE_VERTICAL_BAR = Style.GRAY + StringEscapeUtils.unescapeJava("\u2503");
+ public static final String UNICODE_CAUTION = StringEscapeUtils.unescapeJava("\u26a0");
+ public static final String UNICODE_ARROW_LEFT = StringEscapeUtils.unescapeJava("\u25C0");
+ public static final String UNICODE_ARROW_RIGHT = StringEscapeUtils.unescapeJava("\u25B6");
+ public static final String UNICODE_ARROWS_LEFT = StringEscapeUtils.unescapeJava("\u00AB");
+ public static final String UNICODE_ARROWS_RIGHT = StringEscapeUtils.unescapeJava("\u00BB");
+ public static final String UNICODE_HEART = StringEscapeUtils.unescapeJava("\u2764");
+ private static final FontRenderer FONT_RENDERER = new FontRenderer();
+ private static final String MAX_LENGTH = "11111111111111111111111111111111111111111111111111111";
+ public static final String SERVER_NAME = "MineXD";
+ public static final String SERVER_SITE = "www.minexd.com";
+
+ private Style() {
+ throw new RuntimeException("Cannot instantiate a utility class.");
+ }
+
+ public static String strip(String in) {
+ return ChatColor.stripColor(translate(in));
+ }
+
+ public static String translate(String in) {
+ return ChatColor.translateAlternateColorCodes('&', in);
+ }
+
+ public static List translateLines(List lines) {
+ List toReturn = new ArrayList<>();
+
+ for (String line : lines) {
+ toReturn.add(ChatColor.translateAlternateColorCodes('&', line));
+ }
+
+ return toReturn;
+ }
+
+ public static String formatPlayerNotFoundMessage(String player) {
+ return Style.RED + "Couldn't find a player with the name " + Style.RESET + player +
+ Style.RED + ". Have they joined the network?";
+ }
+
+ public static String formatBrokenProfileMessage(String player) {
+ return Style.RED + "Couldn't load " + Style.RESET + player + Style.RED + "'s profile. Try again later.";
+ }
+
+ public static String formatFilteredPublicMessage(Player player, String message) {
+ return new MessageFormat("{0}[Filtered] {1}{2}: {3}")
+ .format(new Object[]{
+ Style.RED + Style.BOLD, Style.RESET + NucleusAPI.getPrefixedName(player), Style.WHITE, message
+ });
+ }
+
+ public static String[] formatPrivateMessage(String from, String to, String message) {
+ String toMessage = Style.YELLOW + "(To " + to + Style.YELLOW + ") " + message;
+ String fromMessage = Style.YELLOW + "(From " + from + Style.YELLOW + ") " + message;
+ return new String[]{ toMessage, fromMessage };
+ }
+
+ public static String formatFreezeMessage(String sender, String target, String context) {
+ return new MessageFormat("{2} {1}{3}{0} has been {5} by {1}{4}").format(new Object[]{
+ Style.YELLOW, Style.PINK, Style.GOLD + Style.BOLD + Style.UNICODE_CAUTION, target, sender, context
+ });
+ }
+
+ public static String formatMuteChatMessage(String sender, String context) {
+ return new MessageFormat("{0}Public chat has been {3} by {1}{2}")
+ .format(new Object[]{ Style.YELLOW, Style.PINK, sender, context });
+ }
+
+ public static String formatClearChatMessage(String sender) {
+ return new MessageFormat("{0}Public chat has been cleared by {1}{2}")
+ .format(new Object[]{ Style.YELLOW, Style.PINK, sender });
+ }
+
+ public static String formatStaffJoinMessage(String player, String server) {
+ return new MessageFormat("{0}[S] {2}{3} {1}joined {4}")
+ .format(new Object[]{ Style.AQUA, Style.AQUA, Style.RESET, player, server });
+ }
+
+ public static String formatStaffChatMessage(String server, String player, String message) {
+ return new MessageFormat("{0}[S] [{5}] {2}{3}{1}: {4}")
+ .format(new Object[]{ Style.AQUA, Style.AQUA, Style.RESET, player, message, server });
+ }
+
+ public static String formatReportMessage(String player, String target, String message, String server) {
+ return new MessageFormat("{0}[R] [{6}] {2}{4} {1}reported by {2}{3} {1}for: {0}{5}")
+ .format(new Object[]{
+ Style.AQUA, Style.GRAY, Style.RESET, player, target, message, server
+ });
+ }
+
+ public static String formatRequestMessage(String player, String message, String server) {
+ return new MessageFormat("{0}[R] [{5}] {2}{3} {1}requested assistance: {0}{4}")
+ .format(new Object[]{ Style.AQUA, Style.GRAY, Style.RESET, player, message, server });
+ }
+
+ public static String formatArrowHitMessage(String damaged, double health) {
+ return Style.YELLOW + "You shot " + Style.PINK + damaged + Style.YELLOW + "!" + Style.GRAY + " (" + Style.RED +
+ health + Style.DARK_RED + " " + Style.UNICODE_HEART + Style.GRAY + ")";
+ }
+
+ public static String formatPunishmentMessage(String staff, String target, String context) {
+ return new MessageFormat("&r{0} &ahas been {1} by &r{2}").format(new Object[]{ target, context, staff });
+ }
+
+ public static String getBorderLine() {
+ int chatWidth = FONT_RENDERER.getWidth(MAX_LENGTH) / 10 * 9;
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < 100; i++) {
+ sb.append("-");
+
+ if (FONT_RENDERER.getWidth(sb.toString()) >= chatWidth) {
+ break;
+ }
+ }
+
+ return Style.GRAY + Style.STRIKE_THROUGH + sb.toString();
+ }
+
+ public static String center(String string) {
+ StringBuilder preColors = new StringBuilder();
+
+ while (string.startsWith(ChatColor.COLOR_CHAR + "")) {
+ preColors.append(string.substring(0, 2));
+ string = string.substring(2, string.length());
+ }
+
+ int width = FONT_RENDERER.getWidth(string);
+ int chatWidth = FONT_RENDERER.getWidth(MAX_LENGTH);
+
+ if (width == chatWidth) {
+ return string;
+ } else if (width > chatWidth) {
+ String[] words = string.split(" ");
+
+ if (words.length == 1) {
+ return string;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ int total = 0;
+
+ for (String word : words) {
+ int wordWidth = FONT_RENDERER.getWidth(word + " ");
+
+ if (total + wordWidth > chatWidth) {
+ sb.append("\n");
+ total = 0;
+ }
+
+ total += wordWidth;
+ sb.append(word).append(" ");
+ }
+
+ return center(preColors + sb.toString().trim());
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ int diff = (chatWidth) - (width);
+ diff /= 3;
+
+ for (int i = 0; i < 100; i++) {
+ sb.append(" ");
+ if (FONT_RENDERER.getWidth(sb.toString()) >= diff) {
+ break;
+ }
+ }
+
+ sb.append(string);
+
+ return preColors + sb.toString();
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/TaskUtil.java b/src/main/java/me/joeleoli/nucleus/util/TaskUtil.java
new file mode 100644
index 0000000..8355b2c
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/TaskUtil.java
@@ -0,0 +1,28 @@
+package me.joeleoli.nucleus.util;
+
+import me.joeleoli.nucleus.Nucleus;
+import org.bukkit.scheduler.BukkitRunnable;
+
+public class TaskUtil {
+
+ public static void run(Runnable runnable) {
+ Nucleus.getInstance().getServer().getScheduler().runTask(Nucleus.getInstance(), runnable);
+ }
+
+ public static void runTimer(Runnable runnable, long delay, long timer) {
+ Nucleus.getInstance().getServer().getScheduler().runTaskTimer(Nucleus.getInstance(), runnable, delay, timer);
+ }
+
+ public static void runTimer(BukkitRunnable runnable, long delay, long timer) {
+ runnable.runTaskTimer(Nucleus.getInstance(), delay, timer);
+ }
+
+ public static void runLater(Runnable runnable, long delay) {
+ Nucleus.getInstance().getServer().getScheduler().runTaskLater(Nucleus.getInstance(), runnable, delay);
+ }
+
+ public static void runAsync(Runnable runnable) {
+ Nucleus.getInstance().getServer().getScheduler().runTaskAsynchronously(Nucleus.getInstance(), runnable);
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/TextSplitter.java b/src/main/java/me/joeleoli/nucleus/util/TextSplitter.java
new file mode 100644
index 0000000..f9e007c
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/TextSplitter.java
@@ -0,0 +1,35 @@
+package me.joeleoli.nucleus.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TextSplitter {
+
+ public static List split(String text, String prefix) {
+ if (text.length() <= 32) {
+ return Collections.singletonList(prefix + text);
+ }
+
+ final List lines = new ArrayList<>();
+ final String[] split = text.split(" ");
+ StringBuilder builder = new StringBuilder(prefix);
+
+ for (int i = 0; i < split.length; i++) {
+ if (builder.length() + split[i].length() >= 32) {
+ lines.add(builder.toString());
+ builder = new StringBuilder(prefix);
+ }
+
+ builder.append(split[i]);
+ builder.append(" ");
+ }
+
+ if (builder.length() != 0) {
+ lines.add(builder.toString());
+ }
+
+ return lines;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/me/joeleoli/nucleus/util/TimeUtil.java b/src/main/java/me/joeleoli/nucleus/util/TimeUtil.java
new file mode 100644
index 0000000..1ffcc96
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/TimeUtil.java
@@ -0,0 +1,144 @@
+package me.joeleoli.nucleus.util;
+
+import java.sql.Timestamp;
+import java.text.DecimalFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class TimeUtil {
+
+ private static final String HOUR_FORMAT = "%02d:%02d:%02d";
+ private static final String MINUTE_FORMAT = "%02d:%02d";
+
+ private TimeUtil() {
+ throw new RuntimeException("Cannot instantiate a utility class.");
+ }
+
+ public static String millisToTimer(long millis) {
+ long seconds = millis / 1000L;
+
+ if (seconds > 3600L) {
+ return String.format(HOUR_FORMAT, seconds / 3600L, seconds % 3600L / 60L, seconds % 60L);
+ } else {
+ return String.format(MINUTE_FORMAT, seconds / 60L, seconds % 60L);
+ }
+ }
+
+ /**
+ * Return the amount of seconds from milliseconds.
+ * Note: We explicitly use 1000.0F (float) instead of 1000L (long).
+ *
+ * @param millis the amount of time in milliseconds
+ * @return the seconds
+ */
+ public static String millisToSeconds(long millis) {
+ return new DecimalFormat("#0.0").format(millis / 1000.0F);
+ }
+
+ public static String dateToString(Date date) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+
+ return calendar.getTime().toString();
+ }
+
+ public static Timestamp addDuration(long duration) {
+ return truncateTimestamp(new Timestamp(System.currentTimeMillis() + duration));
+ }
+
+ public static Timestamp truncateTimestamp(Timestamp timestamp) {
+ if (timestamp.toLocalDateTime().getYear() > 2037) {
+ timestamp.setYear(2037);
+ }
+
+ return timestamp;
+ }
+
+ public static Timestamp addDuration(Timestamp timestamp) {
+ return truncateTimestamp(new Timestamp(System.currentTimeMillis() + timestamp.getTime()));
+ }
+
+ public static Timestamp fromMillis(long millis) {
+ return new Timestamp(millis);
+ }
+
+ public static Timestamp getCurrentTimestamp() {
+ return new Timestamp(System.currentTimeMillis());
+ }
+
+ public static String millisToRoundedTime(long millis) {
+ millis += 1L;
+
+ long seconds = millis / 1000L;
+ long minutes = seconds / 60L;
+ long hours = minutes / 60L;
+ long days = hours / 24L;
+ long weeks = days / 7L;
+ long months = weeks / 4L;
+ long years = months / 12L;
+
+ if (years > 0) {
+ return years + " year" + (years == 1 ? "" : "s");
+ } else if (months > 0) {
+ return months + " month" + (months == 1 ? "" : "s");
+ } else if (weeks > 0) {
+ return weeks + " week" + (weeks == 1 ? "" : "s");
+ } else if (days > 0) {
+ return days + " day" + (days == 1 ? "" : "s");
+ } else if (hours > 0) {
+ return hours + " hour" + (hours == 1 ? "" : "s");
+ } else if (minutes > 0) {
+ return minutes + " minute" + (minutes == 1 ? "" : "s");
+ } else {
+ return seconds + " second" + (seconds == 1 ? "" : "s");
+ }
+ }
+
+ public static long parseTime(String time) {
+ long totalTime = 0L;
+ boolean found = false;
+ Matcher matcher = Pattern.compile("\\d+\\D+").matcher(time);
+
+ while (matcher.find()) {
+ String s = matcher.group();
+ Long value = Long.parseLong(s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[0]);
+ String type = s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[1];
+
+ switch (type) {
+ case "s":
+ totalTime += value;
+ found = true;
+ break;
+ case "m":
+ totalTime += value * 60;
+ found = true;
+ break;
+ case "h":
+ totalTime += value * 60 * 60;
+ found = true;
+ break;
+ case "d":
+ totalTime += value * 60 * 60 * 24;
+ found = true;
+ break;
+ case "w":
+ totalTime += value * 60 * 60 * 24 * 7;
+ found = true;
+ break;
+ case "M":
+ totalTime += value * 60 * 60 * 24 * 30;
+ found = true;
+ break;
+ case "y":
+ totalTime += value * 60 * 60 * 24 * 365;
+ found = true;
+ break;
+ }
+ }
+
+ return !found ? -1 : totalTime * 1000;
+ }
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/util/TypeCallback.java b/src/main/java/me/joeleoli/nucleus/util/TypeCallback.java
new file mode 100644
index 0000000..a9bc96b
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/util/TypeCallback.java
@@ -0,0 +1,14 @@
+package me.joeleoli.nucleus.util;
+
+import java.io.Serializable;
+
+public interface TypeCallback extends Serializable {
+
+ /**
+ * A callback for running a task on a set of data.
+ *
+ * @param data the data needed to run the task.
+ */
+ void callback(T data);
+
+}
diff --git a/src/main/java/me/joeleoli/nucleus/uuid/UUIDCache.java b/src/main/java/me/joeleoli/nucleus/uuid/UUIDCache.java
new file mode 100644
index 0000000..563022e
--- /dev/null
+++ b/src/main/java/me/joeleoli/nucleus/uuid/UUIDCache.java
@@ -0,0 +1,76 @@
+package me.joeleoli.nucleus.uuid;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import me.joeleoli.nucleus.Nucleus;
+import me.joeleoli.nucleus.util.AtomicString;
+
+public class UUIDCache {
+
+ private static Map nameToUuid = new HashMap<>();
+ private static Map