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 + 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> tasks) { + return null; + } + + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) { + return null; + } + + public T invokeAny(Collection> tasks) { + return null; + } + + public T invokeAny(Collection> 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> genericfuturelistener, GenericFutureListener> ... 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> 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> 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> 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> genericfuturelistener, GenericFutureListener>... 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> build) { + return Maps.builder(implementation, returnInstance, build, null); + } + + public static Builder builder(Map implementation, R returnInstance, Consumer> build, Function mapper) { + return new Builder(implementation, returnInstance, build, mapper); + } + + public static Builder, V> of(Map implementation) { + return Maps.builder(implementation, null, null, null); + } + + public static Builder, T> of(Map implementation, Function mapper) { + return Maps.builder(implementation, null, null, mapper); + } + + public static Stream> flatten(Map.Entry entry) { + return Maps.flatten(entry, Map.class, (f) -> f); + } + + public static Stream> flatten(Map.Entry entry, Class clazz, Function> flatten) { + if (clazz.isInstance(entry.getValue())) { + return flatten.apply((T) entry.getValue()).entrySet().stream().map(e -> new AbstractMap.SimpleEntry((String)entry.getKey() + "." + (String)e.getKey(), e.getValue())).flatMap(e -> Maps.flatten(e, clazz, flatten)); + } + if (entry.getValue() instanceof Collection) { + if (((Collection)entry.getValue()).stream().anyMatch(clazz::isInstance)) { + return Stream.of(new AbstractMap.SimpleEntry(entry.getKey(), ((Collection)entry.getValue()).stream().map(o -> { + if (clazz.isInstance(o)) { + return (flatten.apply((T) o)).entrySet().stream().flatMap(e -> Maps.flatten(new AbstractMap.SimpleEntry((String)entry.getKey() + "." + (String)e.getKey(), e.getValue()), clazz, flatten)).distinct().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + return entry; + }).collect(Collectors.toList()))); + } + } + return Stream.of(entry); + } + + public static Map map(Map map, Class clazz, Function> mapper) { + Map mapped = new HashMap(); + + for(Map.Entry entry : map.entrySet()) { + Object obj = entry.getValue(); + if (clazz.isInstance(obj)) { + mapped.put(entry.getKey(), map(mapper.apply((T) obj), clazz, mapper)); + } else if (entry.getValue() instanceof Collection && ((Collection)entry.getValue()).stream().anyMatch(clazz::isInstance)) { + mapped.put(entry.getKey(), ((Collection)entry.getValue()).stream().map((o) -> clazz.isInstance(o) ? map(mapper.apply((T) o), clazz, mapper) : o)); + } else { + mapped.put(entry.getKey(), obj); + } + } + + return mapped; + } + + public static T recursiveGet(Map map, String path) { + Object object = map.getOrDefault(path, null); + if (path.contains(".") && !path.startsWith(".") && !path.endsWith(".")) { + String[] areas = path.split("\\."); + object = map.getOrDefault(areas[0], null); + if (areas.length >= 2 && object != null) { + object = Maps.getBuriedObject(map, areas); + } + } + return (T) object; + } + + private static T getBuriedObject(Map map, String[] keys) { + int i = 1; + Map endObject = (Map)map.get(keys[0]); + while (Maps.instanceOfStringKeyMap(endObject.get(keys[i]))) { + endObject = (Map)endObject.get(keys[i++]); + } + return (T)endObject.get(keys[i]); + } + + private static boolean instanceOfStringKeyMap(Object obj) { + if (obj instanceof Map) { + Set keys = ((Map)obj).keySet(); + return !keys.isEmpty() && keys.stream().allMatch(s -> s instanceof String); + } + return false; + } + + public static final class Builder { + private final Map map; + private final R returnInstance; + private final Consumer> build; + private final Function mapper; + + private Builder(Map implementation, R returnInstance, Consumer> build, Function mapper) { + this.map = implementation; + this.returnInstance = returnInstance; + this.build = build; + this.mapper = mapper; + } + + public ValueBuilder key(K key) { + return this.key(key, null); + } + + public ValueBuilder key(K key, Predicate requirement) { + return new ValueBuilder(key, requirement); + } + + public R build() { + if (this.returnInstance != null && this.build != null) { + this.build.accept(this.map); + return this.returnInstance; + } + return (R)this.map; + } + + public final class ValueBuilder { + private final K key; + private final Predicate requirement; + + private ValueBuilder(K key, Predicate requirement) { + this.key = key; + this.requirement = requirement; + } + + public Builder value(T value) { + V real = Builder.this.mapper != null ? Builder.this.mapper.apply(value) : (V) value; + if (this.requirement != null && !this.requirement.test(real)) { + return Builder.this; + } + Builder.this.map.put(this.key, real); + return Builder.this; + } + } + } +} diff --git a/src/main/java/gg/spoof/spigot/util/MathUtility.java b/src/main/java/gg/spoof/spigot/util/MathUtility.java new file mode 100644 index 0000000..de878ba --- /dev/null +++ b/src/main/java/gg/spoof/spigot/util/MathUtility.java @@ -0,0 +1,15 @@ +package gg.spoof.spigot.util; + +import java.util.concurrent.ThreadLocalRandom; + +public class MathUtility { + public static int getRandomNumber(int min, int max) { + int realMin = Math.min(min, max); + int realMax = Math.max(min, max); + if (realMax == realMin) { + return realMin; + } + int exclusiveSize = realMax - realMin; + return ThreadLocalRandom.current().nextInt(exclusiveSize + 1) + realMin; + } +} diff --git a/src/main/java/gg/spoof/spigot/util/PingUtil.java b/src/main/java/gg/spoof/spigot/util/PingUtil.java new file mode 100644 index 0000000..c9e28e3 --- /dev/null +++ b/src/main/java/gg/spoof/spigot/util/PingUtil.java @@ -0,0 +1,33 @@ +package gg.spoof.spigot.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.bukkit.entity.Player; + +public class PingUtil { + + private static Method getHandleMethod; + private static Field pingField; + + public static void setPing(Player player, int ping) { + try { + if (getHandleMethod == null) { + getHandleMethod = player.getClass().getDeclaredMethod("getHandle"); + } + getHandleMethod.setAccessible(true); + + Object handle = getHandleMethod.invoke(player); + + if (pingField == null) { + pingField = handle.getClass().getDeclaredField("ping"); + } + pingField.setAccessible(true); + + pingField.setInt(handle, ping); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + +} diff --git a/src/main/java/gg/spoof/spigot/util/ProfileUtil.java b/src/main/java/gg/spoof/spigot/util/ProfileUtil.java new file mode 100644 index 0000000..97d8a15 --- /dev/null +++ b/src/main/java/gg/spoof/spigot/util/ProfileUtil.java @@ -0,0 +1,117 @@ +package gg.spoof.spigot.util; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import gg.spoof.spigot.Spoof; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.URL; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class ProfileUtil { + private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>(); + ; + + private static String readUrl(String urlString) throws Exception { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(urlString).openStream()));) { + int read; + StringBuilder buffer = new StringBuilder(); + char[] chars = new char[1024]; + while ((read = reader.read(chars)) != -1) { + buffer.append(chars, 0, read); + } + return buffer.toString(); + } + } + + public static Profile getProfile(String userOrUuid) throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture async = CompletableFuture.supplyAsync(() -> { + try { + return ProfileUtil.submit(userOrUuid); + } catch (Exception e) { + throw new RuntimeException("Error fetching profile for id " + userOrUuid, e); + } + }, Spoof.POOL); + Profile profile = async.get(10000L, TimeUnit.MILLISECONDS); + CACHE.put(ProfileUtil.strip(userOrUuid), profile); + return profile; + } + + private static String strip(String str) { + return str.replace("-", ""); + } + + private static Profile submit(String str) throws Exception { + String stripped = strip(str); + + if (CACHE.containsKey(stripped)) + return CACHE.get(stripped); + + if (!StringUtil.isUUID(stripped)) + stripped = str; + + final String url = "https://api.ashcon.app/mojang/v2/user/" + stripped; + final JsonUtil jsonUtil = new JsonUtil(readUrl(url)); + + if (jsonUtil.contains("code") && jsonUtil.getInt("code") == 404) + throw new IllegalArgumentException("Error whilst getting player: does not exist"); + + final String username = jsonUtil.getString("username"); + final UUID uuid = UUID.fromString(Objects.requireNonNull(jsonUtil.getString("uuid"))); + final Property property = new Property( + "textures", + jsonUtil.getString("textures.raw.value"), + jsonUtil.getString("textures.raw.signature") + ); + + return new Profile(username, uuid, property); + } + + public static class Profile { + private final UUID uuid; + private final String username; + private final Property property; + + public Profile(String username, UUID uuid, Property property) { + this.uuid = uuid; + this.username = username; + this.property = property; + } + + public UUID getId() { + return this.uuid; + } + + public String getName() { + return this.username; + } + + public Property getProperty() { + return this.property; + } + + public void setSkin(GameProfile gameProfile) { + gameProfile.getProperties().put("textures", this.property); + } + + public GameProfile toGameProfile() { + GameProfile profile = new GameProfile(this.uuid, this.username); + this.setSkin(profile); + return profile; + } + } +} diff --git a/src/main/java/gg/spoof/spigot/util/ReflectionUtil.java b/src/main/java/gg/spoof/spigot/util/ReflectionUtil.java new file mode 100644 index 0000000..966f05c --- /dev/null +++ b/src/main/java/gg/spoof/spigot/util/ReflectionUtil.java @@ -0,0 +1,103 @@ +package gg.spoof.spigot.util; + +import com.google.common.collect.Maps; +import gg.spoof.spigot.Spoof; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; + +public class ReflectionUtil { + private static final Map CACHE = Maps.newHashMap(); + + public static void validateInitBySpoof() { + String spoofClassName = Spoof.class.getName(); + for (StackTraceElement element : new Exception("trace").getStackTrace()) { + if (!element.getClassName().equals(spoofClassName) || !element.getMethodName().equals("onEnable")) continue; + return; + } + throw new IllegalStateException("This module can only be initalized by plugin itself in onEnable"); + } + + public static Field getField(Class klass, String name) throws NoSuchFieldException, SecurityException { + Field field = klass.getDeclaredField(name); + field.setAccessible(true); + return field; + } + + public static Field getField(Object obj, String name) throws NoSuchFieldException, SecurityException { + Field field = obj.getClass().getDeclaredField(name); + field.setAccessible(true); + return field; + } + + public static Method getMethod(Class klass, String name, Class ... parameterTypes) throws NoSuchMethodException, SecurityException { + Method method = klass.getDeclaredMethod(name, parameterTypes); + method.setAccessible(true); + return method; + } + + public static void set(Field field, Object toUse, Object toReplace) { + try { + field.set(toUse, toReplace); + } + catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + public static Object getValue(Object obj, String name) { + try { + Field field = obj.getClass().getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } + catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + public static void setValue(Object obj, String fieldName, Object value) { + try { + Field field; + String className = obj.getClass().getName(); + ClassData data = CACHE.get(className + fieldName); + if (data != null) { + field = data.field; + } else { + field = obj.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + data = new ClassData(obj.getClass(), fieldName, field); + CACHE.put(className + fieldName, data); + } + field.set(obj, value); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + + private static class ClassData { + Class className; + String fieldId; + Field field; + + public ClassData(Class className, String fieldId, Field field) { + this.className = className; + this.fieldId = fieldId; + this.field = field; + } + + public int hashCode() { + return this.className.hashCode() * 31 + this.fieldId.hashCode(); + } + + public boolean equals(Object other) { + if (!(other instanceof ClassData)) { + return false; + } + ClassData otherClassData = (ClassData)other; + return otherClassData.field.equals(this.field) && otherClassData.fieldId.equals(this.fieldId) && otherClassData.className.equals(this.className); + } + } +} diff --git a/src/main/java/gg/spoof/spigot/util/StringUtil.java b/src/main/java/gg/spoof/spigot/util/StringUtil.java new file mode 100644 index 0000000..6fd32f5 --- /dev/null +++ b/src/main/java/gg/spoof/spigot/util/StringUtil.java @@ -0,0 +1,24 @@ +package gg.spoof.spigot.util; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.bukkit.ChatColor; + +public class StringUtil { + public static boolean isUUID(String str) { + return str.matches("[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"); + } + + public static String translate(String message) { + return ChatColor.translateAlternateColorCodes('&', message); + } + + public static List translate(String ... message) { + return Arrays.stream(message).map(StringUtil::translate).collect(Collectors.toList()); + } + + public static List translate(List message) { + return message.stream().map(StringUtil::translate).collect(Collectors.toList()); + } +} diff --git a/src/main/java/gg/spoof/velocity/Spoof.java b/src/main/java/gg/spoof/velocity/Spoof.java new file mode 100644 index 0000000..f38c169 --- /dev/null +++ b/src/main/java/gg/spoof/velocity/Spoof.java @@ -0,0 +1,102 @@ +package gg.spoof.velocity; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import gg.spoof.common.utils.ResourceLoader; +import gg.spoof.velocity.controller.PluginMessageController; +import gg.spoof.velocity.controller.ProxyController; +import gg.spoof.velocity.controller.RedisController; +import gg.spoof.velocity.listener.VelocityPingListener; +import gg.spoof.velocity.util.ServerData; +import lombok.Getter; + +import java.io.IOException; +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 java.util.logging.Logger; +import javax.inject.Inject; + +@Getter +public class Spoof { + + private final ProxyServer proxy; + private final Logger logger; + private final Path pluginDataDir; + private final Map servers = new ConcurrentHashMap<>(); + private boolean debug; + private ProxyController controller; + + @Inject + public Spoof(ProxyServer proxy, Logger logger, @DataDirectory Path pluginDir) { + this.proxy = proxy; + this.logger = logger; + this.pluginDataDir = pluginDir; + } + + public void debug(String message) { + if (this.debug) + this.getLogger().info(message); + } + + public void debug(String message, Throwable t) { + if (this.debug) + this.getLogger().log(Level.INFO, message, t); + } + + @Subscribe + protected void onEnable(ProxyInitializeEvent event) throws IOException { + final Path configFile = this.pluginDataDir.resolve("config.conf"); + ResourceLoader.loadDefaultResource(this.getClass().getClassLoader(), "velocity-config.conf", configFile); + final Config config = ConfigFactory.parseFile(configFile.toFile()); + + this.debug = config.getBoolean("settings.debug"); + this.registerController(config); + this.setupServersCleanup(); + this.getProxy().getEventManager().register(this, new VelocityPingListener(this)); + } + + @Subscribe + protected void onDisable(ProxyShutdownEvent event) { + if (this.controller != null) + this.controller.close(); + } + + private void registerController(Config 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().buildTask(this, () -> { + 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()); + } + }).delay(10L, TimeUnit.SECONDS).repeat(10L, TimeUnit.SECONDS).schedule(); + } +} diff --git a/src/main/java/gg/spoof/velocity/controller/PluginMessageController.java b/src/main/java/gg/spoof/velocity/controller/PluginMessageController.java new file mode 100644 index 0000000..e7036ae --- /dev/null +++ b/src/main/java/gg/spoof/velocity/controller/PluginMessageController.java @@ -0,0 +1,55 @@ +package gg.spoof.velocity.controller; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import gg.spoof.common.proxy.ProxyChannelFormat; +import gg.spoof.velocity.Spoof; + +public class PluginMessageController extends ProxyController { + + protected final ChannelIdentifier proxyboundServerPlayers = MinecraftChannelIdentifier.from("spoof:srv_pl"); + protected final ChannelIdentifier serverboundProxyPlayerCount = MinecraftChannelIdentifier.from("spoof:pr_pl_cnt"); + + public PluginMessageController(Spoof plugin) { + super(plugin); + plugin.getProxy().getChannelRegistrar().register(this.proxyboundServerPlayers); + plugin.getProxy().getEventManager().register(plugin, this); + } + + @Subscribe + public void onPluginMessage(PluginMessageEvent event) { + if (!(event.getSource() instanceof ServerConnection)) + return; + + if (!event.getIdentifier().equals(this.proxyboundServerPlayers)) + 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); + for (RegisteredServer server : this.plugin.getProxy().getAllServers()) { + server.sendPluginMessage(this.serverboundProxyPlayerCount, proxyPlayerCountData); + } + 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/velocity/controller/ProxyController.java b/src/main/java/gg/spoof/velocity/controller/ProxyController.java new file mode 100644 index 0000000..22402ad --- /dev/null +++ b/src/main/java/gg/spoof/velocity/controller/ProxyController.java @@ -0,0 +1,23 @@ +package gg.spoof.velocity.controller; + +import gg.spoof.velocity.Spoof; +import gg.spoof.velocity.util.ServerData; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public abstract class ProxyController { + + protected final Spoof plugin; + + public abstract void close(); + + public int getProxyPlayerCount() { + return this.plugin.getServers().values().stream().mapToInt(ServerData::getOnline).sum(); + } + + public 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/velocity/controller/RedisController.java b/src/main/java/gg/spoof/velocity/controller/RedisController.java new file mode 100644 index 0000000..a1e51e0 --- /dev/null +++ b/src/main/java/gg/spoof/velocity/controller/RedisController.java @@ -0,0 +1,48 @@ +package gg.spoof.velocity.controller; + +import gg.spoof.velocity.Spoof; +import gg.spoof.velocity.controller.redis.RedisPubSub; +import lombok.Getter; +import redis.clients.jedis.Jedis; + +@Getter +public class RedisController extends ProxyController { + + private Jedis jedisRX; + private Jedis jedisTX; + private final RedisPubSub redisPubSub; + + 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); + this.redisPubSub = new RedisPubSub(plugin, this); + + plugin.getProxy().getScheduler().buildTask(plugin, () -> this.jedisRX.subscribe(this.redisPubSub, "spoof:srv_pl")).schedule(); + } + + 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/velocity/controller/redis/RedisPubSub.java b/src/main/java/gg/spoof/velocity/controller/redis/RedisPubSub.java new file mode 100644 index 0000000..10bf83d --- /dev/null +++ b/src/main/java/gg/spoof/velocity/controller/redis/RedisPubSub.java @@ -0,0 +1,44 @@ +package gg.spoof.velocity.controller.redis; + +import gg.spoof.common.proxy.ProxyChannelFormat; +import gg.spoof.velocity.Spoof; +import gg.spoof.velocity.controller.RedisController; +import lombok.RequiredArgsConstructor; +import redis.clients.jedis.JedisPubSub; + +@RequiredArgsConstructor +public class RedisPubSub extends JedisPubSub { + + private final Spoof plugin; + private final RedisController redisController; + + @Override + public void onMessage(String channel, String message) { + try { + final ProxyChannelFormat.ServerPlayers players = ProxyChannelFormat.decodeB64ServerPlayers(message); + this.redisController.updateServerPlayerCount(players.getServerId(), players.getCount()); + } + catch (Exception ex) { + this.plugin.debug("Error receiving server players", ex); + return; + } + try { + int proxyPlayerCount = this.redisController.getProxyPlayerCount(); + this.redisController.getJedisTX().publish("spoof:pr_pl_cnt", ProxyChannelFormat.encodeB64ProxyPlayerCount(proxyPlayerCount)); + this.plugin.debug("Sent total player count " + proxyPlayerCount); + } + catch (Exception ex) { + this.plugin.debug("Error sending proxy player count", ex); + } + } + + @Override + public void onSubscribe(String channel, int subscribedChannels) { + } + + @Override + public void onUnsubscribe(String channel, int subscribedChannels) { + } + + +} diff --git a/src/main/java/gg/spoof/velocity/listener/VelocityPingListener.java b/src/main/java/gg/spoof/velocity/listener/VelocityPingListener.java new file mode 100644 index 0000000..d2fd3de --- /dev/null +++ b/src/main/java/gg/spoof/velocity/listener/VelocityPingListener.java @@ -0,0 +1,21 @@ +package gg.spoof.velocity.listener; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyPingEvent; +import gg.spoof.velocity.Spoof; +import gg.spoof.velocity.util.ServerData; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class VelocityPingListener { + + protected final Spoof plugin; + + @Subscribe + public void onProxyPing(ProxyPingEvent event) { + int count = this.plugin.getServers().values().stream().mapToInt(ServerData::getOnline).sum(); + this.plugin.debug("Setting proxy count to " + count + "."); + event.setPing(event.getPing().asBuilder().onlinePlayers(count).build()); + } + +} diff --git a/src/main/java/gg/spoof/velocity/util/ServerData.java b/src/main/java/gg/spoof/velocity/util/ServerData.java new file mode 100644 index 0000000..7c34ced --- /dev/null +++ b/src/main/java/gg/spoof/velocity/util/ServerData.java @@ -0,0 +1,16 @@ +package gg.spoof.velocity.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/resources/bungee-config.yml b/src/main/resources/bungee-config.yml new file mode 100644 index 0000000..e3b8c81 --- /dev/null +++ b/src/main/resources/bungee-config.yml @@ -0,0 +1,16 @@ +settings: + # This is a developer mode, useful for logging + # various aspects of the plugin to console. + debug: true + + controller: + # This is the controller type you wish to handle. + selected: "redis" + + # Specify your redis credentials here if your server + # includes a proxy. + redis: + # Specify your address:port + address: '127.0.0.1:6379' + # If you use credentials, specify here + password: '' diff --git a/src/main/resources/bungee.yml b/src/main/resources/bungee.yml new file mode 100644 index 0000000..486b261 --- /dev/null +++ b/src/main/resources/bungee.yml @@ -0,0 +1,4 @@ +name: Spoof +version: 2.5.2 +main: gg.spoof.bungee.Spoof +author: "Stellar Dev" diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..ed6d72d --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,184 @@ +settings: + debug: true + + # Whether the player entity should be shown + # in-game, useful for Hub servers. + show-bot-entity: true + + # Add your username or uuid here to gain access to the in-game + # commands. This is to ensure others with full perms cannot + # see the plugin exists. + whitelisted: + - 'SpoofPlayer' + + # If you wish to run within a proxy, + # enable this and specify redis settings below. + proxy-mode: false + + # This is your unique server identifier, this MUST be + # set if using a proxy - e.g. BungeeCord. + server-id: "server" + + # You can optionally run commands from console when a + # spoofed player joins. Use %player% for the players + # username, no slash is needed. + join-commands: [] + join-commands-delay: 0 + + controller: + # This is the controller type you wish to use. + selected: "redis" + + # Specify your redis credentials here if your server + # includes a proxy. + redis: + # Specify your address:port + address: '127.0.0.1:6379' + # If you use credentials, specify here + password: '' + + # This is the location that the bots spawn in when loaded + spawn-locations: + x: 0 + y: 0 + z: 0 + world: "world" + +# These are your list of modules, a developer may ask +# you to add their own module name below. +modules: + # This module connects to Buycraft with fake donations + # and reacting to donations events with our forked buycraft + donations: + enabled: true + settings: + delay: + min: 10 + max: 20 + responders: + min: 1 + max: 3 + responses: + - "gg" + packages: + 1: # The package ID for fake donations + message: + - "&7[&6Buycraft&7] &c&l &7has bought &a1x &6Legendary &7key!" + + # This module has the bots respond to people joining/rejoining + # with random responses and random selected players + welcome: + enabled: false + settings: + delay: + min: 10 + max: 20 + responders: + min: 1 + max: 3 + responses: + join: + - "Welcome to Stellar Dev %player%" + - "Aye looks whos here" + rejoin: + - "Wb" + - "Welcome back retard" + # This module gives random ranks to the bots + # useful to make bots look more realistic + ranks: + enabled: false + settings: + # RANK:CHANCE + rank-chances: + gem: 10 + interstellar: 20 + stellar: 30 + elapsed: 40 + beer: 50 + + # This module automatically fluctuates a random + # bots ping, useful for looking realistic. + ping: + enabled: true + settings: + delay: + min: 1 + max: 10 + ping: + min: 100 + max: 150 + + # This module lets bots pickup items dropped nearby. + # Not recommended, but delay can be tweaked for performance. + pickup: + enabled: true + settings: + delay: + min: 5 + max: 5 + + # This module lets you run a random command on a bot. + # This is useful for having bots automatically teleport etc. + action: + enabled: false + settings: + delay: + min: 1 + max: 10 + actions: + # You can add and remove these, e.g. a 70% and a 30% chance action. + test: + chance: 100 + list: + - '[PLAYER] me woah, cool server!' + + # This module makes a random bot vote (server-sided!). + # Enabling this usually motivates real players to want to vote. + vote: + enabled: false + settings: + expiration: 3600000 + delay: + min: 1 + max: 10 + services: + - 'PMC' + - 'MinecraftMP' + + # This module makes bots automatically join and leave, depending + # on how many real players are currently online. + # + # I'd recommend 1.2 (20%) to 1.25 (25%) on a production server. + fluctuation: + enabled: true + debug: true + settings: + # Legit uuids generator + accounts: + # NameMC, List + generator: "NameMC" + # List generator users + users: + - 'dbba4003-2ef3-4857-ab28-610501f34d51' + - '157e8dba-c2e6-4fe7-8d01-438b8e4cc898' + - '995d6b61-6588-44f6-a919-28f908d06862' + delay: + min: 1 + max: 10 + percent: + min: 2.0 + max: 2.5 + +messages: + help: + - '&8[&b&l⚡&8] &bSpoof &8- &fThe Ultimate Spoofing Solution&7.' + - '&8[&b&l⚡ &b/spoof add&8] &7Add a spoofed player to the server' + - '&8[&b&l⚡ &b/spoof remove&8] &7Remove a spoofed player from the server' + - '&8[&b&l⚡ &b/spoof reload&8] &7Reload the modules and configs' + - '&8[&b&l⚡ &b/spoof modules&8] &7View the modules information' + - '&8[&b&l⚡ &b/spoof auction&8] &7Auction an item under a spoofed player' + - '&8[&b&l⚡ &b/spoof summon&8] &7Summon a spoofed player to your location' + - '&8[&b&l⚡ &b/spoof donation&8] &7Create a fake donation reaction with spoof players' + modules-information: + - '&8[&b&l⚡&8] &bSpoof &8- &fThe Ultimate Spoofing Solution&7.' + - '&8[&b&l⚡&8] &7Modules (&a&7): &b' \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..876e607 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,5 @@ +name: Spoof +version: 2.5.2 +main: gg.spoof.spigot.Spoof +author: "Stellar Dev" +softdepend: ["Votifier", "Vault", "LuckPerms"] \ No newline at end of file diff --git a/src/main/resources/velocity-config.conf b/src/main/resources/velocity-config.conf new file mode 100644 index 0000000..62d753a --- /dev/null +++ b/src/main/resources/velocity-config.conf @@ -0,0 +1,21 @@ +license: licensekey + +settings: { + # This is a developer mode, useful for logging + # various aspects of the plugin to console. + debug: true + + controller: { + # This is the controller type you wish to handle. + selected: "redis" + + # Specify your redis credentials here if your server + # includes a proxy. + redis: { + # Specify your address:port + address: "127.0.0.1:6379" + # If you use credentials, specify here + password: "" + } + } +} diff --git a/src/main/resources/velocity-plugin.json b/src/main/resources/velocity-plugin.json new file mode 100644 index 0000000..8dc817e --- /dev/null +++ b/src/main/resources/velocity-plugin.json @@ -0,0 +1,10 @@ +{ + "name": "spoof", + "id": "spoof", + "main": "gg.spoof.velocity.Spoof", + "version": "2.5.2", + "dependencies": [], + "authors": [ + "Stellar Dev" + ] +}