From 2094e489931c9754b38d909768edc53d901910ea Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:10:37 -0500 Subject: [PATCH 1/4] Initial concept --- build.gradle.kts | 66 +++---- .../randomspawnplus/RandomSpawnPlus.java | 105 +++-------- .../randomspawnplus/commands/CommandRSP.java | 2 +- .../randomspawnplus/commands/CommandWild.java | 36 ++-- .../kscott/randomspawnplus/config/Config.java | 145 +++++++++++++++ .../randomspawnplus/config/GlobalConfig.java | 170 ++++++++++++++++++ .../randomspawnplus/config/LangConfig.java | 107 +++++++++++ .../events/RandomSpawnEvent.java | 7 +- .../events/SpawnCheckEvent.java | 6 +- .../randomspawnplus/events/SpawnType.java | 2 + .../randomspawnplus/hooks/HookInstance.java | 86 +++++++++ .../listeners/RSPDeathListener.java | 14 +- .../listeners/RSPFirstJoinListener.java | 95 +++++----- .../listeners/RSPLoginListener.java | 33 ++-- .../platforms/FoliaPlatform.java | 23 +++ .../platforms/PaperPlatform.java | 23 +++ .../randomspawnplus/platforms/Platforms.java | 14 ++ .../platforms/SpigotPlatform.java | 55 ++++++ .../platforms/UniversalPlatform.java | 28 +++ .../randomspawnplus/spawn/SpawnCacher.java | 7 +- .../randomspawnplus/spawn/SpawnFinder.java | 27 ++- .../randomspawnplus/spawn/SpawnRegion.java | 28 ++- .../kscott/randomspawnplus/util/Chat.java | 14 +- .../randomspawnplus/util/ConfigFile.java | 49 ----- .../randomspawnplus/util/CooldownManager.java | 2 +- .../randomspawnplus/util/Locations.java | 1 - .../randomspawnplus/util/MessageUtil.java | 10 ++ .../kscott/randomspawnplus/util/Numbers.java | 1 - .../randomspawnplus/util/PlatformUtil.java | 27 +++ 29 files changed, 889 insertions(+), 294 deletions(-) create mode 100644 src/main/java/systems/kscott/randomspawnplus/config/Config.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java delete mode 100644 src/main/java/systems/kscott/randomspawnplus/util/ConfigFile.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java diff --git a/build.gradle.kts b/build.gradle.kts index efc9ed4..632b03c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "systems.kscott" -version = "5.1.0" +version = "6.0.0" repositories { mavenCentral() @@ -39,19 +39,25 @@ repositories { name = "devmart-other" url = uri("https://nexuslite.gcnt.net/repos/other/") } + + // ConfigurationMaster API + maven { + name = "ConfigurationMaster-repo" + url = uri("https://ci.pluginwiki.us/plugin/repository/everything/") + } } -val adventureVersion = "4.16.0" +val adventureVersion = "4.17.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") + compileOnly("org.apache.logging.log4j:log4j-api:2.24.1") + compileOnly("it.unimi.dsi:fastutil:8.5.15") api("org.bstats:bstats-bukkit:3.1.0") - api("co.aikar:acf-paper:0.5.1-SNAPSHOT") + api("co.aikar:acf-paper:0.5.1-SNAPSHOT") // Remove api("com.tcoded:FoliaLib:0.4.2") + implementation("com.github.thatsmusic99:ConfigurationMaster-API:v2.0.0-rc.2") compileOnly("net.essentialsx:EssentialsX:2.20.1") compileOnly("net.luckperms:api:5.4") @@ -62,38 +68,38 @@ dependencies { 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 { + options.encoding = "UTF-8" + } -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:.*:.*")) + build.configure { + dependsOn("shadowJar") + } + + shadowJar { + 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") } - 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) diff --git a/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java b/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java index cd7bef6..abe9e53 100644 --- a/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java +++ b/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java @@ -2,47 +2,30 @@ 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.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.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; +import org.bukkit.configuration.file.FileConfiguration; +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()); 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) { @@ -57,42 +40,17 @@ 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()); + Config.loadConfig(); + registerEvents(); registerCommands(); + registerHooks(); - SpawnFinder.initialize(); + SpawnFinder.init(); 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 @@ -101,52 +59,41 @@ public void onDisable() { this.adventure.close(); this.adventure = null; } + SpawnCacher.getInstance().save(); } - public void registerEvents() { + private void registerEvents() { getServer().getPluginManager().registerEvents(new RSPDeathListener(), this); getServer().getPluginManager().registerEvents(new RSPLoginListener(), this); getServer().getPluginManager().registerEvents(new RSPFirstJoinListener(), this); } - public void registerCommands() { + private void registerCommands() { PaperCommandManager manager = new PaperCommandManager(this); manager.registerCommand(new CommandRSP()); - if (configManager.getConfig().getBoolean("wild-enabled")) { + if (configManager.get().getBoolean("wild-enabled")) { manager.registerCommand(new CommandWild()); } } - public IEssentials getEssentials() { - return (IEssentials) getServer().getPluginManager().getPlugin("Essentials"); + public static RandomSpawnPlus getInstance() { + return INSTANCE; } - public @NotNull FileConfiguration getConfig() { - return configManager.getConfig(); + private void registerHooks() { + hookInstance = new HookInstance(this); } - public FileConfiguration getLang() { - return langManager.getConfig(); + public static HookInstance getHooks() { + return hookInstance.getInstance(); } - 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; - } + public @NotNull FileConfiguration getConfig() { + return configManager.getConfig(); } - private boolean setupEconomy() { - RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); - if (rsp == null) return false; - economy = rsp.getProvider(); - return economy != null; + public FileConfiguration getSpawns() { + return spawnsManager.getConfig(); } } diff --git a/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java b/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java index 1274b76..c2fedd6 100644 --- a/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java +++ b/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java @@ -6,9 +6,9 @@ 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; +import org.bukkit.command.CommandSender; @CommandAlias("rsp|randomspawnplus") @Description("Manage the plugin") diff --git a/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java b/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java index fa2ecb3..460f810 100644 --- a/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java +++ b/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.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 org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; import java.time.Instant; @@ -53,15 +53,15 @@ public void wild(CommandSender sender) { String message = Chat.get("wild-tp-cooldown"); - message = message.replace("%delay", Chat.timeLeft(cooldown / 1000 - Instant.now().getEpochSecond())); + message = message.replace("%delay%", Chat.timeLeft(cooldown / 1000 - Instant.now().getEpochSecond())); Chat.msg(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")); + if (RandomSpawnPlus.getInstance().getConfig().getInt("wild-cost") != 0 && RandomSpawnPlus.getHooks().getEconomy() != null && !player.hasPermission("randomspawnplus.wild.bypasscost")) { + if (RandomSpawnPlus.getHooks().getEconomy().has(player, config.getInt("wild-cost"))) { + RandomSpawnPlus.getHooks().getEconomy().withdrawPlayer(player, config.getInt("wild-cost")); } else { Chat.msg(player, Chat.get("wild-no-money")); return; @@ -78,13 +78,13 @@ public void wild(CommandSender sender) { String message = Chat.get("wild-tp") - .replace("%x", Integer.toString(location.getBlockX())) - .replace("%y", Integer.toString(location.getBlockY())) - .replace("%z", Integer.toString(location.getBlockZ())); + .replace("%x%", Integer.toString(location.getBlockX())) + .replace("%y%", Integer.toString(location.getBlockY())) + .replace("%z%", Integer.toString(location.getBlockZ())); Chat.msg(player, message); - if (config.getBoolean("home-on-wild")) { - User user = RandomSpawnPlus.getInstance().getEssentials().getUser(player); + if (config.getBoolean("home-on-wild") && RandomSpawnPlus.getHooks().getEssentials() != null) { + User user = RandomSpawnPlus.getHooks().getEssentials().getUser(player); if (!user.hasHome()) { user.setHome("home", location); user.save(); @@ -117,14 +117,14 @@ public void wildOther(CommandSender sender, String otherPlayerString) { 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())); + .replace("%x%", Integer.toString(location.getBlockX())) + .replace("%y%", Integer.toString(location.getBlockY())) + .replace("%z%", Integer.toString(location.getBlockZ())); Chat.msg(otherPlayer, message); message = Chat.get("wild-tp-other"); - message = message.replace("%player", otherPlayer.getName()); + message = message.replace("%player%", otherPlayer.getName()); Chat.msg(sender, message); RandomSpawnEvent randomSpawnEvent = new RandomSpawnEvent(location, otherPlayer.getPlayer(), SpawnType.WILD_COMMAND); diff --git a/src/main/java/systems/kscott/randomspawnplus/config/Config.java b/src/main/java/systems/kscott/randomspawnplus/config/Config.java new file mode 100644 index 0000000..147cb6d --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/config/Config.java @@ -0,0 +1,145 @@ +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.util.MessageUtil; +import org.bukkit.command.CommandSender; + +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 ConfigLocale configLocale; + private static GlobalConfig globalConfig; + private static LangConfig langConfig; + + 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(); + + try { + loadGlobalConfig(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(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() { + long begin = System.nanoTime(); + LOGGER.info("Loading config..."); + + try { + loadGlobalConfig(true); + } catch (Exception e) { + LOGGER.error("Failed to load " + GLOBAL_CONFIG_FILE_NAME + "!", e); + } + try { + loadLangConfig(true); + } catch (Exception e) { + LOGGER.error("Failed to load " + LANG_CONFIG_FILE_NAME + "!", e); + } + + LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000); + } + + private static void loadGlobalConfig(boolean init) throws Exception { + globalConfig = new GlobalConfig(init, GLOBAL_CONFIG_FILE_NAME); + + globalConfig.saveConfig(); + } + + private static void loadLangConfig(boolean init) throws Exception { + langConfig = new LangConfig(init, LANG_CONFIG_FILE_NAME); + + langConfig.saveConfig(); + } + + 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 getConfigVersion(String lastConfigVer) { + 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/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java b/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java new file mode 100644 index 0000000..1f03122 --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java @@ -0,0 +1,170 @@ +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.List; +import java.util.Map; + +public class GlobalConfig { + + private static ConfigFile configFile; + + public String a; + + public GlobalConfig(boolean init, String fileName) throws Exception { + RandomSpawnPlus instance = RandomSpawnPlus.getInstance(); + configFile = ConfigFile.loadConfig(new File(instance.getDataFolder(), fileName)); + + final String lastVersion = getString("config-version"); + + // Set current config version + configFile.set("config-version", Config.getCurrentConfigVersion()); + + // Get config last version for upgrade task + Config.getConfigVersion(lastVersion); + + // 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(); + } + + private static void initConfig() { + + } + + public void saveConfig() throws Exception { + configFile.save(); + } + + private void structureConfig() { + createTitledSection("General", "general"); + createTitledSection("Random Spawn", "spawn"); + 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/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java b/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java new file mode 100644 index 0000000..623113f --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java @@ -0,0 +1,107 @@ +package systems.kscott.randomspawnplus.config; + +import io.github.thatsmusic99.configurationmaster.api.ConfigFile; +import systems.kscott.randomspawnplus.RandomSpawnPlus; + +import java.io.File; + +public class LangConfig { + + private static ConfigFile configFile; + + public String playerOnly; + public String errorOnFindingSpawn; + public String noSpawnFound; + public String invalidPlayer; + + 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(boolean init, String fileName) throws Exception { + RandomSpawnPlus instance = RandomSpawnPlus.getInstance(); + configFile = ConfigFile.loadConfig(new File(instance.getDataFolder(), fileName)); + + // 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."; + + 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/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java b/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java index 7573acd..ac9638a 100644 --- a/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java +++ b/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java @@ -1,21 +1,16 @@ 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; diff --git a/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java b/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java index e5972c3..93302b5 100644 --- a/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java +++ b/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java @@ -1,12 +1,10 @@ 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(); @@ -33,6 +31,4 @@ 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/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java index a9a2f14..0f60e06 100644 --- a/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java +++ b/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java @@ -1,7 +1,9 @@ package systems.kscott.randomspawnplus.events; public enum SpawnType { + FIRST_JOIN, ON_DEATH, WILD_COMMAND + } diff --git a/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java b/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java new file mode 100644 index 0000000..438e509 --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java @@ -0,0 +1,86 @@ +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 pluginInstance; + + private IEssentials essentials; + private LuckPerms luckPerms; + private static Economy economy; + + public HookInstance(RandomSpawnPlus instance) { + pluginInstance = instance; + + registerHooks(); + } + + private void registerHooks() { + new Metrics(pluginInstance, 6465); + + Plugin essPlugin = pluginInstance.getServer().getPluginManager().getPlugin("Essentials"); + + if (essPlugin != null) { + essentials = (IEssentials) essPlugin; + } + + if (pluginInstance.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 (pluginInstance.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 = pluginInstance.getServer().getServicesManager().getRegistration(LuckPerms.class); + if (rsp != null) { + luckPerms = rsp.getProvider(); + } else { + luckPerms = null; + } + } + + private boolean setupEconomy() { + RegisteredServiceProvider rsp = pluginInstance.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/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java index f0842fd..cf99a0e 100644 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java +++ b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java @@ -1,5 +1,9 @@ package systems.kscott.randomspawnplus.listeners; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +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.configuration.file.FileConfiguration; @@ -8,19 +12,11 @@ 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) { @@ -54,4 +50,4 @@ public void onDeath(PlayerRespawnEvent event) { } } } -} \ 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 index e647b6b..eab5bca 100644 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java +++ b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java @@ -1,6 +1,11 @@ package systems.kscott.randomspawnplus.listeners; import com.earth2me.essentials.User; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.events.RandomSpawnEvent; +import systems.kscott.randomspawnplus.events.SpawnType; +import systems.kscott.randomspawnplus.platforms.UniversalPlatform; +import systems.kscott.randomspawnplus.spawn.SpawnFinder; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.configuration.file.FileConfiguration; @@ -8,63 +13,65 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; 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) { + if (!config.getBoolean("randomspawn-enabled")) { + return; + } + + if (!config.getBoolean("on-first-join")) { + return; + } 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); + if (!RSPLoginListener.firstJoinPlayers.contains(player.getUniqueId())) { + return; + } + + if (config.getBoolean("use-permission-node") && !player.hasPermission("randomspawnplus.randomspawn")) { + RSPLoginListener.firstJoinPlayers.remove(player.getUniqueId()); + return; + } + + try { + Location spawnLoc = SpawnFinder.getInstance().findSpawn(true); + // quiquelhappy start - Prevent essentials home replace + boolean prevent = false; - 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()); - } + 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.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 index 3febc19..8f32ce9 100644 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java +++ b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java @@ -1,11 +1,11 @@ package systems.kscott.randomspawnplus.listeners; +import systems.kscott.randomspawnplus.platforms.UniversalPlatform; 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; @@ -15,23 +15,26 @@ 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(); + if (!config.getBoolean("randomspawn-enabled")) { + return; + } + + if (!config.getBoolean("on-first-join")) { + return; + } + + // TODO: denied message + if (!UniversalPlatform.isAllSpawnRangeChunksGenerated()) { + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "RandomSpawnPlus WIP"); + } - boolean hasPlayed = Bukkit.getServer().getOfflinePlayer(playerUUID).hasPlayedBefore(); + final UUID uuid = event.getUniqueId(); + final boolean hasPlayed = Bukkit.getServer().getOfflinePlayer(uuid).hasPlayedBefore(); - if (!hasPlayed) { - firstJoinPlayers.add(playerUUID); - } - } + if (!hasPlayed) { + firstJoinPlayers.add(uuid); } } -} \ No newline at end of file +} diff --git a/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java b/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java new file mode 100644 index 0000000..0849fa7 --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java @@ -0,0 +1,23 @@ +package systems.kscott.randomspawnplus.platforms; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +public class FoliaPlatform implements Platforms { + + @Override + public void collectNonGeneratedChunks(World level, int minX, int maxX, int minZ, int maxZ) { + throw new UnsupportedOperationException(); + } + + @Override + public void loadPendingGenerateChunks(LongArrayList chunks) { + throw new UnsupportedOperationException(); + } + + @Override + public void broadcastCommandMessage(CommandSender sender, Object message) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java b/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java new file mode 100644 index 0000000..6ddc8b3 --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java @@ -0,0 +1,23 @@ +package systems.kscott.randomspawnplus.platforms; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +public class PaperPlatform implements Platforms { + + @Override + public void collectNonGeneratedChunks(World level, int minX, int maxX, int minZ, int maxZ) { + throw new UnsupportedOperationException(); + } + + @Override + public void loadPendingGenerateChunks(LongArrayList chunks) { + throw new UnsupportedOperationException(); + } + + @Override + public void broadcastCommandMessage(CommandSender sender, Object message) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java b/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java new file mode 100644 index 0000000..f6df403 --- /dev/null +++ b/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; + +public interface Platforms { + + void collectNonGeneratedChunks(World level, int minX, int maxX, int minZ, int maxZ); + + void loadPendingGenerateChunks(LongArrayList chunks); + + void broadcastCommandMessage(CommandSender sender, Object message); +} diff --git a/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java b/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java new file mode 100644 index 0000000..221aaf5 --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java @@ -0,0 +1,55 @@ +package systems.kscott.randomspawnplus.platforms; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.spawn.SpawnFinder; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +public class SpigotPlatform implements Platforms { + + @Override + public void collectNonGeneratedChunks(World level, int minX, int maxX, int minZ, int maxZ) { + int minChunkX = minX >> 4; + int maxChunkX = maxX >> 4; + int minChunkZ = minZ >> 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, false); + + if (!chunk.isGenerated()) { + long chunkKey = (long) chunkX & 0xffffffffL | ((long) chunkZ & 0xffffffffL) << 32; + chunks.add(chunkKey); + } + } + } + + if (!chunks.isEmpty()) { + UniversalPlatform.setPendingGenerateChunksList(chunks); + } else { + UniversalPlatform.finalizeSpawnChunksGeneration(); + } + } + + @Override + public void loadPendingGenerateChunks(LongArrayList chunks) { + for (long chunkKey : chunks) { + int chunkX = (int) chunkKey; + int chunkZ = (int) (chunkKey >>> 32); + + Runnable loadChunk = () -> SpawnFinder.spawnLevel.getChunkAt(chunkX, chunkZ, true); + Bukkit.getScheduler().runTaskAsynchronously(RandomSpawnPlus.getInstance(), loadChunk); + } + } + + @Override + public void broadcastCommandMessage(CommandSender sender, Object message) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java b/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java new file mode 100644 index 0000000..ecc45cf --- /dev/null +++ b/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 pendChunkGens; + 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) { + pendChunkGens = chunks; + } + + public static LongArrayList getPendingGenerateChunksList() { + return pendChunkGens; + } + + 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/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java index 0a84bce..a650b47 100644 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java +++ b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java @@ -1,12 +1,11 @@ package systems.kscott.randomspawnplus.spawn; import com.tcoded.folialib.wrapper.task.WrappedTask; -import lombok.Getter; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.util.Locations; 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 java.util.ArrayList; import java.util.List; @@ -16,9 +15,7 @@ public class SpawnCacher { public static SpawnCacher INSTANCE; - @Getter private boolean spawnsRequireSaving; - @Getter private final List cachedSpawns; private WrappedTask cacheSpawnTask; diff --git a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java index c86b6f6..bfce652 100644 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java +++ b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java @@ -1,15 +1,15 @@ package systems.kscott.randomspawnplus.spawn; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.events.SpawnCheckEvent; +import systems.kscott.randomspawnplus.util.Chat; +import systems.kscott.randomspawnplus.util.Numbers; 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; @@ -17,16 +17,19 @@ public class SpawnFinder { - public static SpawnFinder INSTANCE; - public FileConfiguration config; - ArrayList unsafeBlocks; + private static SpawnFinder INSTANCE; + private static FileConfiguration config; + public static World spawnLevel; + private static ArrayList unsafeBlocks; - public SpawnFinder() { - this.config = RandomSpawnPlus.getInstance().getConfig(); + public static void init() { + config = RandomSpawnPlus.getInstance().getConfig(); /* Setup safeblocks */ List unsafeBlockStrings; unsafeBlockStrings = config.getStringList("unsafe-blocks"); + String spawnLevelName = config.getString("respawn-world"); + spawnLevel = Bukkit.getWorld(spawnLevelName); unsafeBlocks = new ArrayList<>(); for (String string : unsafeBlockStrings) { @@ -34,10 +37,6 @@ public SpawnFinder() { } } - public static void initialize() { - INSTANCE = new SpawnFinder(); - } - public static SpawnFinder getInstance() { return INSTANCE; } @@ -241,6 +240,4 @@ public int getHighestY(World world, int x, int z) { } return minHeight; } - - } diff --git a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java index d04eeed..1c56d6f 100644 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java +++ b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java @@ -1,13 +1,11 @@ package systems.kscott.randomspawnplus.spawn; -import lombok.Getter; - -@Getter public class SpawnRegion { - int minX; - int maxX; - int minZ; - int maxZ; + + 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; @@ -15,4 +13,20 @@ public SpawnRegion(int minX, int maxX, int minZ, int maxZ) { 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/Chat.java b/src/main/java/systems/kscott/randomspawnplus/util/Chat.java index 2660175..c4cfca6 100644 --- a/src/main/java/systems/kscott/randomspawnplus/util/Chat.java +++ b/src/main/java/systems/kscott/randomspawnplus/util/Chat.java @@ -1,11 +1,11 @@ package systems.kscott.randomspawnplus.util; -import lombok.Setter; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.config.Config; +import systems.kscott.randomspawnplus.config.LangConfig; 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; @@ -14,11 +14,10 @@ public class Chat { /* Thanks splodge */ - @Setter - private static FileConfiguration lang; + private static LangConfig lang; public static void initialize() { - lang = RandomSpawnPlus.getInstance().getLang(); + lang = Config.getLangConfig(); } public static void msg(Player player, String... messages) { @@ -63,7 +62,6 @@ public static String formatDoubleValue(double value) { } public static String get(String key) { - return lang.getString(key); + return null; } - } 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 index 73ee9ca..fa29607 100644 --- a/src/main/java/systems/kscott/randomspawnplus/util/CooldownManager.java +++ b/src/main/java/systems/kscott/randomspawnplus/util/CooldownManager.java @@ -1,7 +1,7 @@ package systems.kscott.randomspawnplus.util; -import org.bukkit.entity.Player; import systems.kscott.randomspawnplus.RandomSpawnPlus; +import org.bukkit.entity.Player; import java.time.Instant; import java.util.HashMap; diff --git a/src/main/java/systems/kscott/randomspawnplus/util/Locations.java b/src/main/java/systems/kscott/randomspawnplus/util/Locations.java index a6c4665..157b291 100644 --- a/src/main/java/systems/kscott/randomspawnplus/util/Locations.java +++ b/src/main/java/systems/kscott/randomspawnplus/util/Locations.java @@ -49,5 +49,4 @@ public static List serializeStringList(List locations) { return locStrings; } - } diff --git a/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java b/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java new file mode 100644 index 0000000..b474aa4 --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java @@ -0,0 +1,10 @@ +package systems.kscott.randomspawnplus.util; + +import org.bukkit.command.CommandSender; + +public class MessageUtil { + + public static void broadcastCommandMessage(CommandSender sender, Object message) { + PlatformUtil.getPlatform().broadcastCommandMessage(sender, message); + } +} diff --git a/src/main/java/systems/kscott/randomspawnplus/util/Numbers.java b/src/main/java/systems/kscott/randomspawnplus/util/Numbers.java index e08d8a5..e6e065c 100644 --- a/src/main/java/systems/kscott/randomspawnplus/util/Numbers.java +++ b/src/main/java/systems/kscott/randomspawnplus/util/Numbers.java @@ -15,5 +15,4 @@ public static int getRandomNumberInRange(int min, int max) { public static boolean betweenExclusive(int x, int min, int max) { return x > min && x < max; } - } diff --git a/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java b/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java new file mode 100644 index 0000000..e4711ca --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java @@ -0,0 +1,27 @@ +package systems.kscott.randomspawnplus.util; + +import systems.kscott.randomspawnplus.platforms.Platforms; + +public class PlatformUtil { + + private static Platforms platform; + public static boolean isFolia; + + public static void isFolia() { + if (false) { + isFolia = false; + return; + } + + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + isFolia = true; + } catch (Exception e) { + isFolia = false; + } + } + + public static Platforms getPlatform() { + return platform; + } +} From fb8545b7e715643d4b4ea554d13296edc6071e7d Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Tue, 26 Nov 2024 00:19:33 -0500 Subject: [PATCH 2/4] Keep update --- .github/workflows/build.yml | 15 ++- README.md | 21 +++- build.gradle.kts | 2 +- .../randomspawnplus/RandomSpawnPlus.java | 20 ++-- .../randomspawnplus/commands/CommandRSP.java | 21 ++-- .../randomspawnplus/commands/CommandWild.java | 72 +++++++------- .../randomspawnplus/config/GlobalConfig.java | 98 ++++++++++++++++++- .../randomspawnplus/config/LangConfig.java | 15 ++- .../randomspawnplus/config/SpawnStorage.java | 4 + .../events/SpawnCheckEvent.java | 2 +- .../randomspawnplus/hooks/HookInstance.java | 5 +- .../listeners/RSPDeathListener.java | 50 +++++----- .../listeners/RSPFirstJoinListener.java | 21 ++-- .../listeners/RSPLoginListener.java | 24 ++--- .../randomspawnplus/spawn/SpawnCacher.java | 5 - .../randomspawnplus/spawn/SpawnFinder.java | 20 ++-- .../kscott/randomspawnplus/util/Chat.java | 67 ------------- .../randomspawnplus/util/CooldownManager.java | 26 ----- .../randomspawnplus/util/MessageUtil.java | 27 +++++ .../kscott/randomspawnplus/util/Numbers.java | 18 ---- .../randomspawnplus/util/PlatformUtil.java | 22 +++-- .../kscott/randomspawnplus/util/Util.java | 57 +++++++++++ src/main/resources/plugin.yml | 11 ++- 23 files changed, 347 insertions(+), 276 deletions(-) create mode 100644 src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java delete mode 100644 src/main/java/systems/kscott/randomspawnplus/util/Chat.java delete mode 100644 src/main/java/systems/kscott/randomspawnplus/util/CooldownManager.java delete mode 100644 src/main/java/systems/kscott/randomspawnplus/util/Numbers.java create mode 100644 src/main/java/systems/kscott/randomspawnplus/util/Util.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65df3cb..327acad 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: @@ -20,11 +24,12 @@ jobs: - 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" prerelease: true diff --git a/README.md b/README.md index da0c044..660a502 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 1.12.2 - Latest Minecraft version (1.21.4) - Compatible with Spigot / Paper / Paper Forks / Forge+Bukkit Hybrid Server - Folia Support @@ -17,6 +17,23 @@ - 📫 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? + ## Special Thanks To: Jianke Cloud Host diff --git a/build.gradle.kts b/build.gradle.kts index 632b03c..0070889 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -83,7 +83,7 @@ tasks { } shadowJar { - archiveFileName = "${project.name}-${project.version}.jar" + archiveFileName = "${project.name}-${project.version}.${archiveExtension.get()}" exclude("META-INF/**", // Dreeam - Avoid to include META-INF/maven in Jar "com/cryptomorin/xseries/XBiome*", "com/cryptomorin/xseries/NMSExtras*", diff --git a/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java b/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java index abe9e53..81122b5 100644 --- a/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java +++ b/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java @@ -15,8 +15,7 @@ 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 org.bukkit.configuration.file.FileConfiguration; +import systems.kscott.randomspawnplus.util.PlatformUtil; import org.bukkit.plugin.java.JavaPlugin; public final class RandomSpawnPlus extends JavaPlugin { @@ -24,33 +23,31 @@ 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); - 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); - Chat.setLang(langManager.getConfig()); - Config.loadConfig(); registerEvents(); registerCommands(); registerHooks(); + PlatformUtil.init(); SpawnFinder.init(); SpawnCacher.initialize(); - Chat.initialize(); } @Override @@ -64,6 +61,8 @@ public void onDisable() { } private void registerEvents() { + if (!Config.getGlobalConfig().randomSpawnEnabled) return; + getServer().getPluginManager().registerEvents(new RSPDeathListener(), this); getServer().getPluginManager().registerEvents(new RSPLoginListener(), this); getServer().getPluginManager().registerEvents(new RSPFirstJoinListener(), this); @@ -72,7 +71,8 @@ private void registerEvents() { private void registerCommands() { PaperCommandManager manager = new PaperCommandManager(this); manager.registerCommand(new CommandRSP()); - if (configManager.get().getBoolean("wild-enabled")) { + + if (Config.getGlobalConfig().wildEnabled) { manager.registerCommand(new CommandWild()); } } @@ -89,10 +89,6 @@ public static HookInstance getHooks() { return hookInstance.getInstance(); } - public @NotNull FileConfiguration getConfig() { - return configManager.getConfig(); - } - public FileConfiguration getSpawns() { return spawnsManager.getConfig(); } diff --git a/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java b/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java index c2fedd6..2da4e68 100644 --- a/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java +++ b/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java @@ -7,7 +7,7 @@ import co.aikar.commands.annotation.Description; import co.aikar.commands.annotation.Subcommand; import systems.kscott.randomspawnplus.RandomSpawnPlus; -import systems.kscott.randomspawnplus.util.Chat; +import systems.kscott.randomspawnplus.util.MessageUtil; import org.bukkit.command.CommandSender; @CommandAlias("rsp|randomspawnplus") @@ -18,13 +18,15 @@ 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."); + 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/RandomSpawnPlus5&7." + ); } @Subcommand("reload") @@ -32,7 +34,6 @@ 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."); + 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/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java index 460f810..9dc583c 100644 --- a/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java +++ b/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java @@ -7,15 +7,15 @@ import co.aikar.commands.annotation.Description; 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.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.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import java.time.Instant; @@ -24,66 +24,57 @@ @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.timeLeft(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.getInstance().getConfig().getInt("wild-cost") != 0 && RandomSpawnPlus.getHooks().getEconomy() != null && !player.hasPermission("randomspawnplus.wild.bypasscost")) { - if (RandomSpawnPlus.getHooks().getEconomy().has(player, config.getInt("wild-cost"))) { - RandomSpawnPlus.getHooks().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 = Chat.get("wild-tp") + String message = Config.getLangConfig().wildTp .replace("%x%", Integer.toString(location.getBlockX())) .replace("%y%", Integer.toString(location.getBlockY())) .replace("%z%", Integer.toString(location.getBlockZ())); - Chat.msg(player, message); - if (config.getBoolean("home-on-wild") && RandomSpawnPlus.getHooks().getEssentials() != null) { + MessageUtil.send(player, message); + + if (Config.getGlobalConfig().setHomeOnWild && RandomSpawnPlus.getHooks().getEssentials() != null) { User user = RandomSpawnPlus.getHooks().getEssentials().getUser(player); if (!user.hasHome()) { user.setHome("home", location); @@ -95,44 +86,47 @@ 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") + + String message = Config.getLangConfig().wildTp .replace("%x%", Integer.toString(location.getBlockX())) .replace("%y%", Integer.toString(location.getBlockY())) .replace("%z%", Integer.toString(location.getBlockZ())); - Chat.msg(otherPlayer, message); + MessageUtil.send(otherPlayer, message); + + message = Config.getLangConfig().wildTpOther.replace("%player%", otherPlayer.getName()); - message = Chat.get("wild-tp-other"); - message = message.replace("%player%", otherPlayer.getName()); - Chat.msg(sender, message); + 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/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java b/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java index 1f03122..68ff1d2 100644 --- a/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java +++ b/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java @@ -5,6 +5,8 @@ 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; @@ -12,7 +14,40 @@ public class GlobalConfig { private static ConfigFile configFile; - public String a; + 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 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(boolean init, String fileName) throws Exception { RandomSpawnPlus instance = RandomSpawnPlus.getInstance(); @@ -41,10 +76,66 @@ public GlobalConfig(boolean init, String fileName) throws Exception { // 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."; + 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."; + 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); + randomSpawnOnFirstJoin = getBoolean(spawnControlPath + "on-first-join", randomSpawnOnFirstJoin); + randomSpawnAtBed = getBoolean(spawnControlPath + "spawn-at-bed", randomSpawnAtBed); + unsafeBlocks = getList(spawnControlPath + "unsafe-blocks", unsafeBlocks); + + homeIntegrationMode = getString(hooksPath + "home-integration-mode", homeIntegrationMode); + useHomeOnDeath = getBoolean(hooksPath + "use-home-on-death", useHomeOnDeath); + setHomeOnFirstJoinSpawn = getBoolean(hooksPath + "set-home-first-join-random-spawn", setHomeOnFirstJoinSpawn); + + wildEnabled = getBoolean(wildCommandPath + "wild-enabled", wildEnabled); + wildCooldown = getInt(wildCommandPath + "wild-cooldown", wildCooldown); + wildDelay = getInt(wildCommandPath + "wild-delay", wildDelay); + wildAllowFirstUseForAll = getBoolean(wildCommandPath + "wild-allow-first-use-no-permission", wildAllowFirstUseForAll); + wildCost = getInt(wildCommandPath + "wild-cost", wildCost); + setHomeOnWild = getBoolean(wildCommandPath + "set-home-on-wild", setHomeOnWild); } - private static void initConfig() { + 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 { @@ -53,7 +144,8 @@ public void saveConfig() throws Exception { private void structureConfig() { createTitledSection("General", "general"); - createTitledSection("Random Spawn", "spawn"); + createTitledSection("Random Spawn Control", "spawn-control"); + createTitledSection("Hooks", "hooks"); createTitledSection("/wild", "wild-command"); } diff --git a/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java b/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java index 623113f..8e1b611 100644 --- a/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java +++ b/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java @@ -9,10 +9,11 @@ public class LangConfig { private static ConfigFile configFile; - public String playerOnly; - public String errorOnFindingSpawn; - public String noSpawnFound; - public String invalidPlayer; + 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."; @@ -52,6 +53,12 @@ private void initConfig() { 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); diff --git a/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java b/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java new file mode 100644 index 0000000..fc45a3e --- /dev/null +++ b/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java @@ -0,0 +1,4 @@ +package systems.kscott.randomspawnplus.config; + +public class SpawnStorage { +} diff --git a/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java b/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java index 93302b5..87e6d96 100644 --- a/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java +++ b/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java @@ -10,7 +10,7 @@ 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; diff --git a/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java b/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java index 438e509..4b21bbc 100644 --- a/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java +++ b/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java @@ -23,7 +23,7 @@ public HookInstance(RandomSpawnPlus instance) { } private void registerHooks() { - new Metrics(pluginInstance, 6465); + new Metrics(pluginInstance, 6465); // TODO Note: Use own bstats, since no one update. Plugin essPlugin = pluginInstance.getServer().getPluginManager().getPlugin("Essentials"); @@ -63,8 +63,11 @@ private void setupPermissions() { private boolean setupEconomy() { RegisteredServiceProvider rsp = pluginInstance.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/listeners/RSPDeathListener.java b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java index cf99a0e..baa3851 100644 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java +++ b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java @@ -1,12 +1,12 @@ 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.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -15,38 +15,36 @@ public class RSPDeathListener implements Listener { - private final FileConfiguration config; - @EventHandler(priority = EventPriority.HIGHEST) public void onDeath(PlayerRespawnEvent event) { + if (!Config.getGlobalConfig().randomSpawnOnDeath) { + return; + } 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); + if (player.isDead()) { + if (!config.getBoolean("use-permission-node") || (config.getBoolean("use-permission-node") && player.hasPermission("randomspawnplus.randomspawn"))) { + if (Config.getGlobalConfig().randomSpawnAtBed) { + 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); } } } diff --git a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java index eab5bca..2792b0f 100644 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java +++ b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java @@ -2,42 +2,35 @@ 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.platforms.UniversalPlatform; import systems.kscott.randomspawnplus.spawn.SpawnFinder; +import systems.kscott.randomspawnplus.util.Util; 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.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerJoinEvent; public class RSPFirstJoinListener implements Listener { - private final FileConfiguration config; - @EventHandler(priority = EventPriority.HIGHEST) - public void firstJoinHandler(PlayerJoinEvent event) { - if (!config.getBoolean("randomspawn-enabled")) { - return; - } - - if (!config.getBoolean("on-first-join")) { + public void onFirstJoin(PlayerJoinEvent event) { + if (!Config.getGlobalConfig().randomSpawnOnFirstJoin) { return; } Player player = event.getPlayer(); - if (!RSPLoginListener.firstJoinPlayers.contains(player.getUniqueId())) { + if (!Util.firstJoinPlayers.contains(player.getUniqueId())) { return; } if (config.getBoolean("use-permission-node") && !player.hasPermission("randomspawnplus.randomspawn")) { - RSPLoginListener.firstJoinPlayers.remove(player.getUniqueId()); + Util.firstJoinPlayers.remove(player.getUniqueId()); return; } @@ -72,6 +65,6 @@ public void firstJoinHandler(PlayerJoinEvent event) { return; } - RSPLoginListener.firstJoinPlayers.remove(player.getUniqueId()); + Util.firstJoinPlayers.remove(player.getUniqueId()); } } diff --git a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java index 8f32ce9..a5a4364 100644 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java +++ b/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java @@ -1,40 +1,32 @@ package systems.kscott.randomspawnplus.listeners; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import systems.kscott.randomspawnplus.config.Config; import systems.kscott.randomspawnplus.platforms.UniversalPlatform; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.FileConfiguration; +import systems.kscott.randomspawnplus.util.Util; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import java.util.ArrayList; import java.util.UUID; public class RSPLoginListener implements Listener { - public static ArrayList firstJoinPlayers = new ArrayList<>(); - private final FileConfiguration config; - @EventHandler - public void preLoginHandler(AsyncPlayerPreLoginEvent event) { - if (!config.getBoolean("randomspawn-enabled")) { - return; - } - - if (!config.getBoolean("on-first-join")) { + public void onPreLogin(AsyncPlayerPreLoginEvent event) { + if (!Config.getGlobalConfig().randomSpawnOnFirstJoin) { return; } - // TODO: denied message if (!UniversalPlatform.isAllSpawnRangeChunksGenerated()) { - event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "RandomSpawnPlus WIP"); + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Config.getLangConfig().spawnNotInitialized); } final UUID uuid = event.getUniqueId(); - final boolean hasPlayed = Bukkit.getServer().getOfflinePlayer(uuid).hasPlayedBefore(); + final boolean hasPlayed = RandomSpawnPlus.getInstance().getServer().getOfflinePlayer(uuid).hasPlayedBefore(); if (!hasPlayed) { - firstJoinPlayers.add(uuid); + Util.firstJoinPlayers.add(uuid); } } } diff --git a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java index a650b47..94181b4 100644 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java +++ b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java @@ -5,7 +5,6 @@ import systems.kscott.randomspawnplus.util.Locations; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.configuration.file.FileConfiguration; import java.util.ArrayList; import java.util.List; @@ -34,10 +33,6 @@ public static SpawnCacher getInstance() { } private void cacheSpawns() { - - FileConfiguration spawns = RandomSpawnPlus.getInstance().getSpawns(); - FileConfiguration config = RandomSpawnPlus.getInstance().getConfig(); - SpawnFinder finder = SpawnFinder.getInstance(); List locationStrings = spawns.getStringList("spawns"); diff --git a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java index bfce652..880dafa 100644 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java +++ b/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java @@ -2,14 +2,12 @@ import systems.kscott.randomspawnplus.RandomSpawnPlus; import systems.kscott.randomspawnplus.events.SpawnCheckEvent; -import systems.kscott.randomspawnplus.util.Chat; -import systems.kscott.randomspawnplus.util.Numbers; +import systems.kscott.randomspawnplus.util.Util; 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 java.util.ArrayList; import java.util.List; @@ -17,14 +15,10 @@ public class SpawnFinder { - private static SpawnFinder INSTANCE; - private static FileConfiguration config; public static World spawnLevel; private static ArrayList unsafeBlocks; public static void init() { - config = RandomSpawnPlus.getInstance().getConfig(); - /* Setup safeblocks */ List unsafeBlockStrings; unsafeBlockStrings = config.getStringList("unsafe-blocks"); @@ -37,8 +31,8 @@ public static void init() { } } - public static SpawnFinder getInstance() { - return INSTANCE; + private static void create() { + } public Location getCandidateLocation() { @@ -84,8 +78,8 @@ 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); @@ -175,10 +169,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; } } 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 c4cfca6..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/util/Chat.java +++ /dev/null @@ -1,67 +0,0 @@ -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 systems.kscott.randomspawnplus.config.LangConfig; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.text.NumberFormat; -import java.util.Arrays; -import java.util.Locale; - -public class Chat { - /* Thanks splodge */ - - private static LangConfig lang; - - public static void initialize() { - lang = Config.getLangConfig(); - } - - 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 null; - } -} 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 fa29607..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/util/CooldownManager.java +++ /dev/null @@ -1,26 +0,0 @@ -package systems.kscott.randomspawnplus.util; - -import systems.kscott.randomspawnplus.RandomSpawnPlus; -import org.bukkit.entity.Player; - -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/MessageUtil.java b/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java index b474aa4..e65ef4f 100644 --- a/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java +++ b/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java @@ -1,10 +1,37 @@ package systems.kscott.randomspawnplus.util; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import systems.kscott.randomspawnplus.RandomSpawnPlus; 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); } + + 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 timeLeft(long seconds) { + long days = seconds / 86400L; + long hours = seconds / 3600L % 24L; + long minutes = seconds / 60L % 60L; + long secs = seconds % 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")) : "") + + (secs > 0L ? " " + secs + " " + (secs != 1 ? get("delay.seconds") : get("delay.second")) : ""); + } } 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 e6e065c..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/util/Numbers.java +++ /dev/null @@ -1,18 +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; - } -} diff --git a/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java b/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java index e4711ca..322d5ae 100644 --- a/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java +++ b/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java @@ -1,24 +1,26 @@ package systems.kscott.randomspawnplus.util; +import systems.kscott.randomspawnplus.platforms.FoliaPlatform; import systems.kscott.randomspawnplus.platforms.Platforms; +import systems.kscott.randomspawnplus.platforms.SpigotPlatform; public class PlatformUtil { private static Platforms platform; - public static boolean isFolia; - public static void isFolia() { - if (false) { - isFolia = false; - return; + public static Platforms init() { + // Folia + if (Util.doesClassExists("io.papermc.paper.threadedregions.RegionizedServer")) { + return platform = new FoliaPlatform(); } - try { - Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); - isFolia = true; - } catch (Exception e) { - isFolia = false; + // Paper + if (Util.doesClassExists("io.papermc.paper.configuration.GlobalConfiguration")) { + return platform = new FoliaPlatform(); } + + // Spigot (Fallback) + return platform = new SpigotPlatform(); } public static Platforms getPlatform() { diff --git a/src/main/java/systems/kscott/randomspawnplus/util/Util.java b/src/main/java/systems/kscott/randomspawnplus/util/Util.java new file mode 100644 index 0000000..6f337a2 --- /dev/null +++ b/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/plugin.yml b/src/main/resources/plugin.yml index 8ff757b..f79ff31 100644 --- a/src/main/resources/plugin.yml +++ b/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 From 8f6d4766fa5af4ca745590ca81d2ccfbadbafa42 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 9 May 2025 19:30:53 -0400 Subject: [PATCH 3/4] Upload WIP prorgress --- .github/workflows/build.yml | 8 +- Common/build.gradle.kts | 55 +++++++++ .../randomspawnplus/RandomSpawnPlus.java | 23 ++-- .../randomspawnplus/commands/CommandRSP.java | 9 +- .../randomspawnplus/commands/CommandWild.java | 4 +- .../kscott/randomspawnplus/config/Config.java | 59 +++++++-- .../randomspawnplus/config/GlobalConfig.java | 62 ++++++---- .../randomspawnplus/config/LangConfig.java | 6 +- .../randomspawnplus/config/SpawnStorage.java | 24 ++++ .../events/RandomSpawnEvent.java | 1 - .../events/SpawnCheckEvent.java | 0 .../randomspawnplus/events/SpawnType.java | 0 .../randomspawnplus/hooks/HookInstance.java | 18 +-- .../randomspawnplus/listeners/OnDeath.java | 4 +- .../listeners/OnFirstJoin.java | 4 +- .../randomspawnplus/listeners/OnPreLogin.java | 7 +- .../platforms/UniversalPlatform.java | 6 +- .../randomspawnplus/spawn/SpawnCacher.java | 10 +- .../randomspawnplus/spawn/SpawnFinder.java | 43 ++++--- .../randomspawnplus/spawn/SpawnRegion.java | 0 .../randomspawnplus/util/Locations.java | 0 .../randomspawnplus/util/MessageUtil.java | 49 ++++++++ .../randomspawnplus/util/PlatformUtil.java | 3 +- .../kscott/randomspawnplus/util/Util.java | 0 {src => Common/src}/main/resources/config.yml | 0 {src => Common/src}/main/resources/lang.yml | 0 {src => Common/src}/main/resources/plugin.yml | 0 {src => Common/src}/main/resources/spawns.yml | 0 Platform/Abstraction/build.gradle.kts | 9 ++ .../randomspawnplus/platforms/Platforms.java | 6 +- Platform/Folia/build.gradle.kts | 10 ++ .../platforms/FoliaPlatform.java | 9 +- Platform/Paper/build.gradle.kts | 9 ++ .../platforms/PaperPlatform.java | 86 +++++++++++++ Platform/Spigot/build.gradle.kts | 11 ++ .../platforms/SpigotPlatform.java | 35 ++---- README.md | 19 ++- build-logic/build.gradle.kts | 9 ++ .../kotlin/cn.dreeam.rsp.wrapper.gradle.kts | 66 ++++++++++ build.gradle.kts | 115 ------------------ settings.gradle.kts | 18 +++ .../randomspawnplus/config/SpawnStorage.java | 4 - .../platforms/PaperPlatform.java | 23 ---- .../randomspawnplus/util/MessageUtil.java | 37 ------ 44 files changed, 556 insertions(+), 305 deletions(-) create mode 100644 Common/build.gradle.kts rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java (76%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java (82%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java (96%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/config/Config.java (66%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java (79%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/config/LangConfig.java (94%) create mode 100644 Common/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java (99%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java (100%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/events/SpawnType.java (100%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java (70%) rename src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java => Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java (96%) rename src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java => Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnFirstJoin.java (97%) rename src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java => Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnPreLogin.java (80%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java (84%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java (95%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java (85%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java (100%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/util/Locations.java (100%) create mode 100644 Common/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java (87%) rename {src => Common/src}/main/java/systems/kscott/randomspawnplus/util/Util.java (100%) rename {src => Common/src}/main/resources/config.yml (100%) rename {src => Common/src}/main/resources/lang.yml (100%) rename {src => Common/src}/main/resources/plugin.yml (100%) rename {src => Common/src}/main/resources/spawns.yml (100%) create mode 100644 Platform/Abstraction/build.gradle.kts rename {src => Platform/Abstraction/src}/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java (61%) create mode 100644 Platform/Folia/build.gradle.kts rename {src => Platform/Folia/src}/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java (58%) create mode 100644 Platform/Paper/build.gradle.kts create mode 100644 Platform/Paper/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java create mode 100644 Platform/Spigot/build.gradle.kts rename {src => Platform/Spigot/src}/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java (52%) create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/src/main/kotlin/cn.dreeam.rsp.wrapper.gradle.kts delete mode 100644 build.gradle.kts delete mode 100644 src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java delete mode 100644 src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java delete mode 100644 src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 327acad..ec281a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,11 +13,11 @@ 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 @@ -31,5 +31,5 @@ jobs: 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..5bb9e22 --- /dev/null +++ b/Common/build.gradle.kts @@ -0,0 +1,55 @@ +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.3-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.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 { + 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/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java b/Common/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java similarity index 76% rename from src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java rename to Common/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java index 81122b5..5f7cd8e 100644 --- a/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java @@ -10,10 +10,9 @@ import systems.kscott.randomspawnplus.commands.CommandWild; import systems.kscott.randomspawnplus.config.Config; import systems.kscott.randomspawnplus.hooks.HookInstance; -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.listeners.OnDeath; +import systems.kscott.randomspawnplus.listeners.OnFirstJoin; +import systems.kscott.randomspawnplus.listeners.OnPreLogin; import systems.kscott.randomspawnplus.spawn.SpawnFinder; import systems.kscott.randomspawnplus.util.PlatformUtil; import org.bukkit.plugin.java.JavaPlugin; @@ -39,7 +38,7 @@ public void onEnable() { INSTANCE = this; this.adventure = BukkitAudiences.create(this); - Config.loadConfig(); + Config.loadConfig(INSTANCE); registerEvents(); registerCommands(); @@ -47,7 +46,7 @@ public void onEnable() { PlatformUtil.init(); SpawnFinder.init(); - SpawnCacher.initialize(); + //SpawnCacher.initialize(); } @Override @@ -57,15 +56,15 @@ public void onDisable() { this.adventure = null; } - SpawnCacher.getInstance().save(); + //SpawnCacher.getInstance().save(); } private void registerEvents() { if (!Config.getGlobalConfig().randomSpawnEnabled) return; - getServer().getPluginManager().registerEvents(new RSPDeathListener(), this); - getServer().getPluginManager().registerEvents(new RSPLoginListener(), this); - getServer().getPluginManager().registerEvents(new RSPFirstJoinListener(), this); + getServer().getPluginManager().registerEvents(new OnDeath(), this); + getServer().getPluginManager().registerEvents(new OnPreLogin(), this); + getServer().getPluginManager().registerEvents(new OnFirstJoin(), this); } private void registerCommands() { @@ -88,8 +87,4 @@ private void registerHooks() { public static HookInstance getHooks() { return hookInstance.getInstance(); } - - public FileConfiguration getSpawns() { - return spawnsManager.getConfig(); - } } diff --git a/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java b/Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java similarity index 82% rename from src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java rename to Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java index 2da4e68..75920ba 100644 --- a/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandRSP.java @@ -7,6 +7,7 @@ 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; @@ -18,6 +19,9 @@ 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:^)", "", @@ -25,15 +29,18 @@ public void _main(CommandSender player) { "&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/RandomSpawnPlus5&7." + "&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 96% rename from src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java rename to Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java index 9dc583c..315ef40 100644 --- a/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/commands/CommandWild.java @@ -27,6 +27,7 @@ public class CommandWild extends BaseCommand { @Default @CommandPermission("randomspawnplus.wild") public void wild(CommandSender sender) { + /* if (!(sender instanceof Player)) { MessageUtil.send(sender, Config.getLangConfig().playerOnly); return; @@ -41,7 +42,7 @@ public void wild(CommandSender sender) { if ((cooldown - Instant.now().toEpochMilli()) >= 0) { String message = Config.getLangConfig().wildTpCooldown; - message = message.replace("%delay%", MessageUtil.timeLeft(cooldown / 1000 - Instant.now().getEpochSecond())); + message = message.replace("%delay%", MessageUtil.getStringFromSeconds(cooldown / 1000 - Instant.now().getEpochSecond())); MessageUtil.send(player, message); return; @@ -128,5 +129,6 @@ public void wildOther(CommandSender sender, String otherPlayerString) { } RandomSpawnPlus.getInstance().foliaLib.getImpl().teleportAsync(otherPlayer, location.add(0.5, 0, 0.5)); + */ } } diff --git a/src/main/java/systems/kscott/randomspawnplus/config/Config.java b/Common/src/main/java/systems/kscott/randomspawnplus/config/Config.java similarity index 66% rename from src/main/java/systems/kscott/randomspawnplus/config/Config.java rename to Common/src/main/java/systems/kscott/randomspawnplus/config/Config.java index 147cb6d..ef3a1fa 100644 --- a/src/main/java/systems/kscott/randomspawnplus/config/Config.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/config/Config.java @@ -6,9 +6,15 @@ 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; @@ -18,10 +24,12 @@ public class Config { 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; @@ -34,14 +42,16 @@ public class Config { return CompletableFuture.runAsync(() -> { long begin = System.nanoTime(); + File pluginFolder = RandomSpawnPlus.getInstance().getDataFolder(); + try { - loadGlobalConfig(false); + 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(false); + 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); @@ -52,36 +62,62 @@ public class Config { }); } - public static void loadConfig() { + 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(true); + loadGlobalConfig(pluginFolder, true); } catch (Exception e) { LOGGER.error("Failed to load " + GLOBAL_CONFIG_FILE_NAME + "!", e); } try { - loadLangConfig(true); + 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(boolean init) throws Exception { - globalConfig = new GlobalConfig(init, GLOBAL_CONFIG_FILE_NAME); + 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(boolean init) throws Exception { - langConfig = new LangConfig(init, LANG_CONFIG_FILE_NAME); + 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" + @@ -98,7 +134,10 @@ public static LangConfig getLangConfig() { return langConfig; } - public static void getConfigVersion(String lastConfigVer) { + 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]); } diff --git a/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java b/Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java similarity index 79% rename from src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java rename to Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java index 68ff1d2..d531562 100644 --- a/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java @@ -37,6 +37,7 @@ public class GlobalConfig { 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 @@ -49,26 +50,23 @@ public class GlobalConfig { public int wildCost = 100; public boolean setHomeOnWild = false; - public GlobalConfig(boolean init, String fileName) throws Exception { - RandomSpawnPlus instance = RandomSpawnPlus.getInstance(); - configFile = ConfigFile.loadConfig(new File(instance.getDataFolder(), fileName)); + public GlobalConfig(File pluginFolder, String configFileName, boolean init) throws Exception { + configFile = ConfigFile.loadConfig(new File(pluginFolder, configFileName)); - final String lastVersion = getString("config-version"); + // Get config last version for upgrade task + Config.getLastConfigVersion(getString("config-version")); // Set current config version configFile.set("config-version", Config.getCurrentConfigVersion()); - // Get config last version for upgrade task - Config.getConfigVersion(lastVersion); - // 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" + + "# 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" + "Don't touch this!\n" ); // Pre-structure to force order @@ -94,33 +92,51 @@ private void initConfig() { 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); - randomSpawnOnFirstJoin = getBoolean(spawnControlPath + "on-first-join", randomSpawnOnFirstJoin); - randomSpawnAtBed = getBoolean(spawnControlPath + "spawn-at-bed", randomSpawnAtBed); - unsafeBlocks = getList(spawnControlPath + "unsafe-blocks", unsafeBlocks); + 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); - setHomeOnFirstJoinSpawn = getBoolean(hooksPath + "set-home-first-join-random-spawn", setHomeOnFirstJoinSpawn); - wildEnabled = getBoolean(wildCommandPath + "wild-enabled", wildEnabled); - wildCooldown = getInt(wildCommandPath + "wild-cooldown", wildCooldown); + 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); - wildCost = getInt(wildCommandPath + "wild-cost", wildCost); - setHomeOnWild = getBoolean(wildCommandPath + "set-home-on-wild", setHomeOnWild); + 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() { @@ -135,7 +151,7 @@ private void validateConfigValues() { RandomSpawnPlus.LOGGER.error(error); } - throw new RuntimeException(); + //throw new RuntimeException(); } public void saveConfig() throws Exception { diff --git a/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java b/Common/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java similarity index 94% rename from src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java rename to Common/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java index 8e1b611..46ca64c 100644 --- a/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/config/LangConfig.java @@ -1,7 +1,6 @@ package systems.kscott.randomspawnplus.config; import io.github.thatsmusic99.configurationmaster.api.ConfigFile; -import systems.kscott.randomspawnplus.RandomSpawnPlus; import java.io.File; @@ -30,9 +29,8 @@ public class LangConfig { public String delaySec = "second"; public String delaySecs = "seconds"; - public LangConfig(boolean init, String fileName) throws Exception { - RandomSpawnPlus instance = RandomSpawnPlus.getInstance(); - configFile = ConfigFile.loadConfig(new File(instance.getDataFolder(), fileName)); + public LangConfig(File pluginFolder, String configFileName, boolean init) throws Exception { + configFile = ConfigFile.loadConfig(new File(pluginFolder, configFileName)); // Add config header addCommentRegionBased("general", 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 99% rename from src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java rename to Common/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java index ac9638a..f89428f 100644 --- a/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/events/RandomSpawnEvent.java @@ -13,7 +13,6 @@ public class RandomSpawnEvent extends Event { private final Player player; 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 100% rename from src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java rename to Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java 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 100% rename from src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java rename to Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java diff --git a/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java b/Common/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java similarity index 70% rename from src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java rename to Common/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java index 4b21bbc..adcc99e 100644 --- a/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/hooks/HookInstance.java @@ -10,28 +10,28 @@ public class HookInstance { - private final RandomSpawnPlus pluginInstance; + private final RandomSpawnPlus instance; private IEssentials essentials; private LuckPerms luckPerms; private static Economy economy; - public HookInstance(RandomSpawnPlus instance) { - pluginInstance = instance; + public HookInstance(RandomSpawnPlus pluginInstance) { + instance = pluginInstance; registerHooks(); } private void registerHooks() { - new Metrics(pluginInstance, 6465); // TODO Note: Use own bstats, since no one update. + new Metrics(instance, 6465); // TODO Note: Use own bstats, since no one update. - Plugin essPlugin = pluginInstance.getServer().getPluginManager().getPlugin("Essentials"); + Plugin essPlugin = instance.getServer().getPluginManager().getPlugin("Essentials"); if (essPlugin != null) { essentials = (IEssentials) essPlugin; } - if (pluginInstance.getServer().getPluginManager().getPlugin("LuckPerms") != null) { + if (instance.getServer().getPluginManager().getPlugin("LuckPerms") != null) { try { setupPermissions(); } catch (Exception e) { @@ -41,7 +41,7 @@ private void registerHooks() { RandomSpawnPlus.LOGGER.warn("The LuckPerms API is not detected, so the 'remove-permission-on-first-use' config option will not be enabled."); } - if (pluginInstance.getServer().getPluginManager().getPlugin("Vault") != null) { + if (instance.getServer().getPluginManager().getPlugin("Vault") != null) { try { setupEconomy(); } catch (Exception e) { @@ -53,7 +53,7 @@ private void registerHooks() { } private void setupPermissions() { - RegisteredServiceProvider rsp = pluginInstance.getServer().getServicesManager().getRegistration(LuckPerms.class); + RegisteredServiceProvider rsp = instance.getServer().getServicesManager().getRegistration(LuckPerms.class); if (rsp != null) { luckPerms = rsp.getProvider(); } else { @@ -62,7 +62,7 @@ private void setupPermissions() { } private boolean setupEconomy() { - RegisteredServiceProvider rsp = pluginInstance.getServer().getServicesManager().getRegistration(Economy.class); + RegisteredServiceProvider rsp = instance.getServer().getServicesManager().getRegistration(Economy.class); if (rsp == null) return false; diff --git a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java similarity index 96% rename from src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java rename to Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java index baa3851..f2ae5ba 100644 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPDeathListener.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java @@ -13,7 +13,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerRespawnEvent; -public class RSPDeathListener implements Listener { +public class OnDeath implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onDeath(PlayerRespawnEvent event) { @@ -24,6 +24,7 @@ public void onDeath(PlayerRespawnEvent event) { Player player = event.getPlayer(); if (player.isDead()) { + /* if (!config.getBoolean("use-permission-node") || (config.getBoolean("use-permission-node") && player.hasPermission("randomspawnplus.randomspawn"))) { if (Config.getGlobalConfig().randomSpawnAtBed) { if (player.getBedSpawnLocation() != null) { @@ -46,6 +47,7 @@ public void onDeath(PlayerRespawnEvent event) { Bukkit.getServer().getPluginManager().callEvent(randomSpawnEvent); event.setRespawnLocation(location); } + */ } } } diff --git a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnFirstJoin.java similarity index 97% rename from src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java rename to Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnFirstJoin.java index 2792b0f..9da2d0d 100644 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPFirstJoinListener.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnFirstJoin.java @@ -15,7 +15,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; -public class RSPFirstJoinListener implements Listener { +public class OnFirstJoin implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onFirstJoin(PlayerJoinEvent event) { @@ -29,6 +29,7 @@ public void onFirstJoin(PlayerJoinEvent event) { return; } + /* if (config.getBoolean("use-permission-node") && !player.hasPermission("randomspawnplus.randomspawn")) { Util.firstJoinPlayers.remove(player.getUniqueId()); return; @@ -64,6 +65,7 @@ public void onFirstJoin(PlayerJoinEvent event) { 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/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnPreLogin.java similarity index 80% rename from src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java rename to Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnPreLogin.java index a5a4364..cea47b3 100644 --- a/src/main/java/systems/kscott/randomspawnplus/listeners/RSPLoginListener.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnPreLogin.java @@ -4,13 +4,14 @@ 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 RSPLoginListener implements Listener { +public class OnPreLogin implements Listener { @EventHandler public void onPreLogin(AsyncPlayerPreLoginEvent event) { @@ -23,9 +24,9 @@ public void onPreLogin(AsyncPlayerPreLoginEvent event) { } final UUID uuid = event.getUniqueId(); - final boolean hasPlayed = RandomSpawnPlus.getInstance().getServer().getOfflinePlayer(uuid).hasPlayedBefore(); + final OfflinePlayer player = RandomSpawnPlus.getInstance().getServer().getOfflinePlayer(uuid); - if (!hasPlayed) { + if (!player.hasPlayedBefore()) { Util.firstJoinPlayers.add(uuid); } } diff --git a/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java b/Common/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java similarity index 84% rename from src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java rename to Common/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java index ecc45cf..3b91933 100644 --- a/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/platforms/UniversalPlatform.java @@ -5,17 +5,17 @@ public class UniversalPlatform { // Spawning status - private static LongArrayList pendChunkGens; + 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) { - pendChunkGens = chunks; + pendingChunksForGen = chunks; } public static LongArrayList getPendingGenerateChunksList() { - return pendChunkGens; + return pendingChunksForGen; } public static void finalizeSpawnChunksGeneration() { 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 95% rename from src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java rename to Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java index 94181b4..436cae4 100644 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnCacher.java @@ -21,7 +21,7 @@ public class SpawnCacher { public SpawnCacher() { this.spawnsRequireSaving = false; this.cachedSpawns = new ArrayList<>(); - cacheSpawns(); + //cacheSpawns(); } public static void initialize() { @@ -32,6 +32,7 @@ public static SpawnCacher getInstance() { return INSTANCE; } + /* private void cacheSpawns() { SpawnFinder finder = SpawnFinder.getInstance(); @@ -63,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()); @@ -87,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/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java similarity index 85% rename from src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java rename to Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java index 880dafa..3d1dbb7 100644 --- a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java @@ -1,17 +1,16 @@ package systems.kscott.randomspawnplus.spawn; -import systems.kscott.randomspawnplus.RandomSpawnPlus; -import systems.kscott.randomspawnplus.events.SpawnCheckEvent; -import systems.kscott.randomspawnplus.util.Util; +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.Location; import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.block.Block; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.CompletableFuture; public class SpawnFinder { @@ -19,22 +18,35 @@ public class SpawnFinder { private static ArrayList unsafeBlocks; public static void init() { + initializeSpawnChunks(); /* Setup safeblocks */ List unsafeBlockStrings; - unsafeBlockStrings = config.getStringList("unsafe-blocks"); - String spawnLevelName = config.getString("respawn-world"); - spawnLevel = Bukkit.getWorld(spawnLevelName); + //unsafeBlockStrings = config.getStringList("unsafe-blocks"); - unsafeBlocks = new ArrayList<>(); - for (String string : unsafeBlockStrings) { - unsafeBlocks.add(Material.matchMaterial(string)); - } + //unsafeBlocks = new ArrayList<>(); + //for (String string : unsafeBlockStrings) { + // unsafeBlocks.add(Material.matchMaterial(string)); + //} } - private static void create() { - + private static void initializeSpawnChunks() { + String worldStr = Config.getGlobalConfig().respawnWorld; + 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(); + }); } + /* public Location getCandidateLocation() { String worldString = config.getString("respawn-world"); @@ -234,4 +246,5 @@ public int getHighestY(World world, int x, int z) { } return minHeight; } + */ } diff --git a/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java similarity index 100% rename from src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java rename to Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnRegion.java 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 100% rename from src/main/java/systems/kscott/randomspawnplus/util/Locations.java rename to Common/src/main/java/systems/kscott/randomspawnplus/util/Locations.java 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/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java b/Common/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java similarity index 87% rename from src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java rename to Common/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java index 322d5ae..f0dda1b 100644 --- a/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/util/PlatformUtil.java @@ -1,6 +1,7 @@ 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; @@ -16,7 +17,7 @@ public static Platforms init() { // Paper if (Util.doesClassExists("io.papermc.paper.configuration.GlobalConfiguration")) { - return platform = new FoliaPlatform(); + return platform = new PaperPlatform(); } // Spigot (Fallback) diff --git a/src/main/java/systems/kscott/randomspawnplus/util/Util.java b/Common/src/main/java/systems/kscott/randomspawnplus/util/Util.java similarity index 100% rename from src/main/java/systems/kscott/randomspawnplus/util/Util.java rename to Common/src/main/java/systems/kscott/randomspawnplus/util/Util.java 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 100% rename from src/main/resources/plugin.yml rename to Common/src/main/resources/plugin.yml 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/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java b/Platform/Abstraction/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java similarity index 61% rename from src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java rename to Platform/Abstraction/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java index f6df403..1f3b286 100644 --- a/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java +++ b/Platform/Abstraction/src/main/java/systems/kscott/randomspawnplus/platforms/Platforms.java @@ -4,11 +4,11 @@ import org.bukkit.World; import org.bukkit.command.CommandSender; -public interface Platforms { +import java.util.concurrent.CompletableFuture; - void collectNonGeneratedChunks(World level, int minX, int maxX, int minZ, int maxZ); +public interface Platforms { - void loadPendingGenerateChunks(LongArrayList chunks); + 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/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java b/Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java similarity index 58% rename from src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java rename to Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java index 0849fa7..c1d57fa 100644 --- a/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java +++ b/Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java @@ -4,15 +4,12 @@ import org.bukkit.World; import org.bukkit.command.CommandSender; -public class FoliaPlatform implements Platforms { +import java.util.concurrent.CompletableFuture; - @Override - public void collectNonGeneratedChunks(World level, int minX, int maxX, int minZ, int maxZ) { - throw new UnsupportedOperationException(); - } +public class FoliaPlatform extends PaperPlatform { @Override - public void loadPendingGenerateChunks(LongArrayList chunks) { + public CompletableFuture collectNonGeneratedChunksAsync(World level, int minX, int minZ, int maxX, int maxZ) { 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..28ae793 --- /dev/null +++ b/Platform/Paper/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java @@ -0,0 +1,86 @@ +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/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java b/Platform/Spigot/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java similarity index 52% rename from src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java rename to Platform/Spigot/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java index 221aaf5..cb03f6d 100644 --- a/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java +++ b/Platform/Spigot/src/main/java/systems/kscott/randomspawnplus/platforms/SpigotPlatform.java @@ -1,27 +1,33 @@ package systems.kscott.randomspawnplus.platforms; import it.unimi.dsi.fastutil.longs.LongArrayList; -import systems.kscott.randomspawnplus.RandomSpawnPlus; -import systems.kscott.randomspawnplus.spawn.SpawnFinder; -import org.bukkit.Bukkit; 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 void collectNonGeneratedChunks(World level, int minX, int maxX, int minZ, int maxZ) { + 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 maxChunkX = maxX >> 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, false); + Chunk chunk = level.getChunkAt(chunkX, chunkZ, true); if (!chunk.isGenerated()) { long chunkKey = (long) chunkX & 0xffffffffL | ((long) chunkZ & 0xffffffffL) << 32; @@ -30,22 +36,7 @@ public void collectNonGeneratedChunks(World level, int minX, int maxX, int minZ, } } - if (!chunks.isEmpty()) { - UniversalPlatform.setPendingGenerateChunksList(chunks); - } else { - UniversalPlatform.finalizeSpawnChunksGeneration(); - } - } - - @Override - public void loadPendingGenerateChunks(LongArrayList chunks) { - for (long chunkKey : chunks) { - int chunkX = (int) chunkKey; - int chunkZ = (int) (chunkKey >>> 32); - - Runnable loadChunk = () -> SpawnFinder.spawnLevel.getChunkAt(chunkX, chunkZ, true); - Bukkit.getScheduler().runTaskAsynchronously(RandomSpawnPlus.getInstance(), loadChunk); - } + return chunks; } @Override diff --git a/README.md b/README.md index 660a502..bdf108c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Compatibility -- Support Java 8 and higher +- 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 @@ -34,6 +34,23 @@ 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..50cec87 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + // Support convention plugins written in Kotlin. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build. + `kotlin-dsl` +} + +repositories { + // Use the plugin portal to apply community plugins in convention plugins. + 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..a046acd --- /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/") + } + + // JitPack + maven { + name = "jitpack.io" + url = uri("https://jitpack.io/") + } + + // FoliaLib + maven { + name = "devmart-other" + url = uri("https://nexuslite.gcnt.net/repos/other/") + } + + // ConfigurationMaster API + maven { + name = "ConfigurationMaster-repo" + url = uri("https://ci.pluginwiki.us/plugin/repository/everything/") + } +} + +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 0070889..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,115 +0,0 @@ -plugins { - `java-library` - `maven-publish` - id("com.gradleup.shadow") version "8.3.5" -} - -group = "systems.kscott" -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/") - } - - // JitPack - maven { - name = "jitpack.io" - url = uri("https://jitpack.io") - } - - // FoliaLib - maven { - name = "devmart-other" - url = uri("https://nexuslite.gcnt.net/repos/other/") - } - - // ConfigurationMaster API - maven { - name = "ConfigurationMaster-repo" - url = uri("https://ci.pluginwiki.us/plugin/repository/everything/") - } -} - -val adventureVersion = "4.17.0" - -dependencies { - compileOnly("org.spigotmc:spigot-api:1.21.3-R0.1-SNAPSHOT") - - compileOnly("org.apache.logging.log4j:log4j-api:2.24.1") - compileOnly("it.unimi.dsi:fastutil:8.5.15") - api("org.bstats:bstats-bukkit:3.1.0") - api("co.aikar:acf-paper:0.5.1-SNAPSHOT") // Remove - api("com.tcoded:FoliaLib:0.4.2") - implementation("com.github.thatsmusic99:ConfigurationMaster-API:v2.0.0-rc.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") -} - -configure { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -tasks { - withType { - options.encoding = "UTF-8" - } - - build.configure { - dependsOn("shadowJar") - } - - shadowJar { - archiveFileName = "${project.name}-${project.version}.${archiveExtension.get()}" - 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") - } - - processResources { - filesMatching("**/plugin.yml") { - expand("version" to project.version) - } - } -} - -publishing { - publications.create("maven") { - from(components["java"]) - } -} - diff --git a/settings.gradle.kts b/settings.gradle.kts index 73587a9..e2c36da 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,19 @@ +pluginManagement { + // Include 'plugins build' to define convention plugins. + includeBuild("build-logic") + repositories { + gradlePluginPortal() + maven("https://repo.auxilor.io/repository/maven-public/") + } +} + rootProject.name = "randomspawnplus" + +include( + ":Common", + + ":Platform:Abstraction", + ":Platform:Folia", + ":Platform:Paper", + ":Platform:Spigot", +) \ No newline at end of file diff --git a/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java b/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java deleted file mode 100644 index fc45a3e..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/config/SpawnStorage.java +++ /dev/null @@ -1,4 +0,0 @@ -package systems.kscott.randomspawnplus.config; - -public class SpawnStorage { -} diff --git a/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java b/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java deleted file mode 100644 index 6ddc8b3..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java +++ /dev/null @@ -1,23 +0,0 @@ -package systems.kscott.randomspawnplus.platforms; - -import it.unimi.dsi.fastutil.longs.LongArrayList; -import org.bukkit.World; -import org.bukkit.command.CommandSender; - -public class PaperPlatform implements Platforms { - - @Override - public void collectNonGeneratedChunks(World level, int minX, int maxX, int minZ, int maxZ) { - throw new UnsupportedOperationException(); - } - - @Override - public void loadPendingGenerateChunks(LongArrayList chunks) { - throw new UnsupportedOperationException(); - } - - @Override - public void broadcastCommandMessage(CommandSender sender, Object message) { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java b/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java deleted file mode 100644 index e65ef4f..0000000 --- a/src/main/java/systems/kscott/randomspawnplus/util/MessageUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -package systems.kscott.randomspawnplus.util; - -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import systems.kscott.randomspawnplus.RandomSpawnPlus; -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); - } - - 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 timeLeft(long seconds) { - long days = seconds / 86400L; - long hours = seconds / 3600L % 24L; - long minutes = seconds / 60L % 60L; - long secs = seconds % 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")) : "") - + (secs > 0L ? " " + secs + " " + (secs != 1 ? get("delay.seconds") : get("delay.second")) : ""); - } -} From c72a39a4e717245612173312fa36116b82ba3177 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Tue, 17 Jun 2025 14:46:35 +0800 Subject: [PATCH 4/4] baba boi --- Common/build.gradle.kts | 5 +- .../randomspawnplus/RandomSpawnPlus.java | 3 +- .../randomspawnplus/config/GlobalConfig.java | 1 - .../events/SpawnCheckEvent.java | 1 - .../randomspawnplus/events/SpawnType.java | 1 - .../randomspawnplus/listeners/OnDeath.java | 47 ++++---- .../listeners/OnFirstJoin.java | 6 +- .../randomspawnplus/spawn/SpawnData.java | 10 ++ .../randomspawnplus/spawn/SpawnFinder.java | 110 +++++------------- .../randomspawnplus/spawn/SpawnGenerator.java | 48 ++++++++ .../platforms/FoliaPlatform.java | 3 +- .../platforms/PaperPlatform.java | 3 +- build-logic/build.gradle.kts | 2 - .../kotlin/cn.dreeam.rsp.wrapper.gradle.kts | 12 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 12 +- gradlew.bat | 26 +++-- settings.gradle.kts | 4 +- 20 files changed, 147 insertions(+), 151 deletions(-) create mode 100644 Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnData.java create mode 100644 Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnGenerator.java diff --git a/Common/build.gradle.kts b/Common/build.gradle.kts index 5bb9e22..764e16c 100644 --- a/Common/build.gradle.kts +++ b/Common/build.gradle.kts @@ -6,7 +6,7 @@ plugins { val adventureVersion = "4.17.0" dependencies { - compileOnly("org.spigotmc:spigot-api:1.21.3-R0.1-SNAPSHOT") + compileOnly("org.spigotmc:spigot-api:1.21.5-R0.1-SNAPSHOT") implementation(project(":Platform:Abstraction")) implementation(project(":Platform:Folia")) @@ -18,7 +18,7 @@ dependencies { 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.2") + implementation("com.github.thatsmusic99:ConfigurationMaster-API:v2.0.0-rc.3") compileOnly("net.essentialsx:EssentialsX:2.20.1") compileOnly("net.luckperms:api:5.4") @@ -52,4 +52,3 @@ publishing { 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 index 5f7cd8e..fcdede6 100644 --- a/Common/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/RandomSpawnPlus.java @@ -14,6 +14,7 @@ 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; @@ -45,7 +46,7 @@ public void onEnable() { registerHooks(); PlatformUtil.init(); - SpawnFinder.init(); + SpawnGenerator.init(); //SpawnCacher.initialize(); } diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java b/Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java index d531562..102499e 100644 --- a/Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/config/GlobalConfig.java @@ -115,7 +115,6 @@ private void initConfig() { ); 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"); diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java b/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java index 87e6d96..a4981fc 100644 --- a/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnCheckEvent.java @@ -26,7 +26,6 @@ public static HandlerList getHandlerList() { return HANDLERS_LIST; } - public void setValid(boolean valid, String reason) { this.validReason = reason; this.valid = valid; diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java b/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java index 0f60e06..f7e8770 100644 --- a/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/events/SpawnType.java @@ -5,5 +5,4 @@ public enum SpawnType { FIRST_JOIN, ON_DEATH, WILD_COMMAND - } diff --git a/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java index f2ae5ba..a735482 100644 --- a/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnDeath.java @@ -23,31 +23,28 @@ public void onDeath(PlayerRespawnEvent event) { Player player = event.getPlayer(); - if (player.isDead()) { - /* - if (!config.getBoolean("use-permission-node") || (config.getBoolean("use-permission-node") && player.hasPermission("randomspawnplus.randomspawn"))) { - if (Config.getGlobalConfig().randomSpawnAtBed) { - 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); - } - */ + 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 index 9da2d0d..c92243c 100644 --- a/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnFirstJoin.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/listeners/OnFirstJoin.java @@ -34,9 +34,10 @@ public void onFirstJoin(PlayerJoinEvent event) { Util.firstJoinPlayers.remove(player.getUniqueId()); return; } + */ try { - Location spawnLoc = SpawnFinder.getInstance().findSpawn(true); + Location spawnLoc = SpawnFinder.getRandomSpawn(); // quiquelhappy start - Prevent essentials home replace boolean prevent = false; @@ -55,7 +56,7 @@ public void onFirstJoin(PlayerJoinEvent event) { 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)); + 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?)."); @@ -65,7 +66,6 @@ public void onFirstJoin(PlayerJoinEvent event) { 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/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/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java index 3d1dbb7..acb29ac 100644 --- a/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java +++ b/Common/src/main/java/systems/kscott/randomspawnplus/spawn/SpawnFinder.java @@ -1,49 +1,37 @@ 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.Material; -import org.bukkit.World; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; +import systems.kscott.randomspawnplus.RandomSpawnPlus; +import org.bukkit.Location; public class SpawnFinder { - public static World spawnLevel; - private static ArrayList unsafeBlocks; + public static Location getRandomSpawn() { + boolean valid = false; + Location location = null; - public static void init() { - initializeSpawnChunks(); - /* 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; + } + if (location == null) return null; - private static void initializeSpawnChunks() { - String worldStr = Config.getGlobalConfig().respawnWorld; - 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(); - }); + return location.add(0.5, 1, 0.5); } /* @@ -97,52 +85,6 @@ public Location getCandidateLocation() { 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; 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/Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java b/Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java index c1d57fa..f654de7 100644 --- a/Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java +++ b/Platform/Folia/src/main/java/systems/kscott/randomspawnplus/platforms/FoliaPlatform.java @@ -10,7 +10,8 @@ public class FoliaPlatform extends PaperPlatform { @Override public CompletableFuture collectNonGeneratedChunksAsync(World level, int minX, int minZ, int maxX, int maxZ) { - throw new UnsupportedOperationException(); + // TODO: check if work on folia and remove it. + return super.collectNonGeneratedChunksAsync(level, minX, minZ, maxX, maxZ); } @Override 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 index 28ae793..d70a9af 100644 --- a/Platform/Paper/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java +++ b/Platform/Paper/src/main/java/systems/kscott/randomspawnplus/platforms/PaperPlatform.java @@ -19,6 +19,7 @@ public CompletableFuture collectNonGeneratedChunksAsync(World lev } 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; @@ -57,7 +58,7 @@ private static CompletableFuture checkChunksGenerated(World level LongArrayList nonGeneratedChunks = new LongArrayList(); return CompletableFuture.supplyAsync(() -> { - int count = 0; + int count = 0; for (long chunkKey : partitionChunks) { int chunkX = (int) chunkKey; int chunkZ = (int) (chunkKey >> 32); diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 50cec87..cc02e63 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -1,9 +1,7 @@ plugins { - // Support convention plugins written in Kotlin. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build. `kotlin-dsl` } repositories { - // Use the plugin portal to apply community plugins in convention plugins. 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 index a046acd..2b6e859 100644 --- a/build-logic/src/main/kotlin/cn.dreeam.rsp.wrapper.gradle.kts +++ b/build-logic/src/main/kotlin/cn.dreeam.rsp.wrapper.gradle.kts @@ -27,6 +27,12 @@ repositories { 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" @@ -38,12 +44,6 @@ repositories { name = "devmart-other" url = uri("https://nexuslite.gcnt.net/repos/other/") } - - // ConfigurationMaster API - maven { - name = "ConfigurationMaster-repo" - url = uri("https://ci.pluginwiki.us/plugin/repository/everything/") - } } configure { 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 d64cd4917707c1f8861d8cb53dd15194d4248596..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 34753 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%0AF=STgWRWL+V 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 W(m*lz*HoBlEiM*bU{am~Dx&}eeNGnu 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 e2c36da..e7c85d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,9 +1,7 @@ pluginManagement { - // Include 'plugins build' to define convention plugins. includeBuild("build-logic") repositories { gradlePluginPortal() - maven("https://repo.auxilor.io/repository/maven-public/") } } @@ -16,4 +14,4 @@ include( ":Platform:Folia", ":Platform:Paper", ":Platform:Spigot", -) \ No newline at end of file +)