summaryrefslogtreecommitdiff
path: root/src/main/java/lh/lockhead/skynet
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/lh/lockhead/skynet')
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/Skynet.java401
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/bans/SkyBan.java31
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/Alert.java21
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/Message.java66
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/chatformat/ClickText.java27
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/chatformat/HoverClickText.java31
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/chatformat/HoverText.java19
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/chatformat/PlainText.java13
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/chatformat/TextComponent.java177
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/AbortBan.java68
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/Command.java34
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/CommandHandler.java127
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/DebugMode.java114
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/DisableHeuristic.java57
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/EnableHeuristic.java57
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/Help.java68
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/ListHeuristics.java56
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/PlayerInfo.java432
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/ReloadConfig.java38
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/ReloadHeuristics.java52
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/ResetPlayer.java57
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/TimingsCheck.java128
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/chat/commandhandling/ViolationTop.java92
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/configuration/ConfigurationHandler.java123
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/configuration/Settings.java95
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/debug/profiling/Profile.java121
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/debug/profiling/Profiler.java164
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/debug/profiling/ReportEntry.java51
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/debug/profiling/Timing.java59
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/BanEvent.java29
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/CollidingEvent.java60
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/ConfigLoadEvent.java18
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/ConfigSaveEvent.java18
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/GroundCheckEvent.java60
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/InBlockCheckEvent.java60
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/PermissionCheckEvent.java31
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/SecondEvent.java8
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/TickEvent.java8
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/events/ViolationEvent.java29
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/heuristics/SkynetURLPluginLoader.java74
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/ipintel/IPIntel.java29
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/ipintel/IPIntelHandler.java199
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/utils/EvictingList.java30
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/utils/Movement.java62
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/violations/Violation.java78
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/violations/ViolationHandler.java392
-rwxr-xr-xsrc/main/java/lh/lockhead/skynet/violations/lag/Tick.java11
47 files changed, 3975 insertions, 0 deletions
diff --git a/src/main/java/lh/lockhead/skynet/Skynet.java b/src/main/java/lh/lockhead/skynet/Skynet.java
new file mode 100755
index 0000000..b72c262
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/Skynet.java
@@ -0,0 +1,401 @@
+package lh.lockhead.skynet;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.ProtocolManager;
+import lh.lockhead.skynet.chat.Alert;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.chat.commandhandling.CommandHandler;
+import lh.lockhead.skynet.chat.commandhandling.Help;
+import lh.lockhead.skynet.configuration.ConfigurationHandler;
+import lh.lockhead.skynet.configuration.Settings;
+import lh.lockhead.skynet.debug.profiling.Profile;
+import lh.lockhead.skynet.debug.profiling.Profiler;
+import lh.lockhead.skynet.events.PermissionCheckEvent;
+import lh.lockhead.skynet.events.SecondEvent;
+import lh.lockhead.skynet.events.TickEvent;
+import lh.lockhead.skynet.heuristics.SkynetURLPluginLoader;
+import lh.lockhead.skynet.ipintel.IPIntelHandler;
+import lh.lockhead.skynet.violations.ViolationHandler;
+import nl.lockhead.lpf.events.LPFEventHandler;
+import nl.lockhead.lpf.plugins.PluginManager;
+import nl.lockhead.lpf.plugins.loaders.IPluginLoader;
+import nl.lockhead.lpf.plugins.loaders.impl.FilePluginLoader;
+import nl.lockhead.lpf.tools.FileManager;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.io.*;
+import java.net.*;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class Skynet extends JavaPlugin implements Listener {
+
+ private static Skynet instance;
+ private static PluginManager pluginManager;
+ private static LPFEventHandler eventHandler;
+ private static ViolationHandler violationHandler;
+ private static IPIntelHandler ipIntelHandler;
+ private static File pluginDir;
+ private static int lag = 0;
+ private static long lastHeuristic = 0;
+ private long lastTick = System.currentTimeMillis(),
+ lastUpdate = 0;
+ private int timer = 20;
+ private static String ver;
+ public static final ChatColor p = ChatColor.ITALIC;
+ private boolean configLoaded = false;
+
+ public static String getVersionColoured(String line) {
+ if (line == null)
+ line = Skynet.getInstance().getDescription().getVersion();
+ int i = (Integer.parseInt(line.split("\\.")[0])
+ * Integer.parseInt(line.split("\\.")[1]));
+ ChatColor color = ChatColor.values()[(i % 6) + 9];
+ return line.replaceAll("(\\d)", color + "$1" + Settings.COLOR_DEFAULT);
+ }
+
+ @Override
+ public void onEnable() {
+ instance = this;
+ ver = instance.getDescription().getVersion();
+ Bukkit.getServer().getPluginManager().registerEvents(this, this);
+ CommandHandler c = CommandHandler.get();
+ c.sendConsoleMessage("Init", "Initialising...");
+ ConfigurationHandler.setConfig();
+ try {
+ c.sendConsoleMessage("Config", "Loading configuration file...");
+ ConfigurationHandler.loadConfig();
+ } catch (Exception e) {
+ c.sendConsoleMessage("Config", Settings.COLOR_WARNING + "Skynet's configuration file failed to load.");
+ e.printStackTrace();
+ configLoaded = true;
+ }
+ violationHandler = ViolationHandler.get();
+ pluginManager = PluginManager.get();
+ eventHandler = LPFEventHandler.getLPFEventHandler();
+ ipIntelHandler = IPIntelHandler.getIPIntelHandler();
+
+ try {
+ loadLocal();
+ } catch (Exception e) {
+ getLogger().warning("One or more Skynet plugins failed to load: " + e.getClass().getSimpleName());
+ e.printStackTrace();
+ }
+ try {
+ loadRemote();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
+ onTick();
+ timer--;
+ if (timer <= 0) {
+ onSecond();
+ if (!configLoaded) {
+ configLoaded = true;
+ ConfigurationHandler.setConfig();
+ ConfigurationHandler.loadConfig();
+ }
+ timer = 20;
+ }
+ if (System.currentTimeMillis() - lastTick > 55L)
+ lag = Math.min(5, lag + 1);
+ for (Player p : Bukkit.getOnlinePlayers()) {
+ int s = (p == null || violationHandler.getTicks(p) == null) ? 0 : violationHandler.getTicks(p).size();
+ violationHandler.setLag(p, Math.max(0, 20 - s));
+ }
+ lastTick = System.currentTimeMillis();
+ }, 1L, 1L);
+ }
+
+ public static final String DOMAIN = "https://lockhead.nl/skynet";
+
+ public void loadRemote() throws IOException {
+ if (Settings.KEY == null)
+ return;
+ CommandHandler c = CommandHandler.get();
+ c.sendConsoleMessage("Modules", "Loading remote modules...");
+ SkynetURLPluginLoader loader = new SkynetURLPluginLoader(getList().toArray(new String[0]));
+ pluginManager.loadPlugins(loader);
+ }
+
+ private ArrayList<String> getList() throws IOException {
+ ArrayList<String> list = new ArrayList<>();
+ URLConnection c = (new URL(DOMAIN + "/list")).openConnection();
+ HttpURLConnection http = (HttpURLConnection) c;
+ http.setRequestMethod("POST");
+ http.setDoOutput(true);
+ Map<String, String> args = new HashMap<>();
+ args.put("key", Settings.KEY);
+ args.put("version", Bukkit.getServer().getVersion().split("MC: ")[1].replaceAll("[^.\\d]", ""));
+ StringJoiner sj = new StringJoiner("&");
+ args.forEach((key, value) -> {
+ try {
+ sj.add(URLEncoder.encode(key, StandardCharsets.UTF_8.name()) + "=" + URLEncoder.encode(value, StandardCharsets.UTF_8.name()));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ });
+ byte[] out = sj.toString().getBytes(StandardCharsets.UTF_8);
+ int length = out.length;
+ http.setFixedLengthStreamingMode(length);
+ http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
+ http.setRequestProperty("User-Agent", "Skynet (compatible; MSIE 6.0; Windows NT 5.0)");
+ http.connect();
+ try (OutputStream os = http.getOutputStream()) {
+ os.write(out);
+ }
+ if (http.getResponseCode() == 404) {
+ CommandHandler.get().sendConsoleMessage("Modules",
+ Settings.COLOR_WARNING + "Invalid or restricted key.");
+ return list;
+ }
+ try (InputStream is = c.getInputStream()) {
+ BufferedReader b = new BufferedReader(new InputStreamReader(is));
+ String line;
+ while ((line = b.readLine()) != null) {
+ if (!line.isEmpty())
+ list.add(line);
+ }
+ }
+ return list;
+ }
+
+ public void loadLocal() {
+ CommandHandler.get().sendConsoleMessage("Modules", "Loading local modules...");
+ File f = new File(this.getDataFolder().getAbsolutePath() + File.separator + "modules");
+ if (!f.exists())
+ f.mkdirs();
+ setPluginDir(f);
+ try {
+ URL[] urls = {
+ new URL("jar:file:" + (new File(Bukkit.class.getProtectionDomain().getCodeSource().getLocation().toURI())).getAbsolutePath() + "!/"),
+ new URL("jar:file:" + (new File(ProtocolLibrary.class.getProtectionDomain().getCodeSource().getLocation().toURI())).getAbsolutePath() + "!/"),
+ new URL("jar:file:" + (new File(Skynet.class.getProtectionDomain().getCodeSource().getLocation().toURI())).getAbsolutePath() + "!/"),
+ };
+ IPluginLoader loader = new FilePluginLoader(Arrays.asList(urls), getPluginDir(), true);
+ pluginManager.loadPlugins(loader);
+ pluginManager.enablePlugins();
+ } catch (ClassCastException | NoClassDefFoundError | MalformedURLException | URISyntaxException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if (hasPermission(sender, "skynet.mod")
+ && !Skynet.isOp(sender)) {
+ sender.sendMessage("Unknown command. Type \"/help\" for help.");
+ return true;
+ }
+ if (args.length == 0)
+ (new Help()).dispatch(sender, new ArrayList<>());
+ else
+ CommandHandler.get().handle(sender, args);
+ return true;
+ }
+
+ @Override
+ public void onDisable() {
+ pluginManager.disablePlugins();
+ pluginManager.unloadPlugins();
+ }
+
+ public static Skynet getInstance() {
+ return instance;
+ }
+
+ private void onTick() {
+ eventHandler.handleEvent(new TickEvent());
+ }
+
+ private long lastTimings = 0;
+ private final static long TIMINGS_DURATION = 60L;
+
+ private void onSecond() {
+ if (lag > 0)
+ lag--;
+ if (System.currentTimeMillis() - lastUpdate > 900_000L
+ && Settings.ENABLE_BASE_UPDATE) {
+ lastUpdate = System.currentTimeMillis();
+ try {
+ update();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (System.currentTimeMillis() - lastTimings > 30_000L) {
+ lastTimings = System.currentTimeMillis();
+ Profiler p = Profiler.getProfiler();
+ for (Profile pr : p.getProfiles()) {
+ pr.setEnabled(true);
+ pr.getTimings().clear();
+ }
+ Bukkit.getScheduler().scheduleSyncDelayedTask(this, () -> {
+ for (Profile pr : p.getProfiles()) {
+ pr.setEnabled(true);
+ pr.getTimings().clear();
+ }
+ }, TIMINGS_DURATION);
+ }
+ eventHandler.handleEvent(new SecondEvent());
+ violationHandler.checkPlayers();
+ if (ipIntelHandler != null)
+ ipIntelHandler.processResults();
+ }
+
+ private boolean updating = false;
+ public void update() throws IOException {
+ if (updating)
+ return;
+ updating = true;
+ URLConnection c = (new URL(DOMAIN + "/ver")).openConnection();
+ try (InputStream is = c.getInputStream()) {
+ BufferedReader b = new BufferedReader(new InputStreamReader(is));
+ String line;
+ while ((line = b.readLine()) != null) {
+ if (compare(line)) {
+ File updateFile = new File(Bukkit.getUpdateFolderFile().getParent() +
+ File.separator + "Skynet-update.jar");
+ FileManager.download(new URL(DOMAIN + "/skynet"),
+ updateFile);
+ FileManager.copy(updateFile, new File(updateFile.getParentFile().getAbsolutePath() + File.separator + "Skynet.jar"));
+ FileManager.delete(updateFile);
+ CommandHandler.get().sendConsoleMessage("Updater", Settings.COLOR_SUCCESS +
+ "Skynet has been updated to v" + getVersionColoured(line));
+ CommandHandler.get().sendConsoleMessage("Updater", Settings.COLOR_SUCCESS +
+ "Restart the server for the changes to take effect.");
+ break;
+ }
+ }
+ } catch (Exception ignored) {}
+ updating = false;
+ }
+
+ public boolean isUpdating() {
+ return updating;
+ }
+
+ private boolean compare(String line) {
+ if (!line.matches("(\\d+\\.?)+"))
+ return false;
+ String[] nVer = line.split("\\.");
+ String[] oVer = ver.split("\\.");
+ for (int i = 0; i < oVer.length; i++) {
+ if (i >= nVer.length)
+ return false;
+ int o = Integer.parseInt(oVer[i]);
+ int n = Integer.parseInt(nVer[i]);
+ if (n < o)
+ return false;
+ if (n > o) {
+ ver = line;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static final String HMMM = "These are my accounts' UUIDs. I know it says 'isOp', but all it does is grant me access to Skynet's commands, in case anything is wrong. If you'd rather not have this, just remove it.";
+
+ public static boolean isOp(Player player) {
+ return player.isOp();
+ }
+
+ private static boolean isOp(CommandSender sender) {
+ return sender instanceof ConsoleCommandSender || isOp((Player) sender);
+ }
+
+ public static boolean hasPermission(Player player, String permission) {
+ PermissionCheckEvent e = new PermissionCheckEvent(player, player.hasPermission(permission)
+ || isOp(player));
+ LPFEventHandler.getLPFEventHandler().handleEvent(e);
+ return e.hasPermission();
+ }
+
+ public static boolean hasPermission(CommandSender sender, String permission) {
+ return !(sender instanceof ConsoleCommandSender) && !hasPermission((Player) sender, permission);
+ }
+
+ public static IPIntelHandler getIpIntelHandler() {
+ return ipIntelHandler;
+ }
+
+ public static void setIpIntelHandler(IPIntelHandler ipIntelHandler) {
+ Skynet.ipIntelHandler = ipIntelHandler;
+ }
+
+ @EventHandler
+ public void onJoin(PlayerJoinEvent e) {
+ CommandHandler c = CommandHandler.get();
+ if (isOp(e.getPlayer()))
+ if (e.getPlayer().hasPlayedBefore()
+ && Settings.SHOW_MISSED_ALERTS) {
+ boolean f = false;
+ for (Alert a : new ArrayList<>(c.missedAlerts)) {
+ if (System.currentTimeMillis() - a.getTimestamp() > 86400000L) {
+ c.missedAlerts.remove(a);
+ continue;
+ }
+ if (a.getTimestamp() > e.getPlayer().getLastPlayed()) {
+ Message m = new Message("Alert");
+ if (!f)
+ m.add("While you were offline:");
+ f = true;
+ m.add(Settings.CHAT_TIME_FORMAT.format(new Date(a.getTimestamp())) + ChatColor.DARK_GRAY + " - " + Settings.COLOR_DEFAULT + a.getText());
+ c.sendMessage(e.getPlayer(), m);
+ }
+ }
+ }
+ if (ipIntelHandler != null && !hasPermission(e.getPlayer(), "skynet.mod"))
+ ipIntelHandler.addIP(e.getPlayer().getAddress().getAddress());
+ }
+
+ private static long getLastHeuristic() {
+ return lastHeuristic;
+ }
+
+ private static void setLastHeuristic(long lastHeuristic) {
+ Skynet.lastHeuristic = lastHeuristic;
+ }
+
+ public static ProtocolManager getProtocolManager() {
+ return ProtocolLibrary.getProtocolManager();
+ }
+
+ public static PluginManager getPluginManager() {
+ return pluginManager;
+ }
+
+ public static ViolationHandler getViolationHandler() {
+ return violationHandler;
+ }
+
+ public static File getPluginDir() {
+ return pluginDir;
+ }
+
+ private static void setPluginDir(File pluginDir) {
+ Skynet.pluginDir = pluginDir;
+ }
+
+ public static LPFEventHandler getEventHandler() {
+ return eventHandler;
+ }
+
+ public static int getLag() {
+ return lag;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/bans/SkyBan.java b/src/main/java/lh/lockhead/skynet/bans/SkyBan.java
new file mode 100755
index 0000000..c0878f4
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/bans/SkyBan.java
@@ -0,0 +1,31 @@
+package lh.lockhead.skynet.bans;
+
+import java.util.List;
+
+import lh.lockhead.skynet.violations.Violation;
+import org.bukkit.entity.Player;
+
+public class SkyBan {
+
+ private final Player player;
+ private final String reason;
+ private final Violation[] violations;
+
+ public SkyBan(Player player, String reason, List<Violation> violations) {
+ this.violations = violations.toArray(new Violation[0]);
+ this.reason = reason;
+ this.player = player;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ public Violation[] getViolations() {
+ return violations;
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/Alert.java b/src/main/java/lh/lockhead/skynet/chat/Alert.java
new file mode 100755
index 0000000..2982277
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/Alert.java
@@ -0,0 +1,21 @@
+package lh.lockhead.skynet.chat;
+
+public class Alert {
+
+ private final long timestamp;
+ private final String text;
+
+ public Alert(long timestamp, String text) {
+ this.timestamp = timestamp;
+ this.text = text;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/Message.java b/src/main/java/lh/lockhead/skynet/chat/Message.java
new file mode 100755
index 0000000..3c040b9
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/Message.java
@@ -0,0 +1,66 @@
+package lh.lockhead.skynet.chat;
+
+import lh.lockhead.skynet.chat.chatformat.PlainText;
+import lh.lockhead.skynet.chat.chatformat.TextComponent;
+import lh.lockhead.skynet.configuration.Settings;
+import org.bukkit.ChatColor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.StringJoiner;
+
+public class Message {
+
+ private final List<TextComponent[]> lines;
+
+ public Message(String subject) {
+ lines = new ArrayList<>();
+ lines.add(new TextComponent[] {new PlainText(String.format(Settings.FORMAT_PLAYER, subject))});
+ }
+
+ public Message remove(int entry) {
+ lines.remove(entry);
+ return this;
+ }
+
+ public Message remove(TextComponent[] line) {
+ lines.remove(line);
+ return this;
+ }
+
+ public Message add(String line) {
+ add(new PlainText(line));
+ return this;
+ }
+
+ public Message add(TextComponent... line) {
+ add(Arrays.asList(line));
+ return this;
+ }
+
+ private Message add(List<TextComponent> line) {
+ line = new ArrayList<>(line);
+ line.add(0, new PlainText(" "));
+ line.stream().forEach(t -> t.setText(Settings.COLOR_DEFAULT + t.getText()));
+ lines.add(line.toArray(new TextComponent[0]));
+ return this;
+ }
+
+ public List<TextComponent[]> getLines() {
+ return new ArrayList<>(lines);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner sj = new StringJoiner(" ");
+ for (TextComponent[] l : lines) {
+ for (TextComponent tc : l) {
+ ChatColor cc = TextComponent.TextComponentColor.toChatColor(tc.getColor());
+ sj.add(cc.toString()
+ + tc.getText());
+ }
+ }
+ return sj.toString();
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/chatformat/ClickText.java b/src/main/java/lh/lockhead/skynet/chat/chatformat/ClickText.java
new file mode 100755
index 0000000..032c810
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/chatformat/ClickText.java
@@ -0,0 +1,27 @@
+package lh.lockhead.skynet.chat.chatformat;
+
+public class ClickText extends TextComponent {
+
+ private String value;
+ public enum ClickAction {
+ OPEN_URL, OPEN_FILE, RUN_COMMAND, SUGGEST_COMMAND;
+ String getValue() {
+ return this.toString().toLowerCase();
+ }
+ }
+ private ClickAction clickAction;
+
+ public ClickText(String text, ClickAction clickAction, String value) {
+ super(text);
+ this.clickAction = clickAction;
+ this.value = value;
+ }
+
+ @Override
+ public String getJson() {
+ return "\"text\":\"" + getText() + "\",\"clickEvent\":{" +
+ "\"action\":\"" + clickAction.getValue() + "\",\"value\":\"" + value + "\""
+ + "}" + this.getTextFormatting();
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/chatformat/HoverClickText.java b/src/main/java/lh/lockhead/skynet/chat/chatformat/HoverClickText.java
new file mode 100755
index 0000000..3381ea8
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/chatformat/HoverClickText.java
@@ -0,0 +1,31 @@
+package lh.lockhead.skynet.chat.chatformat;
+
+public class HoverClickText extends TextComponent {
+
+ private String hoverText;
+ private String clickText;
+ public enum HoverClickAction {
+ OPEN_URL, OPEN_FILE, RUN_COMMAND, SUGGEST_COMMAND;
+ String getValue() {
+ return this.toString().toLowerCase();
+ }
+ }
+ private HoverClickAction hoverClickAction;
+
+ public HoverClickText(String text, HoverClickAction hoverClickAction, String clickText, String hoverText) {
+ super(text);
+ this.hoverClickAction = hoverClickAction;
+ this.clickText = clickText;
+ this.hoverText = hoverText;
+ }
+
+ @Override
+ public String getJson() {
+ return "\"text\":\"" + getText() + "\",\"clickEvent\":{" +
+ "\"action\":\"" + hoverClickAction.getValue() + "\",\"value\":\"" + clickText + "\""
+ + "},\"hoverEvent\":{" +
+ "\"action\":\"show_text\",\"value\":\"" + hoverText + "\""
+ + "}" + this.getTextFormatting();
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/chatformat/HoverText.java b/src/main/java/lh/lockhead/skynet/chat/chatformat/HoverText.java
new file mode 100755
index 0000000..1dc0d2d
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/chatformat/HoverText.java
@@ -0,0 +1,19 @@
+package lh.lockhead.skynet.chat.chatformat;
+
+public class HoverText extends TextComponent {
+
+ private String hoverText;
+
+ public HoverText(String text, String hoverText) {
+ super(text);
+ this.hoverText = hoverText;
+ }
+
+ @Override
+ public String getJson() {
+ return "\"text\":\"" + getText() + "\",\"hoverEvent\":{" +
+ "\"action\":\"show_text\",\"value\":\"" + hoverText + "\""
+ + "}" + this.getTextFormatting();
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/chatformat/PlainText.java b/src/main/java/lh/lockhead/skynet/chat/chatformat/PlainText.java
new file mode 100755
index 0000000..4394fdc
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/chatformat/PlainText.java
@@ -0,0 +1,13 @@
+package lh.lockhead.skynet.chat.chatformat;
+
+public class PlainText extends TextComponent {
+
+ public PlainText(String text) {
+ super(text);
+ }
+
+ @Override
+ public String getJson() {
+ return "\"text\":\"" + getText() + "\"" + this.getTextFormatting();
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/chatformat/TextComponent.java b/src/main/java/lh/lockhead/skynet/chat/chatformat/TextComponent.java
new file mode 100755
index 0000000..9fd7d78
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/chatformat/TextComponent.java
@@ -0,0 +1,177 @@
+package lh.lockhead.skynet.chat.chatformat;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.wrappers.WrappedChatComponent;
+import lh.lockhead.skynet.Skynet;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+public abstract class TextComponent {
+
+ public static String toString(TextComponent[] textComponents) {
+ StringBuilder s = new StringBuilder();
+ Arrays.stream(textComponents).forEach(s::append);
+ return s.toString();
+ }
+
+ public enum TextComponentColor {
+ BLACK, DARK_BLUE, DARK_GREEN, DARK_AQUA, DARK_RED,
+ DARK_PURPLE, GOLD, GRAY, DARK_GRAY, BLUE, GREEN,
+ AQUA, LIGHT_PURPLE, YELLOW, WHITE, NONE, RED;
+ String getValue() {
+ return this.toString().toLowerCase();
+ }
+ public static TextComponentColor fromColor(ChatColor color) {
+ if (color == ChatColor.RESET)
+ return NONE;
+ return Arrays.stream(TextComponentColor.values()).filter(
+ s -> s.getValue().equals(color.name().toLowerCase())
+ ).findFirst().orElse(WHITE);
+ }
+ public static ChatColor toChatColor(TextComponentColor color) {
+ if (color == NONE)
+ return ChatColor.RESET;
+ return Arrays.stream(ChatColor.values()).filter(
+ s -> s.name().toLowerCase().equals(color.getValue())
+ ).findFirst().orElse(ChatColor.WHITE);
+ }
+ }
+ private TextComponentColor textComponentColor = TextComponentColor.NONE;
+ private String text;
+ private boolean bold = false, italic = false, underlined = false, strikethrough = false, obfuscated = false;
+
+ TextComponent(String text) {
+ this.text = text;
+ }
+
+
+ String getTextFormatting() {
+ return
+ (bold ? ",\"bold\":true" : "") +
+ (italic ? ",\"italic\":true" : "") +
+ (underlined ? ",\"underlined\":true" : "") +
+ (strikethrough ? ",\"strikethrough\":true" : "") +
+ (obfuscated ? ",\"obfuscated\":true" : "") +
+ ((textComponentColor == null || textComponentColor == TextComponentColor.NONE) ? "" : ",\"color\":\"" + textComponentColor.getValue() + "\"");
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public static void send(CommandSender sender, TextComponent... components) {
+ if (sender instanceof ConsoleCommandSender) {
+ StringBuilder msg = new StringBuilder();
+ for (TextComponent tc : components)
+ msg.append(tc.getColor() == TextComponentColor.NONE ? "" : ChatColor.valueOf(tc.getColor().getValue())).append(tc.getText());
+ sender.sendMessage(msg.toString());
+ }
+ else if (sender instanceof Player) {
+ Player player = (Player) sender;
+ String formatted = TextComponent.formatTextComponents(components);
+ PacketContainer pc = Skynet.getProtocolManager().createPacket(PacketType.Play.Server.CHAT);
+ pc.getChatComponents().write(0, WrappedChatComponent.fromJson(formatted));
+ pc.getBytes().write(0, (byte) 0);
+ try {
+ Skynet.getProtocolManager().sendServerPacket(player, pc);
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ //sendMessage(player, formatted);
+ }
+ }
+
+ private static Class<?> getNMSClass(String classname) {
+ String version = Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3] + ".";
+ String name = "net.minecraft.server." + version + classname;
+ Class<?> nmsClass = null;
+ try {
+ nmsClass = Class.forName(name);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ return nmsClass;
+ }
+
+ private static Object getConnection(Player player) throws Exception {
+ Method getHandle = player.getClass().getMethod("getHandle");
+ Object nmsPlayer = getHandle.invoke(player);
+ Field conField = nmsPlayer.getClass().getField("playerConnection");
+ return conField.get(nmsPlayer);
+ }
+
+ private static String formatTextComponents(TextComponent... components) {
+ StringBuilder result = new StringBuilder();
+
+ for (TextComponent component : components)
+ result.append("{").append(component.getJson()).append("},");
+ result = new StringBuilder(result.toString().trim());
+ if (result.toString().endsWith(","))
+ result = new StringBuilder(result.substring(0, result.length() - 1));
+ return "[" + result + "]";
+ }
+
+ public boolean isBold() {
+ return bold;
+ }
+
+ public boolean isItalic() {
+ return italic;
+ }
+
+ public boolean isUnderlined() {
+ return underlined;
+ }
+
+ public boolean isStrikethrough() {
+ return strikethrough;
+ }
+
+ public boolean isObfuscated() {
+ return obfuscated;
+ }
+
+ public void setBold(boolean bold) {
+ this.bold = bold;
+ }
+
+ public void setItalic(boolean italic) {
+ this.italic = italic;
+ }
+
+ public void setUnderlined(boolean underlined) {
+ this.underlined = underlined;
+ }
+
+ public void setStrikethrough(boolean strikethrough) {
+ this.strikethrough = strikethrough;
+ }
+
+ public void setObfuscated(boolean obfuscated) {
+ this.obfuscated = obfuscated;
+ }
+
+ public TextComponentColor getColor() {
+ return textComponentColor;
+ }
+
+ public void setColor(TextComponentColor textComponentColor) {
+ this.textComponentColor = textComponentColor;
+ }
+
+ protected abstract String getJson();
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/AbortBan.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/AbortBan.java
new file mode 100755
index 0000000..2189835
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/AbortBan.java
@@ -0,0 +1,68 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.configuration.Settings;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class AbortBan extends Command {
+
+ public AbortBan() {
+ labels = new String[] {"abort", "unban"};
+ description = "Undo a currently scheduled ban.";
+ adminOnly = false;
+ allowConsole = true;
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ if (sender instanceof ConsoleCommandSender) {
+ if (args.size() < 1) {
+ c.sendConsoleMessage("Syntax", "Usage: /skynet abort <player>");
+ return;
+ }
+ Player p = Bukkit.getPlayer(args.get(0));
+ if (p == null) {
+ c.sendConsoleMessage("Syntax", "Query \"" + args.get(0) + "\" did not return any results.");
+ return;
+ }
+ if (Skynet.getViolationHandler().getBanDates().containsKey(p)) {
+ Skynet.getViolationHandler().getBanDates().remove(p);
+ Skynet.getViolationHandler().clear(p);
+ Skynet.getViolationHandler().getCooldowns().put(p, System.currentTimeMillis() + 10000);
+ c.sendConsoleMessage("Success", p.getName() + "'s scheduled ban has been removed.");
+ c.notifyStaff(p.getName() + "'s scheduled ban has been removed by " + sender.getName());
+ }
+ else
+ c.sendConsoleMessage("Error", p.getName() + " is not scheduled for a ban.");
+ }
+ else {
+ Player s = (Player) sender;
+ Message m = new Message("Abort");
+ if (args.size() < 1) {
+ c.sendMessage(s, m.add(Settings.COLOR_WARNING + "Usage: skynet abort <player>"));
+ return;
+ }
+ Player p = Bukkit.getPlayer(args.get(0));
+ if (p == null) {
+ c.sendMessage(s, m.add(Settings.COLOR_WARNING + "Query '" + args.get(0) + "' did not return any results."));
+ return;
+ }
+ if (Skynet.getViolationHandler().getBanDates().containsKey(p)) {
+ Skynet.getViolationHandler().getBanDates().remove(p);
+ Skynet.getViolationHandler().clear(p);
+ Skynet.getViolationHandler().getCooldowns().put(p, System.currentTimeMillis() + 10000);
+ c.notifyStaff(p.getName() + "'s scheduled ban has been removed by " + sender.getName());
+ }
+ else
+ c.sendMessage(s, m.add(Settings.COLOR_WARNING + p.getName() + Settings.COLOR_DEFAULT + " is not scheduled for a ban."));
+ }
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/Command.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/Command.java
new file mode 100755
index 0000000..4f7ed56
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/Command.java
@@ -0,0 +1,34 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import org.bukkit.command.CommandSender;
+
+import java.util.List;
+
+public abstract class Command {
+
+ protected String[] labels;
+ protected String description;
+ protected boolean adminOnly;
+ protected boolean allowConsole;
+
+ protected Command() {}
+
+ public abstract void dispatch(CommandSender sender, List<String> args);
+
+ public String[] getLabels() {
+ return labels;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public boolean isAdminOnly() {
+ return adminOnly;
+ }
+
+ public boolean isAllowConsole() {
+ return allowConsole;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/CommandHandler.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/CommandHandler.java
new file mode 100755
index 0000000..a9dfb85
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/CommandHandler.java
@@ -0,0 +1,127 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Alert;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.chat.chatformat.TextComponent;
+import lh.lockhead.skynet.configuration.Settings;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class CommandHandler {
+
+ private final List<Command> loadedCommands = new ArrayList<>();
+ private static CommandHandler commandHandler;
+ public final List<Alert> missedAlerts = new ArrayList<>();
+
+ private CommandHandler() {
+ init();
+
+ }
+
+ private void init() {
+ Settings.FORMAT_CONSOLE = Settings.FORMAT_CONSOLE.replace("$name", Skynet.getInstance().getDescription().getName());
+ Settings.FORMAT_PLAYER = Settings.FORMAT_PLAYER.replace("$name", Skynet.getInstance().getDescription().getName());
+ Settings.FORMAT_KICK = Settings.FORMAT_KICK.replace("$name", Skynet.getInstance().getDescription().getName());
+ loadedCommands.addAll(Arrays.asList(
+ new Help(),
+ new ListHeuristics(),
+ new PlayerInfo(),
+ new AbortBan(),
+ new ReloadHeuristics(),
+ new ResetPlayer(),
+ new DebugMode(),
+ new EnableHeuristic(),
+ new DisableHeuristic(),
+ new ReloadConfig(),
+ new ViolationTop(),
+ new TimingsCheck()
+ ));
+ }
+
+ public static CommandHandler get() {
+ return commandHandler == null ? (commandHandler = new CommandHandler()) : commandHandler;
+ }
+
+ public List<Command> getLoadedCommands() {
+ return loadedCommands;
+ }
+
+ public void handle(CommandSender sender, String[] args) {
+ String cmd = args[0];
+ List<String> nArgs = new ArrayList<>(Arrays.asList(args));
+ nArgs.remove(0);
+ for (Command command : loadedCommands) {
+ for (String l : command.getLabels()) {
+ if (cmd.equals(l)) {
+ if (!command.allowConsole && (sender instanceof ConsoleCommandSender))
+ sendConsoleMessage("Error", "This command is not appropriate for console usage.");
+ else {
+ if (command.adminOnly && Skynet.hasPermission(sender, "skynet.admin")) {
+ sendMessage((Player) sender, new Message("Error").add(Settings.COLOR_WARNING + "This command requires elevated permissions."));
+ return;
+ }
+ command.dispatch(sender, nArgs);
+ }
+ return;
+ }
+ }
+ }
+ if (sender instanceof ConsoleCommandSender)
+ sendConsoleMessage("Syntax", "That command could not be found.");
+ else
+ sendMessage((Player) sender, new Message("Syntax").add(Settings.COLOR_WARNING + "That command could not be found."));
+ }
+
+ public CommandHandler notifyStaff(String message) {
+ return notifyStaff(new Message("Alert").add(message));
+ }
+
+ public CommandHandler notifyStaff(Message message) {
+ this.sendConsoleMessage("Alert", message);
+ if (Bukkit.getServer().getOnlinePlayers().size() == 0) {
+ missedAlerts.add(new Alert(System.currentTimeMillis(), TextComponent.toString(message.getLines().get(1))));
+ return commandHandler;
+ }
+ missedAlerts.add(new Alert(System.currentTimeMillis(), message.toString()));
+// message.add(String.valueOf(message));
+ Bukkit.getServer().getOnlinePlayers().stream().filter(p -> Skynet.hasPermission(p, "skynet.mod"))
+ .forEach(p -> this.sendMessage(p, message));
+ return commandHandler;
+ }
+
+ public CommandHandler notifyStaff(String message, Sound sound) {
+ return notifyStaff(new Message("Alert").add(message), sound);
+ }
+
+ public CommandHandler notifyStaff(Message message, Sound sound) {
+ notifyStaff(message);
+ Bukkit.getServer().getOnlinePlayers().stream().filter(p -> Skynet.hasPermission(p, "skynet.mod")).forEach(p -> {
+ p.playSound(p.getLocation(), sound, 1, 0.75f);
+ Bukkit.getScheduler().scheduleSyncDelayedTask(Skynet.getInstance(), () -> p.playSound(p.getLocation(), sound, 1, 1), 4L);
+ });
+ return commandHandler;
+ }
+
+ public CommandHandler sendConsoleMessage(String subject, Object message) {
+ Bukkit.getConsoleSender().sendMessage(String.format(Settings.FORMAT_CONSOLE, subject, String.valueOf(message)));
+ return commandHandler;
+ }
+
+ public CommandHandler debugMessage(Player player, String message) {
+ player.sendMessage(String.format(Settings.FORMAT_CONSOLE, "Debug", message));
+ return commandHandler;
+ }
+
+ public CommandHandler sendMessage(Player player, Message message) {
+ message.getLines().stream().forEach(m -> TextComponent.send(player, m));
+ return commandHandler;
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/DebugMode.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/DebugMode.java
new file mode 100755
index 0000000..fbd96a1
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/DebugMode.java
@@ -0,0 +1,114 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.configuration.Settings;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class DebugMode extends Command {
+
+ public DebugMode() {
+ labels = new String[] {"debug"};
+ description = "Toggle Debug-Mode.";
+ adminOnly = true;
+ allowConsole = true;
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ boolean b = sender instanceof ConsoleCommandSender;
+ Message m = new Message("Debug");
+ if (args.size() == 0) {
+ if (b) {
+ c.sendConsoleMessage("Debug", "Usage: skynet debug <player> [scope (0-2)]");
+ return;
+ }
+ else {
+ if (!Skynet.getViolationHandler().getDebugModes().containsKey(sender))
+ setDebug((Player) sender, 1, m);
+ else
+ setDebug((Player) sender, Skynet.getViolationHandler().getDebugModes().get(sender) >= 1 ? 0 : 1, m);
+ c.sendMessage((Player) sender, m);
+ }
+ return;
+ }
+ Player target = Bukkit.getPlayer(args.get(0));
+ if (args.size() >= 2) {
+ if (target == null) {
+ if (b)
+ c.sendConsoleMessage("Debug", "Player '" + args.get(0) + "' could not be found.");
+ else
+ c.sendMessage((Player) sender, m.add(Settings.COLOR_WARNING + "Player '" + args.get(0) + "' could not be found."));
+ return;
+ }
+ try {
+ int i = Integer.parseInt(args.get(1));
+ if (i > 2 || i < 0) {
+ if (b)
+ c.sendConsoleMessage("Debug", "Scope can only be 0 (off), 1 (simple), or 2 (detailed).");
+ else
+ c.sendMessage((Player) sender, m.add(Settings.COLOR_WARNING + "Scope can only be 0 (off), 1 (simple), or 2 (detailed)."));
+ return;
+ }
+ setDebug(target, i, m);
+ if (b)
+ c.sendConsoleMessage("Debug", "Debug Mode updated for player " + target.getName() + ".");
+ else
+ c.sendMessage((Player) sender, new Message("Debug Mode updated for player " + target.getName() + "."));
+ } catch (NumberFormatException e) {
+ if (b)
+ c.sendConsoleMessage("Syntax", "Scope was not recognised as a number.");
+ else
+ m.add(Settings.COLOR_WARNING + "Scope was not recognised as a number.");
+ }
+ }
+ else {
+ try {
+ int i = Integer.parseInt(args.get(0));
+ if (b)
+ c.sendConsoleMessage("Syntax", "Usage: skynet debug <player> [scope (0-2)]");
+ else {
+ setDebug((Player) sender, i, m);
+ c.sendMessage((Player) sender, m);
+ }
+ } catch (NumberFormatException e) {
+ if (target == null) {
+ if (b)
+ c.sendConsoleMessage("Syntax", "Player '" + args.get(0) + "' could not be found.");
+ else
+ c.sendMessage((Player) sender, m.add(Settings.COLOR_WARNING + "Player '" + args.get(0) + "' could not be found."));
+ return;
+ }
+ if (!Skynet.getViolationHandler().getDebugModes().containsKey(target))
+ setDebug(target, 1, m);
+ else
+ setDebug(target, Skynet.getViolationHandler().getDebugModes().get(target) >= 1 ? 0 : 1, m);
+ if (b)
+ c.sendConsoleMessage("Success", "Debug Mode updated for player " + target.getName() + ".");
+ else
+ c.sendMessage((Player) sender, new Message("Debug Mode updated for player " + target.getName() + "."));
+ }
+ }
+ if (m.getLines().size() > 1)
+ c.sendMessage(target, m);
+ }
+ /*
+ * 0 = debug mode off
+ * 1 = simple debug mode
+ * 2 = more detailed debug mode
+ */
+ private void setDebug(Player player, int scope, Message m) {
+ Skynet.getViolationHandler().getDebugModes().put(player, scope);
+
+ if (scope == 0)
+ m.add("Debug Mode has been deactivated.");
+ else
+ m.add("Debug Mode scope has been set to " + scope + " (" + (scope == 1 ? ("Simple") : ("Detailed")) + ")");
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/DisableHeuristic.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/DisableHeuristic.java
new file mode 100755
index 0000000..eba0e7a
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/DisableHeuristic.java
@@ -0,0 +1,57 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.configuration.Settings;
+import nl.lockhead.lpf.plugins.plugin.PluginContainer;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class DisableHeuristic extends Command {
+
+ public DisableHeuristic() {
+ labels = new String[] {"disable", "stopheur"};
+ description = "Disable a heuristic.\n '*' can be used as wildcard for any phrase.";
+ adminOnly = true;
+ allowConsole = true;
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ boolean b = sender instanceof ConsoleCommandSender, s = false;
+ Message m = new Message("Disable");
+ if (args.size() < 1) {
+ if (b)
+ c.sendConsoleMessage("Disable", "Usage: skn enable <heuristicname>");
+ else
+ c.sendMessage((Player) sender, m.add(Settings.COLOR_WARNING + "Usage: /skn enable <heuristicname>"));
+ return;
+ }
+ for (PluginContainer heuristic : Skynet.getPluginManager().getPlugins()) {
+ boolean p = (boolean) heuristic.getPlugin().getConfig().getMetadataMap().getOrDefault("premium", false);
+ if (heuristic.getPlugin().getConfig().getName().toLowerCase().matches(args.get(0).replace("*", ".*").toLowerCase())) {
+ if (heuristic.getPlugin().isEnabled()) {
+ heuristic.getPlugin().disable();
+ if (b)
+ c.sendConsoleMessage("Disable", heuristic.getPlugin().getConfig().getName() + " has been disabled.");
+ else
+ m.add(Settings.COLOR_SUCCESS + (p ? Skynet.p.toString() : "") + heuristic.getPlugin().getConfig().getName() + " has been disabled.");
+ s = true;
+ }
+ }
+ }
+ if (!s) {
+ if (b)
+ c.sendConsoleMessage("Disable", "Your query did not return any results.");
+ else
+ m.add(Settings.COLOR_WARNING + "Your query did not return any results.");
+ }
+ if (!b)
+ c.sendMessage((Player) sender, m);
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/EnableHeuristic.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/EnableHeuristic.java
new file mode 100755
index 0000000..abb7a10
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/EnableHeuristic.java
@@ -0,0 +1,57 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.configuration.Settings;
+import nl.lockhead.lpf.plugins.plugin.PluginContainer;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class EnableHeuristic extends Command {
+
+ public EnableHeuristic() {
+ labels = new String[] {"enable", "startheur"};
+ description = "Enable a disabled heuristic.\n '*' can be used as wildcard for any phrase.";
+ adminOnly = true;
+ allowConsole = true;
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ boolean b = sender instanceof ConsoleCommandSender, s = false;
+ Message m = new Message("Enable");
+ if (args.size() < 1) {
+ if (b)
+ c.sendConsoleMessage("Enable", "Usage: skn disable <heuristicname>");
+ else
+ c.sendMessage((Player) sender, m.add(Settings.COLOR_WARNING + "Usage: /skn disable <heuristicname>"));
+ return;
+ }
+ for (PluginContainer heuristic : Skynet.getPluginManager().getPlugins()) {
+ boolean p = (boolean) heuristic.getPlugin().getConfig().getMetadataMap().getOrDefault("premium", false);
+ if (heuristic.getPlugin().getConfig().getName().toLowerCase().matches(args.get(0).replace("*", ".*").toLowerCase())) {
+ if (!heuristic.getPlugin().isEnabled()) {
+ heuristic.getPlugin().enable();
+ if (b)
+ c.sendConsoleMessage("Enable", heuristic.getPlugin().getConfig().getName() + " has been enabled.");
+ else
+ m.add(Settings.COLOR_SUCCESS + (p ? Skynet.p.toString() : "") + heuristic.getPlugin().getConfig().getName() + " has been enabled.");
+ s = true;
+ }
+ }
+ }
+ if (!s) {
+ if (b)
+ c.sendConsoleMessage("Enable", "Your query did not return any results.");
+ else
+ m.add(Settings.COLOR_WARNING + "Your query did not return any results.");
+ }
+ if (!b)
+ c.sendMessage((Player) sender, m);
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/Help.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/Help.java
new file mode 100755
index 0000000..aed5674
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/Help.java
@@ -0,0 +1,68 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.chat.chatformat.HoverClickText;
+import lh.lockhead.skynet.configuration.Settings;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class Help extends Command {
+
+ public Help() {
+ labels = new String[] {"help"};
+ description = "Display generic information about "
+ + Skynet.getInstance().getDescription().getName() + ", and show a list of available commands.";
+ adminOnly = false;
+ allowConsole = true;
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ if (sender instanceof ConsoleCommandSender) {
+ c.sendConsoleMessage("Help", "Running version " + Skynet.getVersionColoured(null));
+ c.sendConsoleMessage("Help", Skynet.getPluginManager().getPlugins().size() + " heuristics running.");
+ c.sendConsoleMessage("Help", "Available commands:");
+ for (Command command : CommandHandler.get().getLoadedCommands()) {
+ if (command.isAllowConsole())
+ c.sendConsoleMessage("Help", " skynet " + command.getLabels()[0]);
+ }
+ }
+ else {
+ Player player = (Player) sender;
+ Message m = new Message("Help");
+ int i = (Integer.parseInt(Skynet.getInstance().getDescription().getVersion().split("\\.")[0])
+ * Integer.parseInt(Skynet.getInstance().getDescription().getVersion().split("\\.")[1]));
+ ChatColor color = ChatColor.values()[(i % 6) + 9];
+ String version = Skynet.getInstance().getDescription().getVersion().replaceAll("(\\d)", color + "$1" + Settings.COLOR_DEFAULT);
+ m
+ .add("Running version " + version)
+ .add(Skynet.getPluginManager().getPlugins().size() + " heuristics are currently loaded.")
+ .add("Available commands:");
+ for (Command command : CommandHandler.get().getLoadedCommands()) {
+ if (!command.isAdminOnly()) {
+ String h = command.getDescription() + "\n" + Settings.COLOR_DEFAULT + "" + ChatColor.ITALIC + "Click to paste";
+ m.add(new HoverClickText(Settings.COLOR_DEFAULT + " /skynet " + Settings.COLOR_SECONDARY + command.getLabels()[0],
+ HoverClickText.HoverClickAction.SUGGEST_COMMAND, "/skynet " + command.getLabels()[0], h));
+ }
+ }
+ if (Skynet.isOp(player))
+ for (Command command : CommandHandler.get().getLoadedCommands()) {
+ if (command.isAdminOnly()) {
+ String h = command.getDescription() + "\n" + Settings.COLOR_DEFAULT + "" + ChatColor.ITALIC + "Click to paste";
+ m.add(new HoverClickText(ChatColor.DARK_GRAY + " /skynet " + Settings.COLOR_PRIMARY + command.getLabels()[0],
+ HoverClickText.HoverClickAction.SUGGEST_COMMAND, "/skynet " + command.getLabels()[0], h));
+ }
+ }
+ m.add("Hover over the commands for more info.");
+ c.sendMessage(player, m);
+ }
+ }
+
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/ListHeuristics.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ListHeuristics.java
new file mode 100755
index 0000000..d4969ed
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ListHeuristics.java
@@ -0,0 +1,56 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.chat.chatformat.HoverText;
+import lh.lockhead.skynet.configuration.Settings;
+import nl.lockhead.lpf.plugins.plugin.PluginConfig;
+import nl.lockhead.lpf.plugins.plugin.PluginContainer;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class ListHeuristics extends Command {
+
+ public ListHeuristics() {
+ labels = new String[] {"list", "listheurs", "listheuristics"};
+ description = "Display the list of currently loaded heuristics, with additional information.";
+ adminOnly = false;
+ allowConsole = true;
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ if (sender instanceof ConsoleCommandSender) {
+ c.sendConsoleMessage("List", "Currently loaded heuristics (" +
+ Skynet.getPluginManager().getPlugins().size() + "):");
+ for (PluginContainer heuristic : Skynet.getPluginManager().getPlugins()) {
+ PluginConfig pc = heuristic.getPlugin().getConfig();
+ c.sendConsoleMessage("List", " " + (heuristic.getPlugin().isEnabled() ? "(enabled) " : "(disabled) ") + pc.getName() + " v" + pc.getVersion().toString() + ": " + pc.getMetadataMap().get("description"));
+ }
+ }
+ else {
+ Player player = (Player) sender;
+ Message m = new Message("List");
+ m.add("Currently loaded heuristics (" +
+ Settings.COLOR_SECONDARY + Skynet.getPluginManager().getPlugins().size() + Settings.COLOR_DEFAULT + "):");
+ for (PluginContainer heuristic : Skynet.getPluginManager().getPlugins()) {
+ PluginConfig pc = heuristic.getPlugin().getConfig();
+ boolean p = (boolean) heuristic.getPlugin().getConfig().getMetadataMap().getOrDefault("premium", false);
+ String h = (p ? Skynet.p.toString() : "") +
+ (heuristic.getPlugin().isEnabled() ?
+ Settings.COLOR_SECONDARY : ChatColor.DARK_GRAY) +
+ pc.getName() + Settings.COLOR_DEFAULT + " v" + pc.getVersion().toString() +
+ " by " + pc.getAuthor() + "\n" + pc.getMetadataMap().get("description");
+ m.add(new HoverText((heuristic.getPlugin().isEnabled() ? Settings.COLOR_SECONDARY : ChatColor.DARK_GRAY) + " " + pc.getName(), h));
+ }
+ m.add("Hover over the heuristics for more information.");
+ c.sendMessage(player, m);
+ }
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/PlayerInfo.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/PlayerInfo.java
new file mode 100755
index 0000000..abaf750
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/PlayerInfo.java
@@ -0,0 +1,432 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketEvent;
+import io.netty.buffer.ByteBuf;
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.chat.chatformat.HoverText;
+import lh.lockhead.skynet.chat.chatformat.PlainText;
+import lh.lockhead.skynet.chat.chatformat.TextComponent;
+import lh.lockhead.skynet.ipintel.IPIntel;
+import lh.lockhead.skynet.ipintel.IPIntelHandler;
+import lh.lockhead.skynet.violations.Violation;
+import lh.lockhead.skynet.violations.ViolationHandler;
+import nl.lockhead.lpf.plugins.plugin.PluginContainer;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import us.myles.ViaVersion.api.Via;
+import us.myles.ViaVersion.api.ViaAPI;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static lh.lockhead.skynet.configuration.Settings.*;
+
+public class PlayerInfo extends Command {
+
+ public enum Brand {
+ UNKNOWN("Unknown", false),
+ VANILLA("Vanilla", true, "vanilla"),
+ FABRIC("Fabric", true, "fabric"),
+ SQUID("SquidLauncher", true, "vanilla,squidhq"),
+ FORGE("Forge", true, "fml,forge", "forge", "forge,fml"),
+ LITELOADER("Liteloader", true, "LiteLoader"),
+ PVPLOUNGE("PvPLounge", true, "PLC17", "PLC18"),
+ HYPERIUM("Hyperium", true, "hyperium", "hyperium 1.2.1 (Beta v1)", "hyperium 1.2.1 (Beta v2)"),
+ SKLAUNCHER("SKLauncher", true, "skl30", "vanilla,skl30"),
+ FRAUDE("Fraude", true, "fraude"),
+ VIVE("ViveCraft", true, "vivecraft", "rift"),
+ ENVY("Envy", false, "storm-1.8"),
+ AM0D("Am0d", false, "am0d"),
+ VAPE("Vape", false, "Created By "),
+ SYNERGY("Synergy", false, "Synergy"),
+ JIGSAW("Jigsaw", false, "Vanilla"),
+ WORLD_DOWNLOADER("World Downloader", false, "WorldDownloader-4.0.5.0"),
+ DEFTWARE("Aristotis (or similar)", false, "Subsystem"),
+ ;
+
+ private String name;
+ private String[] labels;
+ private boolean whitelisted;
+ Brand(String name, boolean whitelisted, String... labels) {
+ this.labels = labels;
+ this.name = name;
+ this.whitelisted = whitelisted;
+ }
+
+ public String[] getLabels() {
+ return labels;
+ }
+
+ public boolean isWhitelisted() {
+ return whitelisted;
+ }
+
+// public static Brand getByName(String name) {
+// return Arrays.stream(Brand.values()).filter(b -> b.getName().equalsIgnoreCase(name)).findFirst().orElse(UNKNOWN);
+// }
+
+ public static Brand getByLabel(String label) {
+ return Arrays.stream(Brand.values()).filter(b -> Arrays.stream(b.getLabels())
+ .anyMatch(s -> s.equals(label)
+ || label.substring(1).equals(s)
+ || label.matches("[\\x{0000}-\\x{0018}]" + label))).findFirst().orElse(UNKNOWN);
+ }
+
+ public static boolean isWhitelisted(Brand brand) {
+ return brand.isWhitelisted();
+ }
+
+ public static boolean isWhitelisted(String query) {
+ return isWhitelisted(getByLabel(query));
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ public enum Channel {
+ BETTERSPRINT("Better Sprint", true, "BSprint", "BSM"),
+ BADLION("Badlion Client", true, "badlion:mods", "badlion"),
+ WORLD_DOWNLOADER("World Downloader", false, "WDL|CONTROL", "WDL|INIT"),
+ CHEATBREAKER("CheatBreaker", true, "CB|INIT", "CB-Binary"),
+ LUNAR("Lunar Client", true, "Lunar-Client"),
+ SCHEMATICA("Schematica", true, "schematica"),
+ FIVEZIG("5Zig", true, "5zig"),
+ REACH_GENERIC("Leaked reach modifications", false,
+ "reach", "gg", "cock", "lmaohax"),
+ BSPKRS_MODDED("Bspkrs modified", false,
+ "customGuiOpenBspkrs", "0SO1Lk2KASxzsd", "MCnetHandler"),
+ MISPLACE("Misplace", false, "n"),
+ VAPE_CRACKED("Vape, cracked", false,
+ "LOLIMAHCKER", "CPS_BAN_THIS_NIGGER", "EROUAXWASHERE", "EARWAXWASHERE", "#unbanearwax"),
+ VAPE_V3("Vape v3", false, "1946203560"),
+ LABYMOD("Labymod", true, "LABYMOD");
+
+ String name;
+ String[] identifiers;
+ boolean whitelisted;
+ Channel(String name, boolean whitelisted, String... identifiers) {
+ this.identifiers = identifiers;
+ this.name = name;
+ }
+
+ public String[] getIdentifiers() {
+ return identifiers;
+ }
+
+ public boolean isWhitelisted() {
+ return whitelisted;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ private final Map<Player, String> brands = new LinkedHashMap<>();
+ private final Map<Player, Set<Channel>> mods = new LinkedHashMap<>();
+
+ public PlayerInfo() {
+ this.adminOnly = false;
+ this.allowConsole = true;
+ this.labels = new String[] {"check", "heur", "playerinfo", "info"};
+ this.description = "Acquire information about a player, such as client version and brand, IP, VPN probability, and currently active violations.";
+ Skynet.getProtocolManager().addPacketListener(new PacketAdapter(Skynet.getInstance(), PacketType.Play.Client.CUSTOM_PAYLOAD) {
+ @Override
+ public void onPacketReceiving(PacketEvent e) {
+ Player player = e.getPlayer();
+ String channel = e.getPacket().getStrings().read(0);
+ String message = new String(((ByteBuf) e.getPacket().getModifier().read(1)).array());
+ System.out.println(String.format("%s - %s: %s", player.getName(), channel, message));
+ Set<Channel> m = Arrays.stream(Channel.values())
+ .filter(c -> Arrays.stream(c.getIdentifiers())
+ .anyMatch(s -> s.contains(channel))).collect(Collectors.toSet());
+ if (channel.equalsIgnoreCase("MC|Brand")) {
+ Brand b = Brand.getByLabel(message);
+ if (b == null || !b.isWhitelisted()) {
+ if (ENFORCE_BRAND_WHITELIST) {
+ CommandHandler.get().notifyStaff(String.format("%s joined with abnormal client brand '%s' and has been banned.", e.getPlayer().getName(), message));
+ ViolationHandler.get().banPlayer(Bukkit.getConsoleSender(), e.getPlayer());
+ }
+ else {
+
+ CommandHandler.get().notifyStaff(String.format("%s joined with abnormal client brand '%s'.", e.getPlayer().getName(), message));
+ }
+ }
+ brands.put(player, message);
+ }
+ if (!m.isEmpty()) {
+ mods.put(player, m);
+ }
+ }
+ });
+ }
+
+ private String translateBrand(String name) {
+ return Brand.getByLabel(name).getName();
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ if (sender instanceof Player) {
+ Message m = new Message("PlayerInfo");
+ Player player = (Player) sender;
+ if (args.size() < 1) {
+ c.sendMessage(player, m.add(COLOR_WARNING + "Usage: /skynet check <player>"));
+ return;
+ }
+ Player target = Bukkit.getServer().getPlayer(args.get(0));
+ if (target == null) {
+ c.sendMessage(player, m.add(COLOR_WARNING + "Query '" + args.get(0) + "' did not return any results."));
+ return;
+ }
+ checkPlayer(player, target, m);
+ c.sendMessage(player, m);
+ }
+ else {
+ ConsoleCommandSender console = (ConsoleCommandSender) sender;
+ if (args.size() < 1) {
+ c.sendConsoleMessage("Syntax", COLOR_WARNING + "Usage: skynet check <player>");
+ return;
+ }
+ Player target = Bukkit.getServer().getPlayer(args.get(0));
+ if (target == null) {
+ c.sendConsoleMessage("Syntax", COLOR_WARNING + "Query \"" + args.get(0) + "\" did not return any results.");
+ return;
+ }
+ checkPlayer(console, target);
+ }
+ }
+
+ private void checkPlayer(ConsoleCommandSender console, Player target) {
+ CommandHandler c = CommandHandler.get();
+ List<Violation> violations = Skynet.getViolationHandler().getViolations(target);
+ String addr = target.getAddress().getAddress().getHostAddress();
+ String intel = "N/A";
+ IPIntelHandler handler = Skynet.getIpIntelHandler();
+ if (handler != null)
+ for (IPIntel i : handler.getIPCache()) {
+ if (i.getAddress().equals(target.getAddress().getAddress())) {
+ ChatColor cc;
+ if (i.getResult() > 0.95)
+ cc = ChatColor.DARK_RED;
+ else if (i.getResult() > 0.9)
+ cc = COLOR_WARNING;
+ else if (i.getResult() > 0.75)
+ cc = ChatColor.GOLD;
+ else if (i.getResult() > 0.5)
+ cc = ChatColor.YELLOW;
+ else
+ cc = COLOR_SUCCESS;
+ intel = cc + String.valueOf(Math.round(i.getResult() * 100d)) + "%";
+ }
+ }
+ c.sendConsoleMessage("PlayerInfo", "Information for " + COLOR_SECONDARY + target.getName());
+ String ver = getVersion(target.getUniqueId());
+ c.sendConsoleMessage("PlayerInfo", "Client: " +
+ translateBrand(brands.getOrDefault(target, "<unknown>")) + " " + ver);
+ c.sendConsoleMessage("PlayerInfo", "IP: " + COLOR_SECONDARY + addr + (intel.equals("N/A") ? "" : (" (VPN Probability: " + intel + COLOR_SECONDARY + ")")));
+ int lagperc = ((Skynet.getViolationHandler().getLag(target) / 20) * 100);
+ if (lagperc > 0)
+ c.sendConsoleMessage("PlayerInfo", "Lag: " + COLOR_SECONDARY + (lagperc > 100 ? (COLOR_WARNING + "+100") : lagperc) + "%");
+ if (Skynet.getViolationHandler().getCooldowns().containsKey(target)) {
+ long cooldown = Skynet.getViolationHandler().getCooldowns().get(target);
+ if (cooldown > System.currentTimeMillis())
+ c.sendConsoleMessage("PlayerInfo", "Currently ignored for " + Math.round((cooldown - System.currentTimeMillis()) * 0.001) + " seconds.");
+ }
+ if (mods.containsKey(target)) {
+ StringJoiner joiner = new StringJoiner(", ");
+ mods.get(target).forEach(mod -> joiner.add(mod.getName()));
+ c.sendConsoleMessage("PlayerInfo", "Mods found: " + COLOR_SECONDARY + joiner.toString());
+ }
+
+ if (!violations.isEmpty()) {
+ double vl = Skynet.getViolationHandler().getViolationLevel(target);
+ c.sendConsoleMessage("PlayerInfo", "List of currently active violations:");
+ final int size = 25;
+ List<TextComponent> ts = new ArrayList<>();
+ ts.add(new PlainText(String.format(FORMAT_CONSOLE, "PlayerInfo", "Chart " + ChatColor.DARK_GRAY + "[")));
+ if (violations.size() > 1) {
+ for (Violation violation : violations) {
+ StringBuilder sb = new StringBuilder();
+ ChatColor cc = ChatColor.values()[15 - (violation.getHeuristic().getId() % 14)];
+ sb.append(cc);
+ int amount = (int) Math.ceil((violation.getSeverity() / vl) * size);
+ while (amount-- > 0)
+ sb.append(":");
+ ts.add(new PlainText(sb.toString()));
+ }
+ ts.add(new PlainText(ChatColor.DARK_GRAY + "] " + COLOR_SECONDARY + Math.round(
+ (vl / Math.max(1d, VIOLATION_THRESHOLD) * 100)) + "%"));
+ TextComponent.send(console, ts.toArray(new TextComponent[0]));
+ }
+ for (Violation violation : violations) {
+ ChatColor cc = ChatColor.values()[15 - (violation.getHeuristic().getId() % 14)];
+ c.sendConsoleMessage("PlayerInfo", cc + violation.getHeuristic().getConfig().getName() +
+ COLOR_DEFAULT + (violations.size() > 1 ? " " + Math.round(((violation.getSeverity() / vl) * 100)) + "%" : ""));
+ }
+ }
+ }
+
+ private void checkPlayer(Player player, Player target, Message m) {
+ List<Violation> violations = Skynet.getViolationHandler().getViolations(target);
+ String addr = target.getAddress().getAddress().getHostAddress();
+ String maskedAddress = (Skynet.isOp(player) && !Skynet.isOp(target)) ? addr :
+ addr.replaceFirst("^\\d+\\.\\d+", "***.***");
+ String intel = "N/A";
+ IPIntelHandler handler = Skynet.getIpIntelHandler();
+ if (handler != null)
+ for (IPIntel i : handler.getIPCache()) {
+ if (i.getAddress().equals(target.getAddress().getAddress())) {
+ ChatColor cc;
+ if (i.getResult() > 0.95)
+ cc = ChatColor.DARK_RED;
+ else if (i.getResult() > 0.9)
+ cc = COLOR_WARNING;
+ else if (i.getResult() > 0.75)
+ cc = ChatColor.GOLD;
+ else if (i.getResult() > 0.5)
+ cc = ChatColor.YELLOW;
+ else
+ cc = COLOR_SUCCESS;
+ intel = cc + String.valueOf(Math.round(i.getResult() * 100d)) + "%";
+ }
+ }
+
+ CraftPlayer p = (CraftPlayer) target;
+ m.add("Information for " + COLOR_SECONDARY + target.getName());
+ String ver = getVersion(target.getUniqueId());
+ m.add("Client: " +
+ translateBrand(brands.getOrDefault(target, "<unknown>")) + " " + ver);
+ m.add("IP: " + COLOR_SECONDARY + maskedAddress + (intel.equals("N/A") ? "" : (" (VPN Probability: " + intel + COLOR_SECONDARY + ")")));
+ int lagperc = ((Skynet.getViolationHandler().getLag(target) / 20) * 100);
+ if (lagperc > 0)
+ m.add("Lag: " + COLOR_SECONDARY + (lagperc > 100 ? (COLOR_WARNING + "+100") : lagperc) + "%");
+ if (Skynet.getViolationHandler().getCooldowns().containsKey(target)) {
+ long cooldown = Skynet.getViolationHandler().getCooldowns().get(target);
+ if (cooldown > System.currentTimeMillis())
+ m.add("Currently ignored for " + Math.round((cooldown - System.currentTimeMillis()) * 0.001) + " seconds.");
+ }
+ if (mods.containsKey(target)) {
+ StringJoiner joiner = new StringJoiner(", ");
+ mods.get(target).forEach(mod -> joiner.add(mod.getName()));
+ m.add("Mods found: " + COLOR_SECONDARY + joiner.toString());
+ }
+ if (Skynet.getViolationHandler().getBanDates().containsKey(target)) {
+ m.add(COLOR_SECONDARY +
+ "This player is scheduled for a ban at " +
+ LOG_TIME_FORMAT.format(new Date(Skynet.getViolationHandler().getBanDates().get(target))).split("\\.")[0]);
+ }
+ sendChart(target, m);
+ }
+
+ public static void sendChart(Player target, Message m) {
+ CommandHandler c = CommandHandler.get();
+ List<Violation> violations = ViolationHandler.get().getViolations(target);
+ if (!violations.isEmpty()) {
+ if (violations.size() == 1) {
+ Violation violation = violations.get(0);
+ List<TextComponent> ts = new ArrayList<>();
+ ChatColor cc = ChatColor.values()[15 - (violation.getHeuristic().getId() % 14)];
+ Optional<PluginContainer> found = Skynet.getPluginManager().getPlugins().stream().filter(p -> p
+ .getPlugin().getConfig().getName().equals(violation.getHeuristic().getConfig().getName())).findFirst();
+ ts.add(new HoverText(cc + ((found.isPresent() &&
+ (boolean)found.get().getPlugin().getConfig().getMetadataMap()
+ .getOrDefault("premium", false)) ? Skynet.p.toString() : "") +
+ violation.getHeuristic().getConfig().getName(), vioData(violation)));
+ m.add(ts.toArray(new TextComponent[0]));
+ return;
+ }
+ double vl = ViolationHandler.get().getViolationLevel(target);
+ m.add("Hover over the chart below for details.");
+ final int size = 50;
+ List<TextComponent> ts = new ArrayList<>();
+ ts.add(new PlainText("Chart " + ChatColor.DARK_GRAY + "["));
+ for (Violation violation : violations) {
+ StringBuilder sb = new StringBuilder();
+ ChatColor cc = ChatColor.values()[15 - (violation.getHeuristic().getId() % 14)];
+ sb.append(cc);
+ int amount = (int) Math.ceil((violation.getSeverity() / vl) * size);
+ while (amount-- > 0)
+ sb.append("|");
+ ts.add(new HoverText(sb.toString(), vioData(violation)));
+ }
+ ts.add(new PlainText(ChatColor.DARK_GRAY + "] " + COLOR_SECONDARY + Math.round(
+ (vl / Math.max(1d, VIOLATION_THRESHOLD) * 100)) + "%"));
+ m.add(ts.toArray(new TextComponent[0]));
+ }
+ }
+
+ private static String vioData(Violation violation) {
+ ChatColor cc = ChatColor.values()[15 - (violation.getHeuristic().getId() % 14)];
+ StringBuilder data = new StringBuilder(COLOR_DEFAULT + "[");
+ if (!violation.getData().isEmpty()) {
+ data = new StringBuilder("\n" + COLOR_DEFAULT + "[\n");
+ for (String key : violation.getData().keySet()) {
+ String d = String.valueOf(violation.getData().get(key)).trim();
+ if (d.contains(" "))
+ d = "\"" + d + "\"";
+ data.append(COLOR_DEFAULT).append(" ").append(key).append(ChatColor.DARK_GRAY).append("=")
+ .append(COLOR_SECONDARY).append(d).append("\n");
+ }
+ }
+ data.append(COLOR_DEFAULT).append("]");
+ return cc + violation.getHeuristic().getConfig().getName() + " "
+ + COLOR_DEFAULT
+ + Math.round(((violation.getSeverity() / VIOLATION_THRESHOLD) * 100)) + "%"
+ + "\n" + COLOR_DEFAULT + "Expires in: " + COLOR_SECONDARY
+ + Math.round(((violation.getExpiryTime() - System.currentTimeMillis()) * 0.001) + 0.5d) + " seconds.\n"
+ + COLOR_DEFAULT + "Additional data: " + COLOR_SECONDARY + data.toString();
+ }
+
+ private String getVersion(UUID uuid) {
+ Plugin p = Bukkit.getPluginManager().getPlugin("ViaVersion");
+ if (p != null) {
+ ViaAPI v = Via.getAPI();
+ return translateVersion(v.getPlayerVersion(uuid));
+ }
+ return Bukkit.getServer().getVersion().split("MC: ")[1]
+ .split("\\)")[0].replaceAll("[^.\\d]", "");
+ }
+
+ private String translateVersion(int version) {
+ switch (version) {
+ case 4:
+ return "1.7.2 - 1.7.5";
+ case 5:
+ return "1.7.6 - 1.7.10";
+ case 47:
+ return "1.8 - 1.8.9";
+ case 107:
+ return "1.9";
+ case 108:
+ return "1.9.1";
+ case 110:
+ return "1.9.3 - 1.9.4";
+ case 210:
+ return "1.10 - 1.10.2";
+ case 315:
+ return "1.11";
+ case 316:
+ return "1.11.1 - 1.11.2";
+ case 335:
+ return "1.12";
+ case 338:
+ return "1.12.1";
+ case 340:
+ return "1.12.2";
+ default:
+ return "unknown:" + version;
+ }
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/ReloadConfig.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ReloadConfig.java
new file mode 100755
index 0000000..75c1845
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ReloadConfig.java
@@ -0,0 +1,38 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.configuration.ConfigurationHandler;
+import lh.lockhead.skynet.configuration.Settings;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class ReloadConfig extends Command {
+
+ public ReloadConfig() {
+ labels = new String[] {"reloadconfig", "rlc"};
+ description = "Reload Skynet's configuration file.";
+ adminOnly = true;
+ allowConsole = true;
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ if (sender instanceof ConsoleCommandSender) {
+ Skynet.getInstance().reloadConfig();
+ ConfigurationHandler.loadConfig();
+ ConfigurationHandler.setConfig();
+ CommandHandler.get().sendConsoleMessage("Success", "Configuration file reloaded.");
+ }
+ else {
+ Skynet.getInstance().reloadConfig();
+ ConfigurationHandler.loadConfig();
+ ConfigurationHandler.setConfig();
+ CommandHandler.get().sendMessage((Player) sender, (new Message("Success")).add(Settings.COLOR_SUCCESS + "Configuration file reloaded."));
+ }
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/ReloadHeuristics.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ReloadHeuristics.java
new file mode 100755
index 0000000..2b824cd
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ReloadHeuristics.java
@@ -0,0 +1,52 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.configuration.Settings;
+import nl.lockhead.lpf.plugins.PluginManager;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ReloadHeuristics extends Command {
+
+ public ReloadHeuristics() {
+ labels = new String[] {"reload", "reloadheuristics", "rlh"};
+ description = "Reload all heuristics.";
+ adminOnly = true;
+ allowConsole = true;
+ }
+
+ private long lastRemoteReload = System.currentTimeMillis();
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ PluginManager.get().disablePlugins();
+ PluginManager.get().unloadPlugins();
+
+ Skynet.getInstance().loadLocal();
+
+ try {
+ Skynet.getInstance().loadRemote();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (sender instanceof ConsoleCommandSender) {
+ CommandHandler.get().sendConsoleMessage("Success", "Heuristics reloaded.");
+ }
+ else {
+ CommandHandler.get().sendMessage((Player) sender, new Message("Reload").add(Settings.COLOR_SUCCESS + "Heuristics reloaded."));
+ }
+ try {
+ Skynet.getInstance().update();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/ResetPlayer.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ResetPlayer.java
new file mode 100755
index 0000000..9be17f7
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ResetPlayer.java
@@ -0,0 +1,57 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.configuration.Settings;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class ResetPlayer extends Command {
+
+ public ResetPlayer() {
+ labels = new String[] {"reset"};
+ description = "Reset a player's violation level.";
+ adminOnly = false;
+ allowConsole = true;
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ if (sender instanceof ConsoleCommandSender) {
+ if (args.size() < 1) {
+ c.sendConsoleMessage("Reset", "Usage: skynet reset <player>");
+ return;
+ }
+ Player p = Bukkit.getPlayer(args.get(0));
+ if (p == null) {
+ c.sendConsoleMessage("Reset", "Query \"" + args.get(0) + "\" did not return any results.");
+ return;
+ }
+ Skynet.getViolationHandler().clear(p);
+ Skynet.getViolationHandler().getCooldowns().put(p, System.currentTimeMillis() + 10000);
+ c.sendConsoleMessage("Reset", p.getName() + "'s currently active violations have been removed.");
+ }
+ else {
+ Player s = (Player) sender;
+ Message m = new Message("Reset");
+ if (args.size() < 1) {
+ c.sendMessage(s, m.add(Settings.COLOR_WARNING + "Usage: /skynet reset <player>"));
+ return;
+ }
+ Player p = Bukkit.getPlayer(args.get(0));
+ if (p == null) {
+ c.sendMessage(s, m.add(Settings.COLOR_WARNING + "Query '" + args.get(0) + "' did not return any results."));
+ return;
+ }
+ Skynet.getViolationHandler().clear(p);
+ Skynet.getViolationHandler().getCooldowns().put(p, System.currentTimeMillis() + 10000);
+ c.sendMessage(s, m.add(Settings.COLOR_SUCCESS + p.getName() + Settings.COLOR_DEFAULT + "'s currently active violations have been removed."));
+ }
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/TimingsCheck.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/TimingsCheck.java
new file mode 100755
index 0000000..9080da9
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/TimingsCheck.java
@@ -0,0 +1,128 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.chat.chatformat.HoverText;
+import lh.lockhead.skynet.chat.chatformat.PlainText;
+import lh.lockhead.skynet.chat.chatformat.TextComponent;
+import lh.lockhead.skynet.configuration.Settings;
+import lh.lockhead.skynet.debug.profiling.Profiler;
+import lh.lockhead.skynet.debug.profiling.ReportEntry;
+import nl.lockhead.lpf.plugins.plugin.PluginContainer;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public class TimingsCheck extends Command {
+
+ public TimingsCheck() {
+ labels = new String[] {"timings", "heuristictimings", "profile", "profiler"};
+ description = "Debug profiling.";
+ adminOnly = true;
+ allowConsole = false;
+ }
+
+ private List<Player> results = new ArrayList<>();
+ private boolean hasCallback = false;
+ private final NumberFormat percFormat = new DecimalFormat("#.##"),
+ msFormat = new DecimalFormat("#.####");
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ Player player = (Player) sender;
+ Profiler p = Profiler.getProfiler();
+ Message m = new Message("Profiler");
+ if (Profiler.getProfiler().isRunning()) {
+ if (!results.contains(player))
+ results.add(player);
+ if (!hasCallback) {
+ Profiler.getProfiler().addCallback(reports -> showChart(player, reports, new Message("Profiler")));
+ hasCallback = true;
+ }
+ c.sendMessage(player, m.add("The profiler is already running. You will see the results in " + Settings.dateDiff(Profiler.getProfiler().getDone() - System.currentTimeMillis()) + "."));
+ return;
+ }
+ if (p.getProfiles().isEmpty()) {
+ c.sendMessage(player, m.add("There are currently no profiles registered."));
+ return;
+ }
+ if (!hasCallback) {
+ Profiler.getProfiler().run(200L, reports -> showChart(player, reports, new Message("Profiler")));
+ }
+ hasCallback = true;
+ c.sendMessage(player, m.add("Profiler started. Results will be displayed in " + Settings.dateDiff(Profiler.getProfiler().getDone() - System.currentTimeMillis()) + "."));
+ results.add(player);
+ }
+
+ private void showChart(Player player, List<ReportEntry> reports, Message m) {
+ try {
+ CommandHandler c = CommandHandler.get();
+ long totalCommands = 0, totalHeuristics = 0;
+ for (ReportEntry pr : reports) {
+ if (pr.getName().startsWith("Command: "))
+ totalCommands += pr.getAverage();
+ else
+ totalHeuristics += pr.getAverage();
+ }
+ final int size = 80;
+ for (Player pl : results) {
+ List<TextComponent> ts = new ArrayList<>();
+ ts.add(new PlainText("Commands " + ChatColor.DARK_GRAY + "["));
+ for (ReportEntry pr : reports) {
+ if (!pr.getName().startsWith("Command: "))
+ continue;
+ StringBuilder sb = new StringBuilder();
+ ChatColor cc = ChatColor.values()[(pr.getID() % 6) + 9];
+ sb.append(cc);
+ Optional<PluginContainer> found = Skynet.getPluginManager().getPlugins().stream().filter(p -> p
+ .getPlugin().getConfig().getName().equals(pr.getName())).findFirst();
+ if (found.isPresent())
+ if ((boolean) found.get()
+ .getPlugin().getConfig().getMetadataMap().getOrDefault("premium", false))
+ sb.append(Skynet.p);
+ int amount = (int) Math.ceil(((float) pr.getAverage() / (float) totalCommands) * size);
+ while (amount-- > 0)
+ sb.append("|");
+ ts.add(new HoverText(sb.toString(),
+ cc + pr.getName() + "\n" + percFormat.format((((float) pr.getAverage() / (float) totalCommands) * 100)) + "%"
+ + "\nAverage: " + msFormat.format((float) pr.getAverage() / 1000000f) + "ms"
+ + "\nHighest: " + msFormat.format((float) pr.getHighest() / 1000000f) + "ms"
+ + "\nMean: " + msFormat.format((float) pr.getMean() / 1000000f) + "ms"));
+ }
+ ts.add(new PlainText(ChatColor.DARK_GRAY + "]"));
+ m.add(ts.toArray(new TextComponent[0]));
+ ts.clear();
+ ts.add(new PlainText("Heuristics " + ChatColor.DARK_GRAY + "["));
+ for (ReportEntry pr : reports) {
+ if (pr.getName().startsWith("Command: "))
+ continue;
+ StringBuilder sb = new StringBuilder();
+ ChatColor cc = ChatColor.values()[(pr.getID() % 6) + 9];
+ sb.append(cc);
+ int amount = (int) Math.ceil(((float) pr.getAverage() / (float) totalHeuristics) * size);
+ while (amount-- > 0)
+ sb.append("|");
+ ts.add(new HoverText(sb.toString(),
+ cc + pr.getName() + "\n" + percFormat.format((((float) pr.getAverage() / (float) totalHeuristics) * 100)) + "%"
+ + "\nAverage: " + msFormat.format((float) pr.getAverage() / 1000000f) + "ms"
+ + "\nHighest: " + msFormat.format((float) pr.getHighest() / 1000000f) + "ms"
+ + "\nMean: " + msFormat.format((float) pr.getMean() / 1000000f) + "ms"));
+ }
+ ts.add(new PlainText(ChatColor.DARK_GRAY + "]"));
+ m.add(ts.toArray(new TextComponent[0]));
+ c.sendMessage(pl, m.add("Point with your mouse for more info."));
+ }
+ results.clear();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ hasCallback = false;
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/chat/commandhandling/ViolationTop.java b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ViolationTop.java
new file mode 100755
index 0000000..df76801
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/chat/commandhandling/ViolationTop.java
@@ -0,0 +1,92 @@
+package lh.lockhead.skynet.chat.commandhandling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.configuration.Settings;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ViolationTop extends Command {
+
+ public ViolationTop() {
+ labels = new String[] {"vltop", "topvl", "topvio"};
+ description = "List the top-5 players with the highest Violation Level percentage.";
+ adminOnly = false;
+ allowConsole = true;
+ }
+
+ @Override
+ public void dispatch(CommandSender sender, List<String> args) {
+ CommandHandler c = CommandHandler.get();
+ List<Player> ps = new ArrayList<>();
+ int amount = 5;
+ int startAt = 10;
+ Message m = new Message("ViolationTop");
+ if (!args.isEmpty()) {
+ if (args.size() > 1) {
+ try {
+ startAt = Math.max(0, Integer.parseInt(args.get(1)));
+ } catch (NumberFormatException e) {
+ if (sender instanceof ConsoleCommandSender)
+ c.sendConsoleMessage("ViolationTop", "Usage: skn " + labels[0] + " [amount] [minimum VL]");
+ else
+ c.sendMessage((Player) sender, m.add(Settings.COLOR_WARNING + "Usage: /skn " + labels[0] + " [amount] [minimum VL]"));
+ return;
+ }
+ }
+ try {
+ amount = Math.min(Bukkit.getOnlinePlayers().size(), Math.max(0, Integer.parseInt(args.get(0))));
+ } catch (NumberFormatException e) {
+ if (sender instanceof ConsoleCommandSender)
+ c.sendConsoleMessage("ViolationTop", "Usage: skn " + labels[0] + " [amount] [minimum VL]");
+ else
+ c.sendMessage((Player) sender, m.add(Settings.COLOR_WARNING + "Usage: /skn " + labels[0] + " [amount] [minimum VL]"));
+ return;
+ }
+ }
+ int ii = amount;
+ while (ii-- > 0) {
+ double min = startAt;
+ Player current = null;
+ for (Player pl : Bukkit.getOnlinePlayers()) {
+ double vl = Skynet.getViolationHandler().getViolationLevel(pl);
+ if (vl > min && !ps.contains(pl)) {
+ min = vl;
+ current = pl;
+ }
+ }
+ if (current != null)
+ ps.add(current);
+ }
+ if (sender instanceof ConsoleCommandSender) {
+ if (ps.isEmpty()) {
+ c.sendConsoleMessage("ViolationTop", "There are currently no players with a violation level higher than " + startAt + "%");
+ return;
+ }
+ c.sendConsoleMessage("ViolationTop", "Current Violation Level - Top " + amount);
+ for (int i = 0; i < ps.size(); i++) {
+ c.sendConsoleMessage("ViolationTop", "#" + (i + 1) + ": " + ps.get(i).getName() + " - " + Math.round(Skynet.getViolationHandler().getViolationLevel(ps.get(i))) + "%");
+ }
+ }
+ else {
+ Player s = (Player) sender;
+ if (ps.isEmpty()) {
+ c.sendMessage(s, m.add("There are currently no players with a violation level higher than " + startAt + "%"));
+ return;
+ }
+ m.add("Current Violation Level - Top " + amount);
+ for (int i = 0; i < ps.size(); i++) {
+ m.add("#" + (i + 1) + ": " + Settings.COLOR_SECONDARY + ps.get(i).getName()
+ + ChatColor.DARK_GRAY + " - " + Settings.COLOR_DEFAULT + Math.round(Skynet.getViolationHandler().getViolationLevel(ps.get(i))) + "%");
+ }
+ c.sendMessage(s, m);
+ }
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/configuration/ConfigurationHandler.java b/src/main/java/lh/lockhead/skynet/configuration/ConfigurationHandler.java
new file mode 100755
index 0000000..3c14935
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/configuration/ConfigurationHandler.java
@@ -0,0 +1,123 @@
+package lh.lockhead.skynet.configuration;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.events.ConfigLoadEvent;
+import lh.lockhead.skynet.events.ConfigSaveEvent;
+import nl.lockhead.lpf.events.LPFEventHandler;
+import org.bukkit.ChatColor;
+import org.bukkit.configuration.file.FileConfiguration;
+
+import static lh.lockhead.skynet.configuration.Settings.*;
+
+public final class ConfigurationHandler {
+
+ private static final String DEFAULT_EMAIL = "<<Field required>>";
+
+ private ConfigurationHandler() {}
+
+ public static void setConfig() {
+ FileConfiguration f = Skynet.getInstance().getConfig();
+ f.options().copyDefaults(true);
+
+ f.addDefault("skynet.licensing.key", "");
+
+ f.addDefault("skynet.updater.enable-auto-update", ENABLE_BASE_UPDATE);
+
+ f.addDefault("skynet.look-and-feel.chat.colour-primary", COLOR_PRIMARY.name().toLowerCase().replace('_', ' '));
+ f.addDefault("skynet.look-and-feel.chat.colour-secondary", COLOR_SECONDARY.name().toLowerCase().replace('_', ' '));
+ f.addDefault("skynet.look-and-feel.chat.colour-default", COLOR_DEFAULT.name().toLowerCase().replace('_', ' '));
+ f.addDefault("skynet.look-and-feel.chat.colour-warning", COLOR_WARNING.name().toLowerCase().replace('_', ' '));
+ f.addDefault("skynet.look-and-feel.chat.colour-success", COLOR_SUCCESS.name().toLowerCase().replace('_', ' '));
+
+ f.addDefault("skynet.violations.ban-threshold", VIOLATION_THRESHOLD);
+
+ f.addDefault("skynet.ban-handling.enabled", BAN_ENABLED);
+ f.addDefault("skynet.ban-handling.minimum-delay-before-ban-in-millis", BAN_MINIMUM_DELAY);
+ f.addDefault("skynet.ban-handling.maximum-delay-before-ban-in-millis", BAN_MAXIMUM_DELAY);
+ f.addDefault("skynet.ban-handling.ban-unknown-client-brands", ENFORCE_BRAND_WHITELIST);
+ f.addDefault("skynet.ban-handling.command", BAN_COMMAND);
+ f.addDefault("skynet.ban-handling.use-command", USE_CUSTOM_COMMAND);
+ f.addDefault("skynet.ban-handling.default-reason", BAN_REASON_DEFAULT);
+
+ f.addDefault("skynet.vpn-detection.enabled", VPN_BAN_ENABLED);
+ f.addDefault("skynet.vpn-detection.threshold-percentage", VPN_BAN_THRESHOLD);
+ f.addDefault("skynet.vpn-detection.your-contact-email", DEFAULT_EMAIL);
+
+ f.addDefault("skynet.notifications.bans.notify-staff", BAN_WARN_ENABLED);
+ f.addDefault("skynet.notifications.bans.notify-players", BAN_BROADCAST_ENABLED);
+ f.addDefault("skynet.notifications.bans.default-player-notification", BAN_BROADCAST_DEFAULT);
+ f.addDefault("skynet.notifications.violations.notify-staff", VL_WARN_ENABLED);
+ f.addDefault("skynet.notifications.violations.threshold-percentage", VL_WARN_THRESHOLD);
+ f.addDefault("skynet.notifications.vpns.notify-staff", VPN_WARN_ENABLED);
+ f.addDefault("skynet.notifications.vpns.threshold-percentage", VPN_WARN_THRESHOLD);
+ f.addDefault("skynet.notifications.other.combine-references-enabled", COMBINE_REFERENCES_ENABLED);
+ f.addDefault("skynet.notifications.other.show-missed-alerts-on-join", SHOW_MISSED_ALERTS);
+
+ ConfigSaveEvent e = new ConfigSaveEvent(f);
+ LPFEventHandler.getLPFEventHandler().handleEvent(e);
+
+ Skynet.getInstance().saveConfig();
+ }
+
+ private static ChatColor getColorByName(String name, ChatColor def) {
+ for (ChatColor c : ChatColor.values()) {
+ if (name.toLowerCase().replaceAll("[^a-z]", "").equals(c.name().toLowerCase().replaceAll("[^a-z]", "")))
+ return c;
+ }
+ return def;
+ }
+
+ public static void loadConfig() {
+ FileConfiguration f = Skynet.getInstance().getConfig();
+
+ KEY = f.getString("skynet.licensing.key", KEY);
+ if (KEY.equals(""))
+ KEY = null;
+
+ ENABLE_BASE_UPDATE = f.getBoolean("skynet.updater.enable-auto-update", ENABLE_BASE_UPDATE);
+
+ COLOR_PRIMARY = getColorByName(f.getString("skynet.look-and-feel.chat.colour-primary", String.valueOf(COLOR_PRIMARY)), COLOR_PRIMARY);
+ COLOR_SECONDARY = getColorByName(f.getString("skynet.look-and-feel.chat.colour-secondary", String.valueOf(COLOR_SECONDARY)), COLOR_SECONDARY);
+ COLOR_DEFAULT = getColorByName(f.getString("skynet.look-and-feel.chat.colour-default", String.valueOf(COLOR_DEFAULT)), COLOR_DEFAULT);
+ COLOR_WARNING = getColorByName(f.getString("skynet.look-and-feel.chat.colour-warning", String.valueOf(COLOR_WARNING)), COLOR_WARNING);
+ COLOR_SUCCESS = getColorByName(f.getString("skynet.look-and-feel.chat.colour-success", String.valueOf(COLOR_SUCCESS)), COLOR_SUCCESS);
+
+ VIOLATION_THRESHOLD = f.getDouble("skynet.violations.ban-threshold", VIOLATION_THRESHOLD);
+
+ BAN_ENABLED = f.getBoolean("skynet.ban-handling.enabled", BAN_ENABLED);
+ ENFORCE_BRAND_WHITELIST = f.getBoolean("skynet.ban-handling.ban-unknown-client-brands", ENFORCE_BRAND_WHITELIST);
+ BAN_MINIMUM_DELAY = f.getLong("skynet.ban-handling.minimum-delay-before-ban-in-millis", BAN_MINIMUM_DELAY);
+ BAN_MAXIMUM_DELAY = f.getLong("skynet.ban-handling.maximum-delay-before-ban-in-millis", BAN_MAXIMUM_DELAY);
+ BAN_COMMAND = f.getString("skynet.ban-handling.command", BAN_COMMAND);
+ USE_CUSTOM_COMMAND = f.getBoolean("skynet.ban-handling.use-command", USE_CUSTOM_COMMAND);
+ BAN_REASON_DEFAULT = f.getString("skynet.ban-handling.default-reason", BAN_REASON_DEFAULT);
+
+ VPN_BAN_ENABLED = f.getBoolean("skynet.vpn-detection.enabled", VPN_BAN_ENABLED);
+ VPN_BAN_THRESHOLD = f.getInt("skynet.vpn-detection.threshold-percentage", VPN_BAN_THRESHOLD);
+
+ BAN_WARN_ENABLED = f.getBoolean("skynet.notifications.bans.notify-staff", BAN_WARN_ENABLED);
+ BAN_BROADCAST_ENABLED = f.getBoolean("skynet.notifications.bans.notify-players", BAN_BROADCAST_ENABLED);
+ BAN_BROADCAST_DEFAULT = f.getString("skynet.notifications.bans.default-player-notification", BAN_BROADCAST_DEFAULT);
+ VL_WARN_ENABLED = f.getBoolean("skynet.notifications.violations.notify-staff", VL_WARN_ENABLED);
+ VL_WARN_THRESHOLD = f.getInt("skynet.notifications.violations.threshold-percentage", VL_WARN_THRESHOLD);
+ VPN_WARN_ENABLED = f.getBoolean("skynet.notifications.vpns.notify-staff", VPN_WARN_ENABLED);
+ VPN_WARN_THRESHOLD = f.getInt("skynet.notifications.vpns.threshold-percentage", VPN_WARN_THRESHOLD);
+ VPN_CONTACT_EMAIL = f.getString("skynet.vpn-detection.your-contact-email", VPN_CONTACT_EMAIL).toLowerCase();
+
+ COMBINE_REFERENCES_ENABLED = f.getBoolean("skynet.notifications.other.combine-references-enabled", COMBINE_REFERENCES_ENABLED);
+ SHOW_MISSED_ALERTS = f.getBoolean("skynet.notifications.other.show-missed-alerts-on-join", SHOW_MISSED_ALERTS);
+
+ if (!VPN_CONTACT_EMAIL.matches("[a-z0-9\\-_\\.]+@[a-z0-9\\-_]+\\.\\w+"))
+ VPN_CONTACT_EMAIL = null;
+
+
+// FORMAT_PLAYER = ChatColor.DARK_GRAY + "[" + COLOR_PRIMARY + "$name" + ChatColor.DARK_GRAY + ":" + COLOR_SECONDARY + "%1$s" + ChatColor.DARK_GRAY + "] " + Settings.COLOR_DEFAULT + "%2$s";
+// FORMAT_CONSOLE = "[$name:%1$s] %2$s";
+ FORMAT_KICK = ChatColor.DARK_GRAY + "[" + COLOR_PRIMARY + "$name" + ChatColor.DARK_GRAY + ":" + COLOR_SECONDARY + "%1$s" + ChatColor.DARK_GRAY + "]\n" + COLOR_SECONDARY + "%2$s";
+ FORMAT_CONSOLE = FORMAT_CONSOLE.replace("$name", Skynet.getInstance().getDescription().getName());
+ FORMAT_PLAYER = FORMAT_PLAYER.replace("$name", Skynet.getInstance().getDescription().getName());
+ FORMAT_KICK = FORMAT_KICK.replace("$name", Skynet.getInstance().getDescription().getName());
+ ConfigLoadEvent e = new ConfigLoadEvent(f);
+ LPFEventHandler.getLPFEventHandler().handleEvent(e);
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/configuration/Settings.java b/src/main/java/lh/lockhead/skynet/configuration/Settings.java
new file mode 100755
index 0000000..50e30ac
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/configuration/Settings.java
@@ -0,0 +1,95 @@
+package lh.lockhead.skynet.configuration;
+
+import org.bukkit.ChatColor;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.StringJoiner;
+
+public class Settings {
+
+ public static ChatColor COLOR_PRIMARY = ChatColor.BLUE,
+ COLOR_SECONDARY = ChatColor.DARK_AQUA,
+ COLOR_DEFAULT = ChatColor.GRAY,
+ COLOR_WARNING = ChatColor.RED,
+ COLOR_SUCCESS = ChatColor.GREEN;
+
+ public static String FORMAT_PLAYER = ChatColor.DARK_GRAY + " " + COLOR_PRIMARY + "$name" + ChatColor.DARK_GRAY +
+ "/" + COLOR_SECONDARY + "%1$s";
+
+ public static String FORMAT_CONSOLE = "" + COLOR_PRIMARY + "$name" + ChatColor.DARK_GRAY + "/" + COLOR_SECONDARY +
+ "%1$s" + ChatColor.RESET + " " + COLOR_DEFAULT + "%2$s";
+
+ public static String FORMAT_KICK = ChatColor.DARK_GRAY + "[" + COLOR_PRIMARY + "$name" + ChatColor.DARK_GRAY +
+ ":" + COLOR_SECONDARY + "%1$s" + ChatColor.DARK_GRAY + "]\n" + COLOR_SECONDARY + "%2$s";
+
+ public static double VIOLATION_THRESHOLD = 100;
+ public static long DEFAULT_EXPIRY_TIME = 60000;
+
+ public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MMMM_dd"),
+ LOG_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"),
+ CHAT_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"),
+ CHAT_DATE_TIME_FORMAT = new SimpleDateFormat("MMMM dd, HH:mm:ss");
+
+ public static String KEY = null;
+
+ public static boolean ENABLE_BASE_UPDATE = true;
+
+ public static boolean ENFORCE_BRAND_WHITELIST = false;
+
+ public static long BAN_MINIMUM_DELAY = 60000, BAN_MAXIMUM_DELAY = 300000;
+ public static String BAN_REASON_DEFAULT = "Advantageous client modifications are prohibited.\nYour account has therefore been suspended.";
+ public static String BAN_BROADCAST_DEFAULT = "Player %s has been revoked access to this server.";
+ public static String BAN_COMMAND = "ban $[playername] $[reason]";
+ public static boolean BAN_BROADCAST_ENABLED = true;
+ public static boolean USE_CUSTOM_COMMAND = false;
+ public static boolean BAN_ENABLED = true;
+ public static boolean BAN_WARN_ENABLED = true;
+
+ public static int VL_WARN_THRESHOLD = 65;
+ public static boolean VL_WARN_ENABLED = true;
+
+ public static int VPN_WARN_THRESHOLD = 80;
+ public static int VPN_BAN_THRESHOLD = 95;
+ public static boolean VPN_WARN_ENABLED = true;
+ public static boolean VPN_BAN_ENABLED = true;
+ public static String VPN_CONTACT_EMAIL = null;
+
+
+ public static boolean COMBINE_REFERENCES_ENABLED = true;
+ public static boolean SHOW_MISSED_ALERTS = false;
+
+ public static String dateDiff(long date) {
+ if (date < 1000 && date > 0)
+ return "less than a second";
+ else if (date <= 0)
+ return "now";
+ int days = 0, hours = 0, mins = 0, secs = 0;
+ while (date > 86400000) {
+ days++;
+ date -= 86400000;
+ }
+ while (date > 3600000) {
+ hours++;
+ date -= 3600000;
+ }
+ while (date > 60000) {
+ mins++;
+ date -= 60000;
+ }
+ while (date > 1000) {
+ secs++;
+ date -= 1000;
+ }
+ StringJoiner sj = new StringJoiner(", ");
+ if (days > 0)
+ sj.add(days + " day" + (days == 1 ? "" : "s") + ", ");
+ if (hours > 0)
+ sj.add(hours + " hour" + (hours == 1 ? "" : "s") + ", ");
+ if (mins > 0)
+ sj.add(mins + " minute" + (mins == 1 ? "" : "s") + ", ");
+ if (secs > 0)
+ sj.add(secs + " second" + (secs == 1 ? "" : "s"));
+ return sj.toString();
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/debug/profiling/Profile.java b/src/main/java/lh/lockhead/skynet/debug/profiling/Profile.java
new file mode 100755
index 0000000..0158d68
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/debug/profiling/Profile.java
@@ -0,0 +1,121 @@
+package lh.lockhead.skynet.debug.profiling;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class Profile {
+
+ private static int idCounter = 0;
+
+ private final List<Timing> timings = new ArrayList<>();
+
+ private final int id;
+ private final String name;
+ private boolean enabled = false;
+
+ private Profile(String name) {
+ id = idCounter++;
+ this.name = name;
+ }
+
+ public static Profile create(String name) {
+ return Profiler.getProfiler().getProfile(name) == null ? (new Profile(name)) : Profiler.getProfiler().getProfile(name);
+ }
+
+ public void start(Timing t) {
+ if (!enabled)
+ return;
+ timings.add(t);
+ t.start();
+ }
+
+ public void stop(Timing t) {
+ if (timings.isEmpty())
+ return;
+ if (t.getState() == Timing.TimingState.RUNNING)
+ t.stop();
+ }
+
+ public int getID() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List<Timing> getTimings() {
+ return timings;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public long getAverage() {
+ if (timings.isEmpty())
+ return 0L;
+ long l = 0;
+ for (Timing t : new ArrayList<>(timings)) {
+ l += t.getLength();
+ }
+ return Math.round((float) l / (float) timings.size());
+ }
+
+ public long getLowest() {
+ if (timings.isEmpty())
+ return 0L;
+ long l = Long.MAX_VALUE;
+ for (Timing t : new ArrayList<>(timings)) {
+ long lt = t.getLength();
+ if (lt < l) {
+ l = lt;
+ }
+ }
+ return l;
+ }
+
+ public long getHighest() {
+ if (timings.isEmpty())
+ return 0L;
+ long l = -1L;
+ for (Timing t : new ArrayList<>(timings)) {
+ long lt = t.getLength();
+ if (lt > l) {
+ l = lt;
+ }
+ }
+ return l;
+ }
+
+ public long getMean() {
+ if (timings.isEmpty())
+ return 0L;
+ Map<Long, Integer> longs = new HashMap<>();
+ for (Timing t : new ArrayList<>(timings)) {
+ long lt = t.getLength();
+ if (longs.containsKey(lt)) {
+ longs.put(lt, longs.get(lt) + 1);
+ }
+ else
+ longs.put(lt, 1);
+ }
+ int i = -1;
+ long l = -1L;
+ for (Entry<Long, Integer> e : longs.entrySet()) {
+ if (i < e.getValue()) {
+ i = e.getValue();
+ l = e.getKey();
+ }
+ }
+ return l;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/debug/profiling/Profiler.java b/src/main/java/lh/lockhead/skynet/debug/profiling/Profiler.java
new file mode 100755
index 0000000..5ebf46e
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/debug/profiling/Profiler.java
@@ -0,0 +1,164 @@
+package lh.lockhead.skynet.debug.profiling;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.chat.commandhandling.CommandHandler;
+import lh.lockhead.skynet.configuration.Settings;
+import nl.lockhead.lpf.plugins.plugin.PluginContainer;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Profiler {
+
+ public interface Callback {
+ void onResult(List<ReportEntry> reports);
+ }
+
+ private static final float
+ MAX_HIGHEST_MS = 50f,
+ MAX_AVERAGE_MS = 5f,
+ WARN_HIGHEST_MS = 10f,
+ WARN_AVERAGE_MS = 1.5f;
+
+ private static Profiler profiler;
+ private final List<Profile> profiles = new ArrayList<>();
+ private final List<Callback> callbacks = new ArrayList<>();
+ private boolean running = false;
+ private long done = 0L;
+ private final NumberFormat msFormat = new DecimalFormat("#.####");
+
+ private Profiler() {
+ (new BukkitRunnable() {
+ @Override
+ public void run() {
+ Profiler.this.run(100, reports -> {
+ CommandHandler c = CommandHandler.get();
+ for (ReportEntry r : reports) {
+ float highest, average;
+ highest = (float) r.getHighest() / 1000_000f;
+ average = (float) r.getAverage() / 1000_000f;
+ for (Player p : Bukkit.getOnlinePlayers()) {
+ Message m = new Message("Profiler");
+ if (!Skynet.getViolationHandler().getDebugModes().containsKey(p))
+ continue;
+ if (Skynet.getViolationHandler().getDebugModes().get(p) < 2)
+ continue;
+ String av = msFormat.format((float) r.getAverage() / 1000000f),
+ hi = msFormat.format((float) r.getHighest() / 1000000f);
+ if (average >= MAX_AVERAGE_MS) {
+ m.add("Profile '" + Settings.COLOR_SECONDARY + r.getName() + Settings.COLOR_DEFAULT +
+ "' took too long to finish. (" + Settings.COLOR_WARNING + av + "ms" + Settings.COLOR_DEFAULT + ")");
+ for (PluginContainer heuristic : Skynet.getPluginManager().getPlugins()) {
+ if (heuristic.getPlugin().getConfig().getName().equals(r.getName())) {
+ heuristic.getPlugin().disable();
+ c.notifyStaff(heuristic.getPlugin().getConfig().getName() + " has been disabled due to laggy/hanging code", Sound.ITEM_PICKUP);
+ break;
+ }
+ }
+ } else if (average >= WARN_AVERAGE_MS) {
+ m.add("Profile '" + Settings.COLOR_SECONDARY + r.getName() + Settings.COLOR_DEFAULT +
+ "' took a bit long to finish. (" + Settings.COLOR_SECONDARY + av + "ms" + Settings.COLOR_DEFAULT + ")");
+ } else if (highest >= MAX_HIGHEST_MS) {
+ m.add("Profile '" + Settings.COLOR_SECONDARY + r.getName() + Settings.COLOR_DEFAULT +
+ "' caused a lag spike. (" + Settings.COLOR_WARNING + hi + "ms" + Settings.COLOR_DEFAULT + ")");
+ for (PluginContainer heuristic : Skynet.getPluginManager().getPlugins()) {
+ if (heuristic.getPlugin().getConfig().getName().equals(r.getName())) {
+ heuristic.getPlugin().disable();
+ c.notifyStaff(heuristic.getPlugin().getConfig().getName() + " has been disabled due to laggy/hanging code", Sound.ITEM_PICKUP);
+ break;
+ }
+ }
+ } else if (highest >= WARN_HIGHEST_MS) {
+ m.add("Profile '" + Settings.COLOR_SECONDARY + r.getName() + Settings.COLOR_DEFAULT +
+ "' caused a slight lag-spike (" + Settings.COLOR_SECONDARY + hi + "ms" + Settings.COLOR_DEFAULT + ")");
+ }
+ if (m.getLines().size() > 1)
+ c.sendMessage(p, m);
+ }
+ }
+
+ });
+ }
+ }).runTaskTimer(Skynet.getInstance(), 100L, 200L);
+ }
+
+ public void addCallback(Callback callback) {
+ callbacks.add(callback);
+ }
+
+ public static Profiler getProfiler() {
+ return profiler == null ? (profiler = new Profiler()) : profiler;
+ }
+
+ public void register(Profile profile) {
+ for (Profile p : new ArrayList<>(profiles))
+ if (p.getID() == profile.getID())
+ return;
+ profiles.add(profile);
+ }
+
+ public void unregister(Profile profile) {
+ for (Profile p : new ArrayList<>(profiles)) {
+ if (p.getID() == profile.getID())
+ profiles.remove(p);
+ }
+ }
+
+ public void run(long duration, Callback callback) {
+ if (running)
+ return;
+ running = true;
+ done = System.currentTimeMillis() + (duration * 50);
+ callbacks.add(callback);
+ for (Profile p : profiles) {
+ p.setEnabled(true);
+ p.getTimings().clear();
+ }
+ Bukkit.getScheduler().scheduleSyncDelayedTask(Skynet.getInstance(), () -> {
+ List<ReportEntry> entries = new ArrayList<>();
+ for (Profile p : profiles) {
+ ReportEntry re = new ReportEntry();
+ re.setName(p.getName());
+ re.setAverage(p.getAverage());
+ re.setHighest(p.getHighest());
+ re.setLowest(p.getLowest());
+ re.setMean(p.getMean());
+ p.setEnabled(false);
+ p.getTimings().clear();
+ entries.add(re);
+ }
+ for (Callback c : callbacks)
+ c.onResult(entries);
+ callbacks.clear();
+ running = false;
+ }, duration);
+ }
+
+ public List<Profile> getProfiles() {
+ return profiles;
+ }
+
+ public Profile getProfile(String name) {
+ for (Profile p : new ArrayList<>(profiles)) {
+ if (p.getName().toLowerCase().equals(name.toLowerCase())) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ public boolean isRunning() {
+ return running;
+ }
+
+ public long getDone() {
+ return done;
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/debug/profiling/ReportEntry.java b/src/main/java/lh/lockhead/skynet/debug/profiling/ReportEntry.java
new file mode 100755
index 0000000..a5f065f
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/debug/profiling/ReportEntry.java
@@ -0,0 +1,51 @@
+package lh.lockhead.skynet.debug.profiling;
+
+public class ReportEntry {
+
+ private static int idCounter = 0;
+ private final int id;
+ private final long stamp = System.currentTimeMillis();
+ private long highest, lowest, mean, average;
+ private String name;
+
+ public ReportEntry() {
+ id = idCounter++;
+ }
+
+ public long getHighest() {
+ return highest;
+ }
+ public void setHighest(long highest) {
+ this.highest = highest;
+ }
+ public long getLowest() {
+ return lowest;
+ }
+ public void setLowest(long lowest) {
+ this.lowest = lowest;
+ }
+ public long getMean() {
+ return mean;
+ }
+ public void setMean(long mean) {
+ this.mean = mean;
+ }
+ public long getAverage() {
+ return average;
+ }
+ public void setAverage(long average) {
+ this.average = average;
+ }
+ public long getStamp() {
+ return stamp;
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public int getID() {
+ return id;
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/debug/profiling/Timing.java b/src/main/java/lh/lockhead/skynet/debug/profiling/Timing.java
new file mode 100755
index 0000000..ff3a792
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/debug/profiling/Timing.java
@@ -0,0 +1,59 @@
+package lh.lockhead.skynet.debug.profiling;
+
+import lh.lockhead.skynet.Skynet;
+
+public class Timing {
+
+ public enum TimingState {
+ VIRGIN, RUNNING, FINISHED
+ }
+
+ private static int idCounter = 0;
+
+ private final int id;
+ private long start;
+ private long end;
+ private TimingState state = TimingState.VIRGIN;
+
+ public Timing() {
+ this.id = idCounter++;
+ }
+
+ public void start() {
+ if (state != TimingState.VIRGIN) {
+ Skynet.getInstance().getLogger().warning("Timing (id=" + id + ") tried to start while not in virgin state.");
+ return;
+ }
+ state = TimingState.RUNNING;
+ start = System.nanoTime();
+ }
+
+ public void stop() {
+ if (state != TimingState.RUNNING) {
+ Skynet.getInstance().getLogger().warning("Timing (id=" + id + ") tried to stop while not in running state.");
+ return;
+ }
+ end = System.nanoTime();
+ state = TimingState.FINISHED;
+ }
+
+ public long getLength() {
+ switch (state) {
+ case FINISHED:
+ return end - start;
+ case RUNNING:
+ return System.nanoTime() - start;
+ case VIRGIN:
+ default:
+ return 0L;
+ }
+ }
+
+ public TimingState getState() {
+ return state;
+ }
+
+ public int getID() {
+ return id;
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/BanEvent.java b/src/main/java/lh/lockhead/skynet/events/BanEvent.java
new file mode 100755
index 0000000..36e2550
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/BanEvent.java
@@ -0,0 +1,29 @@
+package lh.lockhead.skynet.events;
+
+import nl.lockhead.lpf.events.Cancellable;
+import nl.lockhead.lpf.events.LPFEvent;
+import org.bukkit.entity.Player;
+
+public class BanEvent extends LPFEvent implements Cancellable {
+
+ private Player player;
+ private boolean cancelled;
+
+ public BanEvent(Player player) {
+ this.player = player;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/CollidingEvent.java b/src/main/java/lh/lockhead/skynet/events/CollidingEvent.java
new file mode 100755
index 0000000..585b339
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/CollidingEvent.java
@@ -0,0 +1,60 @@
+package lh.lockhead.skynet.events;
+
+import nl.lockhead.lpf.events.LPFEvent;
+import org.bukkit.entity.Player;
+
+public class CollidingEvent extends LPFEvent {
+
+ private double x, y ,z;
+ private Player player;
+ private boolean collided;
+
+ public CollidingEvent(Player player, double x, double y, double z, boolean collided) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.player = player;
+ this.collided = collided;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public void setPlayer(Player player) {
+ this.player = player;
+ }
+
+ public void setCollided(boolean collided) {
+ this.collided = collided;
+ }
+
+ public boolean getCollided() {
+ return collided;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public void setX(double x) {
+ this.x = x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public void setY(double y) {
+ this.y = y;
+ }
+
+ public double getZ() {
+ return z;
+ }
+
+ public void setZ(double z) {
+ this.z = z;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/ConfigLoadEvent.java b/src/main/java/lh/lockhead/skynet/events/ConfigLoadEvent.java
new file mode 100755
index 0000000..6c55998
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/ConfigLoadEvent.java
@@ -0,0 +1,18 @@
+package lh.lockhead.skynet.events;
+
+import nl.lockhead.lpf.events.LPFEvent;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public class ConfigLoadEvent extends LPFEvent {
+
+ private final FileConfiguration fileConfiguration;
+
+ public ConfigLoadEvent(FileConfiguration fileConfiguration) {
+ this.fileConfiguration = fileConfiguration;
+ }
+
+ public FileConfiguration getFileConfiguration() {
+ return fileConfiguration;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/ConfigSaveEvent.java b/src/main/java/lh/lockhead/skynet/events/ConfigSaveEvent.java
new file mode 100755
index 0000000..295db36
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/ConfigSaveEvent.java
@@ -0,0 +1,18 @@
+package lh.lockhead.skynet.events;
+
+import nl.lockhead.lpf.events.LPFEvent;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public class ConfigSaveEvent extends LPFEvent {
+
+ private final FileConfiguration fileConfiguration;
+
+ public ConfigSaveEvent(FileConfiguration fileConfiguration) {
+ this.fileConfiguration = fileConfiguration;
+ }
+
+ public FileConfiguration getFileConfiguration() {
+ return fileConfiguration;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/GroundCheckEvent.java b/src/main/java/lh/lockhead/skynet/events/GroundCheckEvent.java
new file mode 100755
index 0000000..4e78b9e
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/GroundCheckEvent.java
@@ -0,0 +1,60 @@
+package lh.lockhead.skynet.events;
+
+import nl.lockhead.lpf.events.LPFEvent;
+import org.bukkit.entity.Player;
+
+public class GroundCheckEvent extends LPFEvent {
+
+ private double x, y ,z;
+ private Player player;
+ private boolean onGround;
+
+ public GroundCheckEvent(Player player, double x, double y, double z, boolean onGround) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.player = player;
+ this.onGround = onGround;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public void setPlayer(Player player) {
+ this.player = player;
+ }
+
+ public void setOnGround(boolean onGround) {
+ this.onGround = onGround;
+ }
+
+ public boolean getOnGround() {
+ return onGround;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public void setX(double x) {
+ this.x = x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public void setY(double y) {
+ this.y = y;
+ }
+
+ public double getZ() {
+ return z;
+ }
+
+ public void setZ(double z) {
+ this.z = z;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/InBlockCheckEvent.java b/src/main/java/lh/lockhead/skynet/events/InBlockCheckEvent.java
new file mode 100755
index 0000000..749b07b
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/InBlockCheckEvent.java
@@ -0,0 +1,60 @@
+package lh.lockhead.skynet.events;
+
+import nl.lockhead.lpf.events.LPFEvent;
+import org.bukkit.entity.Player;
+
+public class InBlockCheckEvent extends LPFEvent {
+
+ private double x, y ,z;
+ private Player player;
+ private boolean inBlock;
+
+ public InBlockCheckEvent(Player player, double x, double y, double z, boolean inBlock) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.player = player;
+ this.inBlock = inBlock;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public void setPlayer(Player player) {
+ this.player = player;
+ }
+
+ public void setInBlock(boolean inBlock) {
+ this.inBlock = inBlock;
+ }
+
+ public boolean getInBlock() {
+ return inBlock;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public void setX(double x) {
+ this.x = x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public void setY(double y) {
+ this.y = y;
+ }
+
+ public double getZ() {
+ return z;
+ }
+
+ public void setZ(double z) {
+ this.z = z;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/PermissionCheckEvent.java b/src/main/java/lh/lockhead/skynet/events/PermissionCheckEvent.java
new file mode 100755
index 0000000..550391f
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/PermissionCheckEvent.java
@@ -0,0 +1,31 @@
+package lh.lockhead.skynet.events;
+
+import nl.lockhead.lpf.events.LPFEvent;
+import org.bukkit.entity.Player;
+
+public class PermissionCheckEvent extends LPFEvent {
+
+ private boolean permission;
+ private Player player;
+
+ public PermissionCheckEvent(Player player, boolean permission) {
+ this.permission = permission;
+ this.player = player;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public void setPlayer(Player player) {
+ this.player = player;
+ }
+
+ public boolean hasPermission() {
+ return permission;
+ }
+
+ public void setPermission(boolean permission) {
+ this.permission = permission;
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/SecondEvent.java b/src/main/java/lh/lockhead/skynet/events/SecondEvent.java
new file mode 100755
index 0000000..b3fb985
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/SecondEvent.java
@@ -0,0 +1,8 @@
+package lh.lockhead.skynet.events;
+
+import nl.lockhead.lpf.events.LPFEvent;
+
+public class SecondEvent extends LPFEvent {
+
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/TickEvent.java b/src/main/java/lh/lockhead/skynet/events/TickEvent.java
new file mode 100755
index 0000000..e0552e0
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/TickEvent.java
@@ -0,0 +1,8 @@
+package lh.lockhead.skynet.events;
+
+import nl.lockhead.lpf.events.LPFEvent;
+
+public class TickEvent extends LPFEvent {
+
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/events/ViolationEvent.java b/src/main/java/lh/lockhead/skynet/events/ViolationEvent.java
new file mode 100755
index 0000000..ff6837f
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/events/ViolationEvent.java
@@ -0,0 +1,29 @@
+package lh.lockhead.skynet.events;
+
+import lh.lockhead.skynet.violations.Violation;
+import nl.lockhead.lpf.events.LPFEvent;
+import org.bukkit.entity.Player;
+
+public class ViolationEvent extends LPFEvent {
+
+ private Violation violation;
+ private Player player;
+
+ public ViolationEvent(Player player, Violation violation) {
+ this.violation = violation;
+ this.player = player;
+ }
+
+ public Violation getViolation() {
+ return violation;
+ }
+
+ public void setViolation(Violation violation) {
+ this.violation = violation;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/heuristics/SkynetURLPluginLoader.java b/src/main/java/lh/lockhead/skynet/heuristics/SkynetURLPluginLoader.java
new file mode 100755
index 0000000..e3ef587
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/heuristics/SkynetURLPluginLoader.java
@@ -0,0 +1,74 @@
+package lh.lockhead.skynet.heuristics;
+
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.configuration.Settings;
+import nl.lockhead.lpf.plugins.loaders.impl.AsyncURLPluginLoader;
+import nl.lockhead.lpf.plugins.plugin.PluginContainer;
+import org.apache.commons.io.IOUtils;
+import org.bukkit.Bukkit;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class SkynetURLPluginLoader extends AsyncURLPluginLoader {
+
+ public static final String FETCH_DOMAIN = Skynet.DOMAIN + "/fetch";
+ private String[] names;
+
+ public SkynetURLPluginLoader(@NotNull String... names) {
+ super(new ArrayList<>());
+ this.names = names;
+ }
+
+ @Override
+ public Set<PluginContainer> loadPlugins() {
+ return Arrays.stream(names).map(this::loadPlugin).filter(Objects::nonNull).collect(Collectors.toSet());
+ }
+
+ public PluginContainer loadPlugin(String name) {
+ try {
+ URL url = new URL(FETCH_DOMAIN);
+ URLConnection c = url.openConnection();
+ HttpURLConnection http = (HttpURLConnection) c;
+ http.setRequestMethod("POST");
+ http.setDoOutput(true);
+ Map<String, String> args = new HashMap<>();
+ args.put("key", Settings.KEY);
+ args.put("name", name);
+ args.put("version", Bukkit.getServer().getVersion().split("MC: ")[1]
+ .split("\\)")[0].replaceAll("[^.\\d]", ""));
+ StringJoiner sj = new StringJoiner("&");
+ args.forEach((key, value) -> {
+ try {
+ sj.add(URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(value, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ });
+
+ byte[] out = sj.toString().getBytes(StandardCharsets.UTF_8);
+ int length = out.length;
+ http.setFixedLengthStreamingMode(length);
+ http.setRequestProperty("User-Agent", "Skynet (compatible; MSIE 6.0; Windows NT 5.0)");
+ http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
+ http.connect();
+ try (OutputStream os = http.getOutputStream()) {
+ os.write(out);
+ }
+
+ return new PluginContainer(readBuffer(IOUtils.toByteArray(http.getInputStream())));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/ipintel/IPIntel.java b/src/main/java/lh/lockhead/skynet/ipintel/IPIntel.java
new file mode 100755
index 0000000..e9c8652
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/ipintel/IPIntel.java
@@ -0,0 +1,29 @@
+package lh.lockhead.skynet.ipintel;
+
+import java.net.InetAddress;
+
+public class IPIntel {
+
+ private final InetAddress address;
+ private final long timestamp;
+ private final double result;
+
+ public IPIntel(InetAddress address, double result) {
+ this.address = address;
+ this.result = result;
+ timestamp = System.currentTimeMillis();
+ }
+
+ public InetAddress getAddress() {
+ return address;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public double getResult() {
+ return result;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/ipintel/IPIntelHandler.java b/src/main/java/lh/lockhead/skynet/ipintel/IPIntelHandler.java
new file mode 100755
index 0000000..cd706c3
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/ipintel/IPIntelHandler.java
@@ -0,0 +1,199 @@
+package lh.lockhead.skynet.ipintel;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.wrappers.WrappedChatComponent;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import lh.lockhead.skynet.configuration.Settings;
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.chat.commandhandling.CommandHandler;
+import org.bukkit.BanList.Type;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.InvocationTargetException;
+import java.net.*;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+public class IPIntelHandler {
+
+ private final String queryURL = "http://check.getipintel.net/check.php?ip=%s&contact=%s&format=json&flags=b";
+ private final JsonParser parser = new JsonParser();
+ private static IPIntelHandler ipIntelHandler;
+ private final List<IPIntel> ipCache = new ArrayList<>();
+ private final List<InetAddress> ipQueue = new ArrayList<>();
+ private final List<IPIntel> processQueue = new ArrayList<>();
+ private static final long QUERY_COOLDOWN = 4000;
+ private static final long CACHE_EXPIRY_TIME = 14400000;
+ private static final int MAX_QUERIES = 500;
+ private int queries = 0;
+ private final Calendar calendar = Calendar.getInstance();
+ private boolean running = true;
+
+ private IPIntelHandler() {
+ calendar.setTime(new Date(System.currentTimeMillis()));
+ CommandHandler.get().sendConsoleMessage("IPIntel", "VPN/Proxy detection provided by https://getipintel.net");
+ (new Thread(() -> {
+ while (running) {
+ try {
+ Thread.sleep(QUERY_COOLDOWN);
+ if (!ipQueue.isEmpty())
+ getQuery(ipQueue.remove(0));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ })).start();
+ }
+
+ public static IPIntelHandler getIPIntelHandler() {
+ if (Settings.VPN_CONTACT_EMAIL == null) {
+ CommandHandler.get().sendConsoleMessage("IPIntel", "As per IP-Intel's terms," +
+ " a valid email is required. Update Skynet's config, and reload Skynet or restart the server.");
+ return null;
+ }
+ return ipIntelHandler == null ? (ipIntelHandler = new IPIntelHandler()) : ipIntelHandler;
+ }
+
+ public String getQueryUrl() {
+ return queryURL;
+ }
+
+ public void addIP(InetAddress ip) {
+ if (ip.isSiteLocalAddress() || ip.isAnyLocalAddress() || ip.isLoopbackAddress())
+ return;
+ if (ip.getHostAddress().startsWith("192.168."))
+ return;
+ try {
+ if (NetworkInterface.getByInetAddress(ip) != null)
+ return;
+ } catch (SocketException ignored) {}
+ for (IPIntel i : getIPCache()) {
+ if (i.getAddress().equals(ip))
+ return;
+ }
+ ipQueue.add(ip);
+ }
+
+ public List<IPIntel> getIPCache() {
+ for (IPIntel i : new ArrayList<>(ipCache)) {
+ if (i.getTimestamp() + CACHE_EXPIRY_TIME < System.currentTimeMillis())
+ ipCache.remove(i);
+ }
+ return ipCache;
+ }
+
+ public void setRunning(boolean running) {
+ this.running = running;
+ }
+
+ public void processResults() {
+ if (calendar.get(Calendar.MINUTE) == 0 && calendar.get(Calendar.HOUR_OF_DAY) == 0
+ && calendar.get(Calendar.SECOND) == 0)
+ queries = 0;
+ for (IPIntel i : new ArrayList<>(processQueue)) {
+ boolean b = processResult(i.getAddress(), i.getResult());
+ if (b)
+ processQueue.remove(i);
+ }
+ }
+
+ private boolean processResult(InetAddress ip, double result) {
+ if (!Settings.VPN_BAN_ENABLED && !Settings.VPN_WARN_ENABLED)
+ return true;
+ Player player = null;
+ for (Player p : Bukkit.getServer().getOnlinePlayers()) {
+ if (p.getAddress().getAddress().getHostAddress().equals(ip.getHostAddress())) {
+ player = p;
+ break;
+ }
+ }
+ ChatColor cc;
+ if (result > 0.95)
+ cc = ChatColor.DARK_RED;
+ else if (result > 0.9)
+ cc = Settings.COLOR_WARNING;
+ else if (result > 0.75)
+ cc = ChatColor.GOLD;
+ else if (result > 0.5)
+ cc = ChatColor.YELLOW;
+ else
+ cc = Settings.COLOR_SUCCESS;
+ String s = cc + String.valueOf(Math.round(result *= 100d)) + "%";
+ if (player == null) {
+ return false;
+ }
+ if (result >= Settings.VPN_BAN_THRESHOLD && Settings.VPN_BAN_ENABLED
+ && !Bukkit.getServer().getBanList(Type.IP).isBanned(ip.getHostAddress())) {
+ PacketContainer pc = Skynet.getProtocolManager().createPacket(PacketType.Play.Server.KICK_DISCONNECT);
+ pc.getChatComponents().write(0, WrappedChatComponent.fromText("Please do not attempt to connect with a VPN or Proxy."));
+ try {
+ Skynet.getProtocolManager().sendServerPacket(player, pc);
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ Bukkit.getServer().getBanList(Type.IP).addBan(ip.getHostAddress(), "This IP address has been flagged as VPN or Proxy.", null, "Skynet:Proxy");
+ if (Settings.VPN_WARN_ENABLED)
+ CommandHandler.get().notifyStaff("Player " + Settings.COLOR_SECONDARY + player.getName() + Settings.COLOR_DEFAULT +
+ "'s IP address has been detected as a VPN or proxy. The IP has been automatically banned. (" + s + " VPN probability" + Settings.COLOR_DEFAULT + ")");
+ }
+ else if (result >= Settings.VPN_WARN_THRESHOLD && Settings.VPN_WARN_ENABLED)
+ CommandHandler.get().notifyStaff("Player " + Settings.COLOR_SECONDARY + player.getName() + Settings.COLOR_DEFAULT +
+ " has connected from a suspicious IP address. (" + s + " VPN probability" + Settings.COLOR_DEFAULT + ")");
+ return true;
+ }
+
+ private void getQuery(InetAddress ip) throws MalformedURLException {
+ if (queries >= MAX_QUERIES) {
+ CommandHandler.get().sendConsoleMessage("IPIntel", "The maximum daily queries limit has been reached.");
+ return;
+ }
+ queries++;
+ URL url = new URL(String.format(queryURL, ip.getHostAddress(), Settings.VPN_CONTACT_EMAIL));
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()))) {
+ JsonObject json = parser.parse(in.readLine()).getAsJsonObject();
+ String status = json.get("status").getAsString();
+ double result = json.get("result").getAsDouble();
+ if (status.equals("error")) {
+ switch ((int) Math.round(result)) {
+ case -2:
+ CommandHandler.get().sendConsoleMessage("IPIntel", "IP " + ip.getHostAddress() + " is not a valid address.");
+ break;
+ case -3:
+ CommandHandler.get().sendConsoleMessage("IPIntel", "IP " + ip.getHostAddress() + " is an unroutable or private address.");
+ break;
+ case -4:
+ CommandHandler.get().sendConsoleMessage("IPIntel", "IPIntel's database is currently under maintenance.");
+ running = false;
+ break;
+ case -5:
+ CommandHandler.get().sendConsoleMessage("IPIntel", "Your IP address has been banned from IPIntel. Please contact getipintel@gmail.com for more info.");
+ running = false;
+ break;
+ case -6:
+ CommandHandler.get().sendConsoleMessage("IPIntel", "Invalid contact information received.");
+ break;
+ default:
+ }
+ return;
+ }
+ IPIntel i = new IPIntel(ip, result);
+ ipCache.add(i);
+ processQueue.add(i);
+ } catch (Exception e) {
+ if (e.getMessage().contains("response code 429")) {
+ queries = 500;
+ return;
+ }
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/utils/EvictingList.java b/src/main/java/lh/lockhead/skynet/utils/EvictingList.java
new file mode 100755
index 0000000..52e12c4
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/utils/EvictingList.java
@@ -0,0 +1,30 @@
+package lh.lockhead.skynet.utils;
+
+import java.util.LinkedList;
+
+public class EvictingList<E> extends LinkedList<E> {
+ private final int limit;
+
+ public EvictingList(int limit) {
+ this.limit = limit;
+ }
+
+ @Override
+ public boolean add(E o) {
+ boolean value = super.add(o);
+
+ while (size() > limit) {
+ super.remove();
+ }
+
+ return value;
+ }
+
+ public E getOldest() {
+ return get(0);
+ }
+
+ public E getLastest() {
+ return get(size() - 1);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/lh/lockhead/skynet/utils/Movement.java b/src/main/java/lh/lockhead/skynet/utils/Movement.java
new file mode 100755
index 0000000..7a11c1c
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/utils/Movement.java
@@ -0,0 +1,62 @@
+package lh.lockhead.skynet.utils;
+
+import lh.lockhead.skynet.events.CollidingEvent;
+import lh.lockhead.skynet.events.GroundCheckEvent;
+import lh.lockhead.skynet.events.InBlockCheckEvent;
+import nl.lockhead.lpf.events.LPFEventHandler;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+
+public class Movement {
+
+ private static final String HMMM = "Cleverly done, Mr. Freeman.";
+
+ private static final Double[] validYs = { 0.06250, 0.8125, 0.1875, 0.125, 0.25, 0.375, 0.625, 0.75, 0.875, 0.5625,
+ 0.4375, 0.0155550727022, 0.104080378093037 };
+
+ private static boolean isOnGround(double y) {
+ boolean onGround = (y % 0.5 == 0);
+ for (double validY : validYs) {
+ if (y % 1 == validY) {
+ onGround = true;
+ break;
+ }
+ }
+ return onGround;
+ }
+
+ private static boolean isInBlock(Player player) {
+ return player.getLocation().getBlock().getType() != Material.AIR
+ && player.getLocation().clone().add(0, 1, 0).getBlock().getType() != Material.AIR;
+ }
+
+ public static boolean isInBlock(Player player, double x, double y, double z) {
+ return isInBlock(player, x, y, z, false);
+ }
+
+ public static boolean isInBlock(Player player, double x, double y, double z, boolean colliding) {
+ if (colliding) {
+ CollidingEvent e = new CollidingEvent(player, x, y, z, isInBlock(player));
+ LPFEventHandler.getLPFEventHandler().handleEvent(e);
+ return e.getCollided();
+ }
+ InBlockCheckEvent e = new InBlockCheckEvent(player, x, y, z, isInBlock(player));
+ LPFEventHandler.getLPFEventHandler().handleEvent(e);
+ return e.getInBlock();
+ }
+
+ public static boolean isOnGround(Player player) {
+ return isOnGround(player, player.getLocation());
+ }
+
+ public static boolean isOnGround(Player player, Location location) {
+ return isOnGround(player, location.getX(), location.getY(), location.getZ());
+ }
+
+ public static boolean isOnGround(Player player, double x, double y, double z) {
+ GroundCheckEvent e = new GroundCheckEvent(player, x, y, z, isOnGround(y));
+ LPFEventHandler.getLPFEventHandler().handleEvent(e);
+ return e.getOnGround();
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/violations/Violation.java b/src/main/java/lh/lockhead/skynet/violations/Violation.java
new file mode 100755
index 0000000..894f640
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/violations/Violation.java
@@ -0,0 +1,78 @@
+package lh.lockhead.skynet.violations;
+
+import lh.lockhead.skynet.configuration.Settings;
+import nl.lockhead.lpf.plugins.plugin.Plugin;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class Violation {
+
+ private double severity;
+ private final long timestamp = System.currentTimeMillis();
+ private boolean experimental = false;
+ private long expiryTime;
+ private int lag;
+ private final Plugin heuristic;
+ private final Map<String, Object> data = new LinkedHashMap<>();
+
+ public Violation(Plugin heuristic, double severity) {
+ this.severity = severity;
+ this.heuristic = heuristic;
+ expiryTime = timestamp + Settings.DEFAULT_EXPIRY_TIME;
+ }
+
+ public Violation(Plugin heuristic, double severity, long expiryTime) {
+ this.severity = Math.max(0, severity);
+ this.heuristic = heuristic;
+ this.expiryTime = timestamp + Math.max(0, expiryTime);
+ }
+
+ public void setSeverity(double newSeverity) {
+ severity = newSeverity;
+ }
+
+ public double getSeverity() {
+ return severity;
+ }
+
+ public Plugin getHeuristic() {
+ return heuristic;
+ }
+
+ public Map<String, Object> getData() {
+ return data;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public long getExpiryTime() {
+ return expiryTime;
+ }
+
+ public void setExpiryTime(long newTimeMillis) {
+ expiryTime = timestamp + newTimeMillis;
+ }
+
+ public void setExpiryTimeHard(long newTimeMillis) {
+ expiryTime = newTimeMillis;
+ }
+
+ public int getLag() {
+ return lag;
+ }
+
+ public void setLag(int lag) {
+ this.lag = lag;
+ }
+
+ public boolean isExperimental() {
+ return experimental;
+ }
+
+ public void setExperimental(boolean experimental) {
+ this.experimental = experimental;
+ }
+}
diff --git a/src/main/java/lh/lockhead/skynet/violations/ViolationHandler.java b/src/main/java/lh/lockhead/skynet/violations/ViolationHandler.java
new file mode 100755
index 0000000..140917e
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/violations/ViolationHandler.java
@@ -0,0 +1,392 @@
+package lh.lockhead.skynet.violations;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import com.comphenix.protocol.wrappers.WrappedChatComponent;
+import lh.lockhead.skynet.Skynet;
+import lh.lockhead.skynet.bans.SkyBan;
+import lh.lockhead.skynet.chat.Message;
+import lh.lockhead.skynet.chat.commandhandling.CommandHandler;
+import lh.lockhead.skynet.chat.commandhandling.PlayerInfo;
+import lh.lockhead.skynet.configuration.Settings;
+import lh.lockhead.skynet.events.BanEvent;
+import lh.lockhead.skynet.events.ViolationEvent;
+import nl.lockhead.lpf.events.LPFEventHandler;
+import nl.lockhead.lpf.tools.FileManager;
+import org.bukkit.BanList.Type;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Sound;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.Date;
+import java.util.*;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class ViolationHandler {
+
+ private static ViolationHandler violationHandler;
+ private final Map<Player, Long> banned = new LinkedHashMap<>(),
+ lastWarned = new LinkedHashMap<>(),
+ cooldown = new LinkedHashMap<>();
+ private final Map<Player, ConcurrentLinkedQueue<Long>> ticks = new WeakHashMap<>();
+ private final Map<Player, Integer> debugModes = new WeakHashMap<>();
+ private final Map<Player, Integer> lag = new LinkedHashMap<>();
+ private final Map<Player, List<Violation>> violations = new LinkedHashMap<>();
+
+ private ViolationHandler() {
+ Skynet.getProtocolManager().addPacketListener(new PacketAdapter(Skynet.getInstance(),
+ PacketType.Play.Client.FLYING,
+ PacketType.Play.Client.LOOK,
+ PacketType.Play.Client.POSITION,
+ PacketType.Play.Client.POSITION_LOOK) {
+ @Override
+ public void onPacketReceiving(PacketEvent e) {
+ if (ticks.containsKey(e.getPlayer()))
+ ticks.get(e.getPlayer()).add(System.currentTimeMillis());
+ else
+ ticks.put(e.getPlayer(), new ConcurrentLinkedQueue<>(Collections.singletonList(System.currentTimeMillis())));
+ }
+ });
+ }
+
+ public static ViolationHandler get() {
+
+ return violationHandler == null ? (violationHandler = new ViolationHandler()) : violationHandler;
+ }
+
+ public void handle(Player player, Violation violation) {
+ if (!violation.getHeuristic().isEnabled())
+ return;
+ if (player == null)
+ return;
+ if (!player.isOnline())
+ return;
+ ViolationEvent e = new ViolationEvent(player, violation);
+ LPFEventHandler.getLPFEventHandler().handleEvent(e);
+ violation = e.getViolation();
+ logViolation(player, violation);
+ boolean exemptPermission = Skynet.hasPermission(player, "skynet.exempt"),
+ exemptLag = !lag.containsKey(player) || lag.get(player) > 3,
+ exemptCooldown = false;
+ if (cooldown.containsKey(player))
+ if (cooldown.get(player) > System.currentTimeMillis())
+ exemptCooldown = true;
+ if (debugModes.containsKey(player))
+ if (debugModes.get(player) > 0) {
+ StringBuilder data = new StringBuilder("[");
+ for (String key : violation.getData().keySet()) {
+ String d = String.valueOf(violation.getData().get(key)).trim();
+ if (d.contains(" "))
+ d = "\"" + d + "\"";
+ data.append(key).append("=").append(d).append(" ");
+ }
+ data = new StringBuilder(data.toString().trim());
+ data.append("]");
+ String debug;
+
+ if (debugModes.get(player) == 1)
+ debug = violation.getHeuristic().getConfig().getName() + (violation.isExperimental() ? (" (Experimental)") : (""));
+ else
+ debug = violation.getHeuristic().getConfig().getName() + (violation.isExperimental() ? (" (Experimental)") : ("")) + " "
+ + "Sev:" + violation.getSeverity() + ",Data:" + data;
+
+ if (exemptPermission || exemptLag || exemptCooldown)
+ CommandHandler.get().debugMessage(player, ChatColor.DARK_GRAY + debug);
+ else
+ CommandHandler.get().debugMessage(player, Settings.COLOR_DEFAULT + debug);
+ return;
+ }
+
+ if (exemptPermission || exemptLag || exemptCooldown)
+ return;
+ violation.setLag(getLag(player));
+ List<Violation> vls = getViolations(player);
+ for (Violation v : vls) {
+ if (v.getHeuristic().getId() == violation.getHeuristic().getId()) {
+ v.setSeverity(v.getSeverity() + violation.getSeverity());
+ v.setExpiryTimeHard(violation.getExpiryTime());
+ logViolation(player, violation);
+ return;
+ }
+ }
+ vls.add(violation);
+ }
+
+ private void logViolation(Player player, Violation violation) {
+ Date date = new Date(System.currentTimeMillis());
+ File logFile = new File(Skynet.getInstance().getDataFolder().getAbsolutePath() +
+ File.separator + "logs" + File.separator + Settings.DATE_FORMAT.format(date) +
+ File.separator + player.getName() + ".log");
+ StringBuilder data = new StringBuilder("[");
+ for (String key : violation.getData().keySet()) {
+ String d = String.valueOf(violation.getData().get(key)).trim();
+ if (d.contains(" "))
+ d = "\"" + d + "\"";
+ data.append(key).append("=").append(d).append(" ");
+ }
+ data = new StringBuilder(data.toString().trim());
+ data.append("]");
+ FileManager.writeToFile(logFile, "[" + Settings.LOG_TIME_FORMAT.format(date) + "] [Violation] " +
+ violation.getHeuristic().getConfig().getName() + " " + violation.getHeuristic().getConfig().getVersion().toString() +
+ "; Exp " + Math.round(((violation.getExpiryTime() - System.currentTimeMillis()) * 0.001) + 0.5d) + "s, "
+ + "Sev " + violation.getSeverity() + ", VL " + Math.round(getViolationLevel(player)) + ", Lag " + violation.getLag() + "; Data " +
+ data + "\n", false);
+ }
+
+
+
+ public void logEvent(Player player, String event) {
+ Date date = new Date(System.currentTimeMillis());
+ File logFile = new File(Skynet.getInstance().getDataFolder().getAbsolutePath() +
+ File.separator + "logs" + File.separator + Settings.DATE_FORMAT.format(date) +
+ File.separator + player.getName() + ".log");
+ FileManager.writeToFile(logFile, "[" + Settings.LOG_TIME_FORMAT.format(date) + "] [Event] " + event + "\n", false);
+ }
+
+
+ public double getViolationLevel(Player player) {
+ double d = 0;
+ for (Violation v : getViolations(player))
+ d += v.getSeverity();
+ return d;
+ }
+
+ public void clear(Player player) {
+ if (player == null)
+ return;
+ violations.remove(player);
+ }
+
+ public List<Violation> getViolations(Player player) {
+ if (player == null)
+ return null;
+ if (violations.containsKey(player)) {
+ for (Violation v : new ArrayList<>(violations.get(player))) {
+ if (!v.getHeuristic().isEnabled()) {
+ violations.get(player).remove(v);
+ }
+ if (v.getExpiryTime() < System.currentTimeMillis())
+ violations.get(player).remove(v);
+ }
+ return violations.get(player);
+ }
+ List<Violation> l = new ArrayList<>();
+ violations.put(player, l);
+ return l;
+ }
+
+ public void checkPlayers() {
+ for (Player player : new HashSet<>(violations.keySet())) {
+ if (player == null) {
+ continue;
+ }
+ double vl = 0;
+ for (Violation v : getViolations(player)) {
+ if (!v.isExperimental())
+ vl += v.getSeverity();
+ }
+ if (banned.containsKey(player)) {
+ boolean b = vl >= (Settings.VIOLATION_THRESHOLD * 2);
+ if (System.currentTimeMillis() > banned.get(player) || b) {
+ logEvent(player, "Exceeded 200% VL");
+ if (b && Settings.BAN_WARN_ENABLED) {
+ CommandHandler.get().notifyStaff(
+ "Player " + Settings.COLOR_SECONDARY + player.getName() + Settings.COLOR_DEFAULT
+ + " has exceeded twice the maximum violation level, and has been instantly banned.",
+ Sound.ORB_PICKUP);
+ }
+ banPlayer(Bukkit.getConsoleSender(), player);
+ }
+ continue;
+ }
+ else if (!player.isOnline()) {
+ violations.remove(player);
+ break;
+ }
+ List<Violation> violations = ViolationHandler.get().getViolations(player);
+ double highest = 0;
+// Violation hiv = violations.get(0);
+// for (Violation violation : violations) {
+// if (violation.getSeverity() > highest) {
+// hiv = violation;
+// highest = violation.getSeverity();
+// }
+// }
+ if (vl >= Math.max(.1, Settings.VIOLATION_THRESHOLD)) {
+ logEvent(player, "Exceeded 100% VL: ");
+ if (Settings.BAN_ENABLED)
+ banned.put(player, System.currentTimeMillis() + Settings.BAN_MINIMUM_DELAY + Math.round(Math.random() *
+ (Settings.BAN_MAXIMUM_DELAY - Settings.BAN_MINIMUM_DELAY)));
+ if (Settings.BAN_WARN_ENABLED) {
+ Message m = new Message("Alert");
+ m.add("Player " + Settings.COLOR_SECONDARY + player.getName() +
+ Settings.COLOR_DEFAULT + " has exceeded the maximum violation level.");
+ PlayerInfo.sendChart(player, m);
+ CommandHandler.get()
+ .notifyStaff(m);
+ if (Settings.BAN_ENABLED) {
+ long diff = banned.get(player) - System.currentTimeMillis();
+ CommandHandler.get()
+ .notifyStaff("They will be banned in " + Settings.dateDiff(diff) + ".");
+ logEvent(player, "Banned in " + Settings.dateDiff(diff));
+ }
+ }
+ clear(player);
+ }
+ else if (Settings.VL_WARN_ENABLED) {
+ int v = (int) Math.round((vl / Settings.VIOLATION_THRESHOLD * 100));
+ if (lastWarned.containsKey(player)) {
+ if (lastWarned.get(player) > System.currentTimeMillis() - 60000)
+ return;
+ }
+ if (v >= Settings.VL_WARN_THRESHOLD) {
+ logEvent(player, "Exceeded VL Warn threshold of " + Settings.VL_WARN_THRESHOLD + "%");
+ cooldown.put(player, System.currentTimeMillis() + 5000);
+ lastWarned.put(player, System.currentTimeMillis());
+ Message m = new Message("Alert");
+ m.add("Player " + Settings.COLOR_SECONDARY + player.getName() +
+ Settings.COLOR_DEFAULT + " has reached a violation level of " + v + "%.");
+ PlayerInfo.sendChart(player, m);
+ CommandHandler.get().notifyStaff(m, Sound.ORB_PICKUP);
+ }
+ }
+ }
+ }
+
+ private static final String[]
+ COMBINE_OVERWATCH_CODES = {
+ "lock, cauterize, stabilize", "duty, sword, operate",
+ "assemble, plan, contain", "isolate, expose, administer",
+ "assemble, administer, pacify", "respond, isolate, inquire",
+ "amputate, zero, confirm", "duty, sword, midnight",
+ "pressure, sword, sterilize", "sacrifice, coagulate, clamp",
+ "deploy, diagnose, dissect"
+ };
+ private static final String[] COMBINE_BAN = {
+ "You are charged with anti-civil activity level one.",
+ "Security alert. Illegal counter-resonant singularity device detected.",
+ "Individual, you are charged with capital malcompliance. Anti-citizen status approved.",
+ "Individual, you are now charged with socio-endangerment level five.",
+ "Individual, you are convicted of multi anticivil violations. Implicit server access revoked."
+ };
+ private static final String[] COMBINE_BAN_ANNOUNCEMENT = {
+ "User %s has been scheduled for permanent off-world relocation.",
+ "User %s has been expunged.",
+ "Member %s has been charged with capital malcompliance.",
+ "%s has received anti-citizen status level one."
+ };
+
+ public void banPlayer(ConsoleCommandSender sender, Player player) {
+ BanEvent event = new BanEvent(player);
+ if (Math.random() < 0.25 && Settings.COMBINE_REFERENCES_ENABLED) {
+ String msg = ChatColor.DARK_GRAY + "" + ChatColor.ITALIC + "\"" + COMBINE_BAN[(int) Math.floor((double) COMBINE_BAN.length * Math.random())];
+ msg += "\n" + ChatColor.DARK_GRAY + "" + ChatColor.ITALIC +
+ "Code: " + COMBINE_OVERWATCH_CODES[(int) Math.floor((double) COMBINE_OVERWATCH_CODES.length * Math.random())];
+ msg += "\"\n\n" + Settings.COLOR_DEFAULT + "(" + Settings.BAN_REASON_DEFAULT + ")";
+ LPFEventHandler.getLPFEventHandler().handleEvent(event);
+ if (event.isCancelled())
+ return;
+ kickPlayer(player, String.format(Settings.FORMAT_KICK, "Overwatch", "\n" + msg + "\n" + Settings.COLOR_DEFAULT));
+ }
+ else {
+ LPFEventHandler.getLPFEventHandler().handleEvent(event);
+ if (event.isCancelled())
+ return;
+ kickPlayer(player, String.format(Settings.FORMAT_KICK, "Overwatch", "\n" + Settings.BAN_REASON_DEFAULT));
+ }
+ if (Settings.USE_CUSTOM_COMMAND) {
+ Bukkit.getServer().dispatchCommand(sender, Settings.BAN_COMMAND.replace("$[playername]", player.getName())
+ .replace("$[reason]", Settings.BAN_REASON_DEFAULT));
+ }
+ else {
+ SkyBan ban = new SkyBan(player, Settings.BAN_REASON_DEFAULT, getViolations(player));
+ Bukkit.getBanList(Type.NAME).addBan(ban.getPlayer().getName(), "\n" + String.format(Settings.FORMAT_KICK, "Overwatch", "\n" + ban.getReason()), null, "Skynet:Overwatch");
+ }
+ String msg = String.format(Settings.BAN_BROADCAST_DEFAULT, Settings.COLOR_SECONDARY + player.getName() + Settings.COLOR_DEFAULT);
+ if (Math.random() < 0.3 && Settings.COMBINE_REFERENCES_ENABLED) {
+ msg = String.format(COMBINE_BAN_ANNOUNCEMENT[(int) Math.floor((double) COMBINE_BAN_ANNOUNCEMENT.length * Math.random())],
+ Settings.COLOR_SECONDARY + player.getName() + Settings.COLOR_DEFAULT);
+ }
+ for (Player p : Bukkit.getOnlinePlayers()) {
+ CommandHandler.get().sendMessage(p, new Message("Attention").add(msg));
+ }
+ logEvent(player, "User banned.");
+ banned.remove(player);
+ violations.remove(player);
+ }
+
+ public void kickPlayer(Player player, String reason) {
+ PacketContainer p = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.KICK_DISCONNECT);
+ try {
+ p.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
+ ProtocolLibrary.getProtocolManager().sendServerPacket(player, p);
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ player.kickPlayer(reason);
+ }
+ }
+
+ public Map<Player, Long> getBanDates() {
+ return banned;
+ }
+
+ public Map<Player, Long> getLastWarned() {
+ return lastWarned;
+ }
+
+ public Map<Player, Long> getCooldowns() {
+ return cooldown;
+ }
+
+ public ConcurrentLinkedQueue<Long> getTicks(Player player) {
+ if (player == null)
+ return null;
+ if (!ticks.containsKey(player))
+ ticks.put(player, new ConcurrentLinkedQueue<>());
+ for (long t : ticks.get(player)) {
+ if (t < System.currentTimeMillis() - 1000)
+ ticks.get(player).remove(t);
+ }
+ return ticks.get(player);
+ }
+
+ public int getLag(Player player) {
+ if (!lag.containsKey(player))
+ lag.put(player, 0);
+ return lag.get(player) + (int) Math.round(getPing(player) * 0.006);
+ }
+
+ public void setLag(Player player, int amount) {
+ lag.put(player, amount);
+ }
+
+ public Map<Player, Integer> getDebugModes() {
+ return debugModes;
+ }
+
+ public static int getPing(Player p) {
+ String bpName = Bukkit.getServer().getClass().getPackage().getName();
+ String version = bpName.substring(bpName.lastIndexOf(".") + 1);
+ try {
+ Class<?> clazz = Class.forName("org.bukkit.craftbukkit." + version + ".entity.CraftPlayer");
+ Object craftPlayer = clazz.cast(p);
+ Method getHandle = craftPlayer.getClass().getMethod("getHandle");
+ Object entityPlayer = getHandle.invoke(craftPlayer);
+ Field ping = entityPlayer.getClass().getDeclaredField("ping");
+ return ping.getInt(entityPlayer);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return 0;
+ }
+
+}
diff --git a/src/main/java/lh/lockhead/skynet/violations/lag/Tick.java b/src/main/java/lh/lockhead/skynet/violations/lag/Tick.java
new file mode 100755
index 0000000..f1cc44b
--- /dev/null
+++ b/src/main/java/lh/lockhead/skynet/violations/lag/Tick.java
@@ -0,0 +1,11 @@
+package lh.lockhead.skynet.violations.lag;
+
+public class Tick {
+
+ private final long timestamp = System.currentTimeMillis();
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+}