2017-12-30 08:36:36 +01:00
|
|
|
package net.citizensnpcs.util;
|
|
|
|
|
2019-05-24 12:32:51 +02:00
|
|
|
import java.util.EnumSet;
|
2017-12-30 08:36:36 +01:00
|
|
|
import java.util.Random;
|
2019-05-24 12:32:51 +02:00
|
|
|
import java.util.Set;
|
2020-05-05 12:20:56 +02:00
|
|
|
import java.util.UUID;
|
2020-05-12 10:48:21 +02:00
|
|
|
import java.util.regex.Pattern;
|
2017-12-30 08:36:36 +01:00
|
|
|
|
|
|
|
import org.bukkit.Bukkit;
|
2018-12-19 21:44:06 +01:00
|
|
|
import org.bukkit.ChatColor;
|
2017-12-30 08:36:36 +01:00
|
|
|
import org.bukkit.Location;
|
|
|
|
import org.bukkit.Material;
|
2020-11-14 11:41:21 +01:00
|
|
|
import org.bukkit.block.Block;
|
|
|
|
import org.bukkit.block.BlockFace;
|
2017-12-30 08:36:36 +01:00
|
|
|
import org.bukkit.entity.Entity;
|
|
|
|
import org.bukkit.entity.EntityType;
|
|
|
|
import org.bukkit.entity.LivingEntity;
|
|
|
|
import org.bukkit.entity.Player;
|
2018-04-07 10:02:35 +02:00
|
|
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
|
|
|
import org.bukkit.event.player.PlayerInteractEvent;
|
2020-05-01 11:49:25 +02:00
|
|
|
import org.bukkit.scoreboard.Scoreboard;
|
2020-04-30 19:39:07 +02:00
|
|
|
import org.bukkit.scoreboard.Team;
|
2017-12-30 08:36:36 +01:00
|
|
|
import org.bukkit.util.Vector;
|
|
|
|
|
|
|
|
import com.google.common.base.Joiner;
|
|
|
|
import com.google.common.base.Splitter;
|
|
|
|
|
|
|
|
import net.citizensnpcs.api.event.NPCCollisionEvent;
|
|
|
|
import net.citizensnpcs.api.event.NPCPushEvent;
|
|
|
|
import net.citizensnpcs.api.npc.NPC;
|
2021-11-21 15:12:17 +01:00
|
|
|
import net.citizensnpcs.api.util.BoundingBox;
|
2018-07-20 15:57:45 +02:00
|
|
|
import net.citizensnpcs.api.util.SpigotUtil;
|
2020-04-30 19:39:07 +02:00
|
|
|
import net.citizensnpcs.npc.ai.NPCHolder;
|
2017-12-30 08:36:36 +01:00
|
|
|
|
|
|
|
public class Util {
|
|
|
|
// Static class for small (emphasis small) utility methods
|
|
|
|
private Util() {
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void assumePose(Entity entity, float yaw, float pitch) {
|
|
|
|
NMS.look(entity, yaw, pitch);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void callCollisionEvent(NPC npc, Entity entity) {
|
|
|
|
if (NPCCollisionEvent.getHandlerList().getRegisteredListeners().length > 0) {
|
|
|
|
Bukkit.getPluginManager().callEvent(new NPCCollisionEvent(npc, entity));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-02 17:14:00 +01:00
|
|
|
public static Vector callPushEvent(NPC npc, double x, double y, double z) {
|
|
|
|
if (npc == null) {
|
|
|
|
return new Vector(x, y, z);
|
|
|
|
}
|
2022-01-27 03:48:52 +01:00
|
|
|
boolean allowed = !npc.isProtected()
|
2022-01-20 10:10:52 +01:00
|
|
|
|| (npc.data().has(NPC.COLLIDABLE_METADATA) && npc.data().<Boolean> get(NPC.COLLIDABLE_METADATA));
|
2021-01-02 17:14:00 +01:00
|
|
|
if (NPCPushEvent.getHandlerList().getRegisteredListeners().length == 0) {
|
|
|
|
return allowed ? new Vector(x, y, z) : null;
|
|
|
|
}
|
2021-01-03 17:18:34 +01:00
|
|
|
// when another entity collides, this method is called to push the NPC so we prevent it from
|
|
|
|
// doing anything if the event is cancelled.
|
2021-01-02 17:14:00 +01:00
|
|
|
Vector vector = new Vector(x, y, z);
|
2017-12-30 08:36:36 +01:00
|
|
|
NPCPushEvent event = new NPCPushEvent(npc, vector);
|
2021-01-17 00:05:35 +01:00
|
|
|
event.setCancelled(!allowed);
|
2017-12-30 08:36:36 +01:00
|
|
|
Bukkit.getPluginManager().callEvent(event);
|
2021-01-02 17:14:00 +01:00
|
|
|
return !event.isCancelled() ? event.getCollisionVector() : null;
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static float clampYaw(float yaw) {
|
|
|
|
while (yaw < -180.0F) {
|
|
|
|
yaw += 360.0F;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (yaw >= 180.0F) {
|
|
|
|
yaw -= 360.0F;
|
|
|
|
}
|
|
|
|
return yaw;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void faceEntity(Entity entity, Entity at) {
|
|
|
|
if (at == null || entity == null || entity.getWorld() != at.getWorld())
|
|
|
|
return;
|
|
|
|
if (at instanceof LivingEntity) {
|
|
|
|
NMS.look(entity, at);
|
|
|
|
} else {
|
|
|
|
faceLocation(entity, at.getLocation(AT_LOCATION));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void faceLocation(Entity entity, Location to) {
|
|
|
|
faceLocation(entity, to, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void faceLocation(Entity entity, Location to, boolean headOnly) {
|
|
|
|
faceLocation(entity, to, headOnly, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void faceLocation(Entity entity, Location to, boolean headOnly, boolean immediate) {
|
|
|
|
if (to == null || entity.getWorld() != to.getWorld())
|
|
|
|
return;
|
|
|
|
NMS.look(entity, to, headOnly, immediate);
|
|
|
|
}
|
|
|
|
|
2021-05-07 20:02:17 +02:00
|
|
|
public static void generateTeamFor(NPC npc, String name, String teamName) {
|
|
|
|
Scoreboard scoreboard = getDummyScoreboard();
|
|
|
|
Team team = scoreboard.getTeam(teamName);
|
|
|
|
int mode = 2;
|
|
|
|
if (team == null) {
|
|
|
|
team = scoreboard.registerNewTeam(teamName);
|
2021-06-25 11:01:18 +02:00
|
|
|
if (npc.requiresNameHologram()
|
2022-03-23 13:57:18 +01:00
|
|
|
|| npc.data().<Object> get(NPC.Metadata.NAMEPLATE_VISIBLE, true).toString().equals("false")) {
|
2021-05-07 20:02:17 +02:00
|
|
|
NMS.setTeamNameTagVisible(team, false);
|
|
|
|
}
|
|
|
|
mode = 0;
|
|
|
|
}
|
|
|
|
team.addEntry(name);
|
|
|
|
npc.data().set(NPC.SCOREBOARD_FAKE_TEAM_NAME_METADATA, teamName);
|
|
|
|
sendTeamPacketToOnlinePlayers(team, mode);
|
|
|
|
}
|
|
|
|
|
2021-11-21 15:12:17 +01:00
|
|
|
public static Location getCenterLocation(Block block) {
|
|
|
|
Location bloc = block.getLocation();
|
|
|
|
Location center = new Location(bloc.getWorld(), bloc.getBlockX() + 0.5, bloc.getBlockY(),
|
|
|
|
bloc.getBlockZ() + 0.5);
|
|
|
|
BoundingBox bb = NMS.getCollisionBox(block);
|
2021-11-27 05:19:19 +01:00
|
|
|
if (bb != null && (bb.maxY - bb.minY) < 0.6D) {
|
|
|
|
center.setY(center.getY() + (bb.maxY - bb.minY));
|
2021-11-21 15:12:17 +01:00
|
|
|
}
|
|
|
|
return center;
|
|
|
|
}
|
|
|
|
|
2022-02-20 15:34:29 +01:00
|
|
|
public static float getDragonYaw(Entity entity, double motX, double motZ) {
|
|
|
|
Location location = entity.getLocation(AT_LOCATION);
|
|
|
|
double x = location.getX();
|
|
|
|
double z = location.getZ();
|
|
|
|
double tX = x + motX;
|
|
|
|
double tZ = z + motZ;
|
|
|
|
if (z > tZ)
|
|
|
|
return (float) (-Math.toDegrees(Math.atan((x - tX) / (z - tZ))));
|
|
|
|
if (z < tZ) {
|
|
|
|
return (float) (-Math.toDegrees(Math.atan((x - tX) / (z - tZ)))) + 180.0F;
|
|
|
|
}
|
|
|
|
return location.getYaw();
|
|
|
|
}
|
|
|
|
|
2020-05-01 11:49:25 +02:00
|
|
|
public static Scoreboard getDummyScoreboard() {
|
|
|
|
return DUMMY_SCOREBOARD;
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
public static Location getEyeLocation(Entity entity) {
|
|
|
|
return entity instanceof LivingEntity ? ((LivingEntity) entity).getEyeLocation() : entity.getLocation();
|
|
|
|
}
|
|
|
|
|
2021-02-10 15:02:14 +01:00
|
|
|
public static Material getFallbackMaterial(String first, String second) {
|
|
|
|
try {
|
|
|
|
return Material.valueOf(first);
|
|
|
|
} catch (IllegalArgumentException e) {
|
|
|
|
return Material.valueOf(second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
public static Random getFastRandom() {
|
|
|
|
return new XORShiftRNG();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String getMinecraftRevision() {
|
2019-04-29 09:58:07 +02:00
|
|
|
if (MINECRAFT_REVISION == null) {
|
|
|
|
MINECRAFT_REVISION = Bukkit.getServer().getClass().getPackage().getName();
|
|
|
|
}
|
|
|
|
return MINECRAFT_REVISION.substring(MINECRAFT_REVISION.lastIndexOf('.') + 2);
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
|
2020-05-05 12:20:56 +02:00
|
|
|
public static String getTeamName(UUID id) {
|
|
|
|
return "CIT-" + id.toString().replace("-", "").substring(0, 12);
|
|
|
|
}
|
|
|
|
|
2020-11-14 11:41:21 +01:00
|
|
|
public static boolean inBlock(Entity entity) {
|
|
|
|
// TODO: bounding box aware?
|
|
|
|
Location loc = entity.getLocation(AT_LOCATION);
|
|
|
|
if (!Util.isLoaded(loc)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Block in = loc.getBlock();
|
2021-04-30 04:31:25 +02:00
|
|
|
Block above = in.getRelative(BlockFace.UP);
|
|
|
|
return in.getType().isSolid() && above.getType().isSolid() && NMS.isSolid(in) && NMS.isSolid(above);
|
2020-11-14 11:41:21 +01:00
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
public static boolean isAlwaysFlyable(EntityType type) {
|
2018-07-22 08:27:08 +02:00
|
|
|
if (type.name().toLowerCase().equals("vex") || type.name().toLowerCase().equals("parrot")
|
2020-02-21 10:40:27 +01:00
|
|
|
|| type.name().toLowerCase().equals("bee") || type.name().toLowerCase().equals("phantom"))
|
2018-04-07 10:02:35 +02:00
|
|
|
// 1.8.8 compatibility
|
2017-12-30 08:36:36 +01:00
|
|
|
return true;
|
|
|
|
switch (type) {
|
2020-02-14 15:48:40 +01:00
|
|
|
case BAT:
|
|
|
|
case BLAZE:
|
|
|
|
case ENDER_DRAGON:
|
|
|
|
case GHAST:
|
|
|
|
case WITHER:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-22 05:57:03 +01:00
|
|
|
public static boolean isHorse(EntityType type) {
|
|
|
|
String name = type.name();
|
|
|
|
return type == EntityType.HORSE || name.contains("_HORSE") || name.equals("DONKEY") || name.equals("MULE")
|
|
|
|
|| name.equals("LLAMA") || name.equals("TRADER_LLAMA");
|
2018-04-07 10:02:35 +02:00
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
public static boolean isLoaded(Location location) {
|
|
|
|
if (location.getWorld() == null)
|
|
|
|
return false;
|
|
|
|
int chunkX = location.getBlockX() >> 4;
|
|
|
|
int chunkZ = location.getBlockZ() >> 4;
|
|
|
|
return location.getWorld().isChunkLoaded(chunkX, chunkZ);
|
|
|
|
}
|
|
|
|
|
2018-04-07 10:02:35 +02:00
|
|
|
public static boolean isOffHand(PlayerInteractEntityEvent event) {
|
|
|
|
try {
|
|
|
|
return event.getHand() == org.bukkit.inventory.EquipmentSlot.OFF_HAND;
|
|
|
|
} catch (NoSuchMethodError e) {
|
|
|
|
return false;
|
|
|
|
} catch (NoSuchFieldError e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isOffHand(PlayerInteractEvent event) {
|
|
|
|
try {
|
|
|
|
return event.getHand() == org.bukkit.inventory.EquipmentSlot.OFF_HAND;
|
|
|
|
} catch (NoSuchMethodError e) {
|
|
|
|
return false;
|
|
|
|
} catch (NoSuchFieldError e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
public static String listValuesPretty(Enum<?>[] values) {
|
2018-03-09 10:28:20 +01:00
|
|
|
return "<e>" + Joiner.on("<a>, <e>").join(values).toLowerCase();
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean locationWithinRange(Location current, Location target, double range) {
|
|
|
|
if (current == null || target == null)
|
|
|
|
return false;
|
|
|
|
if (current.getWorld() != target.getWorld())
|
|
|
|
return false;
|
2018-10-08 17:25:40 +02:00
|
|
|
return current.distanceSquared(target) <= Math.pow(range, 2);
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static EntityType matchEntityType(String toMatch) {
|
|
|
|
return matchEnum(EntityType.values(), toMatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static <T extends Enum<?>> T matchEnum(T[] values, String toMatch) {
|
|
|
|
toMatch = toMatch.toLowerCase().replace('-', '_').replace(' ', '_');
|
|
|
|
for (T check : values) {
|
|
|
|
if (toMatch.equals(check.name().toLowerCase())
|
|
|
|
|| (toMatch.equals("item") && check == EntityType.DROPPED_ITEM)) {
|
|
|
|
return check; // check for an exact match first
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (T check : values) {
|
|
|
|
String name = check.name().toLowerCase();
|
|
|
|
if (name.replace("_", "").equals(toMatch) || name.startsWith(toMatch)) {
|
|
|
|
return check;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean matchesItemInHand(Player player, String setting) {
|
|
|
|
String parts = setting;
|
2020-01-16 09:17:58 +01:00
|
|
|
if (parts.contains("*") || parts.isEmpty())
|
2017-12-30 08:36:36 +01:00
|
|
|
return true;
|
|
|
|
for (String part : Splitter.on(',').split(parts)) {
|
2020-05-12 10:48:21 +02:00
|
|
|
Material matchMaterial = SpigotUtil.isUsing1_13API() ? Material.matchMaterial(part, false)
|
2019-03-09 07:19:31 +01:00
|
|
|
: Material.matchMaterial(part);
|
|
|
|
if (matchMaterial == null) {
|
|
|
|
if (part.equals("280")) {
|
|
|
|
matchMaterial = Material.STICK;
|
2019-04-29 09:58:07 +02:00
|
|
|
} else if (part.equals("340")) {
|
2019-03-09 07:19:31 +01:00
|
|
|
matchMaterial = Material.BOOK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (matchMaterial == player.getInventory().getItemInHand().getType()) {
|
2017-12-30 08:36:36 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-05-24 12:32:51 +02:00
|
|
|
public static Set<EntityType> optionalEntitySet(String... types) {
|
|
|
|
Set<EntityType> list = EnumSet.noneOf(EntityType.class);
|
|
|
|
for (String type : types) {
|
|
|
|
try {
|
|
|
|
list.add(EntityType.valueOf(type));
|
|
|
|
} catch (IllegalArgumentException e) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
public static String prettyEnum(Enum<?> e) {
|
|
|
|
return e.name().toLowerCase().replace('_', ' ');
|
|
|
|
}
|
|
|
|
|
2020-03-31 15:44:04 +02:00
|
|
|
public static String prettyPrintLocation(Location to) {
|
|
|
|
return String.format("%s at %d, %d, %d (%d, %d)", to.getWorld().getName(), to.getBlockX(), to.getBlockY(),
|
|
|
|
to.getBlockZ(), (int) to.getYaw(), (int) to.getPitch());
|
|
|
|
}
|
|
|
|
|
2021-05-07 20:02:17 +02:00
|
|
|
public static void removeTeamFor(NPC npc, String name) {
|
|
|
|
String teamName = npc.data().get(NPC.SCOREBOARD_FAKE_TEAM_NAME_METADATA, "");
|
|
|
|
if (teamName.isEmpty())
|
|
|
|
return;
|
|
|
|
Team team = getDummyScoreboard().getTeam(teamName);
|
|
|
|
npc.data().remove(NPC.SCOREBOARD_FAKE_TEAM_NAME_METADATA);
|
|
|
|
if (team == null)
|
|
|
|
return;
|
|
|
|
if (team.hasEntry(name)) {
|
|
|
|
if (team.getSize() == 1) {
|
|
|
|
sendTeamPacketToOnlinePlayers(team, 1);
|
|
|
|
team.unregister();
|
|
|
|
} else {
|
|
|
|
team.removeEntry(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-30 19:47:07 +02:00
|
|
|
/**
|
|
|
|
* @param mode
|
|
|
|
* 0 for create, 1 for remove, 2 for update
|
|
|
|
*/
|
|
|
|
public static void sendTeamPacketToOnlinePlayers(Team team, int mode) {
|
|
|
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
2020-05-01 11:49:25 +02:00
|
|
|
NMS.sendTeamPacket(player, team, mode);
|
2020-04-30 19:47:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-29 09:58:07 +02:00
|
|
|
public static String[] splitPlayerName(String coloredName) {
|
|
|
|
String name = coloredName, prefix = null, suffix = null;
|
|
|
|
if (coloredName.length() > 16) {
|
|
|
|
if (coloredName.length() >= 30) {
|
|
|
|
prefix = coloredName.substring(0, 16);
|
|
|
|
int len = 30;
|
|
|
|
name = coloredName.substring(16, 30);
|
|
|
|
String prefixColors = ChatColor.getLastColors(prefix);
|
|
|
|
if (prefixColors.isEmpty()) {
|
|
|
|
if (NON_ALPHABET_MATCHER.matcher(name).matches()) {
|
|
|
|
if (coloredName.length() >= 32) {
|
|
|
|
len = 32;
|
|
|
|
name = coloredName.substring(16, 32);
|
|
|
|
} else if (coloredName.length() == 31) {
|
|
|
|
len = 31;
|
|
|
|
name = coloredName.substring(16, 31);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
prefixColors = ChatColor.RESET.toString();
|
|
|
|
}
|
|
|
|
} else if (prefixColors.length() > 2) {
|
|
|
|
prefixColors = prefixColors.substring(prefixColors.length() - 2);
|
|
|
|
}
|
|
|
|
name = prefixColors + name;
|
|
|
|
suffix = coloredName.substring(len);
|
|
|
|
} else {
|
|
|
|
prefix = coloredName.substring(0, coloredName.length() - 16);
|
|
|
|
name = coloredName.substring(prefix.length());
|
|
|
|
if (prefix.endsWith(String.valueOf(ChatColor.COLOR_CHAR))) {
|
|
|
|
prefix = prefix.substring(0, prefix.length() - 1);
|
|
|
|
name = ChatColor.COLOR_CHAR + name;
|
|
|
|
}
|
|
|
|
String prefixColors = ChatColor.getLastColors(prefix);
|
|
|
|
if (prefixColors.isEmpty() && !NON_ALPHABET_MATCHER.matcher(name).matches()) {
|
|
|
|
prefixColors = ChatColor.RESET.toString();
|
|
|
|
} else if (prefixColors.length() > 2) {
|
|
|
|
prefixColors = prefixColors.substring(prefixColors.length() - 2);
|
|
|
|
}
|
|
|
|
name = prefixColors + name;
|
|
|
|
if (name.length() > 16) {
|
|
|
|
suffix = name.substring(16);
|
|
|
|
name = name.substring(0, 16);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new String[] { name, prefix, suffix };
|
|
|
|
}
|
|
|
|
|
2020-04-30 19:47:07 +02:00
|
|
|
public static void updateNPCTeams(Player toUpdate, int mode) {
|
|
|
|
for (Player player : PlayerUpdateTask.getRegisteredPlayerNPCs()) {
|
|
|
|
NPC npc = ((NPCHolder) player).getNPC();
|
2020-04-30 19:39:07 +02:00
|
|
|
|
|
|
|
String teamName = npc.data().get(NPC.SCOREBOARD_FAKE_TEAM_NAME_METADATA, "");
|
|
|
|
Team team = null;
|
2020-05-12 10:48:21 +02:00
|
|
|
if (teamName.length() == 0 || (team = Util.getDummyScoreboard().getTeam(teamName)) == null)
|
2020-04-30 19:39:07 +02:00
|
|
|
continue;
|
|
|
|
|
2020-04-30 19:47:07 +02:00
|
|
|
NMS.sendTeamPacket(toUpdate, team, mode);
|
2020-04-30 19:39:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
private static final Location AT_LOCATION = new Location(null, 0, 0, 0);
|
2020-05-01 11:49:25 +02:00
|
|
|
private static final Scoreboard DUMMY_SCOREBOARD = Bukkit.getScoreboardManager().getNewScoreboard();
|
2019-04-29 09:58:07 +02:00
|
|
|
private static String MINECRAFT_REVISION;
|
|
|
|
private static final Pattern NON_ALPHABET_MATCHER = Pattern.compile(".*[^A-Za-z0-9_].*");
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|