commit ae4da96810eedb9d76442d86517b34413f5cc9db
Author: Brandon <46827438+disclearing@users.noreply.github.com>
Date: Sun May 21 19:56:35 2023 +0100
the bois
diff --git a/lib/travertine-1.16-168.jar b/lib/travertine-1.16-168.jar
new file mode 100644
index 0000000..be929c8
Binary files /dev/null and b/lib/travertine-1.16-168.jar differ
diff --git a/lib/velocity-3.1.1-98.jar b/lib/velocity-3.1.1-98.jar
new file mode 100644
index 0000000..b732303
Binary files /dev/null and b/lib/velocity-3.1.1-98.jar differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..f672be8
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,99 @@
+
+
+ 4.0.0
+
+ gg.spoofmc.spoofer
+ spoofer
+ 1.0-SNAPSHOT
+
+
+ 8
+ 8
+
+
+
+
+ rip.teams.spigot
+ spigot-api
+ 1.8.8
+ provided
+
+
+ rip.teams.spigot
+ spigot-server
+ 1.8.8
+ provided
+
+
+ travertine
+ travertine
+ 1.16
+ system
+ ${basedir}/lib/travertine-1.16-168.jar
+
+
+ velocity
+ velocity
+ 1.16
+ system
+ ${basedir}/lib/velocity-3.1.1-98.jar
+
+
+ org.projectlombok
+ lombok
+ 1.18.22
+
+
+ org.apache.commons
+ commons-pool2
+ 2.11.1
+
+
+ redis.clients
+ jedis
+ 4.1.1
+
+
+ com.google.code.gson
+ gson
+ 2.9.0
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ 8
+
+
+ org.projectlombok
+ lombok
+ 1.18.20
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.3
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/gg/spoof/bungee/Spoof.java b/src/main/java/gg/spoof/bungee/Spoof.java
new file mode 100644
index 0000000..ba5e6a3
--- /dev/null
+++ b/src/main/java/gg/spoof/bungee/Spoof.java
@@ -0,0 +1,94 @@
+package gg.spoof.bungee;
+
+import gg.spoof.bungee.controller.PluginMessageController;
+import gg.spoof.bungee.controller.ProxyController;
+import gg.spoof.bungee.controller.RedisController;
+import gg.spoof.bungee.listener.BungeePingListener;
+import gg.spoof.bungee.util.ServerData;
+
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+
+import lombok.Getter;
+import net.md_5.bungee.api.plugin.Plugin;
+import net.md_5.bungee.config.Configuration;
+import net.md_5.bungee.config.ConfigurationProvider;
+import net.md_5.bungee.config.YamlConfiguration;
+
+public class Spoof extends Plugin {
+
+ @Getter
+ private static Spoof instance;
+ private final Map servers = new ConcurrentHashMap<>();
+ private boolean debug;
+ private ProxyController controller;
+
+ public Map getServers() {
+ return this.servers;
+ }
+
+ public void debug(String message) {
+ if (this.debug)
+ this.getLogger().info(message);
+ }
+
+ public void debug(String message, Throwable throwable) {
+ if (this.debug)
+ this.getLogger().log(Level.INFO, message, throwable);
+ }
+
+ public void onEnable() {
+ try {
+ instance = this;
+ final Path configFile = this.getDataFolder().toPath().resolve("config.yml");
+// ResourceLoader.loadDefaultResource(this.getClass().getClassLoader(), "bungee-config.yml", configFile);
+ final Configuration config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile.toFile());
+
+ this.debug = config.getBoolean("settings.debug");
+ this.registerController(config);
+ this.setupServersCleanup();
+ this.getProxy().getPluginManager().registerListener(this, new BungeePingListener(this));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void onDisable() {
+ if (this.controller != null)
+ this.controller.close();
+ }
+
+ private void registerController(Configuration config) {
+ String controllerType;
+ switch (controllerType = config.getString("settings.controller.selected").toLowerCase()) {
+ case "redis": {
+ final String[] address = config.getString("settings.controller.redis.address").split(":", 2);
+ final String password = config.getString("settings.controller.redis.password");
+ this.controller = new RedisController(this, address[0], Integer.parseInt(address[1]), password);
+ break;
+ }
+ case "messaging": {
+ this.controller = new PluginMessageController(this);
+ break;
+ }
+ default: {
+ throw new RuntimeException("The controller type '" + controllerType + "' isn't valid!");
+ }
+ }
+ this.getLogger().info("Using " + controllerType + " for the receiver.");
+ }
+
+ private void setupServersCleanup() {
+ this.getProxy().getScheduler().schedule(this, () -> {
+ final long currentTIme = System.currentTimeMillis();
+ for (Map.Entry serverSet : this.servers.entrySet()) {
+ ServerData server = serverSet.getValue();
+ if (currentTIme - server.getLastPing() <= TimeUnit.MINUTES.toMillis(1L)) continue;
+ this.servers.remove(serverSet.getKey());
+ }
+ }, 10L, 10L, TimeUnit.SECONDS);
+ }
+}
diff --git a/src/main/java/gg/spoof/bungee/SpoofAPI.java b/src/main/java/gg/spoof/bungee/SpoofAPI.java
new file mode 100644
index 0000000..29ce938
--- /dev/null
+++ b/src/main/java/gg/spoof/bungee/SpoofAPI.java
@@ -0,0 +1,13 @@
+package gg.spoof.bungee;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class SpoofAPI {
+
+ public static int getProxyCount() {
+ final AtomicInteger count = new AtomicInteger();
+ Spoof.getInstance().getServers().forEach((key, value) -> count.addAndGet(value.getOnline()));
+ return count.get();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/bungee/controller/PluginMessageController.java b/src/main/java/gg/spoof/bungee/controller/PluginMessageController.java
new file mode 100644
index 0000000..a2690bf
--- /dev/null
+++ b/src/main/java/gg/spoof/bungee/controller/PluginMessageController.java
@@ -0,0 +1,48 @@
+package gg.spoof.bungee.controller;
+
+import gg.spoof.bungee.Spoof;
+import gg.spoof.common.proxy.ProxyChannelFormat;
+import net.md_5.bungee.api.connection.Server;
+import net.md_5.bungee.api.event.PluginMessageEvent;
+import net.md_5.bungee.api.plugin.Listener;
+import net.md_5.bungee.event.EventHandler;
+
+public class PluginMessageController extends ProxyController implements Listener {
+
+ public PluginMessageController(Spoof plugin) {
+ super(plugin);
+ plugin.getProxy().registerChannel(ProxyChannelFormat.TAG_SERVER_PLAYERS);
+ plugin.getProxy().getPluginManager().registerListener(plugin, this);
+ }
+
+ @EventHandler
+ public void onPluginMessage(PluginMessageEvent event) {
+ if (!(event.getSender() instanceof Server))
+ return;
+
+ if (!event.getTag().equals(ProxyChannelFormat.TAG_SERVER_PLAYERS))
+ return;
+
+ try {
+ final ProxyChannelFormat.ServerPlayers serverPlayers = ProxyChannelFormat.decodeServerPlayers(event.getData());
+ this.updateServerPlayerCount(serverPlayers.getServerId(), serverPlayers.getCount());
+ } catch (Exception ex) {
+ this.plugin.debug("Error receiving server players", ex);
+ return;
+ }
+
+ try {
+ int proxyPlayerCount = this.getProxyPlayerCount();
+ byte[] proxyPlayerCountData = ProxyChannelFormat.encodeProxyPlayerCount(proxyPlayerCount);
+ this.plugin.getProxy().getServers().values().forEach(serverInfo -> serverInfo.sendData(ProxyChannelFormat.TAG_PROXY_PLAYER_COUNT, proxyPlayerCountData, false));
+ this.plugin.debug("Sent proxy player count " + proxyPlayerCount);
+ } catch (Exception ex) {
+ this.plugin.debug("Error sending proxy player count", ex);
+ }
+ }
+
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/src/main/java/gg/spoof/bungee/controller/ProxyController.java b/src/main/java/gg/spoof/bungee/controller/ProxyController.java
new file mode 100644
index 0000000..4d6c7b5
--- /dev/null
+++ b/src/main/java/gg/spoof/bungee/controller/ProxyController.java
@@ -0,0 +1,23 @@
+package gg.spoof.bungee.controller;
+
+import gg.spoof.bungee.Spoof;
+import gg.spoof.bungee.util.ServerData;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public abstract class ProxyController {
+
+ protected final Spoof plugin;
+
+ public abstract void close();
+
+ protected int getProxyPlayerCount() {
+ return this.plugin.getServers().values().stream().mapToInt(ServerData::getOnline).sum();
+ }
+
+ protected void updateServerPlayerCount(String server, int online) {
+ this.plugin.getServers().computeIfAbsent(server, k -> new ServerData()).setOnline(online);
+ this.plugin.debug("Updated server " + server + " player count to " + online);
+ }
+
+}
diff --git a/src/main/java/gg/spoof/bungee/controller/RedisController.java b/src/main/java/gg/spoof/bungee/controller/RedisController.java
new file mode 100644
index 0000000..58bbb3d
--- /dev/null
+++ b/src/main/java/gg/spoof/bungee/controller/RedisController.java
@@ -0,0 +1,77 @@
+package gg.spoof.bungee.controller;
+
+import gg.spoof.bungee.Spoof;
+import gg.spoof.common.proxy.ProxyChannelFormat;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPubSub;
+
+public class RedisController extends ProxyController {
+
+ private Jedis jedisRX;
+ private Jedis jedisTX;
+
+ public RedisController(final Spoof plugin, String host, int port, String password) {
+ super(plugin);
+ this.jedisRX = this.create(host, port, password);
+ this.jedisTX = this.create(host, port, password);
+
+ plugin.getProxy().getScheduler().runAsync(plugin, () -> {
+ final JedisPubSub jedisPubSub = new JedisPubSub() {
+
+ @Override
+ public void onMessage(String channel, String message) {
+ try {
+ final ProxyChannelFormat.ServerPlayers players = ProxyChannelFormat.decodeB64ServerPlayers(message);
+ updateServerPlayerCount(players.getServerId(), players.getCount());
+ } catch (Exception ex) {
+ plugin.debug("Error receiving server players", ex);
+ return;
+ }
+
+ try {
+ int proxyPlayerCount = RedisController.this.getProxyPlayerCount();
+ jedisTX.publish("spoof:pr_pl_cnt", ProxyChannelFormat.encodeB64ProxyPlayerCount(proxyPlayerCount));
+ plugin.debug("Sent total player count " + proxyPlayerCount);
+ } catch (Exception ex) {
+ plugin.debug("Error sending proxy player count", ex);
+ }
+ }
+
+ @Override
+ public void onSubscribe(String channel, int subscribedChannels) {
+ }
+
+ @Override
+ public void onUnsubscribe(String channel, int subscribedChannels) {
+ }
+ };
+ this.jedisRX.subscribe(jedisPubSub, ProxyChannelFormat.TAG_SERVER_PLAYERS);
+ });
+ }
+
+ private Jedis create(String host, int port, String password) {
+ final Jedis jedis = new Jedis(host, port);
+ jedis.connect();
+
+ if (!password.isEmpty())
+ jedis.auth(password);
+
+ if (!jedis.isConnected())
+ throw new IllegalStateException("Failed to connect to redis");
+
+ return jedis;
+ }
+
+ @Override
+ public void close() {
+ if (this.jedisRX != null) {
+ this.jedisRX.close();
+ this.jedisRX = null;
+ }
+ if (this.jedisTX != null) {
+ this.jedisTX.close();
+ this.jedisTX = null;
+ }
+ }
+
+}
diff --git a/src/main/java/gg/spoof/bungee/listener/BungeePingListener.java b/src/main/java/gg/spoof/bungee/listener/BungeePingListener.java
new file mode 100644
index 0000000..b2efbdc
--- /dev/null
+++ b/src/main/java/gg/spoof/bungee/listener/BungeePingListener.java
@@ -0,0 +1,31 @@
+package gg.spoof.bungee.listener;
+
+import gg.spoof.bungee.Spoof;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import lombok.RequiredArgsConstructor;
+import net.md_5.bungee.api.ServerPing;
+import net.md_5.bungee.api.event.ProxyPingEvent;
+import net.md_5.bungee.api.plugin.Listener;
+import net.md_5.bungee.event.EventHandler;
+
+@RequiredArgsConstructor
+public class BungeePingListener implements Listener {
+
+ protected final Spoof plugin;
+
+ @EventHandler
+ public void onProxyPing(ProxyPingEvent event) {
+ final ServerPing ping = event.getResponse();
+ final ServerPing.Players players = ping.getPlayers();
+ final AtomicInteger count = new AtomicInteger();
+
+ this.plugin.getServers().forEach((key, value) -> count.addAndGet(value.getOnline()));
+ this.plugin.debug("Setting proxy count to " + count + ".");
+
+ ping.setPlayers(new ServerPing.Players(players.getMax(), count.get(), players.getSample()));
+ event.setResponse(ping);
+ }
+
+}
diff --git a/src/main/java/gg/spoof/bungee/util/ServerData.java b/src/main/java/gg/spoof/bungee/util/ServerData.java
new file mode 100644
index 0000000..514acad
--- /dev/null
+++ b/src/main/java/gg/spoof/bungee/util/ServerData.java
@@ -0,0 +1,16 @@
+package gg.spoof.bungee.util;
+
+import lombok.Getter;
+
+@Getter
+public class ServerData {
+
+ private volatile int online;
+ private volatile long lastPing;
+
+ public void setOnline(int online) {
+ this.online = online;
+ this.lastPing = System.currentTimeMillis();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/common/proxy/ProxyChannelFormat.java b/src/main/java/gg/spoof/common/proxy/ProxyChannelFormat.java
new file mode 100644
index 0000000..70d9e0a
--- /dev/null
+++ b/src/main/java/gg/spoof/common/proxy/ProxyChannelFormat.java
@@ -0,0 +1,72 @@
+package gg.spoof.common.proxy;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.io.*;
+import java.util.Base64;
+
+public class ProxyChannelFormat {
+
+ public static final String TAG_SERVER_PLAYERS = "spoof:srv_pl";
+ public static final String TAG_PROXY_PLAYER_COUNT = "spoof:pr_pl_cnt";
+
+ public static ServerPlayers decodeServerPlayers(byte[] message) throws IOException {
+ try (final DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(message))) {
+ final String serverId = dataInputStream.readUTF();
+ final int count = dataInputStream.readInt();
+
+ return new ServerPlayers(serverId, count);
+ }
+ }
+
+ public static ServerPlayers decodeB64ServerPlayers(String message) throws IOException {
+ return decodeServerPlayers(Base64.getDecoder().decode(message));
+ }
+
+ public static byte[] encodeServerPlayers(ServerPlayers serverPlayers) throws IOException {
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try (final DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream)) {
+ dataOutputStream.writeUTF(serverPlayers.getServerId());
+ dataOutputStream.writeInt(serverPlayers.getCount());
+
+ return byteArrayOutputStream.toByteArray();
+ }
+ }
+
+ public static String encodeB64ServerPlayers(ServerPlayers serverPlayers) throws IOException {
+ return Base64.getEncoder().encodeToString(encodeServerPlayers(serverPlayers));
+ }
+
+ public static int decodeProxyPlayerCount(byte[] data) throws IOException {
+ final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
+ try (final DataInputStream dataInputStream = new DataInputStream(byteArrayInputStream)) {
+ return dataInputStream.readInt();
+ }
+ }
+
+ public static int decodeB64ProxyPlayerCount(String message) throws IOException {
+ return decodeProxyPlayerCount(Base64.getDecoder().decode(message));
+ }
+
+ public static byte[] encodeProxyPlayerCount(int var0) throws IOException {
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try (final DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream)) {
+ dataOutputStream.writeInt(var0);
+ return byteArrayOutputStream.toByteArray();
+ }
+ }
+
+ public static String encodeB64ProxyPlayerCount(int var0) throws IOException {
+ return Base64.getEncoder().encodeToString(encodeProxyPlayerCount(var0));
+ }
+
+ @Getter
+ @RequiredArgsConstructor
+ public static class ServerPlayers {
+
+ protected final String serverId;
+ protected final int count;
+
+ }
+}
diff --git a/src/main/java/gg/spoof/common/utils/ResourceLoader.java b/src/main/java/gg/spoof/common/utils/ResourceLoader.java
new file mode 100644
index 0000000..2ebea0f
--- /dev/null
+++ b/src/main/java/gg/spoof/common/utils/ResourceLoader.java
@@ -0,0 +1,26 @@
+package gg.spoof.common.utils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Objects;
+
+public class ResourceLoader {
+
+ public static void loadDefaultResource(ClassLoader classLoader, String var1, Path path) throws IOException {
+ if (Files.exists(path, LinkOption.NOFOLLOW_LINKS)) {
+ final Path parent = path.getParent();
+ Files.createDirectories(parent);
+
+ final InputStream resourceAsStream = classLoader.getResourceAsStream(var1);
+ Files.copy(Objects.requireNonNull(resourceAsStream), parent, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/Spoof.java b/src/main/java/gg/spoof/spigot/Spoof.java
new file mode 100644
index 0000000..9258c47
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/Spoof.java
@@ -0,0 +1,163 @@
+package gg.spoof.spigot;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import gg.spoof.spigot.api.ProxyController;
+import gg.spoof.spigot.api.SpoofConfig;
+import gg.spoof.spigot.api.manager.ModuleManager;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.commands.CmdCommand;
+import gg.spoof.spigot.controller.NoopController;
+import gg.spoof.spigot.controller.RedisController;
+import gg.spoof.spigot.listener.DeathHandler;
+import gg.spoof.spigot.listener.MiscHandler;
+import gg.spoof.spigot.listener.SpawnHandler;
+import gg.spoof.spigot.message.TL;
+import gg.spoof.spigot.module.*;
+import gg.spoof.spigot.module.fluctuation.AccountsRegistry;
+import gg.spoof.spigot.module.fluctuation.FluctuationModule;
+import gg.spoof.spigot.nms.NMSWrapper;
+import lombok.Getter;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.PluginManager;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+
+@Getter
+public class Spoof extends JavaPlugin {
+
+ @Getter
+ private static Spoof plugin;
+ public static Executor POOL;
+ private final SpoofConfig spoofConfig = new SpoofConfig(this);
+ private ProxyController proxyController = new NoopController();
+ private NMSWrapper platform;
+ private AccountsRegistry accounts;
+
+ public Spoof() {
+ plugin = this;
+ }
+
+ public void onEnable() {
+ POOL = Executors.newFixedThreadPool(5, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("%d - Stellar Ghost").build());
+
+ this.saveDefaultConfig();
+ this.loadConfiguration();
+ this.setupCompatibility();
+
+ this.accounts = AccountsRegistry.create(this);
+
+ final PluginManager pluginManager = getServer().getPluginManager();
+ Arrays.asList(
+ new MiscHandler(this),
+ new SpawnHandler(this),
+ new DeathHandler(this),
+ new CmdCommand(this, true)
+ ).forEach(it -> pluginManager.registerEvents(it, this));
+
+ this.setupProxyController();
+
+ Arrays.asList(
+// new PickupModule(this),
+// new PingModule(this),
+ new FluctuationModule(this, true),
+// new ActionModule(this),
+ new WelcomeModule(this)
+// new RankModule(this),
+// new VoteModule(this)
+ ).forEach(ModuleManager::registerModule);
+ }
+
+ public void onDisable() {
+ PlayerManager.getPlayerMap().values().forEach(spoofPlayer -> this.getPlatform().removeSpoofPlayer(spoofPlayer));
+ }
+
+ private void setupCompatibility() {
+ try {
+ final String packageName = Bukkit.getServer().getClass().getPackage().getName();
+ final String version = packageName.substring(packageName.lastIndexOf(".") + 1);
+
+ final Class> aClass = Class.forName("gg.spoof.spigot.nms." + version + ".NMSWrapper");
+ this.platform = (NMSWrapper) aClass.getDeclaredConstructor(Spoof.class).newInstance(this);
+ } catch (Exception e) {
+ this.debug("Error initializing platform support");
+ this.getPluginLoader().disablePlugin(this);
+ }
+ }
+
+ public void loadConfiguration() {
+ this.reloadConfig();
+ final FileConfiguration config = this.getConfig();
+ for (TL message : TL.values()) {
+ if (config.getString("messages." + message.getPath()) == null) continue;
+ message.setMessage(config.getString("messages." + message.getPath()));
+ }
+ this.spoofConfig.load();
+ }
+
+ public void setupProxyController() {
+ this.proxyController.close();
+ this.proxyController = new NoopController();
+
+ try {
+ String controllerType;
+ if (!this.getConfig().getBoolean("settings.proxy-mode", false)) {
+ return;
+ }
+ switch (controllerType = this.getConfig().getString("settings.controller.selected", "redis").toLowerCase(Locale.ENGLISH)) {
+ case "redis": {
+ String[] address = this.getConfig().getString("settings.controller.redis.address").split(":", 2);
+ String password = this.getConfig().getString("settings.controller.redis.password");
+ this.proxyController = new RedisController(this, address[0], Integer.parseInt(address[1]), password);
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("The controller type '" + controllerType + "' isn't valid! Please use the follow (REDIS, MESSAGING)");
+ }
+ }
+ this.getServer().getScheduler().runTaskTimerAsynchronously(this, () -> this.proxyController.sendServerPlayerCount(), 100L, 100L);
+ }
+ catch (Throwable e) {
+ this.getLogger().log(Level.SEVERE, "Failed to init proxy controller", e);
+ }
+ }
+
+ public boolean isWhitelisted(CommandSender commandSender) {
+ if (!(commandSender instanceof Player))
+ return true;
+
+ final Player player = (Player) commandSender;
+ return this.spoofConfig.getWhitelistedUsers().contains(player.getUniqueId().toString())
+ || this.spoofConfig.getWhitelistedUsers().contains(player.getName());
+ }
+
+ public void print(String message) {
+ this.getLogger().info(message);
+ }
+
+ public void print(String message, Throwable t) {
+ this.getLogger().log(Level.INFO, message, t);
+ }
+
+ public void debug(String message) {
+ if (!this.spoofConfig.isDebug())
+ return;
+
+ this.print(message);
+ }
+
+ public void debug(String message, Throwable t) {
+ if (!this.spoofConfig.isDebug())
+ return;
+
+ this.print(message, t);
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/SpoofAPI.java b/src/main/java/gg/spoof/spigot/SpoofAPI.java
new file mode 100644
index 0000000..2df4e74
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/SpoofAPI.java
@@ -0,0 +1,9 @@
+package gg.spoof.spigot;
+
+public class SpoofAPI {
+
+ public static int getTotalCount() {
+ return Spoof.getPlugin().getProxyController().getTotalPlayerCount();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/actions/Action.java b/src/main/java/gg/spoof/spigot/actions/Action.java
new file mode 100644
index 0000000..c8794dd
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/actions/Action.java
@@ -0,0 +1,10 @@
+package gg.spoof.spigot.actions;
+
+import org.bukkit.entity.Player;
+
+@FunctionalInterface
+public interface Action {
+
+ void run(Player var1, String var2);
+
+}
diff --git a/src/main/java/gg/spoof/spigot/actions/ActionRegistry.java b/src/main/java/gg/spoof/spigot/actions/ActionRegistry.java
new file mode 100644
index 0000000..b2d2d0f
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/actions/ActionRegistry.java
@@ -0,0 +1,68 @@
+package gg.spoof.spigot.actions;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.util.StringUtil;
+import lombok.Getter;
+import net.md_5.bungee.chat.ComponentSerializer;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.Server;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Getter
+public class ActionRegistry {
+
+ private final Spoof plugin;
+ private final Map actions = new ConcurrentHashMap<>();
+
+ public ActionRegistry(Spoof plugin) {
+ this.plugin = plugin;
+ this.addAction("console", (player, data) -> plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), data));
+ this.addAction("player", Player::performCommand);
+ this.addAction("broadcast", (player, data) -> plugin.getServer().broadcastMessage(data));
+ this.addAction("message", CommandSender::sendMessage);
+ this.addAction("chat", Player::chat);
+ this.addAction("close", (player, data) -> player.closeInventory());
+ this.addAction("json", (player, data) -> player.spigot().sendMessage(ComponentSerializer.parse(data)));
+ }
+
+ public void addAction(String id, Action action) {
+ this.actions.put(id.toUpperCase(), action);
+ }
+
+ public void runActions(final Player player, List items) {
+ items.forEach(item -> {
+ final String actionData = !item.contains(" ") ? "" : StringUtil.translate(item.split(" ", 2)[1]).replace("%player%", player.getName());
+ final Action action = this.getAction(item);
+ final Server server = player.getServer();
+
+ server.getScheduler().runTask(this.plugin, () -> {
+ if (action != null) {
+ action.run(player, actionData);
+ } else {
+ plugin.getServer().dispatchCommand(server.getConsoleSender(), item);
+ }
+ });
+ });
+ }
+
+ public void runActions(Player player, String... items) {
+ this.runActions(player, Arrays.asList(items));
+ }
+
+ public Action getAction(String item) {
+ boolean singleAction = !item.contains(" ");
+ String actionPrefix = singleAction ? item : item.split(" ", 2)[0].toUpperCase();
+ String rawAction = StringUtils.substringBetween(actionPrefix, "[", "]");
+ if (rawAction != null) {
+ return this.actions.get(rawAction);
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/api/Module.java b/src/main/java/gg/spoof/spigot/api/Module.java
new file mode 100644
index 0000000..7df8ce9
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/api/Module.java
@@ -0,0 +1,98 @@
+package gg.spoof.spigot.api;
+
+import gg.spoof.spigot.api.manager.ModuleManager;
+import java.util.List;
+import org.apache.commons.lang.Validate;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public abstract class Module {
+
+ protected final JavaPlugin plugin;
+
+ protected Module(JavaPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ protected Module() {
+ this.plugin = JavaPlugin.getProvidingPlugin(this.getClass());
+ }
+
+ public abstract String getName();
+
+ public String getIdentifier() {
+ return this.getName().toLowerCase();
+ }
+
+ public boolean isEnabled() {
+ return this.getBoolean("enabled", false);
+ }
+
+ public abstract String getAuthor();
+
+ public abstract String getVersion();
+
+ public String getRequiredPlugin() {
+ return null;
+ }
+
+ public abstract void onEnable();
+
+ public abstract void onDisable();
+
+ public boolean isRegistered() {
+ Validate.notNull(this.getIdentifier(), "Module identifier can not be null!");
+ return ModuleManager.isRegistered(this.getIdentifier());
+ }
+
+ public boolean canRegister() {
+ return this.getRequiredPlugin() == null || Bukkit.getPluginManager().getPlugin(this.getRequiredPlugin()) != null;
+ }
+
+ public boolean register() {
+ Validate.notNull(this.getIdentifier(), "Module identifier can not be null!");
+ return ModuleManager.registerModule(this);
+ }
+
+ public String getString(String path, String def) {
+ return this.plugin.getConfig().getString("modules." + this.getIdentifier().toLowerCase() + "." + path, def);
+ }
+
+ public int getInt(String path, int def) {
+ return this.plugin.getConfig().getInt("modules." + this.getIdentifier().toLowerCase() + "." + path, def);
+ }
+
+ public long getLong(String path, long def) {
+ return this.plugin.getConfig().getLong("modules." + this.getIdentifier().toLowerCase() + "." + path, def);
+ }
+
+ public double getDouble(String path, double def) {
+ return this.plugin.getConfig().getDouble("modules." + this.getIdentifier().toLowerCase() + "." + path, def);
+ }
+
+ public boolean getBoolean(String path, boolean def) {
+ return this.plugin.getConfig().getBoolean("modules." + this.getIdentifier().toLowerCase() + "." + path, def);
+ }
+
+ public List getStringList(String path) {
+ return this.plugin.getConfig().getStringList("modules." + this.getIdentifier().toLowerCase() + "." + path);
+ }
+
+ public Object get(String path, Object def) {
+ return this.plugin.getConfig().get("modules." + this.getIdentifier().toLowerCase() + "." + path, def);
+ }
+
+ public ConfigurationSection getConfigSection(String path) {
+ return this.plugin.getConfig().getConfigurationSection("modules." + this.getIdentifier().toLowerCase() + "." + path);
+ }
+
+ public ConfigurationSection getConfigSection() {
+ return this.plugin.getConfig().getConfigurationSection("modules." + this.getIdentifier().toLowerCase());
+ }
+
+ public boolean configurationContains(String path) {
+ return this.plugin.getConfig().contains("modules." + this.getIdentifier().toLowerCase() + "." + path);
+ }
+}
diff --git a/src/main/java/gg/spoof/spigot/api/ProxyController.java b/src/main/java/gg/spoof/spigot/api/ProxyController.java
new file mode 100644
index 0000000..300ec05
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/api/ProxyController.java
@@ -0,0 +1,11 @@
+package gg.spoof.spigot.api;
+
+public interface ProxyController {
+
+ int getTotalPlayerCount();
+
+ void sendServerPlayerCount();
+
+ void close();
+
+}
diff --git a/src/main/java/gg/spoof/spigot/api/SpoofConfig.java b/src/main/java/gg/spoof/spigot/api/SpoofConfig.java
new file mode 100644
index 0000000..2809723
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/api/SpoofConfig.java
@@ -0,0 +1,53 @@
+package gg.spoof.spigot.api;
+
+import gg.spoof.spigot.Spoof;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.bukkit.Location;
+import org.bukkit.configuration.file.FileConfiguration;
+
+@Getter
+@RequiredArgsConstructor
+public class SpoofConfig {
+
+ private final Spoof plugin;
+ private boolean visible = true;
+ private boolean debug = false;
+ private boolean enableJoinLeave = true;
+ private int joinCommandsDelay = 0;
+ private List joinCommands = Collections.emptyList();
+ private String serverId = "none-specified";
+ private List whitelistedUsers = Collections.emptyList();
+ private String spawnWorld;
+ private double spawnX;
+ private double spawnY;
+ private double spawnZ;
+
+ public void load() {
+ final FileConfiguration config = this.plugin.getConfig();
+ this.visible = config.getBoolean("settings.show-bot-entity", true);
+ this.debug = config.getBoolean("settings.debug", true);
+ this.enableJoinLeave = config.getBoolean("settings.enable-join-leave", true);
+ this.joinCommands = config.getStringList("settings.join-commands");
+ this.joinCommandsDelay = config.getInt("settings.join-commands-delay", this.joinCommandsDelay);
+ this.serverId = config.getString("settings.server-id", "none-specified");
+ this.whitelistedUsers = config.getStringList("settings.whitelisted");
+ this.spawnWorld = config.getString("settings.spawn-locations.world");
+ this.spawnX = config.getInt("settings.spawn-locations.x");
+ this.spawnY = config.getInt("settings.spawn-locations.y");
+ this.spawnZ = config.getInt("settings.spawn-locations.z");
+ }
+
+ public Location getSpawnLocation() {
+ try {
+ return new Location(this.plugin.getServer().getWorld(this.spawnWorld), this.spawnX, this.spawnY, this.spawnZ);
+ } catch (Exception e) {
+ this.plugin.debug("Couldn't find spawn location! Using default location");
+ return this.plugin.getServer().getWorlds().get(0).getSpawnLocation();
+ }
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/api/SpoofPlayer.java b/src/main/java/gg/spoof/spigot/api/SpoofPlayer.java
new file mode 100644
index 0000000..9125481
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/api/SpoofPlayer.java
@@ -0,0 +1,35 @@
+package gg.spoof.spigot.api;
+
+import gg.spoof.spigot.api.manager.PlayerManager;
+import java.util.UUID;
+
+import lombok.Getter;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+@Getter
+public class SpoofPlayer {
+
+ private final UUID id;
+ private final String name;
+ private final PlayerManager.PlayerManagementType managementType;
+ private Player player;
+
+ public SpoofPlayer(UUID id, String name, PlayerManager.PlayerManagementType managementType) {
+ this.id = id;
+ this.name = name;
+ this.managementType = managementType;
+ }
+
+ public Player getPlayer() {
+ return this.player != null ? this.player : Bukkit.getPlayer(this.id);
+ }
+
+ public void setPlayer(Player player) {
+ if (!this.id.equals(player.getUniqueId()))
+ throw new IllegalArgumentException("Id missmatch");
+
+ this.player = player;
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/api/manager/ModuleManager.java b/src/main/java/gg/spoof/spigot/api/manager/ModuleManager.java
new file mode 100644
index 0000000..50012e5
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/api/manager/ModuleManager.java
@@ -0,0 +1,107 @@
+package gg.spoof.spigot.api.manager;
+
+import com.google.common.collect.ImmutableSet;
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.Module;
+import gg.spoof.spigot.module.InternalModule;
+import java.util.AbstractMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang.Validate;
+
+public class ModuleManager {
+
+ private static final Map, Boolean>> modules = new HashMap<>();
+
+ public static boolean isRegistered(String identifier) {
+ return ModuleManager.getRegisteredIdentifiers().stream().filter(id -> id.equalsIgnoreCase(identifier)).findFirst().orElse(null) != null;
+ }
+
+ public static void unregisterModule(String identifier) {
+ Validate.notNull(identifier, "Identifier can not be null");
+ Map.Entry, Boolean> moduleEntry = modules.remove(identifier.toLowerCase());
+ if (moduleEntry != null && moduleEntry.getValue()) {
+ ModuleManager.setModuleDisabled(moduleEntry.getKey());
+ }
+ }
+
+ public static Set getRegisteredIdentifiers() {
+ return ImmutableSet.copyOf(modules.keySet());
+ }
+
+ public static Map> getModules() {
+ return modules.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> (entry.getValue()).getKey()));
+ }
+
+ protected static void unregisterAll() {
+ ModuleManager.unregisterAllProvidedModules();
+ modules.clear();
+ }
+
+ public static void unregisterAllProvidedModules() {
+ if (modules.isEmpty()) {
+ return;
+ }
+ ModuleManager.getModules().values().forEach(module -> {
+ if (module instanceof InternalModule) {
+ ModuleManager.unregisterModule(module.getIdentifier());
+ }
+ });
+ }
+
+ public static boolean registerModule(Module> module) {
+ Validate.notNull(module, "Module can not be null");
+ Validate.notNull(module.getIdentifier(), "Identifier can not be null");
+
+ if (!module.canRegister() || ModuleManager.isRegistered(module.getIdentifier()))
+ return false;
+
+ boolean enabled = module.isEnabled();
+
+ if (enabled)
+ enabled = ModuleManager.setModuleEnabled(module);
+
+ modules.put(module.getIdentifier().toLowerCase(), new AbstractMap.SimpleEntry<>(module, enabled));
+ return true;
+ }
+
+ public static void reloadModules() {
+ modules.values().forEach(moduleEntry -> {
+ boolean enabled;
+ final Module module = moduleEntry.getKey();
+
+ if (moduleEntry.getValue())
+ ModuleManager.setModuleDisabled(module);
+
+
+ if (enabled = module.isEnabled())
+ enabled = ModuleManager.setModuleEnabled(module);
+
+ moduleEntry.setValue(enabled);
+ });
+ }
+
+ protected static boolean setModuleEnabled(Module> module) {
+ try {
+ module.onEnable();
+ return true;
+ }
+ catch (Throwable t) {
+ Spoof.getPlugin().print("Failed to enable module " + module.getName(), t);
+ return false;
+ }
+ }
+
+ protected static boolean setModuleDisabled(Module> module) {
+ try {
+ module.onDisable();
+ return true;
+ }
+ catch (Throwable t) {
+ Spoof.getPlugin().print("Failed to disable module " + module.getName(), t);
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/gg/spoof/spigot/api/manager/PlayerManager.java b/src/main/java/gg/spoof/spigot/api/manager/PlayerManager.java
new file mode 100644
index 0000000..af4236f
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/api/manager/PlayerManager.java
@@ -0,0 +1,89 @@
+package gg.spoof.spigot.api.manager;
+
+import gg.spoof.spigot.api.SpoofPlayer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+import org.bukkit.entity.Player;
+
+public class PlayerManager {
+
+ private static final ConcurrentHashMap PLAYER_MAP = new ConcurrentHashMap<>();
+
+ public static Map getPlayerMap() {
+ return PLAYER_MAP;
+ }
+
+ public static int getLoadedAmount() {
+ return PLAYER_MAP.size();
+ }
+
+ public static int getLoadedAmount(PlayerManagementType mtype) {
+ int counter = 0;
+ for (SpoofPlayer spoofPlayer : PLAYER_MAP.values()) {
+ if (spoofPlayer.getManagementType() != mtype) continue;
+ ++counter;
+ }
+ return counter;
+ }
+
+ public static boolean exists(Player player) {
+ return PLAYER_MAP.containsKey(player.getUniqueId());
+ }
+
+ public static SpoofPlayer getPlayer(Player player) {
+ return PLAYER_MAP.get(player.getUniqueId());
+ }
+
+ public static SpoofPlayer getPlayer(String player) {
+ return PLAYER_MAP.values().stream().filter(spoofPlayer -> spoofPlayer.getName().equalsIgnoreCase(player)).findFirst().orElse(null);
+ }
+
+ public static SpoofPlayer getPlayer(UUID ID) {
+ return PLAYER_MAP.get(ID);
+ }
+
+ public static SpoofPlayer[] getRandomPlayers(int count) {
+ SpoofPlayer[] players = PLAYER_MAP.values().toArray(new SpoofPlayer[0]);
+ Collections.shuffle(Arrays.asList(players));
+ return players.length < count ? players : Arrays.copyOfRange(players, 0, count);
+ }
+
+ public static SpoofPlayer getRandomPlayer() {
+ if (PLAYER_MAP.isEmpty()) {
+ return null;
+ }
+ SpoofPlayer[] players = PLAYER_MAP.values().toArray(new SpoofPlayer[0]);
+ return players[ThreadLocalRandom.current().nextInt(players.length)];
+ }
+
+ public static SpoofPlayer getRandomPlayer(PlayerManagementType mtype) {
+ if (PLAYER_MAP.isEmpty()) {
+ return null;
+ }
+ SpoofPlayer[] players = PLAYER_MAP.values().stream().filter(player -> player.getManagementType() == mtype).toArray(SpoofPlayer[]::new);
+ return players[ThreadLocalRandom.current().nextInt(players.length)];
+ }
+
+ public static SpoofPlayer addPlayer(UUID id, String name, PlayerManagementType mtype) {
+ if (PLAYER_MAP.containsKey(id)) {
+ throw new IllegalArgumentException("Player with id " + id + " already exists");
+ }
+ SpoofPlayer player = new SpoofPlayer(id, name, mtype);
+ PLAYER_MAP.put(id, player);
+ return player;
+ }
+
+ public static void removePlayer(UUID id) {
+ PLAYER_MAP.remove(id);
+ }
+
+ public enum PlayerManagementType {
+ AUTO,
+ MANUAL
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/CmdCommand.java b/src/main/java/gg/spoof/spigot/commands/CmdCommand.java
new file mode 100644
index 0000000..ebfda7a
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/CmdCommand.java
@@ -0,0 +1,103 @@
+package gg.spoof.spigot.commands;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.commands.extend.AbstractCommand;
+import gg.spoof.spigot.commands.subs.*;
+import gg.spoof.spigot.util.ReflectionUtil;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+import org.bukkit.event.server.ServerCommandEvent;
+
+import java.util.*;
+
+public class CmdCommand implements Listener {
+
+ protected final Spoof plugin;
+ protected final Map, AbstractCommand> subcommands = new HashMap<>();
+
+ protected void addCommand(AbstractCommand abstractCommand) {
+ this.subcommands.put(abstractCommand.getClass(), abstractCommand);
+ }
+
+ public CmdCommand(Spoof plugin, boolean premium) {
+ this.plugin = plugin;
+ this.addCommand(new CmdHelp(plugin));
+ this.addCommand(new CmdReload(plugin));
+ if (premium) {
+ this.addCommand(new CmdAdd(plugin));
+ this.addCommand(new CmdAddNm(plugin));
+ this.addCommand(new CmdModules(plugin));
+ this.addCommand(new CmdRemove(plugin));
+ this.addCommand(new CmdSummon(plugin));
+// this.addCommand(new CmdAuction(plugin));
+// this.addCommand(new CmdDonation(plugin));
+ }
+ ReflectionUtil.validateInitBySpoof();
+ }
+
+ @EventHandler
+ protected void onPlayerCommandUse(PlayerCommandPreprocessEvent event) {
+ final Player player = event.getPlayer();
+
+ if (this.plugin.isWhitelisted(player)) {
+ final String message = event.getMessage();
+ final List args = new ArrayList<>(Arrays.asList(message.split(" ")));
+
+ String enteredCommand = (!args.isEmpty() ? args.remove(0) : message).replace("/", "");
+
+ if (enteredCommand.equalsIgnoreCase("spoof")) {
+ event.setCancelled(true);
+ this.handleCommand(player, args);
+ }
+ }
+ }
+
+ @EventHandler
+ protected void onConsoleCommandUse(ServerCommandEvent event) {
+ final String command = event.getCommand();
+ final List args = Arrays.asList(command.split(" "));
+ final String remove = args.remove(0);
+
+ if (remove.equalsIgnoreCase("spoof")) {
+ event.setCancelled(true);
+ this.handleCommand(event.getSender(), args);
+ }
+ }
+
+ protected void handleCommand(CommandSender sender, List args) {
+ if (args.isEmpty()) {
+ this.subcommands.get(CmdHelp.class).execute(sender, args);
+ return;
+ }
+
+ final String commandLabel = args.get(0);
+
+ this.getCommands().forEach(abstractCommand -> {
+ final String label = abstractCommand.getLabel();
+ final List alias = abstractCommand.getAlias();
+
+ if (alias.contains(commandLabel) || label.equalsIgnoreCase(commandLabel)) {
+ this.execute(sender, args, abstractCommand);
+ }
+ });
+ }
+
+ protected void execute(final CommandSender sender, final List args, final AbstractCommand abstractCommand) {
+ final String permission = abstractCommand.getPermission();
+
+ if (sender.hasPermission(permission)) {
+ abstractCommand.execute(sender, args);
+ } else {
+ sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&cYou don't have the permission to do that."));
+ }
+ }
+
+ public Collection getCommands() {
+ return this.subcommands.values();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/extend/AbstractCommand.java b/src/main/java/gg/spoof/spigot/commands/extend/AbstractCommand.java
new file mode 100644
index 0000000..68ec020
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/extend/AbstractCommand.java
@@ -0,0 +1,24 @@
+package gg.spoof.spigot.commands.extend;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.commands.extend.Executable;
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Getter
+public abstract class AbstractCommand implements Executable {
+
+ private final Spoof plugin;
+ private final String label;
+ private final List alias = new ArrayList<>();
+
+ protected AbstractCommand(Spoof plugin, String label, String ... alias) {
+ this.plugin = plugin;
+ this.label = label;
+ this.alias.addAll(Arrays.asList(alias));
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/extend/Executable.java b/src/main/java/gg/spoof/spigot/commands/extend/Executable.java
new file mode 100644
index 0000000..2febfc5
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/extend/Executable.java
@@ -0,0 +1,18 @@
+package gg.spoof.spigot.commands.extend;
+
+import java.util.List;
+import org.bukkit.command.CommandSender;
+
+public interface Executable {
+
+ boolean execute(CommandSender sender, List args);
+
+ String getDescription();
+
+ String getPermission();
+
+ boolean isPlayerRequired();
+
+ List getAutoCompletes();
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/subs/CmdAdd.java b/src/main/java/gg/spoof/spigot/commands/subs/CmdAdd.java
new file mode 100644
index 0000000..395d883
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/subs/CmdAdd.java
@@ -0,0 +1,60 @@
+package gg.spoof.spigot.commands.subs;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.commands.extend.AbstractCommand;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+
+import gg.spoof.spigot.message.Placeholder;
+import gg.spoof.spigot.message.TL;
+import gg.spoof.spigot.nms.NMSWrapper;
+import org.bukkit.command.CommandSender;
+
+public class CmdAdd extends AbstractCommand {
+
+ public CmdAdd(Spoof plugin) {
+ super(plugin, "add");
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, List args) {
+ if (args.size() <= 1) {
+ TL.INVALID_COMMAND_USAGE.send(sender, new Placeholder("", "/spoof add "));
+ return true;
+ }
+
+ final String name = args.get(1);
+ final Spoof plugin = Spoof.getPlugin();
+ plugin.getPlatform().addSpoofPlayer(name, PlayerManager.PlayerManagementType.MANUAL);
+
+ TL.PLAYER_ADDED.send(sender, new Placeholder("", name));
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Add more players";
+ }
+
+ @Override
+ public String getPermission() {
+ return "spoof.add";
+ }
+
+ @Override
+ public boolean isPlayerRequired() {
+ return false;
+ }
+
+ @Override
+ public List getAutoCompletes() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/subs/CmdAddNm.java b/src/main/java/gg/spoof/spigot/commands/subs/CmdAddNm.java
new file mode 100644
index 0000000..eb8aef3
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/subs/CmdAddNm.java
@@ -0,0 +1,68 @@
+package gg.spoof.spigot.commands.subs;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.commands.extend.AbstractCommand;
+import gg.spoof.spigot.message.Placeholder;
+import gg.spoof.spigot.message.TL;
+import gg.spoof.spigot.module.fluctuation.AccountsRegistry;
+import gg.spoof.spigot.module.fluctuation.NameMCAccountsRegistry;
+import org.bukkit.command.CommandSender;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+public class CmdAddNm extends AbstractCommand {
+
+ public CmdAddNm(Spoof plugin) {
+ super(plugin, "addnm");
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, List args) {
+ if (args.size() <= 1) {
+ TL.INVALID_COMMAND_USAGE.send(sender, new Placeholder("", "/spoof add "));
+ return true;
+ }
+
+ final String amount = args.get(1);
+ final Spoof plugin = Spoof.getPlugin();
+
+ try {
+ final int amtI = Integer.parseInt(amount);
+ for (int i = 0; i < amtI; i++) {
+ final AccountsRegistry accounts = plugin.getAccounts();
+ final UUID randomAccount = accounts.getRandomAccount();
+ accounts.takeAccount(randomAccount);
+ plugin.getPlatform().addSpoofPlayer(randomAccount.toString(), PlayerManager.PlayerManagementType.MANUAL);
+ }
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+
+ sender.sendMessage("Done");
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Add more players";
+ }
+
+ @Override
+ public String getPermission() {
+ return "spoof.add";
+ }
+
+ @Override
+ public boolean isPlayerRequired() {
+ return false;
+ }
+
+ @Override
+ public List getAutoCompletes() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/subs/CmdAuction.java b/src/main/java/gg/spoof/spigot/commands/subs/CmdAuction.java
new file mode 100644
index 0000000..06df466
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/subs/CmdAuction.java
@@ -0,0 +1,74 @@
+package gg.spoof.spigot.commands.subs;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.SpoofPlayer;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.commands.extend.AbstractCommand;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+
+import gg.spoof.spigot.message.Placeholder;
+import gg.spoof.spigot.message.TL;
+import org.bukkit.Material;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+public class CmdAuction extends AbstractCommand {
+
+ public CmdAuction(Spoof plugin) {
+ super(plugin, "auction");
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, List args) {
+ final String playerName = args.get(0);
+ final SpoofPlayer spoofPlayer = PlayerManager.getPlayer(playerName);
+
+ final String s = args.get(1);
+ final int price = Integer.parseInt(s);
+
+ if (spoofPlayer == null) {
+ TL.NULL_PLAYER.send(sender, new Placeholder("", playerName));
+ return true;
+ }
+
+ final Player player = spoofPlayer.getPlayer();
+ final ItemStack itemInHand = player.getItemInHand();
+ final Material type = itemInHand.getType();
+
+ if (type == Material.AIR) {
+ TL.MISSING_AUCTION_ITEM.send(sender);
+ return true;
+ }
+
+ // start auction
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Have the spoof player auction something";
+ }
+
+ @Override
+ public String getPermission() {
+ return "spoof.auction";
+ }
+
+ @Override
+ public boolean isPlayerRequired() {
+ return false;
+ }
+
+ @Override
+ public List getAutoCompletes() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/subs/CmdDonation.java b/src/main/java/gg/spoof/spigot/commands/subs/CmdDonation.java
new file mode 100644
index 0000000..790b055
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/subs/CmdDonation.java
@@ -0,0 +1,72 @@
+//package gg.spoof.spigot.commands.subs;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.api.SpoofPlayer;
+//import gg.spoof.spigot.commands.extend.AbstractCommand;
+//
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.io.InputStream;
+//import java.io.OutputStream;
+//import java.util.HashMap;
+//import java.util.List;
+//import java.util.Map;
+//
+//import net.buycraft.plugin.bukkit.events.BuycraftPurchaseEvent;
+//import org.bukkit.command.CommandSender;
+//import org.bukkit.configuration.file.FileConfiguration;
+//import org.bukkit.event.EventHandler;
+//import org.bukkit.event.Listener;
+//
+//public class CmdDonation extends AbstractCommand implements Listener {
+//
+// private final boolean enabled;
+// private final int minDelay;
+// private final int maxDelay;
+// private final int minResponders;
+// private final int maxResponders;
+// private final List responses;
+// private final Map> packages;
+//
+// public CmdDonation(Spoof plugin) {
+// super(plugin, "donation", new String[0]);
+// FileConfiguration config = plugin.getConfig();
+// this.enabled = config.getBoolean("modules.donations.enabled");
+// this.responses = config.getStringList("modules.donations.responses");
+// this.minDelay = config.getInt("modules.donations.settings.delay.min");
+// this.maxDelay = config.getInt("modules.donations.settings.delay.max");
+// this.minResponders = config.getInt("modules.donations.settings.responders.min");
+// this.maxResponders = config.getInt("modules.donations.settings.responders.min");
+// this.packages = new HashMap<>();
+// for (String dKey : config.getConfigurationSection("modules.donations.packages").getKeys(false)) {
+// this.packages.put(dKey, config.getStringList("modules.donations.packages." + dKey + ".message"));
+// }
+// this.getPlugin().getServer().getPluginManager().registerEvents(this, this.getPlugin());
+// }
+//
+// public String getRandomResponse(); was ntv
+//
+// @EventHandler
+// public void onBuycraftPurchaseEvent(BuycraftPurchaseEvent var1); was ntv
+//
+// @Override
+// public boolean execute(CommandSender sender, List args); was ntv
+//
+// @Override
+// public String getDescription(); was ntv
+//
+// @Override
+// public String getPermission(); was ntv
+//
+// @Override
+// public boolean isPlayerRequired(); was ntv
+//
+// @Override
+// public List getAutoCompletes(); was ntv
+//
+// private static void lambda$execute$1(SpoofPlayer var0, String var1); was ntv
+//
+// private static void lambda$onBuycraftPurchaseEvent$0(SpoofPlayer var0, String var1); was ntv
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/commands/subs/CmdHelp.java b/src/main/java/gg/spoof/spigot/commands/subs/CmdHelp.java
new file mode 100644
index 0000000..b453aeb
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/subs/CmdHelp.java
@@ -0,0 +1,53 @@
+package gg.spoof.spigot.commands.subs;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.commands.extend.AbstractCommand;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+
+import gg.spoof.spigot.message.TL;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public class CmdHelp extends AbstractCommand {
+
+ public CmdHelp(Spoof plugin) {
+ super(plugin, "help", "h");
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, List args) {
+ final Spoof plugin = Spoof.getPlugin();
+ final FileConfiguration config = plugin.getConfig();
+
+ TL.message(sender, config.getStringList("messages.help"));
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "A help command";
+ }
+
+ @Override
+ public String getPermission() {
+ return "spoof.help";
+ }
+
+ @Override
+ public boolean isPlayerRequired() {
+ return false;
+ }
+
+ @Override
+ public List getAutoCompletes() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/subs/CmdModules.java b/src/main/java/gg/spoof/spigot/commands/subs/CmdModules.java
new file mode 100644
index 0000000..62336ed
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/subs/CmdModules.java
@@ -0,0 +1,62 @@
+package gg.spoof.spigot.commands.subs;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.manager.ModuleManager;
+import gg.spoof.spigot.commands.extend.AbstractCommand;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+
+import gg.spoof.spigot.message.C;
+import gg.spoof.spigot.message.Placeholder;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public class CmdModules extends AbstractCommand {
+
+ public CmdModules(Spoof plugin) {
+ super(plugin, "modules");
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, List args) {
+ final Spoof plugin = this.getPlugin();
+ final FileConfiguration config = plugin.getConfig();
+ final String configKey = "messages.modules-information";
+
+ config.getStringList(configKey).forEach(s -> {
+ C.color(s,
+ new Placeholder("", ModuleManager.getModules().size()),
+ new Placeholder("", "")
+ );
+ });
+
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "View modules";
+ }
+
+ @Override
+ public String getPermission() {
+ return "spoof.modules";
+ }
+
+ @Override
+ public boolean isPlayerRequired() {
+ return false;
+ }
+
+ @Override
+ public List getAutoCompletes() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/subs/CmdReload.java b/src/main/java/gg/spoof/spigot/commands/subs/CmdReload.java
new file mode 100644
index 0000000..509a7d5
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/subs/CmdReload.java
@@ -0,0 +1,55 @@
+package gg.spoof.spigot.commands.subs;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.manager.ModuleManager;
+import gg.spoof.spigot.commands.extend.AbstractCommand;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+
+import gg.spoof.spigot.message.Placeholder;
+import gg.spoof.spigot.message.TL;
+import org.bukkit.command.CommandSender;
+
+public class CmdReload extends AbstractCommand {
+
+ public CmdReload(Spoof plugin) {
+ super(plugin, "reload", "r");
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, List args) {
+ final Spoof plugin = this.getPlugin();
+ plugin.loadConfiguration();
+ plugin.setupProxyController();
+ ModuleManager.reloadModules();
+
+ TL.RELOADED.send(sender, new Placeholder("", plugin.getDescription().getVersion()));
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Reload the config and messages!";
+ }
+
+ @Override
+ public String getPermission() {
+ return "spoof.reload";
+ }
+
+ @Override
+ public boolean isPlayerRequired() {
+ return false;
+ }
+
+ @Override
+ public List getAutoCompletes() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/subs/CmdRemove.java b/src/main/java/gg/spoof/spigot/commands/subs/CmdRemove.java
new file mode 100644
index 0000000..f4b39e2
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/subs/CmdRemove.java
@@ -0,0 +1,77 @@
+package gg.spoof.spigot.commands.subs;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.SpoofPlayer;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.commands.extend.AbstractCommand;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import gg.spoof.spigot.message.Placeholder;
+import gg.spoof.spigot.message.TL;
+import gg.spoof.spigot.nms.NMSWrapper;
+import gg.spoof.spigot.util.StringUtil;
+import org.bukkit.command.CommandSender;
+
+public class CmdRemove extends AbstractCommand {
+
+ public CmdRemove(Spoof plugin) {
+ super(plugin, "remove");
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, List args) {
+ if (args.size() <= 1) {
+ TL.INVALID_COMMAND_USAGE.send(sender, new Placeholder("", "/spoof remove "));
+ return true;
+ }
+
+ final String playerName = args.get(1);
+ final SpoofPlayer player;
+
+ if (StringUtil.isUUID(playerName)) {
+ player = PlayerManager.getPlayer(UUID.fromString(playerName));
+ } else {
+ player = PlayerManager.getPlayer(playerName);
+ }
+
+ if (player == null) {
+ TL.INVALID_USER.send(sender, new Placeholder("", playerName));
+ return true;
+ }
+
+ final NMSWrapper platform = this.getPlugin().getPlatform();
+ platform.removeSpoofPlayer(player);
+
+ TL.PLAYER_REMOVED.send(sender, new Placeholder("", playerName));
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Remove players";
+ }
+
+ @Override
+ public String getPermission() {
+ return "spoof.remove";
+ }
+
+ @Override
+ public boolean isPlayerRequired() {
+ return false;
+ }
+
+ @Override
+ public List getAutoCompletes() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/commands/subs/CmdSummon.java b/src/main/java/gg/spoof/spigot/commands/subs/CmdSummon.java
new file mode 100644
index 0000000..7d52c14
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/commands/subs/CmdSummon.java
@@ -0,0 +1,67 @@
+package gg.spoof.spigot.commands.subs;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.commands.extend.AbstractCommand;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+
+import gg.spoof.spigot.message.Placeholder;
+import gg.spoof.spigot.message.TL;
+import net.minecraft.server.v1_8_R3.PlayerConnection;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+
+public class CmdSummon extends AbstractCommand {
+
+ public CmdSummon(Spoof plugin) {
+ super(plugin, "summon");
+ }
+
+ @Override
+ public boolean execute(CommandSender commandSender, List args) {
+ if (args.size() <= 1) {
+ TL.INVALID_COMMAND_USAGE.send(commandSender, new Placeholder("", "/spoof summon "));
+ return true;
+ }
+
+ final String name = args.get(1);
+ final Player sender = (Player) commandSender;
+ final Player player = this.getPlugin().getServer().getPlayer(name);
+
+ if (player != null && PlayerManager.exists(player)) {
+ player.teleport(sender.getLocation());
+ TL.PLAYER_SUMMONED.send(commandSender, new Placeholder("", player.getName()));
+ } else TL.NULL_PLAYER.send(commandSender, new Placeholder("", name));
+
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Summon a spoofed player";
+ }
+
+ @Override
+ public String getPermission() {
+ return "spoof.summon";
+ }
+
+ @Override
+ public boolean isPlayerRequired() {
+ return true;
+ }
+
+ @Override
+ public List getAutoCompletes() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/controller/NoopController.java b/src/main/java/gg/spoof/spigot/controller/NoopController.java
new file mode 100644
index 0000000..b0461cc
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/controller/NoopController.java
@@ -0,0 +1,25 @@
+package gg.spoof.spigot.controller;
+
+import gg.spoof.spigot.api.ProxyController;
+import org.bukkit.Bukkit;
+
+public class NoopController implements ProxyController {
+
+ public static NoopController INSTANCE;
+
+ public NoopController() {
+ INSTANCE = this;
+ }
+
+ @Override
+ public int getTotalPlayerCount() {
+ return Bukkit.getOnlinePlayers().size();
+ }
+
+ @Override
+ public void sendServerPlayerCount() {}
+
+ @Override
+ public void close() {}
+
+}
diff --git a/src/main/java/gg/spoof/spigot/controller/RedisController.java b/src/main/java/gg/spoof/spigot/controller/RedisController.java
new file mode 100644
index 0000000..b4abf5b
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/controller/RedisController.java
@@ -0,0 +1,76 @@
+package gg.spoof.spigot.controller;
+
+import gg.spoof.common.proxy.ProxyChannelFormat;
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.ProxyController;
+import gg.spoof.spigot.controller.redis.RedisControllerPubSub;
+import lombok.Data;
+import org.bukkit.Bukkit;
+import redis.clients.jedis.Jedis;
+
+@Data
+public class RedisController implements ProxyController {
+
+ private final Spoof plugin;
+ private Jedis jedisRX;
+ private Jedis jedisTX;
+ private int playerTotalCount = 0;
+
+ private final RedisControllerPubSub controllerPubSub;
+
+ public RedisController(Spoof plugin, String host, int port, String password) {
+ this.plugin = plugin;
+ this.jedisRX = this.create(host, port, password);
+ this.jedisTX = this.create(host, port, password);
+
+ this.controllerPubSub = new RedisControllerPubSub(this.plugin, this);
+
+ plugin.getServer().getScheduler().runTaskAsynchronously(plugin, this::lambda$new$0);
+ }
+
+ private Jedis create(String host, int port, String password) {
+ final Jedis jedis = new Jedis(host, port);
+ jedis.connect();
+
+ if (!password.isEmpty())
+ jedis.auth(password);
+
+ if (!jedis.isConnected())
+ throw new IllegalStateException("Failed to connect to redis");
+
+ return jedis;
+ }
+
+ @Override
+ public int getTotalPlayerCount() {
+ return this.playerTotalCount;
+ }
+
+ @Override
+ public void sendServerPlayerCount() {
+ try {
+ final String channel = "spoof:srv_pl";
+ final String serverId = this.plugin.getSpoofConfig().getServerId();
+ final int size = Bukkit.getOnlinePlayers().size();
+
+ final ProxyChannelFormat.ServerPlayers serverPlayers = new ProxyChannelFormat.ServerPlayers(serverId, size);
+ final String encoded = ProxyChannelFormat.encodeB64ServerPlayers(serverPlayers);
+
+ this.jedisTX.publish(channel, encoded);
+ this.plugin.debug("Sent server player count " + size);
+ } catch (Exception exception) {
+ this.plugin.debug("Error sending server count");
+ }
+ }
+
+ @Override
+ public void close() {
+ this.jedisRX.close();
+ this.jedisTX.close();
+ }
+
+ private void lambda$new$0() {
+ this.jedisRX.subscribe(this.controllerPubSub, "spoof:pr_pl_cnt");
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/controller/redis/RedisControllerPubSub.java b/src/main/java/gg/spoof/spigot/controller/redis/RedisControllerPubSub.java
new file mode 100644
index 0000000..572cc52
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/controller/redis/RedisControllerPubSub.java
@@ -0,0 +1,28 @@
+package gg.spoof.spigot.controller.redis;
+
+import gg.spoof.common.proxy.ProxyChannelFormat;
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.controller.RedisController;
+import lombok.RequiredArgsConstructor;
+import redis.clients.jedis.JedisPubSub;
+
+@RequiredArgsConstructor
+public class RedisControllerPubSub extends JedisPubSub {
+
+ public final Spoof plugin;
+ private final RedisController redisController;
+
+ @Override
+ public void onMessage(String channel, String message) {
+ try {
+ final int i = ProxyChannelFormat.decodeB64ProxyPlayerCount(message);
+ this.redisController.setPlayerTotalCount(i);
+
+ String stringBuilder = "Updated total player count to " + this.redisController.getTotalPlayerCount();
+ this.plugin.debug(stringBuilder);
+ } catch (Exception exception) {
+ this.plugin.debug("Error receiving proxy player count");
+ }
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/listener/DeathHandler.java b/src/main/java/gg/spoof/spigot/listener/DeathHandler.java
new file mode 100644
index 0000000..63f5543
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/listener/DeathHandler.java
@@ -0,0 +1,41 @@
+package gg.spoof.spigot.listener;
+
+import gg.spoof.spigot.Spoof;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.util.MathUtility;
+import lombok.RequiredArgsConstructor;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.scheduler.BukkitScheduler;
+
+@RequiredArgsConstructor
+public class DeathHandler extends BukkitRunnable implements Listener {
+
+ protected final Spoof plugin;
+ private Player player;
+
+ @EventHandler
+ protected void onDeath(PlayerDeathEvent event) {
+ final Player entity = event.getEntity();
+ if (!PlayerManager.exists(entity))
+ return;
+
+ this.player = entity;
+ this.runTaskLater(this.plugin, MathUtility.getRandomNumber(10, 50));
+ }
+
+ @Override
+ public void run() {
+ this.player.spigot().respawn();
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/listener/MiscHandler.java b/src/main/java/gg/spoof/spigot/listener/MiscHandler.java
new file mode 100644
index 0000000..2fb1fa5
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/listener/MiscHandler.java
@@ -0,0 +1,61 @@
+package gg.spoof.spigot.listener;
+
+import gg.spoof.spigot.Spoof;
+
+import gg.spoof.spigot.api.SpoofConfig;
+import gg.spoof.spigot.api.SpoofPlayer;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import lombok.RequiredArgsConstructor;
+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.event.player.PlayerQuitEvent;
+
+import java.util.UUID;
+
+@RequiredArgsConstructor
+public class MiscHandler implements Listener {
+
+ private final Spoof plugin;
+
+ @EventHandler
+ protected void onJoin(PlayerJoinEvent event) {
+ final Player player = event.getPlayer();
+
+ if (PlayerManager.exists(player))
+ this.plugin.getServer().getScheduler().runTaskLater(this.plugin, () -> this.lambdaJoin(player), this.plugin.getSpoofConfig().getJoinCommandsDelay());
+
+ this.plugin.getProxyController().sendServerPlayerCount();
+ }
+
+ @EventHandler
+ protected void onQuit(PlayerQuitEvent event) {
+ this.plugin.getProxyController().sendServerPlayerCount();
+ final UUID uniqueId = event.getPlayer().getUniqueId();
+
+ if (PlayerManager.exists(event.getPlayer())) {
+ final SpoofPlayer player = PlayerManager.getPlayer(uniqueId);
+ PlayerManager.removePlayer(player.getId());
+
+ final String unexpectedQuit = "Unexpected quit of spoof player " + player.getName();
+ final String debugTrace = "Debug trace";
+
+ this.plugin.debug(unexpectedQuit, new Throwable(debugTrace));
+ }
+ }
+
+ private void lambdaJoin(Player player) {
+ final ConsoleCommandSender consoleSender = this.plugin.getServer().getConsoleSender();
+ final SpoofConfig spoofConfig = this.plugin.getSpoofConfig();
+ final String name = player.getName();
+
+ spoofConfig.getJoinCommands().forEach(s -> this.plugin.getServer().dispatchCommand(consoleSender, s.replace("%player%", name)));
+ }
+
+ static Spoof access$000(MiscHandler miscHandler) {
+ return miscHandler.plugin;
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/listener/SpawnHandler.java b/src/main/java/gg/spoof/spigot/listener/SpawnHandler.java
new file mode 100644
index 0000000..e6b888f
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/listener/SpawnHandler.java
@@ -0,0 +1,37 @@
+package gg.spoof.spigot.listener;
+
+import gg.spoof.spigot.Spoof;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import gg.spoof.spigot.api.manager.PlayerManager;
+import lombok.RequiredArgsConstructor;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.spigotmc.event.player.PlayerSpawnLocationEvent;
+
+@RequiredArgsConstructor
+public class SpawnHandler implements Listener {
+
+ protected final Spoof plugin;
+
+ @EventHandler
+ protected void onInitialSpawn(PlayerSpawnLocationEvent event) {
+ final Player player = event.getPlayer();
+
+ if (!PlayerManager.exists(player))
+ return;
+
+ if (player.hasPlayedBefore())
+ return;
+
+ final Location spawnLocation = this.plugin.getSpoofConfig().getSpawnLocation();
+ event.setSpawnLocation(spawnLocation);
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/message/C.java b/src/main/java/gg/spoof/spigot/message/C.java
new file mode 100644
index 0000000..95dfd6f
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/message/C.java
@@ -0,0 +1,66 @@
+package gg.spoof.spigot.message;
+
+import gg.spoof.spigot.message.Placeholder;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.bukkit.ChatColor;
+
+public final class C {
+ public static String color(String s) {
+ return ChatColor.translateAlternateColorCodes('&', s);
+ }
+
+ public static String color(String s, Placeholder ... placeHolders) {
+ String message = s;
+ for (Placeholder placeHolder : placeHolders) {
+ message = C.color(message.replace(placeHolder.getPlaceHolder(), placeHolder.getReplace()));
+ }
+ return message;
+ }
+
+ public static List color(List s) {
+ ArrayList toReturn = new ArrayList();
+ for (String str : s) {
+ toReturn.add(C.color(str));
+ }
+ return toReturn;
+ }
+
+ public static List color(List messages, Placeholder ... placeholders) {
+ ArrayList colored = new ArrayList();
+ Iterator iterator = messages.iterator();
+ while (iterator.hasNext()) {
+ String line;
+ String coloredLine = line = iterator.next();
+ for (Placeholder placeholder : placeholders) {
+ coloredLine = C.color(coloredLine.replace(placeholder.getPlaceHolder(), placeholder.getReplace()));
+ }
+ colored.add(coloredLine);
+ }
+ return colored;
+ }
+
+ public static String strip(String s) {
+ return ChatColor.stripColor(s);
+ }
+
+ public static String strip(String string, Placeholder ... placeHolders) {
+ String message = string;
+ for (Placeholder placeHolder : placeHolders) {
+ message = message.replace(placeHolder.getPlaceHolder(), placeHolder.getReplace());
+ }
+ return ChatColor.stripColor(message);
+ }
+
+ public static String capitalizeFirstLetter(String original) {
+ if (original == null || original.length() == 0) {
+ return original;
+ }
+ return original.substring(0, 1).toUpperCase() + original.substring(1);
+ }
+
+ private C() {
+ throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
+ }
+}
diff --git a/src/main/java/gg/spoof/spigot/message/Placeholder.java b/src/main/java/gg/spoof/spigot/message/Placeholder.java
new file mode 100644
index 0000000..e558410
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/message/Placeholder.java
@@ -0,0 +1,46 @@
+package gg.spoof.spigot.message;
+
+public class Placeholder {
+
+ private final String placeHolder;
+ private final String replace;
+
+ public Placeholder(String placeHolder, String replace) {
+ this.placeHolder = placeHolder;
+ this.replace = replace;
+ }
+
+ public Placeholder(String placeHolder, boolean bool) {
+ this.replace = String.valueOf(bool);
+ this.placeHolder = placeHolder;
+ }
+
+ public Placeholder(String placeHolder, int i) {
+ this.replace = String.valueOf(i);
+ this.placeHolder = placeHolder;
+ }
+
+ public Placeholder(String placeHolder, double i) {
+ this.replace = String.valueOf(i);
+ this.placeHolder = placeHolder;
+ }
+
+ public Placeholder(String placeHolder, long i) {
+ this.replace = String.valueOf(i);
+ this.placeHolder = placeHolder;
+ }
+
+ public Placeholder(String placeHolder, float i) {
+ this.replace = String.valueOf(i);
+ this.placeHolder = placeHolder;
+ }
+
+ public String getPlaceHolder() {
+ return this.placeHolder;
+ }
+
+ public String getReplace() {
+ return this.replace;
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/message/TL.java b/src/main/java/gg/spoof/spigot/message/TL.java
new file mode 100644
index 0000000..fb305b2
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/message/TL.java
@@ -0,0 +1,88 @@
+package gg.spoof.spigot.message;
+
+import gg.spoof.spigot.message.C;
+import gg.spoof.spigot.message.Placeholder;
+import java.util.List;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+public enum TL {
+ NO_PERMISSION("no_permission", "&cYou don't have the permission to do that."),
+ INVALID_ARGUMENT_NUMBER("invalid-number", "&c'' has to be a number"),
+ INVALID_COMMAND_USAGE("invalid-command-usage", "&eIncorrect Usage: &a"),
+ PLAYER_ONLY("player-only", "&cThis command is for players only!"),
+ RELOADED("reload", "&8[&b&l\u26a1&8] &bSpoof v &8- &fThe Ultimate Spoofing Solution&7."),
+ PLAYER_NOT_ONLINE("player-not-online", "&cPlayer seems to be not online!"),
+ INVALID_USER("invalid-user", "&b[&lSpoof&b] &7The username (&b&7) specified is invalid!"),
+ ALREADY_ONLINE("already-online", "&b[&lSpoof&b] &7That player (&b&7) is already online!"),
+ PLAYER_ADDED("player-added", "&b[&lSpoof&b] &7You added &b &7to the server!"),
+ PLAYER_REMOVED("player-removed", "&b[&lSpoof&b] &7You removed &b &7from the server!"),
+ NULL_PLAYER("player-not-found", "&b[&lSpoof&b] &7This spoof player (&b&7) does not exist!"),
+ MISSING_AUCTION_ITEM("missing-auction-item", "&b[&lSpoof&b] &7You must have something in your hand to auction!"),
+ ITEM_AUCTIONED("item-auctioned", "&b[&lSpoof&b] &7You have auction an item under the spoofed player !"),
+ INVALID_DONATION_PACKAGE("invalid-donation-package", "&b[&lSpoof&b] &7This package ID you have provided does not exist!"),
+ PLAYER_SUMMONED("player-teleported", "&aYou have summoned to you!");
+
+ private String path;
+ private String message;
+
+ private TL(String path, String message) {
+ this.path = path;
+ this.message = message;
+ }
+
+ public void send(CommandSender sender) {
+ if (sender instanceof Player) {
+ sender.sendMessage(C.color(this.getMessage()));
+ } else {
+ sender.sendMessage(C.strip(this.getMessage()));
+ }
+ }
+
+ public void send(CommandSender sender, Placeholder ... placeHolders) {
+ if (sender instanceof Player) {
+ sender.sendMessage(C.color(this.getMessage(), placeHolders));
+ } else {
+ sender.sendMessage(C.strip(this.getMessage(), placeHolders));
+ }
+ }
+
+ public void broadcast(Placeholder ... placeholders) {
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ player.sendMessage(C.color(this.getMessage(), placeholders));
+ }
+ }
+
+ public static void message(CommandSender sender, String message) {
+ sender.sendMessage(C.color(message));
+ }
+
+ public static void message(CommandSender sender, String message, Placeholder ... placeHolders) {
+ sender.sendMessage(C.color(message, placeHolders));
+ }
+
+ public static void message(CommandSender sender, List message) {
+ message.forEach(m -> sender.sendMessage(C.color(m)));
+ }
+
+ public static void message(CommandSender sender, List message, Placeholder ... placeHolders) {
+ message.forEach(m -> sender.sendMessage(C.color(m, placeHolders)));
+ }
+
+ public String getPath() {
+ return this.path;
+ }
+
+ public String getMessage() {
+ return this.message;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/src/main/java/gg/spoof/spigot/module/ActionModule$1.java b/src/main/java/gg/spoof/spigot/module/ActionModule$1.java
new file mode 100644
index 0000000..5830ed2
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/ActionModule$1.java
@@ -0,0 +1,24 @@
+//package gg.spoof.spigot.module;
+//
+//import java.util.List;
+//import org.bukkit.entity.Player;
+//import org.bukkit.scheduler.BukkitRunnable;
+//
+//class ActionModule$1 extends BukkitRunnable {
+//
+// ActionModule$1() {
+// }
+//
+// public native void run();
+//
+// private native void performActions();
+//
+// private native void lambda$performActions$3(Player var1, List var2);
+//
+// private static native boolean lambda$performActions$2(List var0);
+//
+// private native List lambda$performActions$1(String var1);
+//
+// private native boolean lambda$performActions$0(int var1, String var2);
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/ActionModule.java b/src/main/java/gg/spoof/spigot/module/ActionModule.java
new file mode 100644
index 0000000..03cb772
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/ActionModule.java
@@ -0,0 +1,54 @@
+//package gg.spoof.spigot.module;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.actions.ActionRegistry;
+//import gg.spoof.spigot.module.InternalModule;
+//import gg.spoof.spigot.util.ReflectionUtil;
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.io.InputStream;
+//import java.io.OutputStream;
+//import java.util.List;
+//import org.bukkit.scheduler.BukkitTask;
+//
+//public class ActionModule extends InternalModule {
+//
+// private int minDelay;
+// private int maxDelay;
+// private List actions;
+// private ActionRegistry actionRegistry;
+// private BukkitTask task;
+//
+// public ActionModule(Spoof plugin) {
+// super(plugin);
+// ReflectionUtil.validateInitBySpoof();
+// }
+//
+// @Override
+// public native String getName();
+//
+// @Override
+// public String getAuthor() {
+// return plugin.getDescription().getAuthors().get(0);
+// }
+//
+// @Override
+// public String getVersion() {
+// return plugin.getDescription().getVersion();
+// }
+//
+// @Override
+// public native void onEnable();
+//
+// @Override
+// public native void onDisable();
+//
+// private native void sched();
+//
+//// static native void access$000(ActionModule var0);
+////
+//// static native List access$100(ActionModule var0);
+////
+//// static native ActionRegistry access$200(ActionModule var0);
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/InternalModule.java b/src/main/java/gg/spoof/spigot/module/InternalModule.java
new file mode 100644
index 0000000..9f81173
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/InternalModule.java
@@ -0,0 +1,12 @@
+package gg.spoof.spigot.module;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.Module;
+
+public abstract class InternalModule extends Module {
+
+ protected InternalModule(Spoof plugin) {
+ super(plugin);
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/module/PickupModule$1.java b/src/main/java/gg/spoof/spigot/module/PickupModule$1.java
new file mode 100644
index 0000000..467e523
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/PickupModule$1.java
@@ -0,0 +1,13 @@
+//package gg.spoof.spigot.module;
+//
+//import org.bukkit.scheduler.BukkitRunnable;
+//
+//class PickupModule$1 extends BukkitRunnable {
+// PickupModule$1() {
+// }
+//
+// public native void run();
+//
+// private native void pickup();
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/PickupModule.java b/src/main/java/gg/spoof/spigot/module/PickupModule.java
new file mode 100644
index 0000000..58233a6
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/PickupModule.java
@@ -0,0 +1,43 @@
+//package gg.spoof.spigot.module;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.module.InternalModule;
+//import org.bukkit.plugin.Plugin;
+//import org.bukkit.scheduler.BukkitTask;
+//
+//public class PickupModule extends InternalModule {
+//
+// private int minDelay;
+// private int maxDelay;
+// private BukkitTask task;
+//
+// public PickupModule(Spoof plugin) {
+// super(plugin);
+// }
+//
+// @Override
+// public native String getName();
+//
+// @Override
+// public String getAuthor() {
+// return plugin.getDescription().getAuthors().get(0);
+// }
+//
+// @Override
+// public String getVersion() {
+// return plugin.getDescription().getVersion();
+// }
+//
+// @Override
+// public native void onEnable();
+//
+// @Override
+// public native void onDisable();
+//
+// private native void sched();
+//
+//// static native void access$000(PickupModule var0);
+////
+//// static native Plugin access$100(PickupModule var0);
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/PingModule$1.java b/src/main/java/gg/spoof/spigot/module/PingModule$1.java
new file mode 100644
index 0000000..fbf15a0
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/PingModule$1.java
@@ -0,0 +1,14 @@
+//package gg.spoof.spigot.module;
+//
+//import org.bukkit.scheduler.BukkitRunnable;
+//
+//class PingModule$1 extends BukkitRunnable {
+//
+// PingModule$1() {
+// }
+//
+// public native void run();
+//
+// private native void updatePing();
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/PingModule.java b/src/main/java/gg/spoof/spigot/module/PingModule.java
new file mode 100644
index 0000000..c2b4331
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/PingModule.java
@@ -0,0 +1,47 @@
+//package gg.spoof.spigot.module;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.module.InternalModule;
+//import org.bukkit.plugin.Plugin;
+//import org.bukkit.scheduler.BukkitTask;
+//
+//public class PingModule extends InternalModule {
+//
+// private int minDelay;
+// private int maxDelay;
+// private int minPing;
+// private int maxPing;
+// private BukkitTask task;
+//
+// public PingModule(Spoof plugin) {
+// super(plugin);
+// }
+//
+// @Override
+// public native String getName();
+//
+// @Override
+// public String getAuthor() {
+// return plugin.getDescription().getAuthors().get(0);
+// }
+//
+// @Override
+// public String getVersion() {
+// return plugin.getDescription().getVersion();
+// }
+//
+// @Override
+// public native void onEnable();
+//
+// @Override
+// public native void onDisable();
+//
+// private native void sched();
+//
+//// static native void access$000(PingModule var0);
+////
+//// static native int access$100(PingModule var0);
+////
+//// static native int access$200(PingModule var0);
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/VoteModule$1.java b/src/main/java/gg/spoof/spigot/module/VoteModule$1.java
new file mode 100644
index 0000000..0bf5529
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/VoteModule$1.java
@@ -0,0 +1,13 @@
+//package gg.spoof.spigot.module;
+//
+//import org.bukkit.scheduler.BukkitRunnable;
+//
+//class VoteModule$1 extends BukkitRunnable {
+// VoteModule$1() {
+// }
+//
+// public native void run();
+//
+// private native void vote();
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/VoteModule.java b/src/main/java/gg/spoof/spigot/module/VoteModule.java
new file mode 100644
index 0000000..583144e
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/VoteModule.java
@@ -0,0 +1,80 @@
+//package gg.spoof.spigot.module;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.module.InternalModule;
+//import gg.spoof.spigot.util.ReflectionUtil;
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.io.InputStream;
+//import java.io.OutputStream;
+//import java.util.List;
+//import java.util.Map;
+//import java.util.UUID;
+//import org.bukkit.plugin.Plugin;
+//import org.bukkit.scheduler.BukkitTask;
+//
+//public class VoteModule extends InternalModule {
+//
+// private long expiration;
+// private int minDelay;
+// private int maxDelay;
+// private List services;
+// private Map voted;
+// private BukkitTask task;
+//
+// public VoteModule(Spoof plugin) {
+// super(plugin);
+// ReflectionUtil.validateInitBySpoof();
+// }
+//
+// @Override
+// public native String getName();
+//
+// @Override
+// public String getAuthor() {
+// return plugin.getDescription().getAuthors().get(0);
+// }
+//
+// @Override
+// public String getVersion() {
+// return plugin.getDescription().getVersion();
+// }
+//
+// @Override
+// public native void onEnable();
+//
+// @Override
+// public native void onDisable();
+//
+// private native void sched();
+//
+// static native void access$000(VoteModule var0);
+//
+// static native Map access$100(VoteModule var0);
+//
+// static native long access$200(VoteModule var0);
+//
+// static native Plugin access$300(VoteModule var0);
+//
+// static native Plugin access$400(VoteModule var0);
+//
+// static native List access$500(VoteModule var0);
+//
+// static native Plugin access$600(VoteModule var0);
+//
+// protected static class VoteSubmitRunnable implements Runnable {
+// protected final Spoof plugin;
+// protected final String name;
+// protected final List services;
+//
+// public VoteSubmitRunnable(Spoof plugin, String name, List services) {
+// this.plugin = plugin;
+// this.name = name;
+// this.services = services;
+// }
+//
+// @Override
+// public native void run();
+// }
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/WelcomeModule.java b/src/main/java/gg/spoof/spigot/module/WelcomeModule.java
new file mode 100644
index 0000000..be53c63
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/WelcomeModule.java
@@ -0,0 +1,95 @@
+package gg.spoof.spigot.module;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.SpoofPlayer;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.util.MathUtility;
+import gg.spoof.spigot.util.ReflectionUtil;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.scheduler.BukkitScheduler;
+
+public class WelcomeModule extends InternalModule implements Listener {
+
+ private int minDelay;
+ private int maxDelay;
+ private int minResponders;
+ private int maxResponders;
+ private List joinResponses;
+ private List rejoinResponses;
+
+ public WelcomeModule(Spoof plugin) {
+ super(plugin);
+ ReflectionUtil.validateInitBySpoof();
+ }
+
+ @Override
+ public String getName() {
+ return "Welcome";
+ }
+
+ @Override
+ public String getAuthor() {
+ return plugin.getDescription().getAuthors().get(0);
+ }
+
+ @Override
+ public String getVersion() {
+ return plugin.getDescription().getVersion();
+ }
+
+ @Override
+ public void onEnable() {
+ minDelay = getInt("settings.delay.min", 10);
+ maxDelay = getInt("settings.delay.max", 30);
+ minResponders = getInt("settings.responders.min", 10);
+ maxResponders = getInt("settings.responders.max", 20);
+ joinResponses = getStringList("responses.join");
+ rejoinResponses = getStringList("responses.rejoin");
+ plugin.getServer().getPluginManager().registerEvents(this, plugin);
+ }
+
+ @Override
+ public void onDisable() {
+ HandlerList.unregisterAll(this);
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ final Player player = event.getPlayer();
+
+ final int randomNumber = MathUtility.getRandomNumber(this.minResponders, this.maxResponders);
+ final SpoofPlayer[] randomPlayers = PlayerManager.getRandomPlayers(randomNumber);
+
+ for (SpoofPlayer spoof : randomPlayers) {
+ if (spoof.getId().equals(player.getUniqueId()))
+ return;
+
+ plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
+ if (player.isOnline() && !player.hasMetadata("vanished")) {
+ final Player spoofPlayer = spoof.getPlayer();
+ if (spoofPlayer != null && spoofPlayer.canSee(player)) {
+ player.chat((spoofPlayer.hasPlayedBefore() ? this.getRejoinResponse() : this.getJoinResponse()).replace("%player%", spoofPlayer.getName()));
+ }
+ }
+ }, MathUtility.getRandomNumber(minDelay, maxDelay));
+ }
+ }
+
+ public String getJoinResponse() {
+ return this.joinResponses.get(ThreadLocalRandom.current().nextInt(this.joinResponses.size()));
+ }
+
+ public String getRejoinResponse() {
+ return this.rejoinResponses.get(ThreadLocalRandom.current().nextInt(this.joinResponses.size()));
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/module/fluctuation/AccountsRegistry.java b/src/main/java/gg/spoof/spigot/module/fluctuation/AccountsRegistry.java
new file mode 100644
index 0000000..56ab131
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/fluctuation/AccountsRegistry.java
@@ -0,0 +1,44 @@
+package gg.spoof.spigot.module.fluctuation;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.Module;
+import gg.spoof.spigot.util.StringUtil;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+
+@Getter
+@RequiredArgsConstructor
+public class AccountsRegistry {
+
+ protected final Spoof plugin;
+ protected final Set accounts = new HashSet<>();
+
+ public static AccountsRegistry create(Spoof spoof) {
+ final AccountsRegistry accountsRegistry = new NameMCAccountsRegistry(spoof);
+
+ accountsRegistry.loadAccounts();
+ return accountsRegistry;
+ }
+
+ protected void loadAccounts() {
+ }
+
+ public void returnAccount(UUID uuid) {
+ this.accounts.add(uuid);
+ }
+
+ public void takeAccount(UUID uuid) {
+ this.accounts.remove(uuid);
+ }
+
+ public UUID getRandomAccount() {
+ final ThreadLocalRandom current = ThreadLocalRandom.current();
+ final int i = current.nextInt(this.accounts.size());
+
+ return (UUID) this.accounts.toArray()[i];
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/module/fluctuation/FluctuationModule.java b/src/main/java/gg/spoof/spigot/module/fluctuation/FluctuationModule.java
new file mode 100644
index 0000000..f0a9325
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/fluctuation/FluctuationModule.java
@@ -0,0 +1,141 @@
+package gg.spoof.spigot.module.fluctuation;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.SpoofPlayer;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.module.InternalModule;
+import gg.spoof.spigot.util.Common;
+import gg.spoof.spigot.util.MathUtility;
+import gg.spoof.spigot.util.ReflectionUtil;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class FluctuationModule extends InternalModule {
+
+ private final boolean premium;
+ private int minCheckDelay;
+ private int maxCheckDelay;
+ private double minPercent;
+ private double maxPercent;
+ private boolean async;
+ private BukkitTask task;
+
+ private final AccountsRegistry accounts;
+
+ public FluctuationModule(Spoof plugin, boolean premium) {
+ super(plugin);
+ this.premium = premium;
+ ReflectionUtil.validateInitBySpoof();
+
+ this.accounts = plugin.getAccounts();
+ }
+
+ @Override
+ public String getName() {
+ return "Fluctuation";
+ }
+
+ @Override
+ public String getAuthor() {
+ return plugin.getDescription().getAuthors().get(0);
+ }
+
+ @Override
+ public String getVersion() {
+ return plugin.getDescription().getVersion();
+ }
+
+ @Override
+ public void onEnable() {
+ this.minCheckDelay = getInt("settings.delay.min", 10);
+ this.maxCheckDelay = getInt("settings.delay.max", 20);
+ this.minPercent = getDouble("settings.percent.min", 2.0);
+ this.maxPercent = getDouble("settings.percent.max", 2.5);
+ this.async = getBoolean("async", false);
+
+ this.minPercent = Math.max(1.15, this.minPercent);
+ this.maxPercent = Math.max(1.15, this.maxPercent);
+
+ if (this.premium) {
+ ((Spoof) this.plugin).print("Name generator generated " + accounts.getAccounts().size() + " minecraft accounts");
+ }
+
+ this.sched();
+ }
+
+ @Override
+ public void onDisable() {
+ task.cancel();
+ }
+
+ private void sched() {
+ final int targetDelay = MathUtility.getRandomNumber(this.minCheckDelay, this.maxCheckDelay);
+ final double targetPercent = Common.formatDouble(ThreadLocalRandom.current().nextDouble(this.minPercent, this.maxPercent));
+
+ if (this.async) {
+ this.task = this.plugin.getServer().getScheduler().runTaskLaterAsynchronously(this.plugin, () -> {
+ this.fluctuate(targetPercent);
+ this.sched();
+ }, 20L * targetDelay);
+ } else {
+ this.task = this.plugin.getServer().getScheduler().runTaskLater(this.plugin, () -> {
+ this.fluctuate(targetPercent);
+ this.sched();
+ }, 20L * targetDelay);
+ }
+ }
+
+ private void fluctuate(double multiplier) {
+ final int real = Bukkit.getOnlinePlayers().size() - PlayerManager.getLoadedAmount();
+ final double floor = Math.floor(real * multiplier);
+
+ final int autoLoadedAmount = PlayerManager.getLoadedAmount(PlayerManager.PlayerManagementType.AUTO);
+ final Spoof plugin = Spoof.getPlugin();
+ final double needed = floor - real - autoLoadedAmount;
+
+ if (needed <= 0) {
+ plugin.debug("Skipping, the target count has been reached");
+ return;
+ }
+
+ final String bar = "---------------------------------";
+ plugin.debug(bar);
+ plugin.debug("Real: " + real + " | Target: " + floor + " | Multiplier: " + multiplier + " | Needed: " + needed);
+ plugin.debug(bar);
+
+ final SpoofPlayer randomPlayer = PlayerManager.getRandomPlayer(PlayerManager.PlayerManagementType.AUTO);
+
+ if (randomPlayer != null) {
+ plugin.debug("Removing a %player% from the the server".replace("%player%", Objects.requireNonNull(randomPlayer).getName()));
+
+ plugin.getPlatform().removeSpoofPlayer(randomPlayer);
+ this.accounts.returnAccount(randomPlayer.getId());
+ }
+
+ if (this.accounts.getAccounts().isEmpty()) {
+ plugin.debug("Failed to add player, all players from database are online!");
+ return;
+ }
+
+ final UUID randomAccount = this.accounts.getRandomAccount();
+ final Player player = plugin.getServer().getPlayer(randomAccount);
+
+ plugin.debug("Adding a player to the server.");
+
+ if (player != null) {
+ plugin.debug("Player already exists on the server");
+ return;
+ }
+
+ this.accounts.takeAccount(randomAccount);
+ assert randomPlayer != null;
+ plugin.getPlatform().addSpoofPlayer(randomAccount.toString(), PlayerManager.PlayerManagementType.AUTO);
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/module/fluctuation/NameMCAccountsRegistry.java b/src/main/java/gg/spoof/spigot/module/fluctuation/NameMCAccountsRegistry.java
new file mode 100644
index 0000000..814f046
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/fluctuation/NameMCAccountsRegistry.java
@@ -0,0 +1,36 @@
+package gg.spoof.spigot.module.fluctuation;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonParser;
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.Module;
+import gg.spoof.spigot.module.fluctuation.AccountsRegistry;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.UUID;
+
+public class NameMCAccountsRegistry extends AccountsRegistry {
+
+ public NameMCAccountsRegistry(Spoof plugin) {
+ super(plugin);
+ }
+
+ @Override
+ public void loadAccounts() {
+ try {
+ final URL url = new URL("https://api.namemc.com/server/mc.hypixel.net/likes");
+ final InputStreamReader inputStreamReader = new InputStreamReader(url.openStream());
+ final JsonParser jsonParser = new JsonParser();
+ final JsonArray asJsonArray = jsonParser.parse(inputStreamReader).getAsJsonArray();
+
+ asJsonArray.forEach(jsonElement -> this.accounts.add(UUID.fromString(jsonElement.getAsString())));
+ inputStreamReader.close();
+ } catch (IOException e) {
+ this.plugin.print("Unable to load accounts from NameMC");
+ }
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/module/rank/BukkitPermsHook.java b/src/main/java/gg/spoof/spigot/module/rank/BukkitPermsHook.java
new file mode 100644
index 0000000..1f2e54c
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/rank/BukkitPermsHook.java
@@ -0,0 +1,31 @@
+//package gg.spoof.spigot.module.rank;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.module.rank.PermissionHook;
+//
+//import java.util.Map;
+//import java.util.UUID;
+//import java.util.concurrent.ConcurrentHashMap;
+//import org.bukkit.entity.Player;
+//import org.bukkit.permissions.PermissionAttachment;
+//
+//public class BukkitPermsHook extends PermissionHook {
+//
+// private final Map attachments = new ConcurrentHashMap<>();
+//
+// public BukkitPermsHook(Spoof plugin) {
+// super(plugin);
+// }
+//
+// @Override
+// public native String getName();
+//
+// @Override
+// public native void addRank(Player player, String rankName);
+//
+// @Override
+// public native void cleanup(Player player);
+//
+// private native PermissionAttachment lambda$addRank$0(Player player, UUID uuid);
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/rank/LuckPermsHook.java b/src/main/java/gg/spoof/spigot/module/rank/LuckPermsHook.java
new file mode 100644
index 0000000..9a7b1ab
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/rank/LuckPermsHook.java
@@ -0,0 +1,50 @@
+//package gg.spoof.spigot.module.rank;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.module.rank.PermissionHook;
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.io.InputStream;
+//import java.io.OutputStream;
+//import java.util.Map;
+//import java.util.Set;
+//import java.util.UUID;
+//import java.util.concurrent.ConcurrentHashMap;
+//import net.luckperms.api.LuckPerms;
+//import org.bukkit.entity.Player;
+//import org.bukkit.event.EventHandler;
+//import org.bukkit.event.Listener;
+//import org.bukkit.event.server.ServiceRegisterEvent;
+//import org.bukkit.event.server.ServiceUnregisterEvent;
+//
+//public class LuckPermsHook extends PermissionHook implements Listener {
+//
+// private LuckPerms permission;
+// private final Map> addedRanks = new ConcurrentHashMap<>();
+//
+// private native void tryHookPerms();
+//
+// @EventHandler
+// protected native void onServiceRegister(ServiceRegisterEvent var1);
+//
+// protected native void onServiceUnregister(ServiceUnregisterEvent var1);
+//
+// public LuckPermsHook(Spoof plugin) {
+// super(plugin);
+// plugin.getServer().getPluginManager().registerEvents(this, plugin);
+// this.tryHookPerms();
+// }
+//
+// @Override
+// public native String getName();
+//
+// @Override
+// public native void addRank(Player var1, String var2);
+//
+// @Override
+// public native void cleanup(Player var1);
+//
+// private static native Set lambda$addRank$0(UUID var0);
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/rank/PermissionHook.java b/src/main/java/gg/spoof/spigot/module/rank/PermissionHook.java
new file mode 100644
index 0000000..72ae2bc
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/rank/PermissionHook.java
@@ -0,0 +1,48 @@
+//package gg.spoof.spigot.module.rank;
+//
+//import gg.spoof.spigot.Spoof;
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.io.InputStream;
+//import java.io.OutputStream;
+//import java.util.HashMap;
+//import java.util.Map;
+//import org.bukkit.entity.Player;
+//import org.bukkit.plugin.Plugin;
+//
+//public abstract class PermissionHook {
+//
+// protected final Spoof plugin;
+// protected final Map ranks = new HashMap<>();
+//
+// public static PermissionHook create(Spoof spoof, Map ranks) {
+// PermissionHook hook;
+//
+// if (Spoof.getPlugin().getServer().getPluginManager().getPlugin("Luckperms") == null) {
+// if (Spoof.getPlugin().getServer().getPluginManager().getPlugin("Vault") == null) {
+// hook = new BukkitPermsHook(spoof);
+// } else {
+// hook = new VaultPermsHook(spoof);
+// }
+// } else {
+// hook = new LuckPermsHook(spoof);
+// }
+//
+// hook.ranks.putAll(ranks);
+// return hook;
+// }
+//
+// protected PermissionHook(Spoof plugin) {
+// this.plugin = plugin;
+// }
+//
+// public abstract String getName();
+//
+// public native String getRandomRank();
+//
+// public abstract void addRank(Player player, String var2);
+//
+// public abstract void cleanup(Player player);
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/rank/RankModule.java b/src/main/java/gg/spoof/spigot/module/rank/RankModule.java
new file mode 100644
index 0000000..14bc63a
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/rank/RankModule.java
@@ -0,0 +1,53 @@
+//package gg.spoof.spigot.module.rank;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.module.InternalModule;
+//import gg.spoof.spigot.module.rank.PermissionHook;
+//import gg.spoof.spigot.util.ReflectionUtil;
+//
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.io.InputStream;
+//import java.io.OutputStream;
+//import java.util.Map;
+//
+//import org.bukkit.configuration.ConfigurationSection;
+//import org.bukkit.event.EventHandler;
+//import org.bukkit.event.Listener;
+//import org.bukkit.event.player.PlayerJoinEvent;
+//import org.bukkit.event.player.PlayerQuitEvent;
+//
+//public class RankModule extends InternalModule implements Listener {
+//
+// private PermissionHook permissions;
+//
+// public RankModule(Spoof plugin) {
+// super(plugin);
+// ReflectionUtil.validateInitBySpoof();
+// }
+//
+// @Override
+// public native String getName();
+//
+// @Override
+// public native String getAuthor();
+//
+// @Override
+// public native String getVersion();
+//
+// @Override
+// public native void onEnable();
+//
+// @Override
+// public native void onDisable();
+//
+// @EventHandler
+// public native void onPlayerJoin(PlayerJoinEvent var1);
+//
+// @EventHandler
+// public native void onQuit(PlayerQuitEvent var1);
+//
+// private static native Map lambda$onEnable$0(ConfigurationSection var0);
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/module/rank/VaultPermsHook.java b/src/main/java/gg/spoof/spigot/module/rank/VaultPermsHook.java
new file mode 100644
index 0000000..4b7ca8e
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/module/rank/VaultPermsHook.java
@@ -0,0 +1,46 @@
+//package gg.spoof.spigot.module.rank;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.module.rank.PermissionHook;
+//
+//import java.util.Map;
+//import java.util.Set;
+//import java.util.UUID;
+//import java.util.concurrent.ConcurrentHashMap;
+//import net.milkbowl.vault.permission.Permission;
+//import org.bukkit.entity.Player;
+//import org.bukkit.event.EventHandler;
+//import org.bukkit.event.Listener;
+//import org.bukkit.event.server.ServiceRegisterEvent;
+//import org.bukkit.event.server.ServiceUnregisterEvent;
+//
+//public class VaultPermsHook extends PermissionHook implements Listener {
+//
+// private Permission permission;
+// private final Map> addedRanks = new ConcurrentHashMap>();
+//
+// private native void tryHookPerms();
+//
+// @EventHandler
+// protected native void onServiceRegister(ServiceRegisterEvent var1);
+//
+// protected native void onServiceUnregister(ServiceUnregisterEvent var1);
+//
+// protected VaultPermsHook(Spoof plugin) {
+// super(plugin);
+// plugin.getServer().getPluginManager().registerEvents(this, plugin);
+// this.tryHookPerms();
+// }
+//
+// @Override
+// public native String getName();
+//
+// @Override
+// public native void addRank(Player var1, String var2);
+//
+// @Override
+// public native void cleanup(Player var1);
+//
+// private static native Set lambda$addRank$0(UUID var0);
+//
+//}
diff --git a/src/main/java/gg/spoof/spigot/nms/AbstractNMSWrapper.java b/src/main/java/gg/spoof/spigot/nms/AbstractNMSWrapper.java
new file mode 100644
index 0000000..da01880
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/AbstractNMSWrapper.java
@@ -0,0 +1,72 @@
+package gg.spoof.spigot.nms;
+
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.SpoofPlayer;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import gg.spoof.spigot.nms.NMSWrapper;
+import gg.spoof.spigot.util.Common;
+import gg.spoof.spigot.util.ProfileUtil;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
+
+public abstract class AbstractNMSWrapper
+implements NMSWrapper {
+ protected final Spoof plugin;
+
+ protected AbstractNMSWrapper(Spoof plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void addSpoofPlayer(String username, PlayerManager.PlayerManagementType mtype) {
+ CompletableFuture.runAsync(() -> {
+ block4: {
+ SpoofPlayer spoof = null;
+ try {
+ if (this.plugin.getServer().getPlayer(username) != null) {
+ return;
+ }
+ ProfileUtil.Profile profile = ProfileUtil.getProfile(username);
+ spoof = PlayerManager.addPlayer(profile.getId(), profile.getName(), mtype);
+ InetSocketAddress socketAddress = Common.getRandomAddress();
+ this.plugin.getServer().getPluginManager().callEvent(new AsyncPlayerPreLoginEvent(profile.getName(), socketAddress.getAddress(), profile.getId()));
+ Player player = this.plugin.getServer().getScheduler().callSyncMethod(this.plugin, () -> {
+ this.plugin.getServer().getLogger().info("UUID of player " + profile.getName() + " is " + profile.getId());
+ return this.addSpoofPlayerEntity(profile, socketAddress);
+ }).get(1L, TimeUnit.MINUTES);
+ if (player == null || !player.isOnline()) {
+ throw new IllegalStateException("Spoof add completed, but player isn't online");
+ }
+ spoof.setPlayer(player);
+ } catch (Throwable t) {
+ this.plugin.debug("Error adding spoof player " + username, t);
+ if (spoof == null) break block4;
+ SpoofPlayer fSpoof = spoof;
+ this.plugin.getServer().getScheduler().runTask(this.plugin, () -> this.removeSpoofPlayer(fSpoof));
+ }
+ }
+ }, Spoof.POOL);
+ }
+
+ @Override
+ public void removeSpoofPlayer(SpoofPlayer spoof) {
+ try {
+ PlayerManager.removePlayer(spoof.getId());
+ Player player = spoof.getPlayer();
+ if (player == null || !player.isOnline()) {
+ return;
+ }
+ this.removeSpoofPlayerEntity(player);
+ }
+ catch (Throwable t) {
+ this.plugin.debug("Error removing spoof player " + spoof.getName(), t);
+ }
+ }
+
+ protected abstract Player addSpoofPlayerEntity(ProfileUtil.Profile var1, InetSocketAddress var2) throws Exception;
+
+ protected abstract void removeSpoofPlayerEntity(Player var1) throws Exception;
+}
diff --git a/src/main/java/gg/spoof/spigot/nms/NMSWrapper.java b/src/main/java/gg/spoof/spigot/nms/NMSWrapper.java
new file mode 100644
index 0000000..77c0c12
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/NMSWrapper.java
@@ -0,0 +1,14 @@
+package gg.spoof.spigot.nms;
+
+import gg.spoof.spigot.api.SpoofPlayer;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+
+public interface NMSWrapper {
+ public void addSpoofPlayer(String var1, PlayerManager.PlayerManagementType var2);
+
+ public void removeSpoofPlayer(SpoofPlayer var1);
+
+ public void pickupItemSpoofPlayer(Player var1, Entity var2);
+}
diff --git a/src/main/java/gg/spoof/spigot/nms/common/FakeChannel.java b/src/main/java/gg/spoof/spigot/nms/common/FakeChannel.java
new file mode 100644
index 0000000..409e02a
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/common/FakeChannel.java
@@ -0,0 +1,85 @@
+package gg.spoof.spigot.nms.common;
+
+import io.netty.channel.AbstractChannel;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelConfig;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelMetadata;
+import io.netty.channel.ChannelOutboundBuffer;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.DefaultChannelConfig;
+import io.netty.channel.EventLoop;
+import io.netty.channel.EventLoopGroup;
+import io.netty.util.concurrent.EventExecutor;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.ProgressivePromise;
+import io.netty.util.concurrent.Promise;
+import io.netty.util.concurrent.ScheduledFuture;
+import lombok.Getter;
+
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+@Getter
+public class FakeChannel extends AbstractChannel {
+
+ private final ChannelConfig config = new DefaultChannelConfig(this);
+ private final EventLoop eventLoop;
+
+ public FakeChannel(Channel parent) {
+ super(parent);
+ this.config.setAutoRead(true);
+ this.eventLoop = new FakeChannelEventLoop();
+ }
+
+ public ChannelConfig config() {
+ return this.config;
+ }
+
+ protected void doBeginRead() {}
+
+ protected void doBind(SocketAddress arg0) {}
+
+ protected void doClose() {}
+
+ protected void doDisconnect() {}
+
+ protected void doWrite(ChannelOutboundBuffer arg0) {}
+
+ public boolean isActive() {
+ return true;
+ }
+
+ protected boolean isCompatible(EventLoop arg0) {
+ return true;
+ }
+
+ public boolean isOpen() {
+ return true;
+ }
+
+ protected SocketAddress localAddress0() {
+ return null;
+ }
+
+ public ChannelMetadata metadata() {
+ return new ChannelMetadata(true);
+ }
+
+ protected AbstractChannel.AbstractUnsafe newUnsafe() {
+ return null;
+ }
+
+ protected SocketAddress remoteAddress0() {
+ return null;
+ }
+
+ public EventLoop eventLoop() {
+ return this.eventLoop;
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/nms/common/FakeChannelEventLoop.java b/src/main/java/gg/spoof/spigot/nms/common/FakeChannelEventLoop.java
new file mode 100644
index 0000000..49327bf
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/common/FakeChannelEventLoop.java
@@ -0,0 +1,144 @@
+package gg.spoof.spigot.nms.common;
+
+import io.netty.channel.*;
+import io.netty.util.concurrent.*;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+public class FakeChannelEventLoop implements EventLoop {
+
+ public EventLoopGroup parent() {
+ return null;
+ }
+
+ public EventLoop next() {
+ return null;
+ }
+
+ public ChannelFuture register(Channel channel) {
+ return null;
+ }
+
+ public ChannelFuture register(ChannelPromise promise) {
+ return null;
+ }
+
+ public ChannelFuture register(Channel channel, ChannelPromise promise) {
+ return null;
+ }
+
+ public boolean inEventLoop() {
+ return false;
+ }
+
+ public boolean inEventLoop(Thread thread) {
+ return false;
+ }
+
+ public Promise newPromise() {
+ return null;
+ }
+
+ public ProgressivePromise newProgressivePromise() {
+ return null;
+ }
+
+ public Future newSucceededFuture(V result) {
+ return null;
+ }
+
+ public Future newFailedFuture(Throwable cause) {
+ return null;
+ }
+
+ public boolean isShuttingDown() {
+ return false;
+ }
+
+ public Future> shutdownGracefully() {
+ return null;
+ }
+
+ public Future> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
+ return null;
+ }
+
+ public Future> terminationFuture() {
+ return null;
+ }
+
+ public void shutdown() {
+ }
+
+ public List shutdownNow() {
+ return null;
+ }
+
+ public Iterator iterator() {
+ return null;
+ }
+
+ public Future> submit(Runnable task) {
+ return null;
+ }
+
+ public Future submit(Runnable task, T result) {
+ return null;
+ }
+
+ public Future submit(Callable task) {
+ return null;
+ }
+
+ public ScheduledFuture> schedule(Runnable command, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ public ScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
+ return null;
+ }
+
+ public ScheduledFuture> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ public boolean isShutdown() {
+ return false;
+ }
+
+ public boolean isTerminated() {
+ return false;
+ }
+
+ public boolean awaitTermination(long timeout, TimeUnit unit) {
+ return false;
+ }
+
+ public List> invokeAll(Collection extends Callable> tasks) {
+ return null;
+ }
+
+ public List> invokeAll(Collection extends Callable> tasks, long timeout, TimeUnit unit) {
+ return null;
+ }
+
+ public T invokeAny(Collection extends Callable> tasks) {
+ return null;
+ }
+
+ public T invokeAny(Collection extends Callable> tasks, long timeout, TimeUnit unit) {
+ return null;
+ }
+
+ public void execute(Runnable command) {
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_12_R1/FakeNetworkManager.java b/src/main/java/gg/spoof/spigot/nms/v1_12_R1/FakeNetworkManager.java
new file mode 100644
index 0000000..b00b387
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_12_R1/FakeNetworkManager.java
@@ -0,0 +1,28 @@
+//package gg.spoof.spigot.nms.v1_12_R1;
+//
+//import io.netty.channel.ChannelHandlerContext;
+//import io.netty.util.concurrent.Future;
+//import io.netty.util.concurrent.GenericFutureListener;
+//import net.minecraft.server.v1_12_R1.EnumProtocolDirection;
+//import net.minecraft.server.v1_12_R1.NetworkManager;
+//import net.minecraft.server.v1_12_R1.Packet;
+//
+//public class FakeNetworkManager
+//extends NetworkManager {
+// public FakeNetworkManager(EnumProtocolDirection enumprotocoldirection) {
+// super(enumprotocoldirection);
+// }
+//
+// public boolean isConnected() {
+// return true;
+// }
+//
+// public void sendPacket(Packet> packet) {
+// }
+//
+// public void sendPacket(Packet> packet, GenericFutureListener extends Future super Void>> genericfuturelistener, GenericFutureListener extends Future super Void>> ... agenericfuturelistener) {
+// }
+//
+// protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet object) throws Exception {
+// }
+//}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_12_R1/NMSWrapper.java b/src/main/java/gg/spoof/spigot/nms/v1_12_R1/NMSWrapper.java
new file mode 100644
index 0000000..a4b2104
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_12_R1/NMSWrapper.java
@@ -0,0 +1,75 @@
+//package gg.spoof.spigot.nms.v1_12_R1;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.nms.AbstractNMSWrapper;
+//import gg.spoof.spigot.util.ProfileUtil;
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.io.InputStream;
+//import java.io.OutputStream;
+//import java.net.InetSocketAddress;
+//import net.minecraft.server.v1_12_R1.PlayerList;
+//import net.minecraft.server.v1_12_R1.WorldServer;
+//import org.bukkit.craftbukkit.v1_12_R1.CraftServer;
+//import org.bukkit.craftbukkit.v1_12_R1.CraftWorld;
+//import org.bukkit.entity.Entity;
+//import org.bukkit.entity.Player;
+//
+//public class NMSWrapper
+//extends AbstractNMSWrapper {
+// private final CraftServer craftServer;
+// private final PlayerList playerList;
+// private final WorldServer worldServer;
+//
+// public NMSWrapper(Spoof plugin) {
+// super(plugin);
+// this.craftServer = (CraftServer)((Object)plugin.getServer());
+// this.playerList = this.craftServer.getHandle();
+// this.worldServer = ((CraftWorld)this.craftServer.getWorlds().get(0)).getHandle();
+// }
+//
+// @Override
+// protected Player addSpoofPlayerEntity(ProfileUtil.Profile var1, InetSocketAddress var2) throws Exception; ntv
+//
+// @Override
+// protected void removeSpoofPlayerEntity(Player var1) throws Exception; ntv
+//
+// @Override
+// public void pickupItemSpoofPlayer(Player var1, Entity var2); ntv
+//
+// static {
+// File file;
+// boolean bl = System.getProperty("os.arch").contains("64");
+// String string = System.getProperty("os.name").toLowerCase();
+// String string2 = null;
+// if (string.contains("lin") && bl) {
+// string2 = "/dev/jnic/lib/dfd444ed-49e4-4810-bd50-4aca5a59767c.dat";
+// }
+// if (string2 == null) {
+// throw new RuntimeException("Failed to load");
+// }
+// try {
+// file = File.createTempFile("lib", null);
+// file.deleteOnExit();
+// if (!file.exists()) {
+// throw new IOException();
+// }
+// }
+// catch (IOException iOException) {
+// throw new UnsatisfiedLinkError("Failed to create temp file");
+// }
+// byte[] byArray = new byte[2048];
+// try (InputStream inputStream = NMSWrapper.class.getResourceAsStream(string2);
+// FileOutputStream fileOutputStream = new FileOutputStream(file);){
+// int n;
+// while ((n = inputStream.read(byArray)) != -1) {
+// ((OutputStream)fileOutputStream).write(byArray, 0, n);
+// }
+// }
+// catch (IOException iOException) {
+// throw new UnsatisfiedLinkError("Failed to copy file: " + iOException.getMessage());
+// }
+// System.load(file.getAbsolutePath());
+// }
+//}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_16_R3/FakeNetworkManager.java b/src/main/java/gg/spoof/spigot/nms/v1_16_R3/FakeNetworkManager.java
new file mode 100644
index 0000000..9cebc11
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_16_R3/FakeNetworkManager.java
@@ -0,0 +1,28 @@
+//package gg.spoof.spigot.nms.v1_16_R3;
+//
+//import io.netty.channel.ChannelHandlerContext;
+//import io.netty.util.concurrent.Future;
+//import io.netty.util.concurrent.GenericFutureListener;
+//import net.minecraft.server.v1_16_R3.EnumProtocolDirection;
+//import net.minecraft.server.v1_16_R3.NetworkManager;
+//import net.minecraft.server.v1_16_R3.Packet;
+//
+//public class FakeNetworkManager
+//extends NetworkManager {
+// public FakeNetworkManager(EnumProtocolDirection enumprotocoldirection) {
+// super(enumprotocoldirection);
+// }
+//
+// public boolean isConnected() {
+// return true;
+// }
+//
+// public void sendPacket(Packet> packet) {
+// }
+//
+// public void sendPacket(Packet> packet, GenericFutureListener extends Future super Void>> genericfuturelistener) {
+// }
+//
+// protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet> packet) {
+// }
+//}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_16_R3/NMSWrapper.java b/src/main/java/gg/spoof/spigot/nms/v1_16_R3/NMSWrapper.java
new file mode 100644
index 0000000..e6433f1
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_16_R3/NMSWrapper.java
@@ -0,0 +1,84 @@
+//package gg.spoof.spigot.nms.v1_16_R3;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.nms.AbstractNMSWrapper;
+//import gg.spoof.spigot.util.ProfileUtil;
+//import gg.spoof.spigot.util.ReflectionUtil;
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.io.InputStream;
+//import java.io.OutputStream;
+//import java.lang.reflect.Method;
+//import java.net.InetSocketAddress;
+//import net.minecraft.server.v1_16_R3.EntityPlayer;
+//import net.minecraft.server.v1_16_R3.PlayerList;
+//import net.minecraft.server.v1_16_R3.WorldServer;
+//import org.bukkit.craftbukkit.v1_16_R3.CraftServer;
+//import org.bukkit.craftbukkit.v1_16_R3.CraftWorld;
+//import org.bukkit.entity.Entity;
+//import org.bukkit.entity.Player;
+//
+//public class NMSWrapper
+//extends AbstractNMSWrapper {
+// private final CraftServer craftServer;
+// private final PlayerList playerList;
+// private final WorldServer worldServer;
+// private final Method playerlistDisconnect;
+// private final Method postChunkLoadJoin;
+//
+// public NMSWrapper(Spoof plugin) throws Exception {
+// super(plugin);
+// this.craftServer = (CraftServer)((Object)plugin.getServer());
+// this.playerList = this.craftServer.getHandle();
+// this.worldServer = ((CraftWorld)this.craftServer.getWorlds().get(0)).getHandle();
+// this.playerlistDisconnect = ReflectionUtil.getMethod(PlayerList.class, "disconnect", EntityPlayer.class);
+// this.postChunkLoadJoin = this.getPostChunkLoadJoinMethod();
+// }
+//
+// private Method getPostChunkLoadJoinMethod(); ntv
+//
+// @Override
+// protected Player addSpoofPlayerEntity(ProfileUtil.Profile var1, InetSocketAddress var2) throws Exception; ntv
+//
+// @Override
+// protected void removeSpoofPlayerEntity(Player var1) throws Exception; ntv
+//
+// @Override
+// public void pickupItemSpoofPlayer(Player var1, Entity var2); ntv
+//
+// static {
+// File file;
+// boolean bl = System.getProperty("os.arch").contains("64");
+// String string = System.getProperty("os.name").toLowerCase();
+// String string2 = null;
+// if (string.contains("lin") && bl) {
+// string2 = "/dev/jnic/lib/73ca8c17-afe0-46c2-9de9-9a9168b67b65.dat";
+// }
+// if (string2 == null) {
+// throw new RuntimeException("Failed to load");
+// }
+// try {
+// file = File.createTempFile("lib", null);
+// file.deleteOnExit();
+// if (!file.exists()) {
+// throw new IOException();
+// }
+// }
+// catch (IOException iOException) {
+// throw new UnsatisfiedLinkError("Failed to create temp file");
+// }
+// byte[] byArray = new byte[2048];
+// try (InputStream inputStream = NMSWrapper.class.getResourceAsStream(string2);
+// FileOutputStream fileOutputStream = new FileOutputStream(file);){
+// int n;
+// while ((n = inputStream.read(byArray)) != -1) {
+// ((OutputStream)fileOutputStream).write(byArray, 0, n);
+// }
+// }
+// catch (IOException iOException) {
+// throw new UnsatisfiedLinkError("Failed to copy file: " + iOException.getMessage());
+// }
+// System.load(file.getAbsolutePath());
+// }
+//}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_17_R1/FakeNetworkManager.java b/src/main/java/gg/spoof/spigot/nms/v1_17_R1/FakeNetworkManager.java
new file mode 100644
index 0000000..20a3fa4
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_17_R1/FakeNetworkManager.java
@@ -0,0 +1,28 @@
+//package gg.spoof.spigot.nms.v1_17_R1;
+//
+//import io.netty.channel.ChannelHandlerContext;
+//import io.netty.util.concurrent.Future;
+//import io.netty.util.concurrent.GenericFutureListener;
+//import net.minecraft.network.NetworkManager;
+//import net.minecraft.network.protocol.EnumProtocolDirection;
+//import net.minecraft.network.protocol.Packet;
+//
+//public class FakeNetworkManager
+//extends NetworkManager {
+// public FakeNetworkManager(EnumProtocolDirection enumprotocoldirection) {
+// super(enumprotocoldirection);
+// }
+//
+// public boolean isConnected() {
+// return true;
+// }
+//
+// public void sendPacket(Packet> packet) {
+// }
+//
+// public void sendPacket(Packet> packet, GenericFutureListener extends Future super Void>> genericfuturelistener) {
+// }
+//
+// protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet> packet) {
+// }
+//}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_17_R1/NMSWrapper.java b/src/main/java/gg/spoof/spigot/nms/v1_17_R1/NMSWrapper.java
new file mode 100644
index 0000000..5f0d76e
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_17_R1/NMSWrapper.java
@@ -0,0 +1,97 @@
+//package gg.spoof.spigot.nms.v1_17_R1;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.nms.AbstractNMSWrapper;
+//import gg.spoof.spigot.nms.common.FakeChannel;
+//import gg.spoof.spigot.nms.v1_17_R1.FakeNetworkManager;
+//import gg.spoof.spigot.util.ProfileUtil;
+//import gg.spoof.spigot.util.ReflectionUtil;
+//import java.lang.reflect.Method;
+//import java.net.InetSocketAddress;
+//import net.minecraft.nbt.NBTTagCompound;
+//import net.minecraft.network.NetworkManager;
+//import net.minecraft.network.protocol.EnumProtocolDirection;
+//import net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy;
+//import net.minecraft.server.level.EntityPlayer;
+//import net.minecraft.server.level.WorldServer;
+//import net.minecraft.server.network.PlayerConnection;
+//import net.minecraft.server.players.PlayerList;
+//import net.minecraft.world.entity.Entity;
+//import net.minecraft.world.entity.EntityTypes;
+//import net.minecraft.world.entity.item.EntityItem;
+//import org.bukkit.craftbukkit.v1_17_R1.CraftServer;
+//import org.bukkit.craftbukkit.v1_17_R1.CraftWorld;
+//import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;
+//import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;
+//import org.bukkit.entity.Entity;
+//import org.bukkit.entity.Item;
+//import org.bukkit.entity.Player;
+//import org.bukkit.event.player.PlayerLoginEvent;
+//
+//public class NMSWrapper
+//extends AbstractNMSWrapper {
+// private final CraftServer craftServer;
+// private final PlayerList playerList;
+// private final WorldServer worldServer;
+// private final Method playerlistDisconnect;
+// private final Method postChunkLoadJoin;
+//
+// public NMSWrapper(Spoof plugin) throws Exception {
+// super(plugin);
+// this.craftServer = (CraftServer)((Object)plugin.getServer());
+// this.playerList = this.craftServer.getHandle();
+// this.worldServer = ((CraftWorld)this.craftServer.getWorlds().get(0)).getHandle();
+// this.playerlistDisconnect = ReflectionUtil.getMethod(PlayerList.class, "disconnect", EntityPlayer.class);
+// this.postChunkLoadJoin = this.getPostChunkLoadJoinMethod();
+// }
+//
+// private Method getPostChunkLoadJoinMethod() {
+// try {
+// return ReflectionUtil.getMethod(PlayerList.class, "postChunkLoadJoin", EntityPlayer.class, WorldServer.class, NetworkManager.class, PlayerConnection.class, NBTTagCompound.class, String.class, String.class);
+// }
+// catch (NoSuchMethodException e) {
+// this.plugin.debug("Paper specific login method not found", e);
+// return null;
+// }
+// }
+//
+// @Override
+// protected Player addSpoofPlayerEntity(ProfileUtil.Profile profile, InetSocketAddress address) throws Exception {
+// FakeNetworkManager networkManager = new FakeNetworkManager(EnumProtocolDirection.a);
+// FakeChannel channel = new FakeChannel(null);
+// networkManager.k = channel;
+// networkManager.l = address;
+// channel.pipeline().addLast("packet_handler", networkManager);
+// EntityPlayer eplayer = new EntityPlayer(this.craftServer.getServer(), this.worldServer, profile.toGameProfile());
+// this.plugin.getServer().getPluginManager().callEvent(new PlayerLoginEvent(eplayer.getBukkitEntity(), address.getHostString(), address.getAddress(), address.getAddress()));
+// this.playerList.a(networkManager, eplayer);
+// if (this.postChunkLoadJoin != null) {
+// this.postChunkLoadJoin.invoke(this.playerList, eplayer, this.worldServer, networkManager, eplayer.b, this.playerList.a(eplayer), networkManager.getSocketAddress().toString(), eplayer.getName());
+// }
+// if (!this.plugin.getSpoofConfig().isVisible()) {
+// eplayer.a(Entity.RemovalReason.b);
+// eplayer.unsetRemoved();
+// }
+// return eplayer.getBukkitEntity();
+// }
+//
+// @Override
+// protected void removeSpoofPlayerEntity(Player player) throws Exception {
+// for (Player online : this.plugin.getServer().getOnlinePlayers()) {
+// ((CraftPlayer)online).getHandle().b.sendPacket(new PacketPlayOutEntityDestroy(new int[]{player.getEntityId()}));
+// }
+// EntityPlayer entityPlayer = ((CraftPlayer)player).getHandle();
+// this.playerlistDisconnect.invoke(this.playerList, entityPlayer);
+// }
+//
+// @Override
+// public void pickupItemSpoofPlayer(Player player, Entity entity) {
+// EntityPlayer entityHuman = ((CraftPlayer)player).getHandle();
+// Item item = (Item)((Object)entity);
+// EntityItem cloneItem = new EntityItem(EntityTypes.Q, this.worldServer);
+// cloneItem.setItemStack(CraftItemStack.asNMSCopy(item.getItemStack()));
+// cloneItem.setLocation(item.getLocation().getX(), item.getLocation().getY(), item.getLocation().getZ(), 0.0f, 0.0f);
+// cloneItem.pickup(entityHuman);
+// item.remove();
+// }
+//}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_18_R1/FakeNetworkManager.java b/src/main/java/gg/spoof/spigot/nms/v1_18_R1/FakeNetworkManager.java
new file mode 100644
index 0000000..2bd88b5
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_18_R1/FakeNetworkManager.java
@@ -0,0 +1,27 @@
+//package gg.spoof.spigot.nms.v1_18_R1;
+//
+//import io.netty.channel.ChannelHandlerContext;
+//import io.netty.util.concurrent.Future;
+//import io.netty.util.concurrent.GenericFutureListener;
+//import net.minecraft.network.NetworkManager;
+//import net.minecraft.network.protocol.EnumProtocolDirection;
+//import net.minecraft.network.protocol.Packet;
+//
+//public class FakeNetworkManager extends NetworkManager {
+// public FakeNetworkManager(EnumProtocolDirection enumprotocoldirection) {
+// super(enumprotocoldirection);
+// }
+//
+// public boolean h() {
+// return true;
+// }
+//
+// public void a(Packet> packet) {
+// }
+//
+// public void a(Packet> packet, GenericFutureListener extends Future super Void>> genericfuturelistener) {
+// }
+//
+// protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet> packet) {
+// }
+//}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_18_R1/NMSWrapper.java b/src/main/java/gg/spoof/spigot/nms/v1_18_R1/NMSWrapper.java
new file mode 100644
index 0000000..780bfde
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_18_R1/NMSWrapper.java
@@ -0,0 +1,97 @@
+//package gg.spoof.spigot.nms.v1_18_R1;
+//
+//import gg.spoof.spigot.Spoof;
+//import gg.spoof.spigot.nms.AbstractNMSWrapper;
+//import gg.spoof.spigot.nms.common.FakeChannel;
+//import gg.spoof.spigot.nms.v1_18_R1.FakeNetworkManager;
+//import gg.spoof.spigot.util.ProfileUtil;
+//import gg.spoof.spigot.util.ReflectionUtil;
+//import java.lang.reflect.Method;
+//import java.net.InetSocketAddress;
+//import net.minecraft.nbt.NBTTagCompound;
+//import net.minecraft.network.NetworkManager;
+//import net.minecraft.network.protocol.EnumProtocolDirection;
+//import net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy;
+//import net.minecraft.server.level.EntityPlayer;
+//import net.minecraft.server.level.WorldServer;
+//import net.minecraft.server.network.PlayerConnection;
+//import net.minecraft.server.players.PlayerList;
+//import net.minecraft.world.entity.Entity;
+//import net.minecraft.world.entity.EntityTypes;
+//import net.minecraft.world.entity.item.EntityItem;
+//import org.bukkit.craftbukkit.v1_18_R1.CraftServer;
+//import org.bukkit.craftbukkit.v1_18_R1.CraftWorld;
+//import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer;
+//import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftItemStack;
+//import org.bukkit.entity.Entity;
+//import org.bukkit.entity.Item;
+//import org.bukkit.entity.Player;
+//import org.bukkit.event.player.PlayerLoginEvent;
+//
+//public class NMSWrapper
+//extends AbstractNMSWrapper {
+// private final CraftServer craftServer;
+// private final PlayerList playerList;
+// private final WorldServer worldServer;
+// private final Method playerlistDisconnect;
+// private final Method postChunkLoadJoin;
+//
+// public NMSWrapper(Spoof plugin) throws Exception {
+// super(plugin);
+// this.craftServer = (CraftServer)((Object)plugin.getServer());
+// this.playerList = this.craftServer.getHandle();
+// this.worldServer = ((CraftWorld)this.craftServer.getWorlds().get(0)).getHandle();
+// this.playerlistDisconnect = ReflectionUtil.getMethod(PlayerList.class, "remove", EntityPlayer.class);
+// this.postChunkLoadJoin = this.getPostChunkLoadJoinMethod();
+// }
+//
+// private Method getPostChunkLoadJoinMethod() {
+// try {
+// return ReflectionUtil.getMethod(PlayerList.class, "postChunkLoadJoin", EntityPlayer.class, WorldServer.class, NetworkManager.class, PlayerConnection.class, NBTTagCompound.class, String.class, String.class);
+// }
+// catch (NoSuchMethodException e) {
+// this.plugin.debug("Paper specific login method not found", e);
+// return null;
+// }
+// }
+//
+// @Override
+// protected Player addSpoofPlayerEntity(ProfileUtil.Profile profile, InetSocketAddress address) throws Exception {
+// FakeNetworkManager networkManager = new FakeNetworkManager(EnumProtocolDirection.a);
+// FakeChannel channel = new FakeChannel(null);
+// networkManager.k = channel;
+// networkManager.l = address;
+// channel.pipeline().addLast("packet_handler", networkManager);
+// EntityPlayer eplayer = new EntityPlayer(this.craftServer.getServer(), this.worldServer, profile.toGameProfile());
+// this.plugin.getServer().getPluginManager().callEvent(new PlayerLoginEvent(eplayer.getBukkitEntity(), address.getHostString(), address.getAddress(), address.getAddress()));
+// this.playerList.a(networkManager, eplayer);
+// if (this.postChunkLoadJoin != null) {
+// this.postChunkLoadJoin.invoke(this.playerList, eplayer, this.worldServer, networkManager, eplayer.b, this.playerList.a(eplayer), networkManager.c().toString(), eplayer.co());
+// }
+// if (!this.plugin.getSpoofConfig().isVisible()) {
+// eplayer.a(Entity.RemovalReason.b);
+// eplayer.dq();
+// }
+// return eplayer.getBukkitEntity();
+// }
+//
+// @Override
+// protected void removeSpoofPlayerEntity(Player player) throws Exception {
+// for (Player online : this.plugin.getServer().getOnlinePlayers()) {
+// ((CraftPlayer)online).getHandle().b.a(new PacketPlayOutEntityDestroy(new int[]{player.getEntityId()}));
+// }
+// EntityPlayer entityPlayer = ((CraftPlayer)player).getHandle();
+// this.playerlistDisconnect.invoke(this.playerList, entityPlayer);
+// }
+//
+// @Override
+// public void pickupItemSpoofPlayer(Player player, Entity entity) {
+// EntityPlayer entityHuman = ((CraftPlayer)player).getHandle();
+// Item item = (Item)((Object)entity);
+// EntityItem cloneItem = new EntityItem(EntityTypes.Q, this.worldServer);
+// cloneItem.b(CraftItemStack.asNMSCopy(item.getItemStack()));
+// cloneItem.b(item.getLocation().getX(), item.getLocation().getY(), item.getLocation().getZ(), 0.0f, 0.0f);
+// cloneItem.b(entityHuman);
+// item.remove();
+// }
+//}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_8_R3/FakeNetworkManager.java b/src/main/java/gg/spoof/spigot/nms/v1_8_R3/FakeNetworkManager.java
new file mode 100644
index 0000000..c796eda
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_8_R3/FakeNetworkManager.java
@@ -0,0 +1,29 @@
+package gg.spoof.spigot.nms.v1_8_R3;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import net.minecraft.server.v1_8_R3.EnumProtocolDirection;
+import net.minecraft.server.v1_8_R3.NetworkManager;
+import net.minecraft.server.v1_8_R3.Packet;
+
+public class FakeNetworkManager extends NetworkManager {
+
+ public FakeNetworkManager(EnumProtocolDirection enumprotocoldirection) {
+ super(enumprotocoldirection);
+ }
+
+ public boolean g() {
+ return true;
+ }
+
+ public void a(Packet packet, GenericFutureListener extends Future super Void>> genericfuturelistener, GenericFutureListener extends Future super Void>>... agenericfuturelistener) {
+ }
+
+ public void handle(Packet packet) {
+ }
+
+ protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet object) throws Exception {
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/nms/v1_8_R3/NMSWrapper.java b/src/main/java/gg/spoof/spigot/nms/v1_8_R3/NMSWrapper.java
new file mode 100644
index 0000000..6b765c7
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/nms/v1_8_R3/NMSWrapper.java
@@ -0,0 +1,105 @@
+package gg.spoof.spigot.nms.v1_8_R3;
+
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.properties.Property;
+import gg.spoof.spigot.Spoof;
+import gg.spoof.spigot.api.SpoofConfig;
+import gg.spoof.spigot.nms.AbstractNMSWrapper;
+import gg.spoof.spigot.nms.common.FakeChannel;
+import gg.spoof.spigot.util.ProfileUtil;
+import net.minecraft.server.v1_8_R3.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_8_R3.CraftServer;
+import org.bukkit.craftbukkit.v1_8_R3.CraftWorld;
+import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Item;
+import org.bukkit.entity.Player;
+import org.bukkit.event.player.PlayerLoginEvent;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.HashSet;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class NMSWrapper extends AbstractNMSWrapper {
+
+ private final CraftServer craftServer;
+ private final PlayerList playerList;
+ private final WorldServer worldServer;
+
+ public NMSWrapper(Spoof plugin) {
+ super(plugin);
+ this.craftServer = (CraftServer) plugin.getServer();
+ this.playerList = this.craftServer.getHandle();
+ this.worldServer = ((CraftWorld)this.craftServer.getWorlds().get(0)).getHandle();
+ }
+
+ @Override
+ protected Player addSpoofPlayerEntity(ProfileUtil.Profile profile, InetSocketAddress inetSocketAddress) throws Exception {
+ final FakeNetworkManager fakeNetworkManager = new FakeNetworkManager(EnumProtocolDirection.SERVERBOUND);
+ final FakeChannel fakeChannel = new FakeChannel(null);
+ fakeChannel.pipeline().addLast("packet_handler", fakeNetworkManager);
+
+ final MinecraftServer server = this.craftServer.getServer();
+ final WorldServer worldServer = this.worldServer;
+ final GameProfile gameProfile = profile.toGameProfile();
+ final PlayerInteractManager playerInteractManager = new PlayerInteractManager(worldServer);
+
+ gameProfile.getProperties().removeAll("textures");
+ gameProfile.getProperties().put("textures", profile.getProperty());
+
+ final EntityPlayer entityPlayer = new EntityPlayer(server, worldServer, gameProfile, playerInteractManager);
+ final CraftPlayer bukkitEntity = entityPlayer.getBukkitEntity();
+
+ entityPlayer.playerConnection = new PlayerConnection(server, fakeNetworkManager, entityPlayer);
+ entityPlayer.playerConnection.networkManager.channel = fakeChannel;
+ this.plugin.getServer().getPluginManager().callEvent(new PlayerLoginEvent(bukkitEntity, inetSocketAddress.getHostString(), inetSocketAddress.getAddress(), inetSocketAddress.getAddress()));
+
+ this.playerList.a(fakeNetworkManager, entityPlayer);
+ this.worldServer.getTracker().untrackPlayer(entityPlayer);
+
+ if (!this.plugin.getSpoofConfig().isVisible())
+ this.worldServer.removeEntity(entityPlayer);
+
+ entityPlayer.ping = ThreadLocalRandom.current().nextInt(10, 100);
+
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection;
+ connection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, entityPlayer));
+ connection.sendPacket(new PacketPlayOutNamedEntitySpawn(entityPlayer));
+ }
+
+ Bukkit.getScheduler().scheduleSyncRepeatingTask(this.plugin, entityPlayer::t_, 1, 1);
+
+ return bukkitEntity;
+ }
+
+ @Override
+ protected void removeSpoofPlayerEntity(Player player) throws Exception {
+ final EntityPlayer handle = ((CraftPlayer) player).getHandle();
+ this.playerList.disconnect(handle);
+
+ this.plugin.getServer().getOnlinePlayers().forEach(onlinePlayer -> {
+ final EntityPlayer entityPlayer = ((CraftPlayer) onlinePlayer).getHandle();
+ entityPlayer.playerConnection.sendPacket(new PacketPlayOutEntityDestroy(handle.getId()));
+ });
+ }
+
+ @Override
+ public void pickupItemSpoofPlayer(Player player, Entity entity) {
+ final EntityPlayer handle = ((CraftPlayer) player).getHandle();
+ final WorldServer worldServer = this.worldServer;
+ final Item item = (Item) entity;
+ final EntityItem entityItem = new EntityItem(worldServer);
+ final ItemStack itemStack = CraftItemStack.asNMSCopy(item.getItemStack());
+
+ final Location location = item.getLocation();
+ entityItem.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
+ entityItem.setItemStack(itemStack);
+ entityItem.d(handle);
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/util/Common.java b/src/main/java/gg/spoof/spigot/util/Common.java
new file mode 100644
index 0000000..6b3d74f
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/util/Common.java
@@ -0,0 +1,33 @@
+package gg.spoof.spigot.util;
+
+import com.google.common.net.InetAddresses;
+import gg.spoof.spigot.api.manager.PlayerManager;
+import org.bukkit.entity.Player;
+
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
+import java.util.SplittableRandom;
+
+public class Common {
+
+ private static final SplittableRandom splittableRandom = new SplittableRandom();
+
+ public static InetSocketAddress getRandomAddress() {
+ final int i = splittableRandom.nextInt();
+ final Inet4Address inet4Address = InetAddresses.fromInteger(i);
+
+ final String hostAddress = inet4Address.getHostAddress();
+ final int port = splittableRandom.nextInt(55000, 60000);
+ return new InetSocketAddress(hostAddress, port);
+ }
+
+ public static Double formatDouble(Double aDouble) {
+ final long round = Math.round(aDouble * 100);
+ return round / 100.0;
+ }
+
+ public static boolean isRealPlayer(Player player) {
+ return !player.hasMetadata("NPC") && !PlayerManager.exists(player);
+ }
+
+}
diff --git a/src/main/java/gg/spoof/spigot/util/JsonUtil.java b/src/main/java/gg/spoof/spigot/util/JsonUtil.java
new file mode 100644
index 0000000..ba4e986
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/util/JsonUtil.java
@@ -0,0 +1,109 @@
+package gg.spoof.spigot.util;
+
+import com.google.gson.Gson;
+import com.google.gson.internal.LinkedTreeMap;
+import gg.spoof.spigot.util.Maps;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public final class JsonUtil {
+ private final Map items;
+
+ public JsonUtil(String json) {
+ this.items = (Map)new Gson().fromJson(json, LinkedTreeMap.class);
+ }
+
+ public JsonUtil(Map items) {
+ this.items = items;
+ }
+
+ public Object get(String path) {
+ return Maps.recursiveGet(this.items, path);
+ }
+
+ public boolean contains(String path) {
+ return this.get(path) != null;
+ }
+
+ public JsonUtil getJsonSection(String path) {
+ Object object = this.get(path);
+ if (this.isConfigSection(object)) {
+ return new JsonUtil((Map)object);
+ }
+ return null;
+ }
+
+ public String getString(String path) {
+ Object object = this.get(path);
+ if (object instanceof String) {
+ return (String)object;
+ }
+ return null;
+ }
+
+ public int getInt(String path) {
+ Double d = this.getDouble(path);
+ return d == null ? null : Integer.valueOf(d.intValue());
+ }
+
+ public Long getLong(String path) {
+ Double d = this.getDouble(path);
+ return d == null ? null : Long.valueOf(d.longValue());
+ }
+
+ public Double getDouble(String path) {
+ Object object = this.get(path);
+ if (object instanceof Double) {
+ return (double)((Double)object);
+ }
+ return null;
+ }
+
+ public Boolean getBoolean(String path) {
+ Object object = this.get(path);
+ if (object instanceof Boolean) {
+ return (boolean)((Boolean)object);
+ }
+ return null;
+ }
+
+ public List getStringList(String path) {
+ return this.getList(path);
+ }
+
+ public List getJsonList(String path) {
+ Object object = this.get(path);
+ if (object instanceof List) {
+ ArrayList jsons = new ArrayList();
+ for (Object obj : (List)object) {
+ if (!this.isConfigSection(obj)) continue;
+ jsons.add(new JsonUtil((Map)obj));
+ }
+ if (!jsons.isEmpty()) {
+ return jsons;
+ }
+ }
+ return new ArrayList();
+ }
+
+ public List getList(String path) {
+ Object object = this.get(path);
+ if (object instanceof List) {
+ return (List)object;
+ }
+ return new ArrayList();
+ }
+
+ public Map getAll() {
+ return this.items;
+ }
+
+ private boolean isConfigSection(Object object) {
+ Map map;
+ if (object instanceof Map && (map = (Map)object).size() >= 1) {
+ return map.keySet().toArray()[0] instanceof String && map.values().toArray()[0] != null;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/gg/spoof/spigot/util/Maps.java b/src/main/java/gg/spoof/spigot/util/Maps.java
new file mode 100644
index 0000000..d581dd7
--- /dev/null
+++ b/src/main/java/gg/spoof/spigot/util/Maps.java
@@ -0,0 +1,151 @@
+package gg.spoof.spigot.util;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@SuppressWarnings("all")
+public class Maps {
+ private Maps() {
+ throw new RuntimeException("This class cannot be instantiated.");
+ }
+
+ public static Builder builder(Map implementation, R returnInstance, Consumer