diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65df3cb..ec281a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,10 @@ -name: Build RandomSpawnPlus5 +name: Build RandomSpawnPlus -on: [ push ] +on: + push: + branches: [ "feat/nextgen" ] + pull_request: + branches: [ "master" ] jobs: build: @@ -9,22 +13,23 @@ jobs: if: "github.actor != 'dependabot[bot]'" steps: - uses: actions/checkout@main - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@main with: - java-version: 17 - distribution: temurin + java-version: 21 + distribution: zulu - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build run: ./gradlew clean build - - name: Release RandomSpawnPlus5 + - name: Release RandomSpawnPlus + if: github.event_name != 'pull_request' uses: marvinpinto/action-automatic-releases@master with: - title: "RandomSpawnPlus5 v5.1.0-SNAPSHOT" - automatic_release_tag: "5.1.0-SNAPSHOT" + title: "RandomSpawnPlus" + automatic_release_tag: "6.0.0-SNAPSHOT" repo_token: "${{ secrets.GITHUB_TOKEN }}" - files: "build/libs/*.jar" + files: "Common/build/libs/*.jar" prerelease: true diff --git a/Common/build.gradle.kts b/Common/build.gradle.kts new file mode 100644 index 0000000..764e16c --- /dev/null +++ b/Common/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + id("cn.dreeam.rsp.wrapper") + id("com.gradleup.shadow") version "8.3.5" +} + +val adventureVersion = "4.17.0" + +dependencies { + compileOnly("org.spigotmc:spigot-api:1.21.5-R0.1-SNAPSHOT") + + implementation(project(":Platform:Abstraction")) + implementation(project(":Platform:Folia")) + implementation(project(":Platform:Paper")) + implementation(project(":Platform:Spigot")) + + compileOnly("org.apache.logging.log4j:log4j-api:2.24.1") + compileOnly("it.unimi.dsi:fastutil:8.5.15") + implementation("org.bstats:bstats-bukkit:3.1.0") + api("co.aikar:acf-paper:0.5.1-SNAPSHOT") // Remove + implementation("com.github.technicallycoded:FoliaLib:0.4.3") + implementation("com.github.thatsmusic99:ConfigurationMaster-API:v2.0.0-rc.3") + + compileOnly("net.essentialsx:EssentialsX:2.20.1") + compileOnly("net.luckperms:api:5.4") + compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") + + api("net.kyori:adventure-platform-bukkit:4.3.4") + api("net.kyori:adventure-api:$adventureVersion") + api("net.kyori:adventure-text-serializer-legacy:$adventureVersion") +} + +tasks { + build.configure { + dependsOn(shadowJar) + } + + shadowJar { + archiveFileName = "${rootProject.name}-${project.version}.${archiveExtension.get()}" + exclude("META-INF/**") // Dreeam - Avoid to include META-INF/maven in Jar + minimize { + exclude(dependency("com.tcoded.folialib:.*:.*")) + } + relocate("net.kyori", "${project.group}.libs.kyori") + relocate("io.github.thatsmusic99.configurationmaster", "${project.group}.libs.configurationmaster") + relocate("org.bstats", "${project.group}.libs.bstats") + relocate("com.tcoded.folialib", "${project.group}.libs.folialib") + } +} + +publishing { + publications.create("maven") { + from(components["java"]) + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java b/Common/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java new file mode 100644 index 0000000..fcdede6 --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java @@ -0,0 +1,91 @@ +package systems.kscott.randomspawnplus; + +import co.aikar.commands.PaperCommandManager; +import com.tcoded.folialib.FoliaLib; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import systems.kscott.randomspawnplus.commands.CommandRSP; +import systems.kscott.randomspawnplus.commands.CommandWild; +import systems.kscott.randomspawnplus.config.Config; +import systems.kscott.randomspawnplus.hooks.HookInstance; +import systems.kscott.randomspawnplus.listeners.OnDeath; +import systems.kscott.randomspawnplus.listeners.OnFirstJoin; +import systems.kscott.randomspawnplus.listeners.OnPreLogin; +import systems.kscott.randomspawnplus.spawn.SpawnFinder; +import systems.kscott.randomspawnplus.spawn.SpawnGenerator; +import systems.kscott.randomspawnplus.util.PlatformUtil; +import org.bukkit.plugin.java.JavaPlugin; + +public final class RandomSpawnPlus extends JavaPlugin { + + private static RandomSpawnPlus INSTANCE; + private static HookInstance hookInstance; + public static final Logger LOGGER = LogManager.getLogger(RandomSpawnPlus.class.getSimpleName()); + private BukkitAudiences adventure; + public FoliaLib foliaLib = new FoliaLib(this); + + public @NotNull BukkitAudiences adventure() { + if (this.adventure == null) { + throw new IllegalStateException("Tried to access Adventure when the plugin was disabled!"); + } + + return this.adventure; + } + + @Override + public void onEnable() { + INSTANCE = this; + this.adventure = BukkitAudiences.create(this); + + Config.loadConfig(INSTANCE); + + registerEvents(); + registerCommands(); + registerHooks(); + + PlatformUtil.init(); + SpawnGenerator.init(); + //SpawnCacher.initialize(); + } + + @Override + public void onDisable() { + if (this.adventure != null) { + this.adventure.close(); + this.adventure = null; + } + + //SpawnCacher.getInstance().save(); + } + + private void registerEvents() { + if (!Config.getGlobalConfig().randomSpawnEnabled) return; + + getServer().getPluginManager().registerEvents(new OnDeath(), this); + getServer().getPluginManager().registerEvents(new OnPreLogin(), this); + getServer().getPluginManager().registerEvents(new OnFirstJoin(), this); + } + + private void registerCommands() { + PaperCommandManager manager = new PaperCommandManager(this); + manager.registerCommand(new CommandRSP()); + + if (Config.getGlobalConfig().wildEnabled) { + manager.registerCommand(new CommandWild()); + } + } + + public static RandomSpawnPlus getInstance() { + return INSTANCE; + } + + private void registerHooks() { + hookInstance = new HookInstance(this); + } + + public static HookInstance getHooks() { + return hookInstance.getInstance(); + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java b/Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java new file mode 100644 index 0000000..75920ba --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java @@ -0,0 +1,46 @@ +package systems.kscott.randomspawnplus.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.platforms.UniversalPlatform; +import systems.kscott.randomspawnplus.util.MessageUtil; +import org.bukkit.command.CommandSender; + +@CommandAlias("rsp|randomspawnplus") +@Description("Manage the plugin") +@CommandPermission("randomspawnplus.manage") +public class CommandRSP extends BaseCommand { + + @Default + @Subcommand("help|h") + public void _main(CommandSender player) { + MessageUtil.send(player, String.valueOf(UniversalPlatform.isAllSpawnRangeChunksGenerated())); + MessageUtil.send(player, String.valueOf(UniversalPlatform.getPendingGenerateChunksList().size())); + /* + MessageUtil.send(player, + "&8[&3RandomSpawnPlus&8] &7Running &bv" + RandomSpawnPlus.getInstance().getDescription().getVersion() + "&7, made with love &a:^)", + "", + "&b/rsp &8- &7The help menu.", + "&b/rsp reload &8- &7Reload the plugin configuration.", + "&b/wild &8- &7Randomly teleport yourself.", + "&b/wild &8- &7Randomly teleport another player.", + "&7Need help? Check out &bhttps://github.com/Winds-Studio/RandomSpawnPlus&7." + ); + */ + } + + @Subcommand("reload") + public void _reload(CommandSender player) { + /* + RandomSpawnPlus.getInstance().getConfigManager().reload(); + RandomSpawnPlus.getInstance().getLangManager().reload(); + RandomSpawnPlus.getInstance().getSpawnsManager().reload(); + */ + MessageUtil.send(player, "&8[&3RandomSpawnPlus&8] &7Reloaded &bconfig.yml&7, &blang.yml&7, and &bspawns.yml&7."); + } +} diff --git a/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java b/Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java similarity index 55% rename from src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java rename to Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java index fa2ecb3..315ef40 100644 --- a/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java @@ -6,17 +6,17 @@ import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Description; import com.earth2me.essentials.User; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.config.Config; import systems.kscott.randomspawnplus.events.RandomSpawnEvent; import systems.kscott.randomspawnplus.events.SpawnType; import systems.kscott.randomspawnplus.spawn.SpawnFinder; -import systems.kscott.randomspawnplus.util.Chat; -import systems.kscott.randomspawnplus.util.CooldownManager; +import systems.kscott.randomspawnplus.util.MessageUtil; +import systems.kscott.randomspawnplus.util.Util; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import java.time.Instant; @@ -24,67 +24,59 @@ @Description("Teleport to a random location") public class CommandWild extends BaseCommand { - private final FileConfiguration config; - - public CommandWild() { - this.config = RandomSpawnPlus.getInstance().getConfig(); - } - @Default @CommandPermission("randomspawnplus.wild") public void wild(CommandSender sender) { + /* if (!(sender instanceof Player)) { - Chat.msg(sender, Chat.get("console-cannot-use")); + MessageUtil.send(sender, Config.getLangConfig().playerOnly); return; } Player player = (Player) sender; - - long cooldown = CooldownManager.getCooldown(player); - + long cooldown = Util.getCooldown(player); if (player.hasPermission("randomspawnplus.wild.bypasscooldown")) { cooldown = 0; } if ((cooldown - Instant.now().toEpochMilli()) >= 0) { - if (config.getBoolean("debug-mode")) - System.out.println(cooldown); + String message = Config.getLangConfig().wildTpCooldown; + message = message.replace("%delay%", MessageUtil.getStringFromSeconds(cooldown / 1000 - Instant.now().getEpochSecond())); - - String message = Chat.get("wild-tp-cooldown"); - message = message.replace("%delay", Chat.timeLeft(cooldown / 1000 - Instant.now().getEpochSecond())); - - Chat.msg(player, message); + MessageUtil.send(player, message); return; - } - if (RandomSpawnPlus.getEconomy() != null && RandomSpawnPlus.getInstance().getConfig().getInt("wild-cost") != 0 && !player.hasPermission("randomspawnplus.wild.bypasscost")) { - if (RandomSpawnPlus.getEconomy().has(player, config.getInt("wild-cost"))) { - RandomSpawnPlus.getEconomy().withdrawPlayer(player, config.getInt("wild-cost")); + + int wildCost = Config.getGlobalConfig().wildCost; + + if (wildCost != 0 && RandomSpawnPlus.getHooks().getEconomy() != null && !player.hasPermission("randomspawnplus.wild.bypasscost")) { + if (RandomSpawnPlus.getHooks().getEconomy().has(player, wildCost)) { + RandomSpawnPlus.getHooks().getEconomy().withdrawPlayer(player, wildCost); } else { - Chat.msg(player, Chat.get("wild-no-money")); + MessageUtil.send(player, Config.getLangConfig().wildNoMoney); return; } } Location location; + try { location = SpawnFinder.getInstance().findSpawn(true); } catch (Exception e) { - Chat.msg(player, Chat.get("error-finding-spawn")); + MessageUtil.send(player, Config.getLangConfig().errorOnFindingSpawn); return; } + String message = Config.getLangConfig().wildTp + .replace("%x%", Integer.toString(location.getBlockX())) + .replace("%y%", Integer.toString(location.getBlockY())) + .replace("%z%", Integer.toString(location.getBlockZ())); - String message = Chat.get("wild-tp") - .replace("%x", Integer.toString(location.getBlockX())) - .replace("%y", Integer.toString(location.getBlockY())) - .replace("%z", Integer.toString(location.getBlockZ())); - Chat.msg(player, message); + MessageUtil.send(player, message); - if (config.getBoolean("home-on-wild")) { - User user = RandomSpawnPlus.getInstance().getEssentials().getUser(player); + if (Config.getGlobalConfig().setHomeOnWild && RandomSpawnPlus.getHooks().getEssentials() != null) { + User user = RandomSpawnPlus.getHooks().getEssentials().getUser(player); if (!user.hasHome()) { user.setHome("home", location); user.save(); @@ -95,44 +87,48 @@ public void wild(CommandSender sender) { Bukkit.getServer().getPluginManager().callEvent(randomSpawnEvent); RandomSpawnPlus.getInstance().foliaLib.getImpl().teleportAsync(player, location.add(0.5, 0, 0.5)); - CooldownManager.addCooldown(player); + Util.addCooldown(player); } @Default @CommandPermission("randomspawnplus.wild.others") public void wildOther(CommandSender sender, String otherPlayerString) { - Player otherPlayer = Bukkit.getPlayer(otherPlayerString); if (otherPlayer == null) { - Chat.msg(sender, Chat.get("invalid-player")); + MessageUtil.send(sender, Config.getLangConfig().invalidPlayer); return; } Location location; + try { location = SpawnFinder.getInstance().findSpawn(true); } catch (Exception e) { - Chat.msg(otherPlayer, Chat.get("error-finding-spawn")); + MessageUtil.send(otherPlayer, Config.getLangConfig().errorOnFindingSpawn); return; } - String message = Chat.get("wild-tp") - .replace("%x", Integer.toString(location.getBlockX())) - .replace("%y", Integer.toString(location.getBlockY())) - .replace("%z", Integer.toString(location.getBlockZ())); - Chat.msg(otherPlayer, message); + String message = Config.getLangConfig().wildTp + .replace("%x%", Integer.toString(location.getBlockX())) + .replace("%y%", Integer.toString(location.getBlockY())) + .replace("%z%", Integer.toString(location.getBlockZ())); + + MessageUtil.send(otherPlayer, message); - message = Chat.get("wild-tp-other"); - message = message.replace("%player", otherPlayer.getName()); - Chat.msg(sender, message); + message = Config.getLangConfig().wildTpOther.replace("%player%", otherPlayer.getName()); + + MessageUtil.send(sender, message); RandomSpawnEvent randomSpawnEvent = new RandomSpawnEvent(location, otherPlayer.getPlayer(), SpawnType.WILD_COMMAND); Bukkit.getServer().getPluginManager().callEvent(randomSpawnEvent); + if (!location.getChunk().isLoaded()) { location.getChunk().load(); } + RandomSpawnPlus.getInstance().foliaLib.getImpl().teleportAsync(otherPlayer, location.add(0.5, 0, 0.5)); + */ } } diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/config/Config.java b/Common/src/main/java/systems/kscott/randomspawnplus/config/Config.java new file mode 100644 index 0000000..ef3a1fa --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/config/Config.java @@ -0,0 +1,184 @@ +package systems.kscott.randomspawnplus.config; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.util.MessageUtil; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; + +public class Config { + + public static final Logger LOGGER = LogManager.getLogger(Config.class.getSimpleName()); + private static final String CURRENT_REGION = Locale.getDefault().getCountry().toUpperCase(Locale.ROOT); + private static final String GLOBAL_CONFIG_FILE_NAME = "config.yml"; + private static final String LANG_CONFIG_FILE_NAME = "lang.yml"; + private static final String SPAWN_STORAGE_FILE_NAME = "spawns.yml"; + + private static ConfigLocale configLocale; + private static GlobalConfig globalConfig; + private static LangConfig langConfig; + private static SpawnStorage spawnStorage; + + private static final String currConfigVer = "3.0"; + private static int lastConfigVerMajor; + private static int lastConfigVerMinor; + private static final int currConfigVerMajor = Integer.parseInt(currConfigVer.split("\\.")[0]); + private static final int currConfigVerMinor = Integer.parseInt(currConfigVer.split("\\.")[1]); + + @Contract(" -> new") + public static @NotNull CompletableFuture reloadAsync(CommandSender sender) { + return CompletableFuture.runAsync(() -> { + long begin = System.nanoTime(); + + File pluginFolder = RandomSpawnPlus.getInstance().getDataFolder(); + + try { + loadGlobalConfig(pluginFolder, false); + } catch (Exception e) { + MessageUtil.broadcastCommandMessage(sender, Component.text("Failed to reload " + GLOBAL_CONFIG_FILE_NAME + ". See error in console!", NamedTextColor.RED)); + LOGGER.error(e); + } + try { + loadLangConfig(pluginFolder, false); + } catch (Exception e) { + MessageUtil.broadcastCommandMessage(sender, Component.text("Failed to reload " + LANG_CONFIG_FILE_NAME + ". See error in console!", NamedTextColor.RED)); + LOGGER.error(e); + } + + final String success = String.format("Successfully reloaded config in %sms.", (System.nanoTime() - begin) / 1_000_000); + MessageUtil.broadcastCommandMessage(sender, Component.text(success, NamedTextColor.GREEN)); + }); + } + + public static void loadConfig(Plugin instance) { + long begin = System.nanoTime(); + LOGGER.info("Loading config..."); + + final File pluginFolder = instance.getDataFolder(); + + try { + createDirectory(pluginFolder); + } catch (Exception e) { + LOGGER.error("Failed to create <{}> plugin folder!", pluginFolder.getAbsolutePath(), e); + } + try { + loadGlobalConfig(pluginFolder, true); + } catch (Exception e) { + LOGGER.error("Failed to load " + GLOBAL_CONFIG_FILE_NAME + "!", e); + } + try { + loadLangConfig(pluginFolder, true); + } catch (Exception e) { + LOGGER.error("Failed to load " + LANG_CONFIG_FILE_NAME + "!", e); + } + try { + loadSpawnStorage(pluginFolder, true); + } catch (Exception e) { + LOGGER.error("Failed to load " + SPAWN_STORAGE_FILE_NAME + "!", e); + } + + LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000); + } + + private static void loadGlobalConfig(File pluginFolder, boolean init) throws Exception { + globalConfig = new GlobalConfig(pluginFolder, GLOBAL_CONFIG_FILE_NAME, init); + + globalConfig.saveConfig(); + } + + private static void loadLangConfig(File pluginFolder, boolean init) throws Exception { + langConfig = new LangConfig(pluginFolder, LANG_CONFIG_FILE_NAME, init); + + langConfig.saveConfig(); + } + + private static void loadSpawnStorage(File pluginFolder, boolean init) throws Exception { + spawnStorage = new SpawnStorage(pluginFolder, SPAWN_STORAGE_FILE_NAME, init); + + spawnStorage.saveConfig(); + } + + public static void createDirectory(File dir) throws IOException { + try { + Files.createDirectories(dir.toPath()); + } catch (FileAlreadyExistsException e) { // Thrown if dir exists but is not a directory + if (dir.delete()) createDirectory(dir); + } + } + + public static String getGlobalConfigHeader() { + return "#############################\n" + + "# RandomSpawnPlus5 #\n" + + "# Version 6.0.0 #\n" + + "# by @89apt89 & @Dreeam #\n" + + "#############################\n"; + } + + public static GlobalConfig getGlobalConfig() { + return globalConfig; + } + + public static LangConfig getLangConfig() { + return langConfig; + } + + public static void getLastConfigVersion(String lastConfigVer) { + // If last is null, then it means the plugin is fist loaded, fallback to current config version + if (lastConfigVer == null) lastConfigVer = currConfigVer; + + lastConfigVerMajor = Integer.parseInt(lastConfigVer.split("\\.")[0]); + lastConfigVerMinor = Integer.parseInt(lastConfigVer.split("\\.")[1]); + } + + public static String getCurrentConfigVersion() { + return currConfigVer; + } + + private ConfigLocale getConfigLocale() { + if (configLocale != null) { + ; + return configLocale; + } + + ConfigLocale locale; + switch (CURRENT_REGION) { + case "EN": { + locale = ConfigLocale.ENGLISH; + break; + } + case "CN": { + locale = ConfigLocale.CHINESE; + break; + } + case "RU": { + locale = ConfigLocale.RUSSIAN; + break; + } + default: { + locale = ConfigLocale.ENGLISH; + break; + } + } + + configLocale = locale; + return locale; + } + + enum ConfigLocale { + ENGLISH, + CHINESE, + RUSSIAN + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java b/Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java new file mode 100644 index 0000000..102499e --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java @@ -0,0 +1,277 @@ +package systems.kscott.randomspawnplus.config; + +import io.github.thatsmusic99.configurationmaster.api.ConfigFile; +import io.github.thatsmusic99.configurationmaster.api.ConfigSection; +import systems.kscott.randomspawnplus.RandomSpawnPlus; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class GlobalConfig { + + private static ConfigFile configFile; + + public boolean randomSpawnEnabled = true; + public String respawnWorld = "world"; + public int spawnCacheCount = 150; + public int spawnCacheTimeout = 1000; + public int spawnFindingFailedThreshold = 50; + + public int spawnRangeMinX = -1000; + public int spawnRangeMaxX = 1000; + public int spawnRangeMinZ = -1000; + public int spawnRangeMaxZ = 1000; + + public boolean blockedSpawnZoneEnabled = true; + public int blockedSpawnZoneMinX = -100; + public int blockedSpawnZoneMaxX = 100; + public int blockedSpawnZoneMinZ = -100; + public int blockedSpawnZoneMaxZ = 100; + + public boolean randomSpawnOnDeath = true; + public boolean randomSpawnOnFirstJoin = true; + public boolean randomSpawnAtBed = true; + public List unsafeBlocks = new ArrayList<>(Arrays.asList( + "#VINE", "#AIR", "WATER", "LAVA" // TODO Note: Add implementation for #Liquid + )); + public boolean randomSpawnUsePermNode = false; + + public String homeIntegrationMode = "essentialsx"; // TODO Note: Use enum + public boolean useHomeOnDeath = true; // TODO Note: override on-death + public boolean setHomeOnFirstJoinSpawn = true; // TODO Note: override on-first-join + + public boolean wildEnabled = true; + public int wildCooldown = 300; + public int wildDelay = 0; + public boolean wildAllowFirstUseForAll = false; + public int wildCost = 100; + public boolean setHomeOnWild = false; + + public GlobalConfig(File pluginFolder, String configFileName, boolean init) throws Exception { + configFile = ConfigFile.loadConfig(new File(pluginFolder, configFileName)); + + // Get config last version for upgrade task + Config.getLastConfigVersion(getString("config-version")); + + // Set current config version + configFile.set("config-version", Config.getCurrentConfigVersion()); + + // Add config header + addCommentRegionBased("config-version", + Config.getGlobalConfigHeader() + + "\n" + + "# NOTE: When modifying values such as spawn-range, and the cache is enabled, you'll need to reset spawns.yml.\n" + + "# This can be accomplished by simply deleting the spawns.yml and restarting the server.\n" + + "\n" + + "Don't touch this!\n" + ); + + // Pre-structure to force order + structureConfig(); + + // Init value for config keys + initConfig(); + + // Validate values and disable plugin directly. + validateConfigValues(); + } + + private void initConfig() { + final String generalPath = "general."; + final String spawnControlPath = "spawn-control."; + final String hooksPath = "hooks."; + final String wildCommandPath = "wild-command."; + + randomSpawnEnabled = getBoolean(generalPath + "random-spawn-enabled", randomSpawnEnabled, "Enable the random spawning feature? (disable this if you just want /wild)"); + respawnWorld = getString(generalPath + "respawn-world", respawnWorld, "Which world to respawn players in?"); + spawnCacheCount = getInt(generalPath + "spawn-cache-count", spawnCacheCount, "How many spawn locations should RandomSpawnPlus aim to keep cached?"); + spawnCacheTimeout = getInt(generalPath + "spawn-cache-timeout", spawnCacheTimeout, "How long the spawn locations cache should be refreshed?"); + spawnFindingFailedThreshold = getInt(generalPath + "spawn-finding-failed-threshold", spawnFindingFailedThreshold, "How many tries to find a valid spawn before the plugin times out?"); + + final String spawnRangePath = generalPath + "spawn-range."; + addComment(generalPath + "spawn-range", "The spawn range"); + spawnRangeMinX = getInt(spawnRangePath + "min-x", spawnRangeMinX); + spawnRangeMaxX = getInt(spawnRangePath + "max-x", spawnRangeMaxX); + spawnRangeMinZ = getInt(spawnRangePath + "min-z", spawnRangeMinZ); + spawnRangeMaxZ = getInt(spawnRangePath + "max-z", spawnRangeMaxZ); + + final String blockedSpawnZonePath = generalPath + "blocked-spawn-zone."; + addComment(generalPath + "blocked-spawn-zone", "The blocking range to prevent spawns in"); + blockedSpawnZoneEnabled = getBoolean(blockedSpawnZonePath + "enabled", blockedSpawnZoneEnabled); + blockedSpawnZoneMinX = getInt(blockedSpawnZonePath + "min-x", blockedSpawnZoneMinX); + blockedSpawnZoneMaxX = getInt(blockedSpawnZonePath + "max-x", blockedSpawnZoneMaxX); + blockedSpawnZoneMinZ = getInt(blockedSpawnZonePath + "min-z", blockedSpawnZoneMinZ); + blockedSpawnZoneMaxZ = getInt(blockedSpawnZonePath + "max-z", blockedSpawnZoneMaxZ); + + randomSpawnOnDeath = getBoolean(spawnControlPath + "on-death", randomSpawnOnDeath, "Will random spawn on player death?"); + randomSpawnOnFirstJoin = getBoolean(spawnControlPath + "on-first-join", randomSpawnOnFirstJoin, "Will random spawn on player first join?"); + randomSpawnAtBed = getBoolean(spawnControlPath + "spawn-at-bed", randomSpawnAtBed, "Will send the player to their bed on death?"); + unsafeBlocks = getList(spawnControlPath + "unsafe-blocks", unsafeBlocks, + "Blocks that the player shouldn't be able to spawn on\n" + + "Please use POST-FLATTENING 1.13+ block IDs - https://jd.papermc.io/paper/1.20/org/bukkit/Material.html\n" + ); + randomSpawnUsePermNode = getBoolean(spawnControlPath + "use-permission-node", randomSpawnUsePermNode, "Will only random spawn for player who has `randomspawnplus.randomspawn` permission node?"); + + homeIntegrationMode = getString(hooksPath + "home-integration-mode", homeIntegrationMode); + useHomeOnDeath = getBoolean(hooksPath + "use-home-on-death", useHomeOnDeath, "Use plugin's home on random spawn when dead"); + setHomeOnFirstJoinSpawn = getBoolean(hooksPath + "set-home-first-join-random-spawn", setHomeOnFirstJoinSpawn, "Use plugin's home on first join random spawn"); + + wildEnabled = getBoolean(wildCommandPath + "wild-enabled", wildEnabled, "Note: changes to this variable will require a restart."); + wildCooldown = getInt(wildCommandPath + "wild-cooldown", wildCooldown, + "Unit: seconds, how long between /wild uses?\n" + + "Set to 0 to disable.\n" + + "Set to -1 to blow up everything." + ); + wildDelay = getInt(wildCommandPath + "wild-delay", wildDelay); + wildAllowFirstUseForAll = getBoolean(wildCommandPath + "wild-allow-first-use-no-permission", wildAllowFirstUseForAll, + "When /wild is ran, should RandomSpawnPlus remove the `randomspawnplus.wild` permission from the executor?\n" + + "NOTE: Requires LuckPerms to be installed to manage permissions." + ); + wildCost = getInt(wildCommandPath + "wild-cost", wildCost, + "How much should it cost for a user to use /wild?\n" + + "Set to 0 to disable this feature.\n" + + "NOTE: Requires Vault & and a Vault-compatible econ plugin to function!" + ); + setHomeOnWild = getBoolean(wildCommandPath + "set-home-on-wild", setHomeOnWild, "Will set an Essentials home if no home is set on /wild"); + } + + private void validateConfigValues() { + List errors = new ArrayList<>(); + + if (RandomSpawnPlus.getInstance().getServer().getWorld(respawnWorld) == null) { + errors.add(""); + } + + // Collect, print then throw error + for (String error: errors) { + RandomSpawnPlus.LOGGER.error(error); + } + + //throw new RuntimeException(); + } + + public void saveConfig() throws Exception { + configFile.save(); + } + + private void structureConfig() { + createTitledSection("General", "general"); + createTitledSection("Random Spawn Control", "spawn-control"); + createTitledSection("Hooks", "hooks"); + createTitledSection("/wild", "wild-command"); + } + + private void createTitledSection(String title, String path) { + configFile.addSection(title); + configFile.addDefault(path, null); + } + + private boolean getBoolean(String path, boolean def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getBoolean(path, def); + } + + private boolean getBoolean(String path, boolean def) { + configFile.addDefault(path, def); + return configFile.getBoolean(path, def); + } + + private String getString(String path, String def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getString(path, def); + } + + private String getString(String path, String def) { + configFile.addDefault(path, def); + return configFile.getString(path, def); + } + + private double getDouble(String path, double def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getDouble(path, def); + } + + private double getDouble(String path, double def) { + configFile.addDefault(path, def); + return configFile.getDouble(path, def); + } + + private int getInt(String path, int def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getInteger(path, def); + } + + private int getInt(String path, int def) { + configFile.addDefault(path, def); + return configFile.getInteger(path, def); + } + + private List getList(String path, List def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getStringList(path); + } + + private List getList(String path, List def) { + configFile.addDefault(path, def); + return configFile.getStringList(path); + } + + private ConfigSection getConfigSection(String path, Map defaultKeyValue, String comment) { + configFile.addDefault(path, null, comment); + configFile.makeSectionLenient(path); + defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object)); + return configFile.getConfigSection(path); + } + + private ConfigSection getConfigSection(String path, Map defaultKeyValue) { + configFile.addDefault(path, null); + configFile.makeSectionLenient(path); + defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object)); + return configFile.getConfigSection(path); + } + + private Boolean getBoolean(String path) { + String value = configFile.getString(path, null); + + return value == null ? null : Boolean.parseBoolean(value); + } + + private String getString(String path) { + return configFile.getString(path, null); + } + + private Double getDouble(String path) { + String value = configFile.getString(path, null); + + return value == null ? null : Double.parseDouble(value); // TODO: Need to check whether need to handle NFE correctly + } + + private Integer getInt(String path) { + String value = configFile.getString(path, null); + + return value == null ? null : Integer.parseInt(value); // TODO: Need to check whether need to handle NFE correctly + } + + private List getList(String path) { + return configFile.getList(path, null); + } + + // TODO, check + private ConfigSection getConfigSection(String path) { + configFile.addDefault(path, null); + configFile.makeSectionLenient(path); + //defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object)); + return configFile.getConfigSection(path); + } + + private void addComment(String path, String comment) { + configFile.addComment(path, comment); + } + + private void addCommentRegionBased(String path, String... comments) { + configFile.addComment(path, comments[0]); // TODO + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java b/Common/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java new file mode 100644 index 0000000..46ca64c --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java @@ -0,0 +1,112 @@ +package systems.kscott.randomspawnplus.config; + +import io.github.thatsmusic99.configurationmaster.api.ConfigFile; + +import java.io.File; + +public class LangConfig { + + private static ConfigFile configFile; + + public String playerOnly = "&c[!] &7You must be a player to execute this command."; + public String errorOnFindingSpawn = "&cUnfortunately, I could not find a valid spawn for you. Please try again."; + public String noSpawnFound = "The spawn cacher is enabled, but there are no spawns cached. RSP will fall back to on-the-fly spawn generation. This is most likely an issue with your configuration, but you can join the support Discord (or contact apt#8099 on Discord) for help."; + public String invalidPlayer = "&c[!] &7That player does not exist!"; + public String spawnNotInitialized = "&c[!] &7Spawn is still initializing, try to join later!"; + + public String wildTp = "&a&l[!] &7You've been teleported to &b%x&7, &b%y&7, &b%z&7."; + public String wildTpOther = "&a&l[!] &b%player&7 has been successfully teleported."; + public String wildTpNonExist = "&c&l[!] &7That player doesn't exist."; + public String wildTpCooldown = "&c&l[!] &7You must wait&c%delay&7 to use that command."; + public String wildNoMoney = "&c[!] &7You don't have enough money to use &b/wild&7!"; + + public String delayDay = "day"; + public String delayDays = "days"; + public String delayHour = "hour"; + public String delayHours = "hours"; + public String delayMin = "minute"; + public String delayMins = "minutes"; + public String delaySec = "second"; + public String delaySecs = "seconds"; + + public LangConfig(File pluginFolder, String configFileName, boolean init) throws Exception { + configFile = ConfigFile.loadConfig(new File(pluginFolder, configFileName)); + + // Add config header + addCommentRegionBased("general", + Config.getGlobalConfigHeader() + + "\n" + + "\n" + ); + + // Pre-structure to force order + structureConfig(); + + // Init value for config keys + initConfig(); + } + + private void initConfig() { + final String generalPath = "general."; + final String commandPath = "command."; + final String delayPath = "delay."; + + playerOnly = getString(generalPath + "player-only", playerOnly); + errorOnFindingSpawn = getString(generalPath + "error-finding-spawn", errorOnFindingSpawn); + noSpawnFound = getString(generalPath + "no-spawn-found", noSpawnFound); // TODO Note: Should remove? + invalidPlayer = getString(generalPath + "invalid-player", invalidPlayer); + spawnNotInitialized = getString(generalPath + "spawn-not-initialized", spawnNotInitialized); + + wildTp = getString(commandPath + "wild-tp", wildTp, "Placeholders: %x% | %y% | %z%"); + wildTpOther = getString(commandPath + "wild-tp-other", wildTpOther, "Placeholders: %player%"); + wildTpNonExist = getString(commandPath + "wild-tp-doesnt-exist", wildTpNonExist); + wildTpCooldown = getString(commandPath + "wild-tp-cooldown", wildTpCooldown, "Placeholders: %delay%"); + wildNoMoney = getString(commandPath + "wild-no-money", wildNoMoney); + + delayDay = getString(delayPath + "day", delayDay); + delayDays = getString(delayPath + "days", delayDays); + delayHour = getString(delayPath + "hour", delayHour); + delayHours = getString(delayPath + "hours", delayHours); + delayMin = getString(delayPath + "minute", delayMin); + delayMins = getString(delayPath + "minutes", delayMins); + delaySec = getString(delayPath + "second", delaySec); + delaySecs = getString(delayPath + "seconds", delaySecs); + } + + public void saveConfig() throws Exception { + configFile.save(); + } + + private void structureConfig() { + createTitledSection("General", "general"); + createTitledSection("Command", "command"); + createTitledSection("Delay", "delay"); + } + + private void createTitledSection(String title, String path) { + configFile.addSection(title); + configFile.addDefault(path, null); + } + + private String getString(String path, String def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getString(path, def); + } + + private String getString(String path, String def) { + configFile.addDefault(path, def); + return configFile.getString(path, def); + } + + private String getString(String path) { + return configFile.getString(path, null); + } + + private void addComment(String path, String comment) { + configFile.addComment(path, comment); + } + + private void addCommentRegionBased(String path, String... comments) { + configFile.addComment(path, comments[0]); // TODO + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java b/Common/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java new file mode 100644 index 0000000..9d84d7e --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java @@ -0,0 +1,24 @@ +package systems.kscott.randomspawnplus.config; + +import io.github.thatsmusic99.configurationmaster.api.ConfigFile; + +import java.io.File; + +public class SpawnStorage { + + private static ConfigFile configFile; + + public SpawnStorage(File pluginFolder, String configFileName, boolean init) throws Exception { + configFile = ConfigFile.loadConfig(new File(pluginFolder, configFileName)); + + // Init value for config keys + initConfig(); + } + + private void initConfig() { + } + + public void saveConfig() throws Exception { + configFile.save(); + } +} diff --git a/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java b/Common/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java similarity index 91% rename from src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java rename to Common/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java index 7573acd..f89428f 100644 --- a/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java @@ -1,24 +1,18 @@ package systems.kscott.randomspawnplus.events; -import lombok.Getter; +import org.jetbrains.annotations.NotNull; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.NotNull; public class RandomSpawnEvent extends Event { - @Getter private static final HandlerList HANDLERS_LIST = new HandlerList(); - @Getter private final Location location; - @Getter private final Player player; - @Getter private final SpawnType spawnType; - public RandomSpawnEvent(Location location, Player player, SpawnType spawnType) { this.location = location; this.player = player; diff --git a/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java b/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java similarity index 91% rename from src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java rename to Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java index e5972c3..a4981fc 100644 --- a/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java @@ -1,18 +1,16 @@ package systems.kscott.randomspawnplus.events; -import lombok.Getter; +import org.jetbrains.annotations.NotNull; import org.bukkit.Location; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.NotNull; -@Getter public class SpawnCheckEvent extends Event { private static final HandlerList HANDLERS_LIST = new HandlerList(); private final Location location; private boolean valid; - private String validReason = "UNK"; + private String validReason = "Unknown"; public SpawnCheckEvent(Location location) { this.location = location; @@ -28,11 +26,8 @@ public static HandlerList getHandlerList() { return HANDLERS_LIST; } - public void setValid(boolean valid, String reason) { this.validReason = reason; this.valid = valid; } - - } diff --git a/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java b/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java similarity index 99% rename from src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java rename to Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java index a9a2f14..f7e8770 100644 --- a/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java @@ -1,6 +1,7 @@ package systems.kscott.randomspawnplus.events; public enum SpawnType { + FIRST_JOIN, ON_DEATH, WILD_COMMAND diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java b/Common/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java new file mode 100644 index 0000000..adcc99e --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java @@ -0,0 +1,89 @@ +package systems.kscott.randomspawnplus.hooks; + +import net.ess3.api.IEssentials; +import net.luckperms.api.LuckPerms; +import net.milkbowl.vault.economy.Economy; +import org.bstats.bukkit.Metrics; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredServiceProvider; + +public class HookInstance { + + private final RandomSpawnPlus instance; + + private IEssentials essentials; + private LuckPerms luckPerms; + private static Economy economy; + + public HookInstance(RandomSpawnPlus pluginInstance) { + instance = pluginInstance; + + registerHooks(); + } + + private void registerHooks() { + new Metrics(instance, 6465); // TODO Note: Use own bstats, since no one update. + + Plugin essPlugin = instance.getServer().getPluginManager().getPlugin("Essentials"); + + if (essPlugin != null) { + essentials = (IEssentials) essPlugin; + } + + if (instance.getServer().getPluginManager().getPlugin("LuckPerms") != null) { + try { + setupPermissions(); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + RandomSpawnPlus.LOGGER.warn("The LuckPerms API is not detected, so the 'remove-permission-on-first-use' config option will not be enabled."); + } + + if (instance.getServer().getPluginManager().getPlugin("Vault") != null) { + try { + setupEconomy(); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + RandomSpawnPlus.LOGGER.warn("The Vault API is not detected, so /wild cost will not be enabled."); + } + } + + private void setupPermissions() { + RegisteredServiceProvider rsp = instance.getServer().getServicesManager().getRegistration(LuckPerms.class); + if (rsp != null) { + luckPerms = rsp.getProvider(); + } else { + luckPerms = null; + } + } + + private boolean setupEconomy() { + RegisteredServiceProvider rsp = instance.getServer().getServicesManager().getRegistration(Economy.class); + + if (rsp == null) return false; + + economy = rsp.getProvider(); + + return economy != null; + } + + public HookInstance getInstance() { + return this; + } + + public IEssentials getEssentials() { + return essentials; + } + + public LuckPerms getLuckPerms() { + return luckPerms; + } + + public Economy getEconomy() { + return economy; + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java new file mode 100644 index 0000000..a735482 --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java @@ -0,0 +1,50 @@ +package systems.kscott.randomspawnplus.listeners; + +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.config.Config; +import systems.kscott.randomspawnplus.events.RandomSpawnEvent; +import systems.kscott.randomspawnplus.events.SpawnType; +import systems.kscott.randomspawnplus.spawn.SpawnFinder; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerRespawnEvent; + +public class OnDeath implements Listener { + + @EventHandler(priority = EventPriority.HIGHEST) + public void onDeath(PlayerRespawnEvent event) { + if (!Config.getGlobalConfig().randomSpawnOnDeath) { + return; + } + + Player player = event.getPlayer(); + + if (!player.isDead()) return; + + if (Config.getGlobalConfig().randomSpawnUsePermNode && !player.hasPermission("randomspawnplus.randomspawn")) + return; + + if (Config.getGlobalConfig().randomSpawnAtBed && player.getBedSpawnLocation() != null) { + event.setRespawnLocation(player.getBedSpawnLocation()); + return; + } + + Location location; + + try { + location = SpawnFinder.getRandomSpawn(); + } catch (Exception e) { + RandomSpawnPlus.getInstance().getLogger().warning("The spawn finder failed to find a valid spawn, and has not given " + player.getName() + " a random spawn. If you find this happening a lot, then raise the 'spawn-finder-tries-before-timeout' key in the config."); + return; + } + + RandomSpawnEvent randomSpawnEvent = new RandomSpawnEvent(location, player, SpawnType.ON_DEATH); + + Bukkit.getServer().getPluginManager().callEvent(randomSpawnEvent); + event.setRespawnLocation(location); + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnFirstJoin.java b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnFirstJoin.java new file mode 100644 index 0000000..c92243c --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnFirstJoin.java @@ -0,0 +1,72 @@ +package systems.kscott.randomspawnplus.listeners; + +import com.earth2me.essentials.User; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.config.Config; +import systems.kscott.randomspawnplus.events.RandomSpawnEvent; +import systems.kscott.randomspawnplus.events.SpawnType; +import systems.kscott.randomspawnplus.spawn.SpawnFinder; +import systems.kscott.randomspawnplus.util.Util; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +public class OnFirstJoin implements Listener { + + @EventHandler(priority = EventPriority.HIGHEST) + public void onFirstJoin(PlayerJoinEvent event) { + if (!Config.getGlobalConfig().randomSpawnOnFirstJoin) { + return; + } + + Player player = event.getPlayer(); + + if (!Util.firstJoinPlayers.contains(player.getUniqueId())) { + return; + } + + /* + if (config.getBoolean("use-permission-node") && !player.hasPermission("randomspawnplus.randomspawn")) { + Util.firstJoinPlayers.remove(player.getUniqueId()); + return; + } + */ + + try { + Location spawnLoc = SpawnFinder.getRandomSpawn(); + // quiquelhappy start - Prevent essentials home replace + boolean prevent = false; + + if (config.getBoolean("essentials-home-on-first-spawn") && RandomSpawnPlus.getHooks().getEssentials() != null) { + User user = RandomSpawnPlus.getHooks().getEssentials().getUser(player); + if (!user.hasHome()) { + user.setHome("home", spawnLoc); + user.save(); + } else { + prevent = true; + } + } + + if (!prevent) { + RandomSpawnPlus.getInstance().foliaLib.getImpl().runLater(() -> { + RandomSpawnEvent randomSpawnEvent = new RandomSpawnEvent(spawnLoc, player, SpawnType.FIRST_JOIN); + + Bukkit.getServer().getPluginManager().callEvent(randomSpawnEvent); + RandomSpawnPlus.getInstance().foliaLib.getImpl().teleportAsync(player, spawnLoc); + }, 3); + } else { + RandomSpawnPlus.getInstance().getLogger().warning("The spawn finder prevented a teleport for " + player.getUniqueId() + ", since essentials sethome is enabled and the player already had a home (perhaps old player data?)."); + } + // quiquelhappy end + } catch (Exception e) { + RandomSpawnPlus.getInstance().getLogger().warning("The spawn finder failed to find a valid spawn, and has not given " + player.getUniqueId() + " a random spawn. If you find this happening a lot, then raise the 'spawn-finder-tries-before-timeout' key in the config."); + return; + } + + Util.firstJoinPlayers.remove(player.getUniqueId()); + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnPreLogin.java b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnPreLogin.java new file mode 100644 index 0000000..cea47b3 --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnPreLogin.java @@ -0,0 +1,33 @@ +package systems.kscott.randomspawnplus.listeners; + +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.config.Config; +import systems.kscott.randomspawnplus.platforms.UniversalPlatform; +import systems.kscott.randomspawnplus.util.Util; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; + +import java.util.UUID; + +public class OnPreLogin implements Listener { + + @EventHandler + public void onPreLogin(AsyncPlayerPreLoginEvent event) { + if (!Config.getGlobalConfig().randomSpawnOnFirstJoin) { + return; + } + + if (!UniversalPlatform.isAllSpawnRangeChunksGenerated()) { + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Config.getLangConfig().spawnNotInitialized); + } + + final UUID uuid = event.getUniqueId(); + final OfflinePlayer player = RandomSpawnPlus.getInstance().getServer().getOfflinePlayer(uuid); + + if (!player.hasPlayedBefore()) { + Util.firstJoinPlayers.add(uuid); + } + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java b/Common/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java new file mode 100644 index 0000000..3b91933 --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java @@ -0,0 +1,28 @@ +package systems.kscott.randomspawnplus.platforms; + +import it.unimi.dsi.fastutil.longs.LongArrayList; + +public class UniversalPlatform { + + // Spawning status + private static LongArrayList pendingChunksForGen; + private static boolean isAllSpawnRangeChunksGenerated; + + // Check whether need to use method, or public field to access directly. + // Or move to interface as default? + public static void setPendingGenerateChunksList(LongArrayList chunks) { + pendingChunksForGen = chunks; + } + + public static LongArrayList getPendingGenerateChunksList() { + return pendingChunksForGen; + } + + public static void finalizeSpawnChunksGeneration() { + isAllSpawnRangeChunksGenerated = true; + } + + public static boolean isAllSpawnRangeChunksGenerated() { + return isAllSpawnRangeChunksGenerated; + } +} diff --git a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java similarity index 88% rename from src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java rename to Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java index 0a84bce..436cae4 100644 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java @@ -1,12 +1,10 @@ package systems.kscott.randomspawnplus.spawn; import com.tcoded.folialib.wrapper.task.WrappedTask; -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.configuration.file.FileConfiguration; import systems.kscott.randomspawnplus.RandomSpawnPlus; import systems.kscott.randomspawnplus.util.Locations; +import org.bukkit.Bukkit; +import org.bukkit.Location; import java.util.ArrayList; import java.util.List; @@ -16,16 +14,14 @@ public class SpawnCacher { public static SpawnCacher INSTANCE; - @Getter private boolean spawnsRequireSaving; - @Getter private final List cachedSpawns; private WrappedTask cacheSpawnTask; public SpawnCacher() { this.spawnsRequireSaving = false; this.cachedSpawns = new ArrayList<>(); - cacheSpawns(); + //cacheSpawns(); } public static void initialize() { @@ -36,11 +32,8 @@ public static SpawnCacher getInstance() { return INSTANCE; } + /* private void cacheSpawns() { - - FileConfiguration spawns = RandomSpawnPlus.getInstance().getSpawns(); - FileConfiguration config = RandomSpawnPlus.getInstance().getConfig(); - SpawnFinder finder = SpawnFinder.getInstance(); List locationStrings = spawns.getStringList("spawns"); @@ -71,19 +64,20 @@ private void cacheSpawns() { } cacheSpawnTask = RandomSpawnPlus.getInstance().foliaLib.getImpl().runTimer(() -> { - /* Wait for all spawns to be cached */ + Wait for all spawns to be cached if (newLocations.size() <= missingLocations) { if (RandomSpawnPlus.getInstance().getConfig().getBoolean("debug-mode")) { System.out.println(newLocations.size() + ", " + missingLocations); } } else { cachedSpawns.addAll(newLocations); - /* Save spawns to file */ + Save spawns to file save(); RandomSpawnPlus.getInstance().foliaLib.getImpl().cancelTask(cacheSpawnTask); } }, 10, 10); } + */ public Location getRandomSpawn() { int element = ThreadLocalRandom.current().nextInt(cachedSpawns.size()); @@ -95,8 +89,10 @@ public void deleteSpawn(Location location) { spawnsRequireSaving = true; } + /* public void save() { RandomSpawnPlus.getInstance().getSpawnsManager().getConfig().set("spawns", cachedSpawns); RandomSpawnPlus.getInstance().getSpawnsManager().save(); } + */ } diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnData.java b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnData.java new file mode 100644 index 0000000..35feab6 --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnData.java @@ -0,0 +1,10 @@ +package systems.kscott.randomspawnplus.spawn; + +import org.bukkit.Material; + +import java.util.ArrayList; + +public class SpawnData { + + private static ArrayList unsafeBlocks; +} diff --git a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java similarity index 72% rename from src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java rename to Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java index c86b6f6..acb29ac 100644 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java @@ -1,47 +1,40 @@ package systems.kscott.randomspawnplus.spawn; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.configuration.file.FileConfiguration; import systems.kscott.randomspawnplus.RandomSpawnPlus; -import systems.kscott.randomspawnplus.events.SpawnCheckEvent; -import systems.kscott.randomspawnplus.util.Chat; -import systems.kscott.randomspawnplus.util.Numbers; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; +import org.bukkit.Location; public class SpawnFinder { - public static SpawnFinder INSTANCE; - public FileConfiguration config; - ArrayList unsafeBlocks; - - public SpawnFinder() { - this.config = RandomSpawnPlus.getInstance().getConfig(); + public static Location getRandomSpawn() { + boolean valid = false; + Location location = null; - /* Setup safeblocks */ - List unsafeBlockStrings; - unsafeBlockStrings = config.getStringList("unsafe-blocks"); + int tries = 0; + while (!valid) { + if (tries >= 30) { + throw new Exception(); + } + if (SpawnCacher.getInstance().getCachedSpawns().isEmpty()) { + RandomSpawnPlus.getInstance().getLogger().severe(Chat.get("no-spawns-cached")); + } + if (!SpawnCacher.getInstance().getCachedSpawns().isEmpty()) { + location = SpawnCacher.getInstance().getRandomSpawn(); + } else { + location = getCandidateLocation(); + } + valid = checkSpawn(location); - unsafeBlocks = new ArrayList<>(); - for (String string : unsafeBlockStrings) { - unsafeBlocks.add(Material.matchMaterial(string)); + if (!valid) { + SpawnCacher.getInstance().deleteSpawn(location); + } + tries = tries + 1; } - } - - public static void initialize() { - INSTANCE = new SpawnFinder(); - } + if (location == null) return null; - public static SpawnFinder getInstance() { - return INSTANCE; + return location.add(0.5, 1, 0.5); } + /* public Location getCandidateLocation() { String worldString = config.getString("respawn-world"); @@ -85,59 +78,13 @@ public Location getCandidateLocation() { maxZ = region.getMaxZ(); } - int candidateX = Numbers.getRandomNumberInRange(minX, maxX); - int candidateZ = Numbers.getRandomNumberInRange(minZ, maxZ); + int candidateX = Util.getRandomNumberInRange(minX, maxX); + int candidateZ = Util.getRandomNumberInRange(minZ, maxZ); int candidateY = getHighestY(world, candidateX, candidateZ); return new Location(world, candidateX, candidateY, candidateZ); } - private Location getValidLocation(boolean useSpawnCaching) throws Exception { - boolean useCache = config.getBoolean("enable-spawn-cacher"); - - boolean valid = false; - - Location location = null; - - int tries = 0; - while (!valid) { - if (tries >= 30) { - throw new Exception(); - } - if (SpawnCacher.getInstance().getCachedSpawns().isEmpty()) { - RandomSpawnPlus.getInstance().getLogger().severe(Chat.get("no-spawns-cached")); - } - if (useCache && useSpawnCaching && !SpawnCacher.getInstance().getCachedSpawns().isEmpty()) { - location = SpawnCacher.getInstance().getRandomSpawn(); - } else { - location = getCandidateLocation(); - } - valid = checkSpawn(location); - - if (!valid && useCache && useSpawnCaching) { - SpawnCacher.getInstance().deleteSpawn(location); - } - tries = tries + 1; - } - if (location == null) return null; - return location; - } - - public Location findSpawn(boolean useSpawnCaching) throws Exception { - - Location location = getValidLocation(useSpawnCaching); - if (location == null) return null; - - if (config.getBoolean("debug-mode")) { - Location locClone = location.clone(); - System.out.println(locClone.getBlock().getType()); - System.out.println(locClone.add(0, 1, 0).getBlock().getType()); - System.out.println(locClone.add(0, 1, 0).getBlock().getType()); - System.out.println("Spawned at " + location.getBlockX() + ", " + location.getBlockY() + ", " + location.getBlockZ()); - } - return location.add(0, 1, 0); - } - public boolean checkSpawn(Location location) { if (location == null) return false; @@ -176,10 +123,10 @@ public boolean checkSpawn(Location location) { } if (blockedSpawnRange) { - if (Numbers.betweenExclusive((int) location.getX(), blockedMinX, blockedMaxX)) { + if (Util.betweenExclusive((int) location.getX(), blockedMinX, blockedMaxX)) { isValid = false; } - if (Numbers.betweenExclusive((int) location.getZ(), blockedMinZ, blockedMaxZ)) { + if (Util.betweenExclusive((int) location.getZ(), blockedMinZ, blockedMaxZ)) { isValid = false; } } @@ -241,6 +188,5 @@ public int getHighestY(World world, int x, int z) { } return minHeight; } - - + */ } diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnGenerator.java b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnGenerator.java new file mode 100644 index 0000000..cd15bcd --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnGenerator.java @@ -0,0 +1,48 @@ +package systems.kscott.randomspawnplus.spawn; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import systems.kscott.randomspawnplus.config.Config; +import systems.kscott.randomspawnplus.platforms.UniversalPlatform; +import systems.kscott.randomspawnplus.util.PlatformUtil; +import org.bukkit.Bukkit; +import org.bukkit.World; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class SpawnGenerator { + + public static void init() { + initSpawnChunks(); + + /* Setup safeblocks */ + List unsafeBlockStrings; + //unsafeBlockStrings = config.getStringList("unsafe-blocks"); + + //unsafeBlocks = new ArrayList<>(); + //for (String string : unsafeBlockStrings) { + // unsafeBlocks.add(Material.matchMaterial(string)); + //} + } + + private static void initSpawnChunks() { + String worldStr = Config.getGlobalConfig().respawnWorld; + World spawnLevel = Bukkit.getWorld(worldStr); + int minX = Config.getGlobalConfig().spawnRangeMinX; + int minZ = Config.getGlobalConfig().spawnRangeMinZ; + int maxX = Config.getGlobalConfig().spawnRangeMaxX; + int maxZ = Config.getGlobalConfig().spawnRangeMaxZ; + + long start = System.currentTimeMillis(); + CompletableFuture prepareChunksTask = PlatformUtil.getPlatform().collectNonGeneratedChunksAsync(spawnLevel, minX, minZ, maxX, maxZ); + + prepareChunksTask.thenAccept($ -> { + System.out.println("Prepare chunks took " + (System.currentTimeMillis() - start) + "ms"); + UniversalPlatform.finalizeSpawnChunksGeneration(); + }); + } + + private static void initSpawnPoints() { + + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java new file mode 100644 index 0000000..1c56d6f --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java @@ -0,0 +1,32 @@ +package systems.kscott.randomspawnplus.spawn; + +public class SpawnRegion { + + private final int minX; + private final int maxX; + private final int minZ; + private final int maxZ; + + public SpawnRegion(int minX, int maxX, int minZ, int maxZ) { + this.minX = minX; + this.maxX = maxX; + this.minZ = minZ; + this.maxZ = maxZ; + } + + public int getMinX() { + return minX; + } + + public int getMaxX() { + return maxX; + } + + public int getMinZ() { + return minZ; + } + + public int getMaxZ() { + return maxZ; + } +} diff --git a/src/main/java/systems/kscott/randomspawnplus/util/Locations.java b/Common/src/main/java/systems/kscott/randomspawnplus/util/Locations.java similarity index 99% rename from src/main/java/systems/kscott/randomspawnplus/util/Locations.java rename to Common/src/main/java/systems/kscott/randomspawnplus/util/Locations.java index a6c4665..157b291 100644 --- a/src/main/java/systems/kscott/randomspawnplus/util/Locations.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/util/Locations.java @@ -49,5 +49,4 @@ public static List serializeStringList(List locations) { return locStrings; } - } diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java b/Common/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java new file mode 100644 index 0000000..fe3165e --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java @@ -0,0 +1,49 @@ +package systems.kscott.randomspawnplus.util; + +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.config.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class MessageUtil { + + public static void broadcastCommandMessage(CommandSender sender, Object message) { + PlatformUtil.getPlatform().broadcastCommandMessage(sender, message); + } + + // move to NMS module + public static void send(Player player, String... messages) { + for (String message : messages) { + RandomSpawnPlus.getInstance().adventure().player(player).sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message)); + } + } + + public static void send(CommandSender sender, String... messages) { + for (String message : messages) { + RandomSpawnPlus.getInstance().adventure().sender(sender).sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message)); + } + } + + public static String getStringFromSeconds(long seconds) { + final long days = seconds / 86400L; + final long hours = seconds / 3600L % 24L; + final long minutes = seconds / 60L % 60L; + final long secs = seconds % 60L; + + final String daysStr = days > 0L + ? " " + days + " " + (days != 1 ? Config.getLangConfig().delayDays : Config.getLangConfig().delayDay) + : ""; + final String hoursStr = hours > 0L + ? " " + hours + " " + (hours != 1 ? Config.getLangConfig().delayHours : Config.getLangConfig().delayHour) + : ""; + final String minsStr = minutes > 0L + ? " " + minutes + " " + (minutes != 1 ? Config.getLangConfig().delayMins : Config.getLangConfig().delayMin) + : ""; + final String secsStr = secs > 0L + ? " " + secs + " " + (secs != 1 ? Config.getLangConfig().delaySecs : Config.getLangConfig().delaySec) + : ""; + + return daysStr + hoursStr + minsStr + secsStr; + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java b/Common/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java new file mode 100644 index 0000000..f0dda1b --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java @@ -0,0 +1,30 @@ +package systems.kscott.randomspawnplus.util; + +import systems.kscott.randomspawnplus.platforms.FoliaPlatform; +import systems.kscott.randomspawnplus.platforms.PaperPlatform; +import systems.kscott.randomspawnplus.platforms.Platforms; +import systems.kscott.randomspawnplus.platforms.SpigotPlatform; + +public class PlatformUtil { + + private static Platforms platform; + + public static Platforms init() { + // Folia + if (Util.doesClassExists("io.papermc.paper.threadedregions.RegionizedServer")) { + return platform = new FoliaPlatform(); + } + + // Paper + if (Util.doesClassExists("io.papermc.paper.configuration.GlobalConfiguration")) { + return platform = new PaperPlatform(); + } + + // Spigot (Fallback) + return platform = new SpigotPlatform(); + } + + public static Platforms getPlatform() { + return platform; + } +} diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/util/Util.java b/Common/src/main/java/systems/kscott/randomspawnplus/util/Util.java new file mode 100644 index 0000000..6f337a2 --- /dev/null +++ b/Common/src/main/java/systems/kscott/randomspawnplus/util/Util.java @@ -0,0 +1,57 @@ +package systems.kscott.randomspawnplus.util; + +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import org.bukkit.entity.Player; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +public class Util { + + public static List firstJoinPlayers = new ArrayList<>(); + public static Map cooldowns = new HashMap<>(); + + public static int getRandomNumberInRange(int min, int max) { + + if (min >= max) { + throw new IllegalArgumentException("max must be greater than min"); + } + + return ThreadLocalRandom.current().nextInt((max - min) + 1) + min; + } + + public static boolean betweenExclusive(int x, int min, int max) { + return x > min && x < max; + } + + public static void addCooldown(Player p) { + int cooldown = RandomSpawnPlus.getInstance().getConfig().getInt("wild-cooldown"); + long now = Instant.now().toEpochMilli(); + long future = now + TimeUnit.SECONDS.toMillis(cooldown); + + cooldowns.put(p.getUniqueId().toString(), future); + } + + public static long getCooldown(Player p) { + if (!cooldowns.containsKey(p.getUniqueId().toString())) { + return 0; + } + + return cooldowns.get(p.getUniqueId().toString()); + } + + public static boolean doesClassExists(String clazz) { + try { + Class.forName(clazz); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/src/main/resources/config.yml b/Common/src/main/resources/config.yml similarity index 100% rename from src/main/resources/config.yml rename to Common/src/main/resources/config.yml diff --git a/src/main/resources/lang.yml b/Common/src/main/resources/lang.yml similarity index 100% rename from src/main/resources/lang.yml rename to Common/src/main/resources/lang.yml diff --git a/src/main/resources/plugin.yml b/Common/src/main/resources/plugin.yml similarity index 89% rename from src/main/resources/plugin.yml rename to Common/src/main/resources/plugin.yml index 8ff757b..f79ff31 100644 --- a/src/main/resources/plugin.yml +++ b/Common/src/main/resources/plugin.yml @@ -1,10 +1,15 @@ -name: RandomSpawnPlus5 +name: RandomSpawnPlus version: ${version} main: systems.kscott.randomspawnplus.RandomSpawnPlus api-version: 1.13 folia-supported: true -softdepend: [Essentials, Factions, Vault, LuckPerms] -authors: [ 89apt89, Dreeam ] +softdepend: + - Essentials + - Vault + - LuckPerms +authors: + - 89apt89 + - Dreeam-qwq description: A comprehensive random spawning plugin for modern Minecraft versions. load: POSTWORLD diff --git a/src/main/resources/spawns.yml b/Common/src/main/resources/spawns.yml similarity index 100% rename from src/main/resources/spawns.yml rename to Common/src/main/resources/spawns.yml diff --git a/Platform/Abstraction/build.gradle.kts b/Platform/Abstraction/build.gradle.kts new file mode 100644 index 0000000..faf7492 --- /dev/null +++ b/Platform/Abstraction/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("cn.dreeam.rsp.wrapper") +} + +dependencies { + compileOnly("org.spigotmc:spigot-api:1.13.2-R0.1-SNAPSHOT") + + compileOnly("it.unimi.dsi:fastutil:8.5.15") +} diff --git a/Platform/Abstraction/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java b/Platform/Abstraction/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java new file mode 100644 index 0000000..1f3b286 --- /dev/null +++ b/Platform/Abstraction/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java @@ -0,0 +1,14 @@ +package systems.kscott.randomspawnplus.platforms; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +import java.util.concurrent.CompletableFuture; + +public interface Platforms { + + CompletableFuture collectNonGeneratedChunksAsync(World level, int minX, int minZ, int maxX, int maxZ); + + void broadcastCommandMessage(CommandSender sender, Object message); +} diff --git a/Platform/Folia/build.gradle.kts b/Platform/Folia/build.gradle.kts new file mode 100644 index 0000000..6116e15 --- /dev/null +++ b/Platform/Folia/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("cn.dreeam.rsp.wrapper") +} + +dependencies { + compileOnly(project(":Platform:Abstraction")) + compileOnly(project(":Platform:Paper")) + + compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT") +} diff --git a/Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java b/Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java new file mode 100644 index 0000000..f654de7 --- /dev/null +++ b/Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java @@ -0,0 +1,21 @@ +package systems.kscott.randomspawnplus.platforms; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +import java.util.concurrent.CompletableFuture; + +public class FoliaPlatform extends PaperPlatform { + + @Override + public CompletableFuture collectNonGeneratedChunksAsync(World level, int minX, int minZ, int maxX, int maxZ) { + // TODO: check if work on folia and remove it. + return super.collectNonGeneratedChunksAsync(level, minX, minZ, maxX, maxZ); + } + + @Override + public void broadcastCommandMessage(CommandSender sender, Object message) { + throw new UnsupportedOperationException(); + } +} diff --git a/Platform/Paper/build.gradle.kts b/Platform/Paper/build.gradle.kts new file mode 100644 index 0000000..790fa3e --- /dev/null +++ b/Platform/Paper/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("cn.dreeam.rsp.wrapper") +} + +dependencies { + compileOnly(project(":Platform:Abstraction")) + + compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") +} diff --git a/Platform/Paper/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java b/Platform/Paper/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java new file mode 100644 index 0000000..d70a9af --- /dev/null +++ b/Platform/Paper/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java @@ -0,0 +1,87 @@ +package systems.kscott.randomspawnplus.platforms; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +public class PaperPlatform implements Platforms { + + @Override + public CompletableFuture collectNonGeneratedChunksAsync(World level, int minX, int minZ, int maxX, int maxZ) { + return collectNonGeneratedChunks(level, minX, minZ, maxX, maxZ); + } + + private static final AtomicInteger counter = new AtomicInteger(0); + + private CompletableFuture collectNonGeneratedChunks(World level, int minX, int minZ, int maxX, int maxZ) { + int minChunkX = minX >> 4; + int minChunkZ = minZ >> 4; + int maxChunkX = maxX >> 4; + int maxChunkZ = maxZ >> 4; + + LongArrayList chunks = new LongArrayList(); + LongArrayList nonGeneratedChunksTotal = new LongArrayList(); + List> futures = new ArrayList<>(); + int parallelThreads = Runtime.getRuntime().availableProcessors() * 4; + + // Collect all chunks into one list + for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { + for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { + long chunkKey = Chunk.getChunkKey(chunkX, chunkZ); + chunks.add(chunkKey); + } + } + + int partitionSize = chunks.size() / parallelThreads; + + for (int i = 0; i < chunks.size(); i += partitionSize) { + int end = Math.min(chunks.size(), i + partitionSize); + LongList partitionList = chunks.subList(i, end); + CompletableFuture partitionChunksCollectTask = checkChunksGenerated(level, partitionList); + + partitionChunksCollectTask.thenAccept(nonGeneratedChunksTotal::addAll); + futures.add(partitionChunksCollectTask); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply($ -> nonGeneratedChunksTotal); + } + + private static CompletableFuture checkChunksGenerated(World level, LongList partitionChunks) { + LongArrayList nonGeneratedChunks = new LongArrayList(); + + return CompletableFuture.supplyAsync(() -> { + int count = 0; + for (long chunkKey : partitionChunks) { + int chunkX = (int) chunkKey; + int chunkZ = (int) (chunkKey >> 32); + + counter.incrementAndGet(); + if (count++ == 1000) { + System.out.println(counter.get()); + System.out.println("NON chunks size: " + nonGeneratedChunks.size()); + count = 0; + } + + if (!level.isChunkGenerated(chunkX, chunkZ)) { + nonGeneratedChunks.add(chunkKey); + } + } + + return nonGeneratedChunks; + } + ); + } + + @Override + public void broadcastCommandMessage(CommandSender sender, Object message) { + throw new UnsupportedOperationException(); + } +} diff --git a/Platform/Spigot/build.gradle.kts b/Platform/Spigot/build.gradle.kts new file mode 100644 index 0000000..9b54e33 --- /dev/null +++ b/Platform/Spigot/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("cn.dreeam.rsp.wrapper") +} + +dependencies { + compileOnly(project(":Platform:Abstraction")) + + compileOnly("org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT") + + compileOnly("it.unimi.dsi:fastutil:8.5.15") +} diff --git a/Platform/Spigot/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java b/Platform/Spigot/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java new file mode 100644 index 0000000..cb03f6d --- /dev/null +++ b/Platform/Spigot/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java @@ -0,0 +1,46 @@ +package systems.kscott.randomspawnplus.platforms; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +import java.util.concurrent.CompletableFuture; + +public class SpigotPlatform implements Platforms { + + @Override + public CompletableFuture collectNonGeneratedChunksAsync(World level, int minX, int minZ, int maxX, int maxZ) { + throw new IllegalStateException("SpigotPlatform does not support loadNonGeneratedChunksAsync"); +// return CompletableFuture.runAsync( +// () -> collectNonGeneratedChunks(level, minX, minZ, maxX, maxZ) +// ); + } + + private LongArrayList collectNonGeneratedChunks(World level, int minX, int minZ, int maxX, int maxZ) { + int minChunkX = minX >> 4; + int minChunkZ = minZ >> 4; + int maxChunkX = maxX >> 4; + int maxChunkZ = maxZ >> 4; + + LongArrayList chunks = new LongArrayList(); + + for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { + for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { + Chunk chunk = level.getChunkAt(chunkX, chunkZ, true); + + if (!chunk.isGenerated()) { + long chunkKey = (long) chunkX & 0xffffffffL | ((long) chunkZ & 0xffffffffL) << 32; + chunks.add(chunkKey); + } + } + } + + return chunks; + } + + @Override + public void broadcastCommandMessage(CommandSender sender, Object message) { + throw new UnsupportedOperationException(); + } +} diff --git a/README.md b/README.md index da0c044..bdf108c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# RandomSpawnPlus5 +# RandomSpawnPlus 🔀 A random spawn plugin for modern Minecraft version ## Compatibility -- Support Java 8 and higher -- Support 1.12.2 - Latest Minecraft version (1.21.1) +- Support Java 17 and higher +- Support 1.12.2 - Latest Minecraft version (1.21.4) - Compatible with Spigot / Paper / Paper Forks / Forge+Bukkit Hybrid Server - Folia Support @@ -17,6 +17,40 @@ - 📫 Discord: `dreeam___` | QQ: `2682173972` +## TODO + +if Chunky/others enabled, hook them to generate/load + +spawn height, Compatible with lower version players + +Check ESS (Home integration), and whether override on-death +and on-first-join in listener and other kind of places + +Optimize implementations in Util + +spawn status command + +reload listeners + +spawn or spawns? + +- [ ] CommandRSP +- [ ] CommandWild +- [ ] Config +- [ ] GlobalConfig +- [ ] LangConfig +- [ ] SpawnStorage +- [ ] OnDeath +- [ ] OnFirstJoin +- [ ] FoliaPlatform +- [ ] PaperPlatform +- [ ] Platforms +- [ ] UniversalPlatform +- [ ] SpawnCacher +- [ ] SpawnFinder +- [ ] SpawnRegion +- [ ] Config comments improve + ## Special Thanks To: Jianke Cloud Host diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..cc02e63 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() +} diff --git a/build-logic/src/main/kotlin/cn.dreeam.rsp.wrapper.gradle.kts b/build-logic/src/main/kotlin/cn.dreeam.rsp.wrapper.gradle.kts new file mode 100644 index 0000000..2b6e859 --- /dev/null +++ b/build-logic/src/main/kotlin/cn.dreeam.rsp.wrapper.gradle.kts @@ -0,0 +1,66 @@ +plugins { + `java-library` + `maven-publish` +} + +group = "systems.kscott.randomspawnplus" +version = "6.0.0" + +repositories { + mavenCentral() + + // PaperMC + maven { + name = "papermc-repo" + url = uri("https://repo.papermc.io/repository/maven-public/") + } + + // EssentialsX + maven { + name = "ess-repo" + url = uri("https://repo.essentialsx.net/releases/") + } + + // acf-paper + maven { + name = "aikar-repo" + url = uri("https://repo.aikar.co/content/groups/aikar/") + } + + // ConfigurationMaster API + maven { + name = "ConfigurationMaster-repo" + url = uri("https://repo.bsdevelopment.org/releases/") + } + + // JitPack + maven { + name = "jitpack.io" + url = uri("https://jitpack.io/") + } + + // FoliaLib + maven { + name = "devmart-other" + url = uri("https://nexuslite.gcnt.net/repos/other/") + } +} + +configure { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +tasks { + withType { + options.encoding = "UTF-8" + } + + processResources { + filesMatching("**/plugin.yml") { + expand( + "version" to project.version + ) + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index efc9ed4..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,109 +0,0 @@ -plugins { - `java-library` - `maven-publish` - id("com.gradleup.shadow") version "8.3.5" -} - -group = "systems.kscott" -version = "5.1.0" - -repositories { - mavenCentral() - - // PaperMC - maven { - name = "papermc-repo" - url = uri("https://repo.papermc.io/repository/maven-public/") - } - - // EssentialsX - maven { - name = "ess-repo" - url = uri("https://repo.essentialsx.net/releases/") - } - - // acf-paper - maven { - name = "aikar-repo" - url = uri("https://repo.aikar.co/content/groups/aikar/") - } - - // JitPack - maven { - name = "jitpack.io" - url = uri("https://jitpack.io") - } - - // FoliaLib - maven { - name = "devmart-other" - url = uri("https://nexuslite.gcnt.net/repos/other/") - } -} - -val adventureVersion = "4.16.0" - -dependencies { - compileOnly("org.spigotmc:spigot-api:1.21.3-R0.1-SNAPSHOT") - - compileOnly("org.apache.commons:commons-lang3:3.17.0") - compileOnly("org.projectlombok:lombok:1.18.32") - annotationProcessor("org.projectlombok:lombok:1.18.32") - api("org.bstats:bstats-bukkit:3.1.0") - api("co.aikar:acf-paper:0.5.1-SNAPSHOT") - api("com.tcoded:FoliaLib:0.4.2") - - compileOnly("net.essentialsx:EssentialsX:2.20.1") - compileOnly("net.luckperms:api:5.4") - compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") - - api("net.kyori:adventure-platform-bukkit:4.3.4") - api("net.kyori:adventure-api:$adventureVersion") - api("net.kyori:adventure-text-serializer-legacy:$adventureVersion") -} - -tasks.withType { - options.encoding = "UTF-8" -} - -configure { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -tasks.build.configure { - dependsOn("shadowJar") -} - -tasks.withType { - archiveFileName = "${project.name}-${project.version}.jar" - exclude("META-INF/**", // Dreeam - Avoid to include META-INF/maven in Jar - "com/cryptomorin/xseries/XBiome*", - "com/cryptomorin/xseries/NMSExtras*", - "com/cryptomorin/xseries/NoteBlockMusic*", - "com/cryptomorin/xseries/SkullCacheListener*") - minimize { - exclude(dependency("com.tcoded.folialib:.*:.*")) - } - relocate("net.kyori", "systems.kscott.randomspawnplus.libs.kyori") - relocate("co.aikar.commands", "systems.kscott.randomspawnplus.libs.acf.commands") - relocate("co.aikar.locales", "systems.kscott.randomspawnplus.libs.acf.locales") - relocate("com.cryptomorin.xseries", "systems.kscott.randomspawnplus.libs.xseries") - relocate("org.bstats", "systems.kscott.randomspawnplus.libs.bstats") - relocate("com.tcoded.folialib", "systems.kscott.randomspawnplus.libs.folialib") -} - -tasks { - processResources { - filesMatching("**/plugin.yml") { - expand("version" to project.version) - } - } -} - -publishing { - publications.create("maven") { - from(components["java"]) - } -} - diff --git a/gradle.properties b/gradle.properties index 06d0ef9..fcee178 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.caching=true org.gradle.parallel=true -org.gradle.vfs.watch=false \ No newline at end of file +org.gradle.vfs.watch=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd49..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e09..ff23a68 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..23d15a9 100644 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle.kts b/settings.gradle.kts index 73587a9..e7c85d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,17 @@ +pluginManagement { + includeBuild("build-logic") + repositories { + gradlePluginPortal() + } +} + rootProject.name = "randomspawnplus" + +include( + ":Common", + + ":Platform:Abstraction", + ":Platform:Folia", + ":Platform:Paper", + ":Platform:Spigot", +) diff --git a/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java b/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java deleted file mode 100644 index cd7bef6..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java +++ /dev/null @@ -1,152 +0,0 @@ -package systems.kscott.randomspawnplus; - -import co.aikar.commands.PaperCommandManager; -import com.tcoded.folialib.FoliaLib; -import lombok.Getter; -import net.ess3.api.IEssentials; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import net.luckperms.api.LuckPerms; -import net.milkbowl.vault.economy.Economy; -import org.bstats.bukkit.Metrics; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.NotNull; -import systems.kscott.randomspawnplus.commands.CommandRSP; -import systems.kscott.randomspawnplus.commands.CommandWild; -import systems.kscott.randomspawnplus.listeners.RSPDeathListener; -import systems.kscott.randomspawnplus.listeners.RSPFirstJoinListener; -import systems.kscott.randomspawnplus.listeners.RSPLoginListener; -import systems.kscott.randomspawnplus.spawn.SpawnCacher; -import systems.kscott.randomspawnplus.spawn.SpawnFinder; -import systems.kscott.randomspawnplus.util.Chat; -import systems.kscott.randomspawnplus.util.ConfigFile; - -public final class RandomSpawnPlus extends JavaPlugin { - - private static RandomSpawnPlus INSTANCE; - public FoliaLib foliaLib = new FoliaLib(this); - - @Getter - private ConfigFile configManager; - @Getter - private ConfigFile langManager; - @Getter - private ConfigFile spawnsManager; - - @Getter - private static Economy economy = null; - @Getter - private LuckPerms luckPerms; - - public static RandomSpawnPlus getInstance() { - return INSTANCE; - } - - private BukkitAudiences adventure; - public @NotNull BukkitAudiences adventure() { - if (this.adventure == null) { - throw new IllegalStateException("Tried to access Adventure when the plugin was disabled!"); - } - return this.adventure; - } - - @Override - public void onEnable() { - - INSTANCE = this; - this.adventure = BukkitAudiences.create(this); - - configManager = new ConfigFile("config.yml"); - langManager = new ConfigFile("lang.yml"); - spawnsManager = new ConfigFile("spawns.yml"); - - Chat.setLang(langManager.getConfig()); - - registerEvents(); - registerCommands(); - - SpawnFinder.initialize(); - SpawnCacher.initialize(); - Chat.initialize(); - - new Metrics(this, 6465); - - if (getServer().getPluginManager().getPlugin("LuckPerms") != null) { - /* LuckPerms is installed */ - try { - setupPermissions(); - } catch (Exception e) { - e.printStackTrace(); - } - } else { - getLogger().warning("The LuckPerms API is not detected, so the 'remove-permission-on-first-use' config option will not be enabled."); - } - - if (getServer().getPluginManager().getPlugin("Vault") != null) { - /* Vault is installed */ - try { - setupEconomy(); - } catch (Exception e) { - e.printStackTrace(); - } - } else { - getLogger().warning("The Vault API is not detected, so /wild cost will not be enabled."); - } - } - - @Override - public void onDisable() { - if (this.adventure != null) { - this.adventure.close(); - this.adventure = null; - } - SpawnCacher.getInstance().save(); - } - - public void registerEvents() { - getServer().getPluginManager().registerEvents(new RSPDeathListener(), this); - getServer().getPluginManager().registerEvents(new RSPLoginListener(), this); - getServer().getPluginManager().registerEvents(new RSPFirstJoinListener(), this); - } - - public void registerCommands() { - PaperCommandManager manager = new PaperCommandManager(this); - manager.registerCommand(new CommandRSP()); - if (configManager.getConfig().getBoolean("wild-enabled")) { - manager.registerCommand(new CommandWild()); - } - } - - public IEssentials getEssentials() { - return (IEssentials) getServer().getPluginManager().getPlugin("Essentials"); - } - - public @NotNull FileConfiguration getConfig() { - return configManager.getConfig(); - } - - public FileConfiguration getLang() { - return langManager.getConfig(); - } - - public FileConfiguration getSpawns() { - return spawnsManager.getConfig(); - } - - private void setupPermissions() { - RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(LuckPerms.class); - if (rsp != null) { - luckPerms = rsp.getProvider(); - } else { - luckPerms = null; - } - } - - private boolean setupEconomy() { - RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); - if (rsp == null) return false; - economy = rsp.getProvider(); - return economy != null; - } -} diff --git a/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java b/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java deleted file mode 100644 index 1274b76..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java +++ /dev/null @@ -1,38 +0,0 @@ -package systems.kscott.randomspawnplus.commands; - -import co.aikar.commands.BaseCommand; -import co.aikar.commands.annotation.CommandAlias; -import co.aikar.commands.annotation.CommandPermission; -import co.aikar.commands.annotation.Default; -import co.aikar.commands.annotation.Description; -import co.aikar.commands.annotation.Subcommand; -import org.bukkit.command.CommandSender; -import systems.kscott.randomspawnplus.RandomSpawnPlus; -import systems.kscott.randomspawnplus.util.Chat; - -@CommandAlias("rsp|randomspawnplus") -@Description("Manage the plugin") -@CommandPermission("randomspawnplus.manage") -public class CommandRSP extends BaseCommand { - - @Default - @Subcommand("help|h") - public void _main(CommandSender player) { - Chat.msg(player, "&8[&3RandomSpawnPlus&8] &7Running &bv" + RandomSpawnPlus.getInstance().getDescription().getVersion() + "&7, made with love &a:^)"); - Chat.msg(player, ""); - Chat.msg(player, "&b/rsp &8- &7The help menu."); - Chat.msg(player, "&b/rsp reload &8- &7Reload the plugin configuration."); - Chat.msg(player, "&b/wild &8- &7Randomly teleport yourself."); - Chat.msg(player, "&b/wild &8- &7Randomly teleport another player."); - Chat.msg(player, "&7Need help? Check out &bhttps://github.com/Winds-Studio/RandomSpawnPlus5&7."); - } - - @Subcommand("reload") - public void _reload(CommandSender player) { - RandomSpawnPlus.getInstance().getConfigManager().reload(); - RandomSpawnPlus.getInstance().getLangManager().reload(); - RandomSpawnPlus.getInstance().getSpawnsManager().reload(); - Chat.setLang(RandomSpawnPlus.getInstance().getLangManager().getConfig()); - Chat.msg(player, "&8[&3RandomSpawnPlus&8] &7Reloaded &bconfig.yml&7, &blang.yml&7, and &bspawns.yml&7."); - } -} diff --git a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java deleted file mode 100644 index f0842fd..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java +++ /dev/null @@ -1,57 +0,0 @@ -package systems.kscott.randomspawnplus.listeners; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerRespawnEvent; -import systems.kscott.randomspawnplus.RandomSpawnPlus; -import systems.kscott.randomspawnplus.events.RandomSpawnEvent; -import systems.kscott.randomspawnplus.events.SpawnType; -import systems.kscott.randomspawnplus.spawn.SpawnFinder; - -public class RSPDeathListener implements Listener { - - private final FileConfiguration config; - - public RSPDeathListener() { - this.config = RandomSpawnPlus.getInstance().getConfig(); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onDeath(PlayerRespawnEvent event) { - - Player player = event.getPlayer(); - - if (config.getBoolean("randomspawn-enabled")) { - if (config.getBoolean("on-death")) { - if (player.isDead()) { - if (!config.getBoolean("use-permission-node") || (config.getBoolean("use-permission-node") && player.hasPermission("randomspawnplus.randomspawn"))) { - if (config.getBoolean("spawn-at-bed")) { - if (player.getBedSpawnLocation() != null) { - event.setRespawnLocation(player.getBedSpawnLocation()); - return; - } - } - - Location location; - try { - location = SpawnFinder.getInstance().findSpawn(true).add(0.5, 0, 0.5); - } catch (Exception e) { - RandomSpawnPlus.getInstance().getLogger().warning("The spawn finder failed to find a valid spawn, and has not given " + player.getName() + " a random spawn. If you find this happening a lot, then raise the 'spawn-finder-tries-before-timeout' key in the config."); - return; - } - - RandomSpawnEvent randomSpawnEvent = new RandomSpawnEvent(location, player, SpawnType.ON_DEATH); - - Bukkit.getServer().getPluginManager().callEvent(randomSpawnEvent); - event.setRespawnLocation(location); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java deleted file mode 100644 index e647b6b..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java +++ /dev/null @@ -1,70 +0,0 @@ -package systems.kscott.randomspawnplus.listeners; - -import com.earth2me.essentials.User; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import systems.kscott.randomspawnplus.RandomSpawnPlus; -import systems.kscott.randomspawnplus.events.RandomSpawnEvent; -import systems.kscott.randomspawnplus.events.SpawnType; -import systems.kscott.randomspawnplus.spawn.SpawnFinder; - -public class RSPFirstJoinListener implements Listener { - - private final FileConfiguration config; - - public RSPFirstJoinListener() { - this.config = RandomSpawnPlus.getInstance().getConfig(); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void firstJoinHandler(PlayerJoinEvent event) { - - Player player = event.getPlayer(); - - if (config.getBoolean("randomspawn-enabled")) { - if (config.getBoolean("on-first-join")) { - if (RSPLoginListener.firstJoinPlayers.contains(player.getUniqueId())) { - if (config.getBoolean("use-permission-node") && !player.hasPermission("randomspawnplus.randomspawn")) { - RSPLoginListener.firstJoinPlayers.remove(player.getUniqueId()); - } else { - try { - Location spawnLoc = SpawnFinder.getInstance().findSpawn(true); - // quiquelhappy start - Prevent essentials home replace - boolean prevent = false; - if (config.getBoolean("essentials-home-on-first-spawn")) { - User user = RandomSpawnPlus.getInstance().getEssentials().getUser(player); - if(!user.hasHome()){ - user.setHome("home", spawnLoc); - user.save(); - } else { - prevent = true; - } - } - if (!prevent) { - RandomSpawnPlus.getInstance().foliaLib.getImpl().runLater(() -> { - RandomSpawnEvent randomSpawnEvent = new RandomSpawnEvent(spawnLoc, player, SpawnType.FIRST_JOIN); - - Bukkit.getServer().getPluginManager().callEvent(randomSpawnEvent); - RandomSpawnPlus.getInstance().foliaLib.getImpl().teleportAsync(player, spawnLoc.add(0.5, 0, 0.5)); - }, 3); - } else { - RandomSpawnPlus.getInstance().getLogger().warning("The spawn finder prevented a teleport for " + player.getUniqueId() + ", since essentials sethome is enabled and the player already had a home (perhaps old player data?)."); - } - // quiquelhappy end - } catch (Exception e) { - RandomSpawnPlus.getInstance().getLogger().warning("The spawn finder failed to find a valid spawn, and has not given " + player.getUniqueId() + " a random spawn. If you find this happening a lot, then raise the 'spawn-finder-tries-before-timeout' key in the config."); - return; - } - RSPLoginListener.firstJoinPlayers.remove(player.getUniqueId()); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java deleted file mode 100644 index 3febc19..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java +++ /dev/null @@ -1,37 +0,0 @@ -package systems.kscott.randomspawnplus.listeners; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import systems.kscott.randomspawnplus.RandomSpawnPlus; - -import java.util.ArrayList; -import java.util.UUID; - -public class RSPLoginListener implements Listener { - - public static ArrayList firstJoinPlayers = new ArrayList<>(); - private final FileConfiguration config; - - - public RSPLoginListener() { - this.config = RandomSpawnPlus.getInstance().getConfig(); - } - - @EventHandler - public void preLoginHandler(AsyncPlayerPreLoginEvent event) { - if (config.getBoolean("randomspawn-enabled")) { - if (config.getBoolean("on-first-join")) { - UUID playerUUID = event.getUniqueId(); - - boolean hasPlayed = Bukkit.getServer().getOfflinePlayer(playerUUID).hasPlayedBefore(); - - if (!hasPlayed) { - firstJoinPlayers.add(playerUUID); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java deleted file mode 100644 index d04eeed..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java +++ /dev/null @@ -1,18 +0,0 @@ -package systems.kscott.randomspawnplus.spawn; - -import lombok.Getter; - -@Getter -public class SpawnRegion { - int minX; - int maxX; - int minZ; - int maxZ; - - public SpawnRegion(int minX, int maxX, int minZ, int maxZ) { - this.minX = minX; - this.maxX = maxX; - this.minZ = minZ; - this.maxZ = maxZ; - } -} diff --git a/src/main/java/systems/kscott/randomspawnplus/util/Chat.java b/src/main/java/systems/kscott/randomspawnplus/util/Chat.java deleted file mode 100644 index 2660175..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/util/Chat.java +++ /dev/null @@ -1,69 +0,0 @@ -package systems.kscott.randomspawnplus.util; - -import lombok.Setter; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import systems.kscott.randomspawnplus.RandomSpawnPlus; - -import java.text.NumberFormat; -import java.util.Arrays; -import java.util.Locale; - -public class Chat { - /* Thanks splodge */ - - @Setter - private static FileConfiguration lang; - - public static void initialize() { - lang = RandomSpawnPlus.getInstance().getLang(); - } - - public static void msg(Player player, String... messages) { - Arrays.stream(messages).forEach((s) -> RandomSpawnPlus.getInstance().adventure().player(player).sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(s))); - } - - public static void msg(CommandSender sender, String... messages) { - Arrays.stream(messages).forEach((s) -> RandomSpawnPlus.getInstance().adventure().sender(sender).sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(s))); - } - - public static void msgAll(String... messages) { - Arrays.stream(messages).forEach((s) -> RandomSpawnPlus.getInstance().adventure().all().sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(s))); - } - - public static String uppercaseFirst(String str) { - return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase(); - } - - public static String formatMs(long ms) { - long seconds = ms / 1000L % 60L; - long minutes = ms / 60000L % 60L; - long hours = ms / 3600000L % 24L; - return (hours > 0L ? hours + "h " : "") + (minutes > 0L ? minutes + "m " : "") + seconds + "s"; - } - - public static String timeLeft(long timeoutSeconds) { - long days = timeoutSeconds / 86400L; - long hours = timeoutSeconds / 3600L % 24L; - long minutes = timeoutSeconds / 60L % 60L; - long seconds = timeoutSeconds % 60L; - return (days > 0L ? " " + days + " " + (days != 1 ? get("delay.days") : get("delay.day")) : "") - + (hours > 0L ? " " + hours + " " + (hours != 1 ? get("delay.hours") : get("delay.hour")) : "") - + (minutes > 0L ? " " + minutes + " " + (minutes != 1 ? get("delay.minutes") : get("delay.minute")) : "") - + (seconds > 0L ? " " + seconds + " " + (seconds != 1 ? get("delay.seconds") : get("delay.second")) : ""); - } - - public static String formatDoubleValue(double value) { - NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH); - nf.setMaximumFractionDigits(2); - nf.setMinimumFractionDigits(2); - return nf.format(value); - } - - public static String get(String key) { - return lang.getString(key); - } - -} diff --git a/src/main/java/systems/kscott/randomspawnplus/util/ConfigFile.java b/src/main/java/systems/kscott/randomspawnplus/util/ConfigFile.java deleted file mode 100644 index 4324dd0..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/util/ConfigFile.java +++ /dev/null @@ -1,49 +0,0 @@ -package systems.kscott.randomspawnplus.util; - -import lombok.Getter; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import systems.kscott.randomspawnplus.RandomSpawnPlus; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Paths; - -public class ConfigFile { - - @Getter - private FileConfiguration config; - private final String fileName; - - public ConfigFile(String fileName) { - this.fileName = fileName; - reload(); - } - - private File createFile() { - File customConfigFile = new File(RandomSpawnPlus.getInstance().getDataFolder(), fileName); - if (!customConfigFile.exists() || customConfigFile.getParentFile().mkdirs()) { - RandomSpawnPlus.getInstance().saveResource(fileName, false); - } - return customConfigFile; - } - - public void reload() { - File customConfigFile = createFile(); - config = new YamlConfiguration(); - try { - config.load(customConfigFile); - } catch (IOException | InvalidConfigurationException e) { - e.printStackTrace(); - } - } - - public void save() { - try { - config.save(Paths.get(RandomSpawnPlus.getInstance().getDataFolder().getAbsolutePath(), fileName).toString()); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/systems/kscott/randomspawnplus/util/CooldownManager.java b/src/main/java/systems/kscott/randomspawnplus/util/CooldownManager.java deleted file mode 100644 index 73ee9ca..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/util/CooldownManager.java +++ /dev/null @@ -1,26 +0,0 @@ -package systems.kscott.randomspawnplus.util; - -import org.bukkit.entity.Player; -import systems.kscott.randomspawnplus.RandomSpawnPlus; - -import java.time.Instant; -import java.util.HashMap; -import java.util.concurrent.TimeUnit; - -public class CooldownManager { - public static HashMap cooldowns = new HashMap<>(); - - public static void addCooldown(Player p) { - int cooldown = RandomSpawnPlus.getInstance().getConfig().getInt("wild-cooldown"); - long now = Instant.now().toEpochMilli(); - long future = now + TimeUnit.SECONDS.toMillis(cooldown); - cooldowns.put(p.getUniqueId().toString(), future); - } - - public static long getCooldown(Player p) { - if (!cooldowns.containsKey(p.getUniqueId().toString())) { - return 0; - } - return cooldowns.get(p.getUniqueId().toString()); - } -} diff --git a/src/main/java/systems/kscott/randomspawnplus/util/Numbers.java b/src/main/java/systems/kscott/randomspawnplus/util/Numbers.java deleted file mode 100644 index e08d8a5..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/util/Numbers.java +++ /dev/null @@ -1,19 +0,0 @@ -package systems.kscott.randomspawnplus.util; - -import java.util.concurrent.ThreadLocalRandom; - -public class Numbers { - public static int getRandomNumberInRange(int min, int max) { - - if (min >= max) { - throw new IllegalArgumentException("max must be greater than min"); - } - - return ThreadLocalRandom.current().nextInt((max - min) + 1) + min; - } - - public static boolean betweenExclusive(int x, int min, int max) { - return x > min && x < max; - } - -}