├── src ├── com │ └── geitenijs │ │ └── keepchunks │ │ ├── updatechecker │ │ ├── VersionResponse.java │ │ └── UpdateCheck.java │ │ ├── Main.java │ │ ├── Hooks.java │ │ ├── commands │ │ ├── Command_Main.java │ │ ├── Command_Reload.java │ │ ├── Command_List.java │ │ ├── Command_Help.java │ │ ├── Command_Releaseall.java │ │ ├── hooks │ │ │ ├── Chunkinfo_WE.java │ │ │ ├── Chunkinfo_WG.java │ │ │ ├── Releaseregion_WG.java │ │ │ ├── Keepregion_WG.java │ │ │ ├── Releaseregion_WE.java │ │ │ └── Keepregion_WE.java │ │ ├── Command_Keepchunk.java │ │ ├── Command_Releasechunk.java │ │ ├── Command_Releaseregion.java │ │ ├── Command_Keepregion.java │ │ ├── CommandWrapper.java │ │ ├── Command_Chunkinfo.java │ │ ├── Command_Releaserail.java │ │ └── Command_Keeprail.java │ │ ├── Events.java │ │ ├── Strings.java │ │ ├── Utilities.java │ │ └── Metrics.java └── plugin.yml ├── README.md ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── LICENSE.txt /src/com/geitenijs/keepchunks/updatechecker/VersionResponse.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.updatechecker; 2 | 3 | public enum VersionResponse { 4 | 5 | LATEST, 6 | FOUND_NEW, 7 | 8 | UNAVAILABLE 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KeepChunks 2 | [SpigotMC Resource Page](https://www.spigotmc.org/resources/23307/) 3 | 4 | KeepChunks allows you to select chunks to keep them loaded in the Minecraft server memory, even when there are no players around. This can be useful for large redstone circuits, minecart railroads, commandblock systems and even for reducing teleport lag. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/Main.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks; 2 | 3 | import org.bukkit.plugin.java.JavaPlugin; 4 | 5 | public class Main extends JavaPlugin { 6 | public static Main plugin; 7 | 8 | public void onEnable() { 9 | Main.plugin = this; 10 | Hooks.registerHooks(); 11 | Utilities.createConfigs(); 12 | Utilities.registerCommandsAndCompletions(); 13 | Utilities.registerEvents(); 14 | Utilities.loadChunks(); 15 | Utilities.startTasks(); 16 | Utilities.startMetrics(); 17 | Utilities.pluginBanner(); 18 | Utilities.done(); 19 | } 20 | 21 | public void onDisable() { 22 | Utilities.reloadConfigFile(); 23 | Utilities.saveConfigFile(); 24 | Utilities.reloadDataFile(); 25 | Utilities.saveDataFile(); 26 | Utilities.stopTasks(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/Hooks.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks; 2 | 3 | import com.sk89q.worldedit.bukkit.WorldEditPlugin; 4 | import com.sk89q.worldguard.bukkit.WorldGuardPlugin; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.plugin.Plugin; 7 | 8 | public class Hooks { 9 | 10 | public static boolean WorldEdit; 11 | public static boolean WorldGuard; 12 | 13 | public static void registerHooks() { 14 | hookWorldEdit(); 15 | hookWorldGuard(); 16 | } 17 | 18 | private static void hookWorldEdit() { 19 | final Plugin wePlugin = Bukkit.getPluginManager().getPlugin("WorldEdit"); 20 | WorldEdit = wePlugin instanceof WorldEditPlugin; 21 | } 22 | 23 | private static void hookWorldGuard() { 24 | final Plugin wgPlugin = Bukkit.getPluginManager().getPlugin("WorldGuard"); 25 | WorldGuard = wgPlugin instanceof WorldGuardPlugin; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Execute command '...' 16 | 2. Use feature '....' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Environment (please complete the following information):** 26 | - OS: [e.g. Linux] 27 | - Server software [e.g. Spigot, Paper] 28 | - Server version [e.g. 1.14.4] 29 | - KeepChunks version [e.g. v1.7.0] 30 | - WorldEdit version (if applicable) [e.g. 7.0.0-SNAPSHOT;1234-567a89b] 31 | - WorldGuard version (if applicable) [e.g. 7.0.0-SNAPSHOT;1234-567a89b] 32 | - Full list of plugins 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /src/plugin.yml: -------------------------------------------------------------------------------- 1 | name: KeepChunks 2 | version: 1.7.3 3 | api-version: 1.18 4 | description: The most powerful chunk loading plugin! 5 | author: Geitenijs 6 | website: https://www.spigotmc.org/resources/23307 7 | load: POSTWORLD 8 | 9 | softdepend: [WorldEdit, FastAsyncWorldEdit, WorldGuard, Multiverse-Core, MultiWorld, My_Worlds] 10 | 11 | main: com.geitenijs.keepchunks.Main 12 | 13 | commands: 14 | keepchunks: 15 | description: Keep chunks loaded. 16 | kc: 17 | description: Keep chunks loaded. 18 | 19 | permissions: 20 | keepchunks.help: 21 | default: op 22 | keepchunks.reload: 23 | default: op 24 | keepchunks.list: 25 | default: op 26 | keepchunks.chunkinfo: 27 | default: op 28 | keepchunks.keepchunk: 29 | default: op 30 | keepchunks.keepregion: 31 | default: op 32 | keepchunks.keeprail: 33 | default: op 34 | keepchunks.releaseall: 35 | default: op 36 | keepchunks.releasechunk: 37 | default: op 38 | keepchunks.releaseregion: 39 | default: op 40 | keepchunks.releaserail: 41 | default: op 42 | keepchunks.notify.update: 43 | default: op -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Main.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import org.bukkit.command.Command; 6 | import org.bukkit.command.CommandExecutor; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.command.TabCompleter; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class Command_Main implements CommandExecutor, TabCompleter { 14 | 15 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 16 | Utilities.msg(s, Strings.IGFULLPREFIX + "&fRunning &9v" + Strings.VERSION); 17 | Utilities.msg(s, Strings.IGFULLPREFIX + "&fMade by &6" + Strings.AUTHOR + "&f, since 2015"); 18 | return true; 19 | } 20 | 21 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 22 | ArrayList tabs = new ArrayList<>(); 23 | tabs.add("help"); 24 | tabs.add("reload"); 25 | tabs.add("list"); 26 | tabs.add("chunkinfo"); 27 | tabs.add("keepchunk"); 28 | tabs.add("keepregion"); 29 | tabs.add("keeprail"); 30 | tabs.add("releaseall"); 31 | tabs.add("releasechunk"); 32 | tabs.add("releaseregion"); 33 | tabs.add("releaserail"); 34 | return CommandWrapper.filterTabs(tabs, args); 35 | } 36 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Reload.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Hooks; 4 | import com.geitenijs.keepchunks.Strings; 5 | import com.geitenijs.keepchunks.Utilities; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.command.TabCompleter; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class Command_Reload implements CommandExecutor, TabCompleter { 15 | 16 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 17 | if (args.length == 1) { 18 | Utilities.reloadConfigFile(); 19 | Utilities.reloadDataFile(); 20 | Hooks.registerHooks(); 21 | Utilities.msg(s, Strings.IGPREFIX + "&aThe configuration file has been reloaded."); 22 | if (!Utilities.chunks.isEmpty()) { 23 | Utilities.msg(s, Strings.IGPREFIX + "&7&oReloading all chunks..."); 24 | Utilities.loadChunks(); 25 | Utilities.msg(s, Strings.IGPREFIX + "&aAll &f" + Utilities.chunks.size() + "&a chunks have been reloaded."); 26 | } 27 | } else { 28 | Utilities.msg(s, Strings.RELOADUSAGE); 29 | } 30 | return true; 31 | } 32 | 33 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 34 | ArrayList tabs = new ArrayList<>(); 35 | tabs.clear(); 36 | return CommandWrapper.filterTabs(tabs, args); 37 | } 38 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | LICENSE AND TERMS OF KEEPCHUNKS, BY GEITENIJS 2 | LAST UPDATED ON 31/12/2019 3 | 4 | APPLIES TO USAGE, DISTRIBUTION, AND MODIFICATION OF SOFTWARE AND ITS SOURCE. 5 | 6 | Copyright (c) 2015-2020 Geitenijs 7 | 8 | All rights are reserved to change, modify, or update this license without 9 | any prior notice or disclosure. 10 | Any such changes or modification shall be effective immediately 11 | from the “LAST UPDATED ON” date located at the top of this license. 12 | 13 | YOU ARE ALLOWED TO: 14 | - Run this software on your server. 15 | - Modify the source code for personal use only. 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to use, or modify, 19 | and to permit persons to whom the Software is furnished to do so, for personal use only. 20 | 21 | YOU ARE NOT ALLOWED TO: 22 | - Claim this software or source code to be a work of your own. 23 | - Distribute original or "official" versions of this software. 24 | - Distribute modified or "unofficial" versions of this software. 25 | 26 | This license strictly prohibits the person obtaining a copy of this software 27 | and associated documentation files (the "Software"), to publish, distribute, 28 | sublicense, and/or sell copies of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 36 | SOFTWARE. 37 | -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_List.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import org.bukkit.command.Command; 6 | import org.bukkit.command.CommandExecutor; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.command.TabCompleter; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class Command_List implements CommandExecutor, TabCompleter { 14 | 15 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 16 | if (args.length == 1) { 17 | if (Utilities.chunks.isEmpty()) { 18 | Utilities.msg(s, Strings.IGPREFIX + "&cThere don't seem to be any marked chunks."); 19 | } else { 20 | Utilities.msg(s, Strings.LINE); 21 | for (final String chunk : Utilities.chunks) { 22 | final String[] chunkCoordinates = chunk.split("#"); 23 | final int x = Integer.parseInt(chunkCoordinates[0]); 24 | final int z = Integer.parseInt(chunkCoordinates[1]); 25 | final String world = chunkCoordinates[2]; 26 | Utilities.msg(s, "&fChunk &9(" + x + ", " + z + ") &fin &6'" + world + "'&f."); 27 | } 28 | Utilities.msg(s, Strings.LINE); 29 | Utilities.msg(s, Strings.IGPREFIX + "&aA total of &f" + Utilities.chunks.size() + "&a chunks are currently marked."); 30 | } 31 | } else { 32 | Utilities.msg(s, Strings.LISTUSAGE); 33 | } 34 | return true; 35 | } 36 | 37 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 38 | ArrayList tabs = new ArrayList<>(); 39 | tabs.clear(); 40 | return CommandWrapper.filterTabs(tabs, args); 41 | } 42 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Help.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import org.bukkit.command.Command; 6 | import org.bukkit.command.CommandExecutor; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.command.TabCompleter; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class Command_Help implements CommandExecutor, TabCompleter { 14 | 15 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 16 | if (args.length == 1) { 17 | Utilities.msg(s, Strings.LINE); 18 | Utilities.msg(s, "&8/&akc help &7-&f Shows this list"); 19 | Utilities.msg(s, "&8/&akc reload &7-&f Reload the plugin"); 20 | Utilities.msg(s, "&8/&akc list &7-&f List all marked chunks"); 21 | Utilities.msg(s, "&8/&akc chunkinfo &7-&f Info about chunks"); 22 | Utilities.msg(s, "&8/&akc keepchunk &7-&f Keep a single chunk loaded"); 23 | Utilities.msg(s, "&8/&akc keepregion &7-&f Keep multiple chunks loaded"); 24 | Utilities.msg(s, "&8/&akc keeprail &7-&f Keep a railroad loaded"); 25 | Utilities.msg(s, "&8/&akc releaseall &7-&f Release all marked chunks"); 26 | Utilities.msg(s, "&8/&akc releasechunk &7-&f Release a single chunk"); 27 | Utilities.msg(s, "&8/&akc releaseregion &7-&f Release multiple chunks"); 28 | Utilities.msg(s, "&8/&akc releaserail &7-&f Release a railroad"); 29 | Utilities.msg(s, Strings.LINE); 30 | } else { 31 | Utilities.msg(s, Strings.HELPUSAGE); 32 | } 33 | return true; 34 | } 35 | 36 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 37 | ArrayList tabs = new ArrayList<>(); 38 | return CommandWrapper.filterTabs(tabs, args); 39 | } 40 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Releaseall.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.command.TabCompleter; 10 | import org.bukkit.entity.Player; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class Command_Releaseall implements CommandExecutor, TabCompleter { 16 | 17 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 18 | if (args.length == 1) { 19 | if (Utilities.config.getBoolean("general.releaseallprotection")) { 20 | if (s instanceof Player) { 21 | Utilities.msg(s, Strings.ONLYCONSOLE); 22 | return false; 23 | } 24 | } 25 | if (Utilities.chunks.isEmpty()) { 26 | Utilities.msg(s, Strings.IGPREFIX + "&cThere don't seem to be any marked chunks."); 27 | } else { 28 | Utilities.msg(s, Strings.IGPREFIX + "&7&oReleasing all chunks..."); 29 | int totalChunks = Utilities.chunks.size(); 30 | for (final String chunk : Utilities.chunks) { 31 | final String[] chunkCoordinates = chunk.split("#"); 32 | final int x = Integer.parseInt(chunkCoordinates[0]); 33 | final int z = Integer.parseInt(chunkCoordinates[1]); 34 | final String world = chunkCoordinates[2]; 35 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, false); 36 | } 37 | Utilities.chunks.clear(); 38 | Utilities.data.set("chunks", new ArrayList<>()); 39 | Utilities.saveDataFile(); 40 | Utilities.reloadDataFile(); 41 | Utilities.msg(s, Strings.IGPREFIX + "&aAll &f" + totalChunks + "&a marked chunks have been released."); 42 | } 43 | } else { 44 | Utilities.msg(s, Strings.RELEASEALLUSAGE); 45 | } 46 | return true; 47 | } 48 | 49 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 50 | ArrayList tabs = new ArrayList<>(); 51 | tabs.clear(); 52 | return CommandWrapper.filterTabs(tabs, args); 53 | } 54 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/updatechecker/UpdateCheck.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.updatechecker; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.google.common.base.Preconditions; 5 | import com.google.common.io.Resources; 6 | import com.google.common.net.HttpHeaders; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.plugin.java.JavaPlugin; 9 | 10 | import javax.annotation.Nonnull; 11 | import javax.net.ssl.HttpsURLConnection; 12 | import java.io.IOException; 13 | import java.net.HttpURLConnection; 14 | import java.net.URL; 15 | import java.nio.charset.Charset; 16 | import java.util.Objects; 17 | import java.util.function.BiConsumer; 18 | 19 | public class UpdateCheck { 20 | 21 | private static final String SPIGOT_URL = "https://api.spigotmc.org/legacy/update.php?resource=%d"; 22 | 23 | private final JavaPlugin javaPlugin; 24 | 25 | private final String currentVersion; 26 | private int resourceId = -1; 27 | private BiConsumer versionResponse; 28 | 29 | private UpdateCheck(@Nonnull JavaPlugin javaPlugin) { 30 | this.javaPlugin = Objects.requireNonNull(javaPlugin, "javaPlugin"); 31 | this.currentVersion = Strings.VERSION; 32 | } 33 | 34 | public static UpdateCheck of(@Nonnull JavaPlugin javaPlugin) { 35 | return new UpdateCheck(javaPlugin); 36 | } 37 | 38 | public UpdateCheck resourceId(int resourceId) { 39 | this.resourceId = resourceId; 40 | return this; 41 | } 42 | 43 | public UpdateCheck handleResponse(@Nonnull BiConsumer versionResponse) { 44 | this.versionResponse = versionResponse; 45 | return this; 46 | } 47 | 48 | public void check() { 49 | Objects.requireNonNull(this.javaPlugin, "javaPlugin"); 50 | Objects.requireNonNull(this.currentVersion, "currentVersion"); 51 | Preconditions.checkState(this.resourceId != -1, "resource id not set"); 52 | Objects.requireNonNull(this.versionResponse, "versionResponse"); 53 | 54 | Bukkit.getScheduler().runTaskAsynchronously(this.javaPlugin, () -> { 55 | try { 56 | HttpURLConnection httpURLConnection = (HttpsURLConnection) new URL(String.format(SPIGOT_URL, this.resourceId)).openConnection(); 57 | httpURLConnection.setRequestMethod("GET"); 58 | httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT, "Mozilla/5.0"); 59 | 60 | String fetchedVersion = Resources.toString(httpURLConnection.getURL(), Charset.defaultCharset()); 61 | 62 | boolean latestVersion = fetchedVersion.equalsIgnoreCase(this.currentVersion); 63 | 64 | Bukkit.getScheduler().runTask(this.javaPlugin, () -> this.versionResponse.accept(latestVersion ? VersionResponse.LATEST : VersionResponse.FOUND_NEW, latestVersion ? this.currentVersion : fetchedVersion)); 65 | } catch (IOException ex) { 66 | Bukkit.getScheduler().runTask(this.javaPlugin, () -> this.versionResponse.accept(VersionResponse.UNAVAILABLE, null)); 67 | } 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/Events.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Chunk; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.player.PlayerJoinEvent; 9 | import org.bukkit.event.world.ChunkUnloadEvent; 10 | import org.bukkit.event.world.WorldLoadEvent; 11 | import org.bukkit.event.world.WorldUnloadEvent; 12 | 13 | import java.util.ArrayList; 14 | import java.util.HashSet; 15 | import java.util.List; 16 | 17 | public class Events implements Listener { 18 | 19 | @EventHandler(priority = EventPriority.HIGHEST) 20 | public void onChunkUnload(ChunkUnloadEvent e) { 21 | final Chunk currentChunk = e.getChunk(); 22 | final String chunk = currentChunk.getX() + "#" + currentChunk.getZ() + "#" 23 | + currentChunk.getWorld().getName(); 24 | if (new HashSet<>(Utilities.chunks).contains(chunk)) { 25 | try { 26 | Bukkit.getServer().getWorld(currentChunk.getWorld().getName()).loadChunk(currentChunk.getX(), currentChunk.getZ()); 27 | Bukkit.getServer().getWorld(currentChunk.getWorld().getName()).loadChunk(currentChunk.getX(), currentChunk.getZ(), true); 28 | } catch (NullPointerException ignored) { 29 | } 30 | } 31 | } 32 | 33 | @EventHandler(priority = EventPriority.HIGHEST) 34 | public void onWorldUnload(WorldUnloadEvent e) { 35 | final List worlds = new ArrayList<>(); 36 | for (final String chunk : Utilities.chunks) { 37 | final String world = chunk.split("#")[2]; 38 | worlds.add(world.toLowerCase()); 39 | } 40 | if (worlds.contains(e.getWorld().getName().toLowerCase())) { 41 | e.setCancelled(true); 42 | } 43 | } 44 | 45 | @EventHandler(priority = EventPriority.HIGHEST) 46 | public void onWorldLoad(WorldLoadEvent e) { 47 | for (final String chunk : Utilities.chunks) { 48 | final String[] chunkCoordinates = chunk.split("#"); 49 | final int x = Integer.parseInt(chunkCoordinates[0]); 50 | final int z = Integer.parseInt(chunkCoordinates[1]); 51 | final String world = chunkCoordinates[2]; 52 | try { 53 | Bukkit.getServer().getWorld(world).loadChunk(x, z); 54 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, true); 55 | } catch (NullPointerException ignored) { 56 | } 57 | } 58 | } 59 | 60 | @EventHandler(priority = EventPriority.MONITOR) 61 | public void onUpdateAvailable(PlayerJoinEvent e) { 62 | if ((e.getPlayer().hasPermission("keepchunks.notify.update")) && Utilities.config.getBoolean("updates.check") && Utilities.config.getBoolean("updates.notify") && Utilities.updateAvailable()) { 63 | Bukkit.getScheduler().runTaskLaterAsynchronously(Main.plugin, () -> { 64 | Utilities.msg(e.getPlayer(), Strings.IGPREFIX + "&fA new &a" + Strings.PLUGIN + "&f update is available: &av" + Utilities.updateVersion() + "&f!"); 65 | Utilities.msg(e.getPlayer(), Strings.IGPREFIX + "&fDownload @ &a" + Strings.WEBSITE + "&f."); 66 | }, 90L); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/hooks/Chunkinfo_WE.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands.hooks; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import com.geitenijs.keepchunks.commands.CommandWrapper; 6 | import com.sk89q.worldedit.IncompleteRegionException; 7 | import com.sk89q.worldedit.LocalSession; 8 | import com.sk89q.worldedit.WorldEdit; 9 | import com.sk89q.worldedit.bukkit.BukkitAdapter; 10 | import com.sk89q.worldedit.bukkit.BukkitPlayer; 11 | import com.sk89q.worldedit.math.BlockVector3; 12 | import com.sk89q.worldedit.regions.Region; 13 | import org.bukkit.Chunk; 14 | import org.bukkit.Location; 15 | import org.bukkit.OfflinePlayer; 16 | import org.bukkit.command.Command; 17 | import org.bukkit.command.CommandExecutor; 18 | import org.bukkit.command.CommandSender; 19 | import org.bukkit.command.TabCompleter; 20 | import org.bukkit.entity.Player; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | public class Chunkinfo_WE implements CommandExecutor, TabCompleter { 26 | 27 | int totalChunks = 0; 28 | 29 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 30 | totalChunks = 0; 31 | if (s instanceof Player) { 32 | try { 33 | Player player = ((OfflinePlayer) s).getPlayer(); 34 | BukkitPlayer bPlayer = BukkitAdapter.adapt(player); 35 | LocalSession session = WorldEdit.getInstance().getSessionManager().get(bPlayer); 36 | final Region sel = session.getSelection(bPlayer.getWorld()); 37 | BlockVector3 max = sel.getMaximumPoint(); 38 | BlockVector3 min = sel.getMinimumPoint(); 39 | assert player != null; 40 | Location maxPoint = new Location(player.getWorld(), max.x(), max.y(), max.z()); 41 | Location minPoint = new Location(player.getWorld(), min.x(), min.y(), min.z()); 42 | final int maxPointX = sel.getMaximumPoint().x(); 43 | final int maxPointZ = sel.getMaximumPoint().z(); 44 | final int minPointX = sel.getMinimumPoint().x(); 45 | final int minPointZ = sel.getMinimumPoint().z(); 46 | final Chunk chunkMax = maxPoint.getChunk(); 47 | final Chunk chunkMin = minPoint.getChunk(); 48 | final int maxZ = chunkMax.getZ(); 49 | final int maxX = chunkMax.getX(); 50 | final int minX = chunkMin.getX(); 51 | final int minZ = chunkMin.getZ(); 52 | final String world = sel.getWorld().getName(); 53 | for (int x = minX; x <= maxX; ++x) { 54 | for (int z = minZ; z <= maxZ; ++z) { 55 | ++totalChunks; 56 | } 57 | } 58 | Utilities.msg(s, Strings.LINE); 59 | Utilities.msg(s, "&fWorldEdit region: &c(" + minPointX + ", " + minPointZ + ") (" + maxPointX + ", " + maxPointZ + ")"); 60 | Utilities.msg(s, "&fChunk coords: &9(" + minX + ", " + minZ + ") (" + maxX + ", " + maxZ + ")"); 61 | Utilities.msg(s, "&fWorld: &6" + world); 62 | Utilities.msg(s, "&fTotal amount of chunks: &c" + totalChunks); 63 | Utilities.msg(s, Strings.LINE); 64 | } catch (IncompleteRegionException e) { 65 | Utilities.msg(s, Strings.WEFIRST); 66 | } 67 | } else { 68 | Utilities.msg(s, Strings.ONLYPLAYER); 69 | } 70 | return true; 71 | } 72 | 73 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 74 | ArrayList tabs = new ArrayList<>(); 75 | return CommandWrapper.filterTabs(tabs, args); 76 | } 77 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/Strings.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public class Strings { 7 | 8 | public static final String PLUGINCOLOURED = "&2&lKeep&8&lChunks"; 9 | public static final String IGFULLPREFIX = PLUGINCOLOURED + " &7&l» "; 10 | public static final String IGPREFIX = "&2&lK&8&lC &7&l» "; 11 | public static final String VERSION = "1.7.3"; 12 | public static final String AUTHOR = "Geitenijs"; 13 | public static final String NOPERM = Strings.IGPREFIX + "&cYou don't have permission to do that."; 14 | public static final String UNUSABLE = Strings.IGPREFIX + "&cOne or more values you've entered are unusable."; 15 | public static final String ONLYCONSOLE = Strings.IGPREFIX + "&cYou can only do that from the console."; 16 | public static final String ONLYPLAYER = Strings.IGPREFIX + "&cYou can only do that as an in-game player."; 17 | public static final String WEFIRST = Strings.IGPREFIX + "&cPlease select an area with WorldEdit first."; 18 | public static final String NOWE = Strings.IGPREFIX + "&cPlease install WorldEdit 7.0.0+ or FAWE 2.0.0+ first."; 19 | public static final String NOWG = Strings.IGPREFIX + "&cPlease install WorldGuard 7.0.0+ first."; 20 | public static final String LINE = "&8&m----------&r " + PLUGINCOLOURED + " &8&m----------"; 21 | public static final String HELPUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc help"; 22 | public static final String RELOADUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc reload"; 23 | public static final String LISTUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc list"; 24 | public static final String CHUNKINFOUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc chunkinfo &f(&ccoords &f|&c coords &f|&c current &f|&c worldedit &f|&c worldguard &f)"; 25 | public static final String KEEPCHUNKUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc keepchunk &f(&ccoords &f|&c current&f)"; 26 | public static final String KEEPREGIONUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc keepregion &f(&ccoords &f|&c worldedit &f| &cworldguard &f)"; 27 | public static final String KEEPRAILUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc keeprail &f(&ccoords &f|&c current&f)"; 28 | public static final String RELEASEALLUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc releaseall"; 29 | public static final String RELEASECHUNKUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc releasechunk &f(&ccoords &f|&c current&f)"; 30 | public static final String RELEASERAILUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc releaserail &f(&ccoords &f|&c current&f)"; 31 | public static final String RELEASEREGIONUSAGE = Strings.IGPREFIX + "&fUsage: &c/kc releaseregion &f(&ccoords &f|&c worldedit &f| &cworldguard &f)"; 32 | static final String PLUGIN = "KeepChunks"; 33 | static final String INTERNALPREFIX = "[KeepChunks] "; 34 | static final int RESOURCEID = 23307; 35 | static final String WEBSITE = "https://www.spigotmc.org/resources/" + Strings.RESOURCEID; 36 | static final List ASCIILOGO = Arrays.asList( 37 | "", 38 | " _ _ _______ _ _ ", 39 | "(_) | | (_______) | | | v" + Strings.VERSION, 40 | " _____| |_____ _____ ____ _ | |__ _ _ ____ | | _ ___ ", 41 | "| _ _) ___ | ___ | _ \\| | | _ \\| | | | _ \\| |_/ )/___)", 42 | "| | \\ \\| ____| ____| |_| | |_____| | | | |_| | | | | _ (|___ |", 43 | "|_| \\_)_____)_____) __/ \\______)_| |_|____/|_| |_|_| \\_|___/ ", 44 | " |_| ", 45 | "" 46 | ); 47 | static final String NOSTAT = "None"; 48 | } 49 | -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/hooks/Chunkinfo_WG.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands.hooks; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import com.geitenijs.keepchunks.commands.CommandWrapper; 6 | import com.sk89q.worldedit.bukkit.BukkitAdapter; 7 | import com.sk89q.worldedit.math.BlockVector3; 8 | import com.sk89q.worldguard.WorldGuard; 9 | import com.sk89q.worldguard.protection.managers.RegionManager; 10 | import com.sk89q.worldguard.protection.regions.ProtectedRegion; 11 | import com.sk89q.worldguard.protection.regions.RegionContainer; 12 | import org.bukkit.Bukkit; 13 | import org.bukkit.Chunk; 14 | import org.bukkit.Location; 15 | import org.bukkit.World; 16 | import org.bukkit.command.Command; 17 | import org.bukkit.command.CommandExecutor; 18 | import org.bukkit.command.CommandSender; 19 | import org.bukkit.command.TabCompleter; 20 | import org.bukkit.entity.Player; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | public class Chunkinfo_WG implements CommandExecutor, TabCompleter { 26 | 27 | int totalChunks = 0; 28 | 29 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 30 | totalChunks = 0; 31 | final String region = args[2]; 32 | final String world = args[3]; 33 | if (Bukkit.getWorld(world) == null) { 34 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 35 | return false; 36 | } 37 | World realWorld = Bukkit.getWorld(world); 38 | assert realWorld != null; 39 | com.sk89q.worldedit.world.World weWorld = BukkitAdapter.adapt(realWorld); 40 | RegionManager manager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(weWorld); 41 | assert manager != null; 42 | if (manager.getRegion(region) == null) { 43 | Utilities.msg(s, Strings.IGPREFIX + "&cRegion &f'" + region + "'&c doesn't exist, or is invalid."); 44 | } else { 45 | BlockVector3 max = manager.getRegion(region).getMaximumPoint(); 46 | BlockVector3 min = manager.getRegion(region).getMinimumPoint(); 47 | Location maxPoint = new Location(realWorld, max.x(), max.y(), max.z()); 48 | Location minPoint = new Location(realWorld, min.x(), min.y(), min.z()); 49 | final Chunk chunkMax = maxPoint.getChunk(); 50 | final Chunk chunkMin = minPoint.getChunk(); 51 | final int maxPointX = maxPoint.getBlockX(); 52 | final int maxPointZ = maxPoint.getBlockZ(); 53 | final int minPointX = minPoint.getBlockX(); 54 | final int minPointZ = minPoint.getBlockZ(); 55 | final int maxZ = chunkMax.getZ(); 56 | final int maxX = chunkMax.getX(); 57 | final int minX = chunkMin.getX(); 58 | final int minZ = chunkMin.getZ(); 59 | for (int x = minX; x <= maxX; ++x) { 60 | for (int z = minZ; z <= maxZ; ++z) { 61 | ++totalChunks; 62 | } 63 | } 64 | Utilities.msg(s, Strings.LINE); 65 | Utilities.msg(s, "&fWorldGuard region &2" + region + "&f: &c(" + minPointX + ", " + minPointZ + ") (" + maxPointX + ", " + maxPointZ + ")"); 66 | Utilities.msg(s, "&fChunk coords: &9(" + minX + ", " + minZ + ") (" + maxX + ", " + maxZ + ")"); 67 | Utilities.msg(s, "&fWorld: &6" + world); 68 | Utilities.msg(s, "&fTotal amount of chunks: &c" + totalChunks); 69 | Utilities.msg(s, Strings.LINE); 70 | } 71 | return true; 72 | } 73 | 74 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 75 | ArrayList tabs = new ArrayList<>(); 76 | String[] newArgs = CommandWrapper.getArgs(args); 77 | if (s instanceof Player player) { 78 | if (newArgs.length == 2) { 79 | RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); 80 | RegionManager regionManager = container.get(BukkitAdapter.adapt(player.getWorld())); 81 | if (regionManager != null) { 82 | if (!regionManager.getRegions().isEmpty()) { 83 | for (ProtectedRegion region : regionManager.getRegions().values()) { 84 | tabs.add(region.getId()); 85 | } 86 | } else { 87 | tabs.add(""); 88 | } 89 | } 90 | } 91 | if (newArgs.length == 3) { 92 | Location loc = player.getLocation(); 93 | tabs.add(loc.getWorld().getName()); 94 | } 95 | } else { 96 | if (newArgs.length == 2) { 97 | tabs.add(""); 98 | } 99 | if (newArgs.length == 3) { 100 | tabs.add(""); 101 | } 102 | } 103 | return CommandWrapper.filterTabs(tabs, args); 104 | } 105 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/hooks/Releaseregion_WG.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands.hooks; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import com.geitenijs.keepchunks.commands.CommandWrapper; 6 | import com.sk89q.worldedit.bukkit.BukkitAdapter; 7 | import com.sk89q.worldedit.math.BlockVector3; 8 | import com.sk89q.worldguard.WorldGuard; 9 | import com.sk89q.worldguard.protection.managers.RegionManager; 10 | import com.sk89q.worldguard.protection.regions.ProtectedRegion; 11 | import com.sk89q.worldguard.protection.regions.RegionContainer; 12 | import org.bukkit.Bukkit; 13 | import org.bukkit.Chunk; 14 | import org.bukkit.Location; 15 | import org.bukkit.World; 16 | import org.bukkit.command.Command; 17 | import org.bukkit.command.CommandExecutor; 18 | import org.bukkit.command.CommandSender; 19 | import org.bukkit.command.TabCompleter; 20 | import org.bukkit.entity.Player; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | public class Releaseregion_WG implements CommandExecutor, TabCompleter { 26 | 27 | int totalChunks = 0; 28 | 29 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 30 | totalChunks = 0; 31 | final String region = args[2]; 32 | final String world = args[3]; 33 | if (Bukkit.getWorld(world) == null) { 34 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 35 | return false; 36 | } 37 | World realWorld = Bukkit.getWorld(world); 38 | assert realWorld != null; 39 | com.sk89q.worldedit.world.World weWorld = BukkitAdapter.adapt(realWorld); 40 | RegionManager manager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(weWorld); 41 | assert manager != null; 42 | if (manager.getRegion(region) == null) { 43 | Utilities.msg(s, Strings.IGPREFIX + "&cRegion &f'" + region + "'&c doesn't exist, or is invalid."); 44 | } else { 45 | BlockVector3 max = manager.getRegion(region).getMaximumPoint(); 46 | BlockVector3 min = manager.getRegion(region).getMinimumPoint(); 47 | Location maxPoint = new Location(realWorld, max.x(), max.y(), max.z()); 48 | Location minPoint = new Location(realWorld, min.x(), min.y(), min.z()); 49 | final Chunk chunkMax = maxPoint.getChunk(); 50 | final Chunk chunkMin = minPoint.getChunk(); 51 | final int maxZ = chunkMax.getZ(); 52 | final int maxX = chunkMax.getX(); 53 | final int minX = chunkMin.getX(); 54 | final int minZ = chunkMin.getZ(); 55 | Utilities.msg(s, Strings.IGPREFIX + "&7&oReleasing chunks between &9&o(" + minX + ", " + minZ + ")&7&o & &9&o(" + maxX + ", " + maxZ + ")&7&o in &6&o'" + world + "'&7&o..."); 56 | for (int x = minX; x <= maxX; ++x) { 57 | for (int z = minZ; z <= maxZ; ++z) { 58 | final String chunk = x + "#" + z + "#" + world; 59 | if (Utilities.chunks.contains(chunk)) { 60 | ++totalChunks; 61 | Utilities.chunks.remove(chunk); 62 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, false); 63 | } 64 | } 65 | } 66 | if (totalChunks == 0) { 67 | Utilities.msg(s, Strings.IGPREFIX + "&cNo chunk within your region was already marked."); 68 | return true; 69 | } 70 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 71 | Utilities.saveDataFile(); 72 | Utilities.reloadDataFile(); 73 | Utilities.msg(s, Strings.IGPREFIX + "&fSuccessfully released a total of &9" + totalChunks + "&f chunks!"); 74 | } 75 | return true; 76 | } 77 | 78 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 79 | ArrayList tabs = new ArrayList<>(); 80 | String[] newArgs = CommandWrapper.getArgs(args); 81 | if (s instanceof Player player) { 82 | if (newArgs.length == 2) { 83 | RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); 84 | RegionManager regionManager = container.get(BukkitAdapter.adapt(player.getWorld())); 85 | if (regionManager != null) { 86 | if (!regionManager.getRegions().isEmpty()) { 87 | for (ProtectedRegion region : regionManager.getRegions().values()) { 88 | tabs.add(region.getId()); 89 | } 90 | } else { 91 | tabs.add(""); 92 | } 93 | } 94 | } 95 | if (newArgs.length == 3) { 96 | Location loc = player.getLocation(); 97 | tabs.add(loc.getWorld().getName()); 98 | } 99 | } else { 100 | if (newArgs.length == 2) { 101 | tabs.add(""); 102 | } 103 | if (newArgs.length == 3) { 104 | tabs.add(""); 105 | } 106 | } 107 | return CommandWrapper.filterTabs(tabs, args); 108 | } 109 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/hooks/Keepregion_WG.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands.hooks; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import com.geitenijs.keepchunks.commands.CommandWrapper; 6 | import com.sk89q.worldedit.bukkit.BukkitAdapter; 7 | import com.sk89q.worldedit.math.BlockVector3; 8 | import com.sk89q.worldguard.WorldGuard; 9 | import com.sk89q.worldguard.protection.managers.RegionManager; 10 | import com.sk89q.worldguard.protection.regions.ProtectedRegion; 11 | import com.sk89q.worldguard.protection.regions.RegionContainer; 12 | import org.bukkit.Bukkit; 13 | import org.bukkit.Chunk; 14 | import org.bukkit.Location; 15 | import org.bukkit.World; 16 | import org.bukkit.command.Command; 17 | import org.bukkit.command.CommandExecutor; 18 | import org.bukkit.command.CommandSender; 19 | import org.bukkit.command.TabCompleter; 20 | import org.bukkit.entity.Player; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | public class Keepregion_WG implements CommandExecutor, TabCompleter { 26 | 27 | int totalChunks = 0; 28 | 29 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 30 | totalChunks = 0; 31 | final String region = args[2]; 32 | final String world = args[3]; 33 | if (Bukkit.getWorld(world) == null) { 34 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 35 | return false; 36 | } 37 | World realWorld = Bukkit.getWorld(world); 38 | assert realWorld != null; 39 | com.sk89q.worldedit.world.World weWorld = BukkitAdapter.adapt(realWorld); 40 | RegionManager manager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(weWorld); 41 | assert manager != null; 42 | if (manager.getRegion(region) == null) { 43 | Utilities.msg(s, Strings.IGPREFIX + "&cRegion &f'" + region + "'&c doesn't exist, or is invalid."); 44 | } else { 45 | BlockVector3 max = manager.getRegion(region).getMaximumPoint(); 46 | BlockVector3 min = manager.getRegion(region).getMinimumPoint(); 47 | Location maxPoint = new Location(realWorld, max.x(), max.y(), max.z()); 48 | Location minPoint = new Location(realWorld, min.x(), min.y(), min.z()); 49 | final Chunk chunkMax = maxPoint.getChunk(); 50 | final Chunk chunkMin = minPoint.getChunk(); 51 | final int maxZ = chunkMax.getZ(); 52 | final int maxX = chunkMax.getX(); 53 | final int minX = chunkMin.getX(); 54 | final int minZ = chunkMin.getZ(); 55 | Utilities.msg(s, Strings.IGPREFIX + "&7&oMarking chunks between &9&o(" + minX + ", " + minZ + ")&7&o & &9&o(" + maxX + ", " + maxZ + ")&7&o in &6&o'" + world + "'&7&o..."); 56 | for (int x = minX; x <= maxX; ++x) { 57 | for (int z = minZ; z <= maxZ; ++z) { 58 | final String chunk = x + "#" + z + "#" + world; 59 | if (!Utilities.chunks.contains(chunk)) { 60 | ++totalChunks; 61 | Utilities.chunks.add(chunk); 62 | Bukkit.getServer().getWorld(world).loadChunk(x, z); 63 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, true); 64 | } 65 | } 66 | } 67 | if (totalChunks == 0) { 68 | Utilities.msg(s, Strings.IGPREFIX + "&cEvery chunk within your region was already marked."); 69 | return true; 70 | } 71 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 72 | Utilities.saveDataFile(); 73 | Utilities.reloadDataFile(); 74 | Utilities.msg(s, Strings.IGPREFIX + "&fSuccessfully marked a total of &9" + totalChunks + "&f chunks!"); 75 | } 76 | return true; 77 | } 78 | 79 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 80 | ArrayList tabs = new ArrayList<>(); 81 | String[] newArgs = CommandWrapper.getArgs(args); 82 | if (s instanceof Player player) { 83 | if (newArgs.length == 2) { 84 | RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); 85 | RegionManager regionManager = container.get(BukkitAdapter.adapt(player.getWorld())); 86 | if (regionManager != null) { 87 | if (!regionManager.getRegions().isEmpty()) { 88 | for (ProtectedRegion region : regionManager.getRegions().values()) { 89 | tabs.add(region.getId()); 90 | } 91 | } else { 92 | tabs.add(""); 93 | } 94 | } 95 | } 96 | if (newArgs.length == 3) { 97 | Location loc = player.getLocation(); 98 | tabs.add(loc.getWorld().getName()); 99 | } 100 | } else { 101 | if (newArgs.length == 2) { 102 | tabs.add(""); 103 | } 104 | if (newArgs.length == 3) { 105 | tabs.add(""); 106 | } 107 | } 108 | return CommandWrapper.filterTabs(tabs, args); 109 | } 110 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Keepchunk.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.Chunk; 7 | import org.bukkit.Location; 8 | import org.bukkit.command.Command; 9 | import org.bukkit.command.CommandExecutor; 10 | import org.bukkit.command.CommandSender; 11 | import org.bukkit.command.TabCompleter; 12 | import org.bukkit.entity.Player; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | public class Command_Keepchunk implements CommandExecutor, TabCompleter { 18 | 19 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 20 | if (args.length == 2) { 21 | if (args[1].equalsIgnoreCase("current")) { 22 | if (s instanceof Player) { 23 | final Chunk currentChunk = ((Player) s).getLocation().getChunk(); 24 | final int x = currentChunk.getX(); 25 | final int z = currentChunk.getZ(); 26 | final String world = currentChunk.getWorld().getName(); 27 | final String chunk = x + "#" + z + "#" + world; 28 | if (Utilities.chunks.contains(chunk)) { 29 | Utilities.msg(s, Strings.IGPREFIX + "&cChunk &f(" + x + ", " + z + ")&c in &f'" + world + "'&c is already marked."); 30 | } else { 31 | Utilities.chunks.add(chunk); 32 | Bukkit.getServer().getWorld(world).loadChunk(x, z); 33 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, true); 34 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 35 | Utilities.saveDataFile(); 36 | Utilities.reloadDataFile(); 37 | Utilities.msg(s, Strings.IGPREFIX + "&fMarked chunk &9(" + x + ", " + z + ")&f in &6'" + world + "'&f."); 38 | } 39 | } else { 40 | Utilities.msg(s, Strings.ONLYPLAYER); 41 | } 42 | } else { 43 | Utilities.msg(s, Strings.KEEPCHUNKUSAGE); 44 | } 45 | } else if (args.length == 5) { 46 | if (args[1].equalsIgnoreCase("coords")) { 47 | try { 48 | final int x = Integer.parseInt(args[2]); 49 | final int z = Integer.parseInt(args[3]); 50 | final String world = args[4]; 51 | if (Bukkit.getWorld(world) == null) { 52 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 53 | return false; 54 | } 55 | final String chunk = x + "#" + z + "#" + world; 56 | if (Utilities.chunks.contains(chunk)) { 57 | Utilities.msg(s, Strings.IGPREFIX + "&cChunk &f(" + x + ", " + z + ")&c in &f'" + world + "'&c is already marked."); 58 | } else { 59 | Utilities.chunks.add(chunk); 60 | Bukkit.getServer().getWorld(world).loadChunk(x, z); 61 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, true); 62 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 63 | Utilities.saveDataFile(); 64 | Utilities.reloadDataFile(); 65 | Utilities.msg(s, Strings.IGPREFIX + "&fMarked chunk &9(" + x + ", " + z + ")&f in &6'" + world + "'&f."); 66 | } 67 | } catch (NumberFormatException ex) { 68 | Utilities.msg(s, Strings.UNUSABLE); 69 | } 70 | } else { 71 | Utilities.msg(s, Strings.KEEPCHUNKUSAGE); 72 | } 73 | } else { 74 | Utilities.msg(s, Strings.KEEPCHUNKUSAGE); 75 | } 76 | return true; 77 | } 78 | 79 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 80 | ArrayList tabs = new ArrayList<>(); 81 | String[] newArgs = CommandWrapper.getArgs(args); 82 | if (newArgs.length == 1) { 83 | tabs.add("current"); 84 | tabs.add("coords"); 85 | } 86 | if (args[1].equals("coords")) { 87 | if (s instanceof Player player) { 88 | Location loc = player.getLocation(); 89 | if (newArgs.length == 2) { 90 | tabs.add(String.valueOf(loc.getChunk().getX())); 91 | } 92 | if (newArgs.length == 3) { 93 | tabs.add(String.valueOf(loc.getChunk().getZ())); 94 | } 95 | if (newArgs.length == 4) { 96 | tabs.add(loc.getWorld().getName()); 97 | } 98 | } else { 99 | if (newArgs.length == 2) { 100 | tabs.add(""); 101 | } 102 | if (newArgs.length == 3) { 103 | tabs.add(""); 104 | } 105 | if (newArgs.length == 4) { 106 | tabs.add(""); 107 | } 108 | } 109 | } 110 | if (args[1].equals("current")) { 111 | tabs.clear(); 112 | } 113 | return CommandWrapper.filterTabs(tabs, args); 114 | } 115 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Releasechunk.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.Chunk; 7 | import org.bukkit.Location; 8 | import org.bukkit.command.Command; 9 | import org.bukkit.command.CommandExecutor; 10 | import org.bukkit.command.CommandSender; 11 | import org.bukkit.command.TabCompleter; 12 | import org.bukkit.entity.Player; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | public class Command_Releasechunk implements CommandExecutor, TabCompleter { 18 | 19 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 20 | if (args.length == 2) { 21 | if (args[1].equalsIgnoreCase("current")) { 22 | if (s instanceof Player) { 23 | final Chunk currentChunk = ((Player) s).getLocation().getChunk(); 24 | final int x = currentChunk.getX(); 25 | final int z = currentChunk.getZ(); 26 | final String world = currentChunk.getWorld().getName(); 27 | final String chunk = x + "#" + z + "#" + world; 28 | if (!Utilities.chunks.contains(chunk)) { 29 | Utilities.msg(s, Strings.IGPREFIX + "&cChunk &f(" + x + ", " + z + ")&c in &f'" + world + "'&c isn't marked."); 30 | } else { 31 | Utilities.chunks.remove(chunk); 32 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, false); 33 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 34 | Utilities.saveDataFile(); 35 | Utilities.reloadDataFile(); 36 | Utilities.msg(s, Strings.IGPREFIX + "&fReleased chunk &9(" + x + ", " + z + ")&f in &6'" + world + "'&f."); 37 | } 38 | } else { 39 | Utilities.msg(s, Strings.ONLYPLAYER); 40 | } 41 | } else { 42 | Utilities.msg(s, Strings.RELEASECHUNKUSAGE); 43 | } 44 | 45 | } else if (args.length == 5) { 46 | if (args[1].equalsIgnoreCase("coords")) { 47 | try { 48 | final int x = Integer.parseInt(args[2]); 49 | final int z = Integer.parseInt(args[3]); 50 | final String world = args[4]; 51 | if (Bukkit.getWorld(world) == null) { 52 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 53 | return false; 54 | } 55 | final String chunk = x + "#" + z + "#" + world; 56 | if (!Utilities.chunks.contains(chunk)) { 57 | Utilities.msg(s, Strings.IGPREFIX + "&cChunk &f(" + x + ", " + z + ")&c in &f'" + world + "'&c isn't marked."); 58 | } else { 59 | Utilities.chunks.remove(chunk); 60 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, false); 61 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 62 | Utilities.saveDataFile(); 63 | Utilities.reloadDataFile(); 64 | Utilities.msg(s, Strings.IGPREFIX + "&fReleased chunk &9(" + x + ", " + z + ")&f in &6'" + world + "'&f."); 65 | } 66 | } catch (NumberFormatException ex) { 67 | Utilities.msg(s, Strings.UNUSABLE); 68 | } 69 | } else { 70 | Utilities.msg(s, Strings.RELEASECHUNKUSAGE); 71 | } 72 | } else { 73 | Utilities.msg(s, Strings.RELEASECHUNKUSAGE); 74 | } 75 | return true; 76 | } 77 | 78 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 79 | ArrayList tabs = new ArrayList<>(); 80 | String[] newArgs = CommandWrapper.getArgs(args); 81 | if (newArgs.length == 1) { 82 | tabs.add("current"); 83 | tabs.add("coords"); 84 | } 85 | if (args[1].equals("coords")) { 86 | if (s instanceof Player player) { 87 | Location loc = player.getLocation(); 88 | if (newArgs.length == 2) { 89 | tabs.add(String.valueOf(loc.getChunk().getX())); 90 | } 91 | if (newArgs.length == 3) { 92 | tabs.add(String.valueOf(loc.getChunk().getZ())); 93 | } 94 | if (newArgs.length == 4) { 95 | tabs.add(loc.getWorld().getName()); 96 | } 97 | if (newArgs.length > 4) { 98 | tabs.clear(); 99 | } 100 | } else { 101 | if (newArgs.length == 2) { 102 | tabs.add(""); 103 | } 104 | if (newArgs.length == 3) { 105 | tabs.add(""); 106 | } 107 | if (newArgs.length == 4) { 108 | tabs.add(""); 109 | } 110 | if (newArgs.length > 4) { 111 | tabs.clear(); 112 | } 113 | } 114 | } 115 | if (args[1].equals("current")) { 116 | tabs.clear(); 117 | } 118 | return CommandWrapper.filterTabs(tabs, args); 119 | } 120 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/hooks/Releaseregion_WE.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands.hooks; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import com.geitenijs.keepchunks.commands.CommandWrapper; 6 | import com.sk89q.worldedit.IncompleteRegionException; 7 | import com.sk89q.worldedit.LocalSession; 8 | import com.sk89q.worldedit.WorldEdit; 9 | import com.sk89q.worldedit.bukkit.BukkitAdapter; 10 | import com.sk89q.worldedit.bukkit.BukkitPlayer; 11 | import com.sk89q.worldedit.math.BlockVector3; 12 | import com.sk89q.worldedit.regions.Region; 13 | import org.bukkit.Bukkit; 14 | import org.bukkit.Chunk; 15 | import org.bukkit.Location; 16 | import org.bukkit.OfflinePlayer; 17 | import org.bukkit.command.Command; 18 | import org.bukkit.command.CommandExecutor; 19 | import org.bukkit.command.CommandSender; 20 | import org.bukkit.command.TabCompleter; 21 | import org.bukkit.entity.Player; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | public class Releaseregion_WE implements CommandExecutor, TabCompleter { 27 | 28 | int totalChunks = 0; 29 | 30 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 31 | totalChunks = 0; 32 | if (s instanceof Player) { 33 | try { 34 | Player player = ((OfflinePlayer) s).getPlayer(); 35 | BukkitPlayer bPlayer = BukkitAdapter.adapt(player); 36 | LocalSession session = WorldEdit.getInstance().getSessionManager().get(bPlayer); 37 | final Region sel = session.getSelection(bPlayer.getWorld()); 38 | BlockVector3 max = sel.getMaximumPoint(); 39 | BlockVector3 min = sel.getMinimumPoint(); 40 | assert player != null; 41 | Location maxPoint = new Location(player.getWorld(), max.x(), max.y(), max.z()); 42 | Location minPoint = new Location(player.getWorld(), min.x(), min.y(), min.z()); 43 | final Chunk chunkMax = maxPoint.getChunk(); 44 | final Chunk chunkMin = minPoint.getChunk(); 45 | final int maxZ = chunkMax.getZ(); 46 | final int maxX = chunkMax.getX(); 47 | final int minX = chunkMin.getX(); 48 | final int minZ = chunkMin.getZ(); 49 | final String world = sel.getWorld().getName(); 50 | Utilities.msg(s, Strings.IGPREFIX + "&7&oReleasing chunks between &9&o(" + minX + ", " + minZ + ")&7&o & &9&o(" + maxX + ", " + maxZ + ")&7&o in &6&o'" + world + "'&7&o..."); 51 | for (int x = minX; x <= maxX; ++x) { 52 | for (int z = minZ; z <= maxZ; ++z) { 53 | final String chunk = x + "#" + z + "#" + world; 54 | if (Utilities.chunks.contains(chunk)) { 55 | ++totalChunks; 56 | Utilities.chunks.remove(chunk); 57 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, false); 58 | } 59 | } 60 | } 61 | if (totalChunks == 0) { 62 | Utilities.msg(s, Strings.IGPREFIX + "&cNo chunk within your region was already marked."); 63 | return true; 64 | } 65 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 66 | Utilities.saveDataFile(); 67 | Utilities.reloadDataFile(); 68 | Utilities.msg(s, Strings.IGPREFIX + "&fSuccessfully released a total of &9" + totalChunks + "&f chunks!"); 69 | } catch (IncompleteRegionException e) { 70 | Utilities.msg(s, Strings.WEFIRST); 71 | } 72 | } else { 73 | Utilities.msg(s, Strings.ONLYPLAYER); 74 | } 75 | return true; 76 | } 77 | 78 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 79 | ArrayList tabs = new ArrayList<>(); 80 | String[] newArgs = CommandWrapper.getArgs(args); 81 | Player player = (Player) s; 82 | Location loc = player.getLocation(); 83 | if (args[1].equals("worldedit")) { 84 | return CommandWrapper.filterTabs(tabs, args); 85 | } 86 | if (args[1].equals("coords")) { 87 | try { 88 | Player wePlayer = ((OfflinePlayer) s).getPlayer(); 89 | BukkitPlayer bPlayer = BukkitAdapter.adapt(wePlayer); 90 | LocalSession session = WorldEdit.getInstance().getSessionManager().get(bPlayer); 91 | final Region sel = session.getSelection(bPlayer.getWorld()); 92 | BlockVector3 max = sel.getMaximumPoint(); 93 | BlockVector3 min = sel.getMinimumPoint(); 94 | assert wePlayer != null; 95 | Location maxPoint = new Location(wePlayer.getWorld(), max.x(), max.y(), max.z()); 96 | Location minPoint = new Location(wePlayer.getWorld(), min.x(), min.y(), min.z()); 97 | final Chunk chunkMax = maxPoint.getChunk(); 98 | final Chunk chunkMin = minPoint.getChunk(); 99 | final int maxZ = chunkMax.getZ(); 100 | final int maxX = chunkMax.getX(); 101 | final int minX = chunkMin.getX(); 102 | final int minZ = chunkMin.getZ(); 103 | final String world = sel.getWorld().getName(); 104 | if (newArgs.length == 2) { 105 | tabs.add(String.valueOf(minX)); 106 | } 107 | if (newArgs.length == 3) { 108 | tabs.add(String.valueOf(minZ)); 109 | } 110 | if (newArgs.length == 4) { 111 | tabs.add(String.valueOf(maxX)); 112 | } 113 | if (newArgs.length == 5) { 114 | tabs.add(String.valueOf(maxZ)); 115 | } 116 | if (newArgs.length == 6) { 117 | tabs.add(world); 118 | } 119 | } catch (IncompleteRegionException e) { 120 | if (newArgs.length == 2) { 121 | tabs.add(String.valueOf(loc.getChunk().getX())); 122 | } 123 | if (newArgs.length == 3) { 124 | tabs.add(String.valueOf(loc.getChunk().getZ())); 125 | } 126 | if (newArgs.length == 4) { 127 | tabs.add(String.valueOf(loc.getChunk().getX())); 128 | } 129 | if (newArgs.length == 5) { 130 | tabs.add(String.valueOf(loc.getChunk().getZ())); 131 | } 132 | if (newArgs.length == 6) { 133 | tabs.add(loc.getWorld().getName()); 134 | } 135 | } 136 | } 137 | return CommandWrapper.filterTabs(tabs, args); 138 | } 139 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/hooks/Keepregion_WE.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands.hooks; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import com.geitenijs.keepchunks.commands.CommandWrapper; 6 | import com.sk89q.worldedit.IncompleteRegionException; 7 | import com.sk89q.worldedit.LocalSession; 8 | import com.sk89q.worldedit.WorldEdit; 9 | import com.sk89q.worldedit.bukkit.BukkitAdapter; 10 | import com.sk89q.worldedit.bukkit.BukkitPlayer; 11 | import com.sk89q.worldedit.math.BlockVector3; 12 | import com.sk89q.worldedit.regions.Region; 13 | import org.bukkit.Bukkit; 14 | import org.bukkit.Chunk; 15 | import org.bukkit.Location; 16 | import org.bukkit.OfflinePlayer; 17 | import org.bukkit.command.Command; 18 | import org.bukkit.command.CommandExecutor; 19 | import org.bukkit.command.CommandSender; 20 | import org.bukkit.command.TabCompleter; 21 | import org.bukkit.entity.Player; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | public class Keepregion_WE implements CommandExecutor, TabCompleter { 27 | 28 | int totalChunks = 0; 29 | 30 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 31 | totalChunks = 0; 32 | if (s instanceof Player) { 33 | try { 34 | Player player = ((OfflinePlayer) s).getPlayer(); 35 | BukkitPlayer bPlayer = BukkitAdapter.adapt(player); 36 | LocalSession session = WorldEdit.getInstance().getSessionManager().get(bPlayer); 37 | final Region sel = session.getSelection(bPlayer.getWorld()); 38 | BlockVector3 max = sel.getMaximumPoint(); 39 | BlockVector3 min = sel.getMinimumPoint(); 40 | assert player != null; 41 | Location maxPoint = new Location(player.getWorld(), max.x(), max.y(), max.z()); 42 | Location minPoint = new Location(player.getWorld(), min.x(), min.y(), min.z()); 43 | final Chunk chunkMax = maxPoint.getChunk(); 44 | final Chunk chunkMin = minPoint.getChunk(); 45 | final int maxZ = chunkMax.getZ(); 46 | final int maxX = chunkMax.getX(); 47 | final int minX = chunkMin.getX(); 48 | final int minZ = chunkMin.getZ(); 49 | final String world = sel.getWorld().getName(); 50 | Utilities.msg(s, Strings.IGPREFIX + "&7&oMarking chunks between &9&o(" + minX + ", " + minZ + ")&7&o & &9&o(" + maxX + ", " + maxZ + ")&7&o in &6&o'" + world + "'&7&o..."); 51 | for (int x = minX; x <= maxX; ++x) { 52 | for (int z = minZ; z <= maxZ; ++z) { 53 | final String chunk = x + "#" + z + "#" + world; 54 | if (!Utilities.chunks.contains(chunk)) { 55 | ++totalChunks; 56 | Utilities.chunks.add(chunk); 57 | Bukkit.getServer().getWorld(world).loadChunk(x, z); 58 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, true); 59 | } 60 | } 61 | } 62 | if (totalChunks == 0) { 63 | Utilities.msg(s, Strings.IGPREFIX + "&cEvery chunk within your region was already marked."); 64 | return true; 65 | } 66 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 67 | Utilities.saveDataFile(); 68 | Utilities.reloadDataFile(); 69 | Utilities.msg(s, Strings.IGPREFIX + "&fSuccessfully marked a total of &9" + totalChunks + "&f chunks!"); 70 | } catch (IncompleteRegionException e) { 71 | Utilities.msg(s, Strings.WEFIRST); 72 | } 73 | } else { 74 | Utilities.msg(s, Strings.ONLYPLAYER); 75 | } 76 | return true; 77 | } 78 | 79 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 80 | ArrayList tabs = new ArrayList<>(); 81 | String[] newArgs = CommandWrapper.getArgs(args); 82 | Player player = (Player) s; 83 | Location loc = player.getLocation(); 84 | if (args[1].equals("worldedit")) { 85 | return CommandWrapper.filterTabs(tabs, args); 86 | } 87 | if (args[1].equals("coords")) { 88 | try { 89 | Player wePlayer = ((OfflinePlayer) s).getPlayer(); 90 | BukkitPlayer bPlayer = BukkitAdapter.adapt(wePlayer); 91 | LocalSession session = WorldEdit.getInstance().getSessionManager().get(bPlayer); 92 | final Region sel = session.getSelection(bPlayer.getWorld()); 93 | BlockVector3 max = sel.getMaximumPoint(); 94 | BlockVector3 min = sel.getMinimumPoint(); 95 | assert wePlayer != null; 96 | Location maxPoint = new Location(wePlayer.getWorld(), max.x(), max.y(), 97 | max.z()); 98 | Location minPoint = new Location(wePlayer.getWorld(), min.x(), min.y(), 99 | min.z()); 100 | final Chunk chunkMax = maxPoint.getChunk(); 101 | final Chunk chunkMin = minPoint.getChunk(); 102 | final int maxZ = chunkMax.getZ(); 103 | final int maxX = chunkMax.getX(); 104 | final int minX = chunkMin.getX(); 105 | final int minZ = chunkMin.getZ(); 106 | final String world = sel.getWorld().getName(); 107 | if (newArgs.length == 2) { 108 | tabs.add(String.valueOf(minX)); 109 | } 110 | if (newArgs.length == 3) { 111 | tabs.add(String.valueOf(minZ)); 112 | } 113 | if (newArgs.length == 4) { 114 | tabs.add(String.valueOf(maxX)); 115 | } 116 | if (newArgs.length == 5) { 117 | tabs.add(String.valueOf(maxZ)); 118 | } 119 | if (newArgs.length == 6) { 120 | tabs.add(world); 121 | } 122 | } catch (IncompleteRegionException e) { 123 | if (newArgs.length == 2) { 124 | tabs.add(String.valueOf(loc.getChunk().getX())); 125 | } 126 | if (newArgs.length == 3) { 127 | tabs.add(String.valueOf(loc.getChunk().getZ())); 128 | } 129 | if (newArgs.length == 4) { 130 | tabs.add(String.valueOf(loc.getChunk().getX())); 131 | } 132 | if (newArgs.length == 5) { 133 | tabs.add(String.valueOf(loc.getChunk().getZ())); 134 | } 135 | if (newArgs.length == 6) { 136 | tabs.add(loc.getWorld().getName()); 137 | } 138 | } 139 | } 140 | return CommandWrapper.filterTabs(tabs, args); 141 | } 142 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Releaseregion.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Hooks; 4 | import com.geitenijs.keepchunks.Strings; 5 | import com.geitenijs.keepchunks.Utilities; 6 | import com.geitenijs.keepchunks.commands.hooks.Releaseregion_WE; 7 | import com.geitenijs.keepchunks.commands.hooks.Releaseregion_WG; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.Location; 10 | import org.bukkit.command.Command; 11 | import org.bukkit.command.CommandExecutor; 12 | import org.bukkit.command.CommandSender; 13 | import org.bukkit.command.TabCompleter; 14 | import org.bukkit.entity.Player; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import static java.lang.Integer.max; 20 | import static java.lang.Integer.min; 21 | 22 | public class Command_Releaseregion implements CommandExecutor, TabCompleter { 23 | 24 | int totalChunks = 0; 25 | private CommandExecutor WEReleaseregion; 26 | private CommandExecutor WGReleaseregion; 27 | private TabCompleter WEReleaseregionTab; 28 | private TabCompleter WGReleaseregionTab; 29 | 30 | Command_Releaseregion() { 31 | if (Hooks.WorldEdit) { 32 | WEReleaseregion = new Releaseregion_WE(); 33 | WEReleaseregionTab = new Releaseregion_WE(); 34 | } 35 | if (Hooks.WorldGuard) { 36 | WGReleaseregion = new Releaseregion_WG(); 37 | WGReleaseregionTab = new Releaseregion_WG(); 38 | } 39 | } 40 | 41 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 42 | totalChunks = 0; 43 | if (args.length == 2) { 44 | if (args[1].equalsIgnoreCase("worldedit")) { 45 | if (Hooks.WorldEdit) { 46 | return WEReleaseregion.onCommand(s, c, label, args); 47 | } else { 48 | Utilities.msg(s, Strings.NOWE); 49 | } 50 | } else { 51 | Utilities.msg(s, Strings.RELEASEREGIONUSAGE); 52 | } 53 | } else if (args.length == 4) { 54 | if (args[1].equalsIgnoreCase("worldguard")) { 55 | if (Hooks.WorldGuard) { 56 | return WGReleaseregion.onCommand(s, c, label, args); 57 | } else { 58 | Utilities.msg(s, Strings.NOWG); 59 | } 60 | } else { 61 | Utilities.msg(s, Strings.RELEASEREGIONUSAGE); 62 | } 63 | } else if (args.length == 7) { 64 | if (args[1].equalsIgnoreCase("coords")) { 65 | try { 66 | final int X1 = Integer.parseInt(args[2]); 67 | final int Z1 = Integer.parseInt(args[3]); 68 | final int X2 = Integer.parseInt(args[4]); 69 | final int Z2 = Integer.parseInt(args[5]); 70 | final int minX = min(X1, X2); 71 | final int minZ = min(Z1, Z2); 72 | final int maxX = max(X1, X2); 73 | final int maxZ = max(Z1, Z2); 74 | final String world = args[6]; 75 | if (Bukkit.getWorld(world) == null) { 76 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 77 | return false; 78 | } 79 | Utilities.msg(s, Strings.IGPREFIX + "&7&oReleasing chunks between &9&o(" + minX + ", " + minZ + ")&7&o & &9&o(" + maxX + ", " + maxZ + ")&7&o in &6&o'" + world + "'&7&o..."); 80 | for (int x = minX; x <= maxX; ++x) { 81 | for (int z = minZ; z <= maxZ; ++z) { 82 | final String chunk = x + "#" + z + "#" + world; 83 | if (Utilities.chunks.contains(chunk)) { 84 | ++totalChunks; 85 | Utilities.chunks.remove(chunk); 86 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, false); 87 | } 88 | } 89 | } 90 | if (totalChunks == 0) { 91 | Utilities.msg(s, Strings.IGPREFIX + "&cNo chunk within your region was already marked."); 92 | return true; 93 | } 94 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 95 | Utilities.saveDataFile(); 96 | Utilities.reloadDataFile(); 97 | Utilities.msg(s, Strings.IGPREFIX + "&fSuccessfully released a total of &9" + totalChunks + "&f chunks!"); 98 | } catch (NumberFormatException ex) { 99 | Utilities.msg(s, Strings.UNUSABLE); 100 | } 101 | } else { 102 | Utilities.msg(s, Strings.RELEASEREGIONUSAGE); 103 | } 104 | } else { 105 | Utilities.msg(s, Strings.RELEASEREGIONUSAGE); 106 | } 107 | return true; 108 | } 109 | 110 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 111 | ArrayList tabs = new ArrayList<>(); 112 | String[] newArgs = CommandWrapper.getArgs(args); 113 | if (newArgs.length == 1) { 114 | tabs.add("coords"); 115 | tabs.add("worldedit"); 116 | tabs.add("worldguard"); 117 | } 118 | if (args[1].equals("coords")) { 119 | if (s instanceof Player player) { 120 | Location loc = player.getLocation(); 121 | if (Hooks.WorldEdit) { 122 | return WEReleaseregionTab.onTabComplete(s, c, label, args); 123 | } else { 124 | if (newArgs.length == 2) { 125 | tabs.add(String.valueOf(loc.getChunk().getX())); 126 | } 127 | if (newArgs.length == 3) { 128 | tabs.add(String.valueOf(loc.getChunk().getZ())); 129 | } 130 | if (newArgs.length == 4) { 131 | tabs.add(String.valueOf(loc.getChunk().getX())); 132 | } 133 | if (newArgs.length == 5) { 134 | tabs.add(String.valueOf(loc.getChunk().getZ())); 135 | } 136 | if (newArgs.length == 6) { 137 | tabs.add(loc.getWorld().getName()); 138 | } 139 | if (newArgs.length > 6) { 140 | tabs.clear(); 141 | } 142 | return CommandWrapper.filterTabs(tabs, args); 143 | } 144 | } else { 145 | if (newArgs.length == 2) { 146 | tabs.add(""); 147 | } 148 | if (newArgs.length == 3) { 149 | tabs.add(""); 150 | } 151 | if (newArgs.length == 4) { 152 | tabs.add(""); 153 | } 154 | if (newArgs.length == 5) { 155 | tabs.add(""); 156 | } 157 | if (newArgs.length == 6) { 158 | tabs.add(""); 159 | } 160 | if (newArgs.length > 6) { 161 | tabs.clear(); 162 | } 163 | } 164 | } 165 | if (args[1].equals("worldedit")) { 166 | if (Hooks.WorldEdit) { 167 | return WEReleaseregionTab.onTabComplete(s, c, label, args); 168 | } 169 | } 170 | if (args[1].equals("worldguard")) { 171 | if (Hooks.WorldGuard) { 172 | return WGReleaseregionTab.onTabComplete(s, c, label, args); 173 | } 174 | } 175 | return CommandWrapper.filterTabs(tabs, args); 176 | } 177 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Keepregion.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Hooks; 4 | import com.geitenijs.keepchunks.Strings; 5 | import com.geitenijs.keepchunks.Utilities; 6 | import com.geitenijs.keepchunks.commands.hooks.Keepregion_WE; 7 | import com.geitenijs.keepchunks.commands.hooks.Keepregion_WG; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.Location; 10 | import org.bukkit.command.Command; 11 | import org.bukkit.command.CommandExecutor; 12 | import org.bukkit.command.CommandSender; 13 | import org.bukkit.command.TabCompleter; 14 | import org.bukkit.entity.Player; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import static java.lang.Integer.max; 20 | import static java.lang.Integer.min; 21 | 22 | public class Command_Keepregion implements CommandExecutor, TabCompleter { 23 | 24 | int totalChunks = 0; 25 | private CommandExecutor WEKeepregion; 26 | private CommandExecutor WGKeepregion; 27 | private TabCompleter WEKeepregionTab; 28 | private TabCompleter WGKeepregionTab; 29 | 30 | Command_Keepregion() { 31 | if (Hooks.WorldEdit) { 32 | WEKeepregion = new Keepregion_WE(); 33 | WEKeepregionTab = new Keepregion_WE(); 34 | } 35 | if (Hooks.WorldGuard) { 36 | WGKeepregion = new Keepregion_WG(); 37 | WGKeepregionTab = new Keepregion_WG(); 38 | } 39 | } 40 | 41 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 42 | totalChunks = 0; 43 | if (args.length == 2) { 44 | if (args[1].equalsIgnoreCase("worldedit")) { 45 | if (Hooks.WorldEdit) { 46 | return WEKeepregion.onCommand(s, c, label, args); 47 | } else { 48 | Utilities.msg(s, Strings.NOWE); 49 | } 50 | } else { 51 | Utilities.msg(s, Strings.KEEPREGIONUSAGE); 52 | } 53 | } else if (args.length == 4) { 54 | if (args[1].equalsIgnoreCase("worldguard")) { 55 | if (Hooks.WorldGuard) { 56 | return WGKeepregion.onCommand(s, c, label, args); 57 | } else { 58 | Utilities.msg(s, Strings.NOWG); 59 | } 60 | } else { 61 | Utilities.msg(s, Strings.KEEPREGIONUSAGE); 62 | } 63 | } else if (args.length == 7) { 64 | if (args[1].equalsIgnoreCase("coords")) { 65 | try { 66 | final int X1 = Integer.parseInt(args[2]); 67 | final int Z1 = Integer.parseInt(args[3]); 68 | final int X2 = Integer.parseInt(args[4]); 69 | final int Z2 = Integer.parseInt(args[5]); 70 | final int minX = min(X1, X2); 71 | final int minZ = min(Z1, Z2); 72 | final int maxX = max(X1, X2); 73 | final int maxZ = max(Z1, Z2); 74 | final String world = args[6]; 75 | if (Bukkit.getWorld(world) == null) { 76 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 77 | return false; 78 | } 79 | Utilities.msg(s, Strings.IGPREFIX + "&7&oMarking chunks between &9&o(" + minX + ", " + minZ + ")&7&o & &9&o(" + maxX + ", " + maxZ + ")&7&o in &6&o'" + world + "'&7&o..."); 80 | for (int x = minX; x <= maxX; ++x) { 81 | for (int z = minZ; z <= maxZ; ++z) { 82 | final String chunk = x + "#" + z + "#" + world; 83 | if (!Utilities.chunks.contains(chunk)) { 84 | ++totalChunks; 85 | Utilities.chunks.add(chunk); 86 | Bukkit.getServer().getWorld(world).loadChunk(x, z); 87 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, true); 88 | } 89 | } 90 | } 91 | if (totalChunks == 0) { 92 | Utilities.msg(s, Strings.IGPREFIX + "&cEvery chunk within your region was already marked."); 93 | return true; 94 | } 95 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 96 | Utilities.saveDataFile(); 97 | Utilities.reloadDataFile(); 98 | Utilities.msg(s, Strings.IGPREFIX + "&fSuccessfully marked a total of &9" + totalChunks + "&f chunks!"); 99 | } catch (NumberFormatException ex) { 100 | Utilities.msg(s, Strings.UNUSABLE); 101 | } 102 | } else { 103 | Utilities.msg(s, Strings.KEEPREGIONUSAGE); 104 | } 105 | } else { 106 | Utilities.msg(s, Strings.KEEPREGIONUSAGE); 107 | } 108 | return true; 109 | } 110 | 111 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 112 | ArrayList tabs = new ArrayList<>(); 113 | String[] newArgs = CommandWrapper.getArgs(args); 114 | if (newArgs.length == 1) { 115 | tabs.add("coords"); 116 | tabs.add("worldedit"); 117 | tabs.add("worldguard"); 118 | } 119 | if (args[1].equals("coords")) { 120 | if (s instanceof Player player) { 121 | Location loc = player.getLocation(); 122 | if (Hooks.WorldEdit) { 123 | return WEKeepregionTab.onTabComplete(s, c, label, args); 124 | } else { 125 | if (newArgs.length == 2) { 126 | tabs.add(String.valueOf(loc.getChunk().getX())); 127 | } 128 | if (newArgs.length == 3) { 129 | tabs.add(String.valueOf(loc.getChunk().getZ())); 130 | } 131 | if (newArgs.length == 4) { 132 | tabs.add(String.valueOf(loc.getChunk().getX())); 133 | } 134 | if (newArgs.length == 5) { 135 | tabs.add(String.valueOf(loc.getChunk().getZ())); 136 | } 137 | if (newArgs.length == 6) { 138 | tabs.add(loc.getWorld().getName()); 139 | } 140 | if (newArgs.length > 6) { 141 | tabs.clear(); 142 | } 143 | return CommandWrapper.filterTabs(tabs, args); 144 | } 145 | } else { 146 | if (newArgs.length == 2) { 147 | tabs.add(""); 148 | } 149 | if (newArgs.length == 3) { 150 | tabs.add(""); 151 | } 152 | if (newArgs.length == 4) { 153 | tabs.add(""); 154 | } 155 | if (newArgs.length == 5) { 156 | tabs.add(""); 157 | } 158 | if (newArgs.length == 6) { 159 | tabs.add(""); 160 | } 161 | if (newArgs.length > 6) { 162 | tabs.clear(); 163 | } 164 | } 165 | } 166 | if (args[1].equals("worldedit")) { 167 | if (Hooks.WorldEdit) { 168 | return WEKeepregionTab.onTabComplete(s, c, label, args); 169 | } 170 | } 171 | if (args[1].equals("worldguard")) { 172 | if (Hooks.WorldGuard) { 173 | return WGKeepregionTab.onTabComplete(s, c, label, args); 174 | } 175 | } 176 | return CommandWrapper.filterTabs(tabs, args); 177 | } 178 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/CommandWrapper.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import org.bukkit.command.Command; 6 | import org.bukkit.command.CommandExecutor; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.command.TabCompleter; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Iterator; 12 | import java.util.List; 13 | 14 | public class CommandWrapper implements CommandExecutor, TabCompleter { 15 | private final CommandExecutor MainCommand; 16 | private final CommandExecutor HelpCommand; 17 | private final CommandExecutor ReloadCommand; 18 | private final CommandExecutor ListCommand; 19 | private final CommandExecutor ChunkinfoCommand; 20 | private final CommandExecutor KeepchunkCommand; 21 | private final CommandExecutor KeepregionCommand; 22 | private final CommandExecutor KeeprailCommand; 23 | private final CommandExecutor ReleaseallCommand; 24 | private final CommandExecutor ReleasechunkCommand; 25 | private final CommandExecutor ReleaseregionCommand; 26 | private final CommandExecutor ReleaserailCommand; 27 | private final TabCompleter MainTab; 28 | private final TabCompleter HelpTab; 29 | private final TabCompleter ReloadTab; 30 | private final TabCompleter ListTab; 31 | private final TabCompleter ChunkinfoTab; 32 | private final TabCompleter KeepchunkTab; 33 | private final TabCompleter KeepregionTab; 34 | private final TabCompleter KeeprailTab; 35 | private final TabCompleter ReleaseallTab; 36 | private final TabCompleter ReleasechunkTab; 37 | private final TabCompleter ReleaseregionTab; 38 | private final TabCompleter ReleaserailTab; 39 | 40 | public CommandWrapper() { 41 | MainCommand = new Command_Main(); 42 | HelpCommand = new Command_Help(); 43 | ReloadCommand = new Command_Reload(); 44 | ListCommand = new Command_List(); 45 | ChunkinfoCommand = new Command_Chunkinfo(); 46 | KeepchunkCommand = new Command_Keepchunk(); 47 | KeepregionCommand = new Command_Keepregion(); 48 | KeeprailCommand = new Command_Keeprail(); 49 | ReleaseallCommand = new Command_Releaseall(); 50 | ReleasechunkCommand = new Command_Releasechunk(); 51 | ReleaseregionCommand = new Command_Releaseregion(); 52 | ReleaserailCommand = new Command_Releaserail(); 53 | MainTab = new Command_Main(); 54 | HelpTab = new Command_Help(); 55 | ReloadTab = new Command_Reload(); 56 | ListTab = new Command_List(); 57 | ChunkinfoTab = new Command_Chunkinfo(); 58 | KeepchunkTab = new Command_Keepchunk(); 59 | KeepregionTab = new Command_Keepregion(); 60 | KeeprailTab = new Command_Keeprail(); 61 | ReleaseallTab = new Command_Releaseall(); 62 | ReleasechunkTab = new Command_Releasechunk(); 63 | ReleaseregionTab = new Command_Releaseregion(); 64 | ReleaserailTab = new Command_Releaserail(); 65 | } 66 | 67 | public static ArrayList filterTabs(ArrayList list, String[] origArgs) { 68 | if (origArgs.length == 0) 69 | return list; 70 | Iterator itel = list.iterator(); 71 | String label = origArgs[origArgs.length - 1].toLowerCase(); 72 | while (itel.hasNext()) { 73 | String name = itel.next(); 74 | if (name.toLowerCase().startsWith(label)) 75 | continue; 76 | itel.remove(); 77 | } 78 | return list; 79 | } 80 | 81 | public static String[] getArgs(String[] args) { 82 | ArrayList newArgs = new ArrayList<>(); 83 | for (int i = 0; i < args.length - 1; i++) { 84 | String s = args[i]; 85 | if (s.trim().isEmpty()) 86 | continue; 87 | newArgs.add(s); 88 | } 89 | return newArgs.toArray(new String[0]); 90 | } 91 | 92 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 93 | if (c.getName().equalsIgnoreCase("keepchunks") || c.getName().equalsIgnoreCase("kc")) { 94 | if (args.length == 0) { 95 | return MainCommand.onCommand(s, c, label, args); 96 | } else if (args[0].equalsIgnoreCase("help")) { 97 | if (s.hasPermission("keepchunks.help")) { 98 | return HelpCommand.onCommand(s, c, label, args); 99 | } else { 100 | Utilities.msg(s, Strings.NOPERM); 101 | } 102 | } else if (args[0].equalsIgnoreCase("reload")) { 103 | if (s.hasPermission("keepchunks.reload")) { 104 | return ReloadCommand.onCommand(s, c, label, args); 105 | } else { 106 | Utilities.msg(s, Strings.NOPERM); 107 | } 108 | } else if (args[0].equalsIgnoreCase("list")) { 109 | if (s.hasPermission("keepchunks.list")) { 110 | return ListCommand.onCommand(s, c, label, args); 111 | } else { 112 | Utilities.msg(s, Strings.NOPERM); 113 | } 114 | } else if (args[0].equalsIgnoreCase("chunkinfo")) { 115 | if (s.hasPermission("keepchunks.chunkinfo")) { 116 | return ChunkinfoCommand.onCommand(s, c, label, args); 117 | } else { 118 | Utilities.msg(s, Strings.NOPERM); 119 | } 120 | } else if (args[0].equalsIgnoreCase("keepchunk")) { 121 | if (s.hasPermission("keepchunks.keepchunk")) { 122 | return KeepchunkCommand.onCommand(s, c, label, args); 123 | } else { 124 | Utilities.msg(s, Strings.NOPERM); 125 | } 126 | } else if (args[0].equalsIgnoreCase("keepregion")) { 127 | if (s.hasPermission("keepchunks.keepregion")) { 128 | return KeepregionCommand.onCommand(s, c, label, args); 129 | } else { 130 | Utilities.msg(s, Strings.NOPERM); 131 | } 132 | } else if (args[0].equalsIgnoreCase("keeprail")) { 133 | if (s.hasPermission("keepchunks.keeprail")) { 134 | return KeeprailCommand.onCommand(s, c, label, args); 135 | } else { 136 | Utilities.msg(s, Strings.NOPERM); 137 | } 138 | } else if (args[0].equalsIgnoreCase("releaseall")) { 139 | if (s.hasPermission("keepchunks.releaseall")) { 140 | return ReleaseallCommand.onCommand(s, c, label, args); 141 | } else { 142 | Utilities.msg(s, Strings.NOPERM); 143 | } 144 | } else if (args[0].equalsIgnoreCase("releasechunk")) { 145 | if (s.hasPermission("keepchunks.releasechunk")) { 146 | return ReleasechunkCommand.onCommand(s, c, label, args); 147 | } else { 148 | Utilities.msg(s, Strings.NOPERM); 149 | } 150 | } else if (args[0].equalsIgnoreCase("releaseregion")) { 151 | if (s.hasPermission("keepchunks.releaseregion")) { 152 | return ReleaseregionCommand.onCommand(s, c, label, args); 153 | } else { 154 | Utilities.msg(s, Strings.NOPERM); 155 | } 156 | } else if (args[0].equalsIgnoreCase("releaserail")) { 157 | if (s.hasPermission("keepchunks.releaserail")) { 158 | return ReleaserailCommand.onCommand(s, c, label, args); 159 | } else { 160 | Utilities.msg(s, Strings.NOPERM); 161 | } 162 | } else { 163 | Utilities.msg(s, Strings.IGPREFIX + "&cThat command does not exist."); 164 | } 165 | } 166 | return true; 167 | } 168 | 169 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 170 | if (c.getName().equalsIgnoreCase("keepchunks") || c.getName().equalsIgnoreCase("kc")) { 171 | if (args.length == 1) { 172 | if (s.hasPermission("keepchunks.help")) { 173 | return MainTab.onTabComplete(s, c, label, args); 174 | } 175 | } else if (args[0].equalsIgnoreCase("help")) { 176 | if (s.hasPermission("keepchunks.help")) { 177 | return HelpTab.onTabComplete(s, c, label, args); 178 | } 179 | } else if (args[0].equalsIgnoreCase("reload")) { 180 | if (s.hasPermission("keepchunks.reload")) { 181 | return ReloadTab.onTabComplete(s, c, label, args); 182 | } 183 | } else if (args[0].equalsIgnoreCase("list")) { 184 | if (s.hasPermission("keepchunks.list")) { 185 | return ListTab.onTabComplete(s, c, label, args); 186 | } 187 | } else if (args[0].equalsIgnoreCase("chunkinfo")) { 188 | if (s.hasPermission("keepchunks.chunkinfo")) { 189 | return ChunkinfoTab.onTabComplete(s, c, label, args); 190 | } 191 | } else if (args[0].equalsIgnoreCase("keepchunk")) { 192 | if (s.hasPermission("keepchunks.keepchunk")) { 193 | return KeepchunkTab.onTabComplete(s, c, label, args); 194 | } 195 | } else if (args[0].equalsIgnoreCase("keepregion")) { 196 | if (s.hasPermission("keepchunks.keepregion")) { 197 | return KeepregionTab.onTabComplete(s, c, label, args); 198 | } 199 | } else if (args[0].equalsIgnoreCase("keeprail")) { 200 | if (s.hasPermission("keepchunks.keeprail")) { 201 | return KeeprailTab.onTabComplete(s, c, label, args); 202 | } 203 | } else if (args[0].equalsIgnoreCase("releaseall")) { 204 | if (s.hasPermission("keepchunks.releaseall")) { 205 | return ReleaseallTab.onTabComplete(s, c, label, args); 206 | } 207 | } else if (args[0].equalsIgnoreCase("releasechunk")) { 208 | if (s.hasPermission("keepchunks.releasechunk")) { 209 | return ReleasechunkTab.onTabComplete(s, c, label, args); 210 | } 211 | } else if (args[0].equalsIgnoreCase("releaseregion")) { 212 | if (s.hasPermission("keepchunks.releaseregion")) { 213 | return ReleaseregionTab.onTabComplete(s, c, label, args); 214 | } 215 | } else if (args[0].equalsIgnoreCase("releaserail")) { 216 | if (s.hasPermission("keepchunks.releaserail")) { 217 | return ReleaserailTab.onTabComplete(s, c, label, args); 218 | } 219 | } 220 | } 221 | return null; 222 | } 223 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Chunkinfo.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Hooks; 4 | import com.geitenijs.keepchunks.Strings; 5 | import com.geitenijs.keepchunks.Utilities; 6 | import com.geitenijs.keepchunks.commands.hooks.Chunkinfo_WE; 7 | import com.geitenijs.keepchunks.commands.hooks.Chunkinfo_WG; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.Chunk; 10 | import org.bukkit.Location; 11 | import org.bukkit.command.Command; 12 | import org.bukkit.command.CommandExecutor; 13 | import org.bukkit.command.CommandSender; 14 | import org.bukkit.command.TabCompleter; 15 | import org.bukkit.entity.Entity; 16 | import org.bukkit.entity.Player; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import static java.lang.Integer.max; 22 | import static java.lang.Integer.min; 23 | 24 | public class Command_Chunkinfo implements CommandExecutor, TabCompleter { 25 | 26 | int totalChunks = 0; 27 | private CommandExecutor WEChunkinfo; 28 | private CommandExecutor WGChunkinfo; 29 | private TabCompleter WEChunkinfoTab; 30 | private TabCompleter WGChunkinfoTab; 31 | 32 | Command_Chunkinfo() { 33 | if (Hooks.WorldEdit) { 34 | WEChunkinfo = new Chunkinfo_WE(); 35 | WEChunkinfoTab = new Chunkinfo_WE(); 36 | } 37 | if (Hooks.WorldGuard) { 38 | WGChunkinfo = new Chunkinfo_WG(); 39 | WGChunkinfoTab = new Chunkinfo_WG(); 40 | } 41 | } 42 | 43 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 44 | totalChunks = 0; 45 | if (args.length == 2) { 46 | if (args[1].equalsIgnoreCase("current")) { 47 | if (s instanceof Player) { 48 | final Chunk currentChunk = ((Entity) s).getLocation().getChunk(); 49 | final int x = currentChunk.getX(); 50 | final int z = currentChunk.getZ(); 51 | final int playerX = ((Player) s).getLocation().getBlockX(); 52 | final int playerY = ((Player) s).getLocation().getBlockY(); 53 | final int playerZ = ((Player) s).getLocation().getBlockZ(); 54 | final String world = currentChunk.getWorld().getName(); 55 | final String chunk = x + "#" + z + "#" + world; 56 | Utilities.msg(s, Strings.LINE); 57 | Utilities.msg(s, "&fPlayer: &c(" + playerX + ", " + playerY + ", " + playerZ + ")"); 58 | Utilities.msg(s, "&fChunk coords: &9(" + x + ", " + z + ")"); 59 | Utilities.msg(s, "&fWorld: &6" + world); 60 | Utilities.msg(s, ""); 61 | if (Utilities.chunks.contains(chunk)) { 62 | Utilities.msg(s, "&fMarked: &2Yes"); 63 | } else { 64 | Utilities.msg(s, "&fMarked: &4No"); 65 | } 66 | if (Bukkit.getServer().getWorld(world).isChunkForceLoaded(x, z)) { 67 | Utilities.msg(s, "&fForce-loaded: &2Yes"); 68 | } else { 69 | Utilities.msg(s, "&fForce-loaded: &4No"); 70 | } 71 | if (Bukkit.getServer().getWorld(world).isChunkLoaded(x, z)) { 72 | Utilities.msg(s, "&fCurrently in memory: &2Yes"); 73 | } else { 74 | Utilities.msg(s, "&fCurrently in memory: &4No"); 75 | } 76 | if (Utilities.chunks.contains(chunk) && !Bukkit.getServer().getWorld(world).isChunkForceLoaded(x, z)) { 77 | Utilities.msg(s, "&c&oPlease reload the plugin to update force-loaded chunks."); 78 | } 79 | Utilities.msg(s, Strings.LINE); 80 | } else { 81 | Utilities.msg(s, Strings.ONLYPLAYER); 82 | } 83 | } else if (args[1].equalsIgnoreCase("worldedit")) { 84 | if (Hooks.WorldEdit) { 85 | return WEChunkinfo.onCommand(s, c, label, args); 86 | } else { 87 | Utilities.msg(s, Strings.NOWE); 88 | } 89 | } else { 90 | Utilities.msg(s, Strings.CHUNKINFOUSAGE); 91 | } 92 | } else if (args.length == 4) { 93 | if (args[1].equalsIgnoreCase("worldguard")) { 94 | if (Hooks.WorldGuard) { 95 | return WGChunkinfo.onCommand(s, c, label, args); 96 | } else { 97 | Utilities.msg(s, Strings.NOWG); 98 | } 99 | } else { 100 | Utilities.msg(s, Strings.CHUNKINFOUSAGE); 101 | } 102 | } else if (args.length == 5) { 103 | if (args[1].equalsIgnoreCase("coords")) { 104 | try { 105 | final int x = Integer.parseInt(args[2]); 106 | final int z = Integer.parseInt(args[3]); 107 | final String world = args[4]; 108 | if (Bukkit.getWorld(world) == null) { 109 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 110 | return false; 111 | } 112 | final String chunk = x + "#" + z + "#" + world; 113 | Utilities.msg(s, Strings.LINE); 114 | Utilities.msg(s, "&fChunk coords: &9(" + x + ", " + z + ")"); 115 | Utilities.msg(s, "&fWorld: &6" + world); 116 | Utilities.msg(s, ""); 117 | if (Utilities.chunks.contains(chunk)) { 118 | Utilities.msg(s, "&fMarked: &2Yes"); 119 | } else { 120 | Utilities.msg(s, "&fMarked: &4No"); 121 | } 122 | if (Bukkit.getServer().getWorld(world).isChunkForceLoaded(x, z)) { 123 | Utilities.msg(s, "&fForce-loaded: &2Yes"); 124 | } else { 125 | Utilities.msg(s, "&fForce-loaded: &4No"); 126 | } 127 | if (Bukkit.getServer().getWorld(world).isChunkLoaded(x, z)) { 128 | Utilities.msg(s, "&fCurrently in memory: &2Yes"); 129 | } else { 130 | Utilities.msg(s, "&fCurrently in memory: &4No"); 131 | } 132 | if (Utilities.chunks.contains(chunk) && !Bukkit.getServer().getWorld(world).isChunkForceLoaded(x, z)) { 133 | Utilities.msg(s, "&c&oPlease reload the plugin to update force-loaded chunks."); 134 | } 135 | Utilities.msg(s, Strings.LINE); 136 | } catch (NumberFormatException ex) { 137 | Utilities.msg(s, Strings.UNUSABLE); 138 | } 139 | } else { 140 | Utilities.msg(s, Strings.CHUNKINFOUSAGE); 141 | } 142 | } else if (args.length == 7) { 143 | if (args[1].equalsIgnoreCase("coords")) { 144 | try { 145 | final int X1 = Integer.parseInt(args[2]); 146 | final int Z1 = Integer.parseInt(args[3]); 147 | final int X2 = Integer.parseInt(args[4]); 148 | final int Z2 = Integer.parseInt(args[5]); 149 | final int minX = min(X1, X2); 150 | final int minZ = min(Z1, Z2); 151 | final int maxX = max(X1, X2); 152 | final int maxZ = max(Z1, Z2); 153 | final String world = args[6]; 154 | if (Bukkit.getWorld(world) == null) { 155 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 156 | return false; 157 | } 158 | for (int x = minX; x <= maxX; ++x) { 159 | for (int z = minZ; z <= maxZ; ++z) { 160 | ++totalChunks; 161 | } 162 | } 163 | Utilities.msg(s, Strings.LINE); 164 | Utilities.msg(s, "&fChunk coords: &9(" + minX + ", " + minZ + ") (" + maxX + ", " + maxZ + ")"); 165 | Utilities.msg(s, "&fWorld: &6" + world); 166 | Utilities.msg(s, "&fTotal amount of chunks: &c" + totalChunks); 167 | Utilities.msg(s, Strings.LINE); 168 | } catch (NumberFormatException ex) { 169 | Utilities.msg(s, Strings.UNUSABLE); 170 | } 171 | } else { 172 | Utilities.msg(s, Strings.CHUNKINFOUSAGE); 173 | } 174 | } else { 175 | Utilities.msg(s, Strings.CHUNKINFOUSAGE); 176 | } 177 | return true; 178 | } 179 | 180 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 181 | ArrayList tabs = new ArrayList<>(); 182 | String[] newArgs = CommandWrapper.getArgs(args); 183 | if (newArgs.length == 1) { 184 | tabs.add("current"); 185 | tabs.add("coords"); 186 | tabs.add("worldedit"); 187 | tabs.add("worldguard"); 188 | } 189 | if (args[1].equals("coords")) { 190 | if (s instanceof Player player) { 191 | Location loc = player.getLocation(); 192 | if (newArgs.length == 2) { 193 | tabs.add(String.valueOf(loc.getChunk().getX())); 194 | } 195 | if (newArgs.length == 3) { 196 | tabs.add(String.valueOf(loc.getChunk().getZ())); 197 | } 198 | if (newArgs.length == 4) { 199 | tabs.add(String.valueOf(loc.getChunk().getX())); 200 | tabs.add(loc.getWorld().getName()); 201 | } 202 | if (newArgs.length == 5) { 203 | tabs.add(String.valueOf(loc.getChunk().getZ())); 204 | } 205 | if (newArgs.length == 6) { 206 | tabs.add(loc.getWorld().getName()); 207 | } 208 | } else { 209 | if (newArgs.length == 2) { 210 | tabs.add(""); 211 | } 212 | if (newArgs.length == 3) { 213 | tabs.add(""); 214 | } 215 | if (newArgs.length == 4) { 216 | tabs.add(""); 217 | tabs.add(""); 218 | } 219 | if (newArgs.length == 5) { 220 | tabs.add(""); 221 | } 222 | if (newArgs.length == 6) { 223 | tabs.add(""); 224 | } 225 | } 226 | } 227 | if (args[1].equals("current")) { 228 | tabs.clear(); 229 | } 230 | if (args[1].equals("worldedit")) { 231 | if (Hooks.WorldEdit) { 232 | return WEChunkinfoTab.onTabComplete(s, c, label, args); 233 | } 234 | } 235 | if (args[1].equals("worldguard")) { 236 | if (Hooks.WorldGuard) { 237 | return WGChunkinfoTab.onTabComplete(s, c, label, args); 238 | } 239 | } 240 | return CommandWrapper.filterTabs(tabs, args); 241 | } 242 | 243 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Releaserail.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import org.bukkit.*; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.command.TabCompleter; 10 | import org.bukkit.entity.Player; 11 | 12 | import java.util.*; 13 | 14 | public class Command_Releaserail implements CommandExecutor, TabCompleter { 15 | 16 | int totalRails = 0; 17 | int totalChunks = 0; 18 | 19 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 20 | totalRails = 0; 21 | totalChunks = 0; 22 | if (args.length == 2) { 23 | if (args[1].equalsIgnoreCase("current")) { 24 | if (s instanceof Player) { 25 | Location loc = ((Player) s).getLocation(); 26 | loc.setX(loc.getBlockX()); 27 | loc.setY(loc.getBlockY()); 28 | loc.setZ(loc.getBlockZ()); 29 | loc.setPitch(0.0f); 30 | loc.setYaw(0.0f); 31 | Material m = loc.getBlock().getType(); 32 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 33 | HashSet explored = new HashSet<>(); 34 | Queue agenda = new LinkedList<>(); 35 | if (!isRail) { 36 | Utilities.msg(s, Strings.IGPREFIX + "&cThere don't seem to be rails at your location."); 37 | return true; 38 | } else { 39 | Utilities.msg(s, Strings.IGPREFIX + "&7&oLooking for rails at &9&o(" + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")&7&o in &6&o'" + loc.getWorld().getName() + "'&7&o..."); 40 | agenda.add(loc); 41 | while (!agenda.isEmpty()) { 42 | Location cur = agenda.peek(); 43 | agenda.remove(); 44 | explored.add(cur); 45 | getAdjacent(cur, explored, agenda); 46 | ++totalRails; 47 | } 48 | Utilities.msg(s, Strings.IGPREFIX + "&fFound &c" + totalRails + "&f rails!"); 49 | if (totalChunks == 0) { 50 | Utilities.msg(s, Strings.IGPREFIX + "&cNo chunk around your railroad was already marked."); 51 | return true; 52 | } 53 | Utilities.msg(s, Strings.IGPREFIX + "&fReleased a total of &9" + totalChunks + "&f chunks in &6'" + loc.getWorld().getName() + "'&f."); 54 | } 55 | } else { 56 | Utilities.msg(s, Strings.ONLYPLAYER); 57 | } 58 | } else { 59 | Utilities.msg(s, Strings.RELEASERAILUSAGE); 60 | } 61 | } else if (args.length == 6) { 62 | if (args[1].equalsIgnoreCase("coords")) { 63 | try { 64 | final int x = Integer.parseInt(args[2]); 65 | final int y = Integer.parseInt(args[3]); 66 | final int z = Integer.parseInt(args[4]); 67 | final String world = args[5]; 68 | if (Bukkit.getWorld(world) == null) { 69 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 70 | return false; 71 | } 72 | World realWorld = Bukkit.getWorld(world); 73 | Location loc = new Location(realWorld, x, y, z); 74 | loc.setX(loc.getBlockX()); 75 | loc.setY(loc.getBlockY()); 76 | loc.setZ(loc.getBlockZ()); 77 | loc.setPitch(0.0f); 78 | loc.setYaw(0.0f); 79 | Material m = loc.getBlock().getType(); 80 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 81 | HashSet explored = new HashSet<>(); 82 | Queue agenda = new LinkedList<>(); 83 | if (!isRail) { 84 | Utilities.msg(s, Strings.IGPREFIX + "&cThere don't seem to be rails at that location."); 85 | return true; 86 | } else { 87 | Utilities.msg(s, Strings.IGPREFIX + "&7&oLooking for rails at &9&o(" + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")&7&o in &6&o'" + loc.getWorld().getName() + "'&7&o..."); 88 | agenda.add(loc); 89 | while (!agenda.isEmpty()) { 90 | Location cur = agenda.peek(); 91 | agenda.remove(); 92 | explored.add(cur); 93 | getAdjacent(cur, explored, agenda); 94 | ++totalRails; 95 | } 96 | Utilities.msg(s, Strings.IGPREFIX + "&fFound &c" + totalRails + "&f rails!"); 97 | if (totalChunks == 0) { 98 | Utilities.msg(s, Strings.IGPREFIX + "&cNo chunk around your railroad was already marked."); 99 | return true; 100 | } 101 | Utilities.msg(s, Strings.IGPREFIX + "&fReleased a total of &9" + totalChunks + "&f chunks in &6'" + loc.getWorld().getName() + "'&f."); 102 | } 103 | } catch (NumberFormatException ex) { 104 | Utilities.msg(s, Strings.UNUSABLE); 105 | } 106 | } else { 107 | Utilities.msg(s, Strings.RELEASERAILUSAGE); 108 | } 109 | } else { 110 | Utilities.msg(s, Strings.RELEASERAILUSAGE); 111 | } 112 | return true; 113 | } 114 | 115 | public void getN(Location pos, HashSet history, Queue todo) { 116 | for (int i = -1; i < 2; ++i) { 117 | Location candidate = new Location(pos.getWorld(), pos.getBlockX(), pos.getBlockY() + i, pos.getBlockZ() - 1); 118 | Material m = candidate.getBlock().getType(); 119 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 120 | if (isRail && !history.contains(candidate) && !todo.contains(candidate)) { 121 | updateData(candidate.getChunk()); 122 | todo.add(candidate); 123 | } 124 | } 125 | } 126 | 127 | public void getE(Location pos, HashSet history, Queue todo) { 128 | for (int i = -1; i < 2; ++i) { 129 | Location candidate = new Location(pos.getWorld(), pos.getBlockX() + 1, pos.getBlockY() + i, pos.getBlockZ()); 130 | Material m = candidate.getBlock().getType(); 131 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 132 | if (isRail && !history.contains(candidate) && !todo.contains(candidate)) { 133 | updateData(candidate.getChunk()); 134 | todo.add(candidate); 135 | } 136 | } 137 | } 138 | 139 | public void getS(Location pos, HashSet history, Queue todo) { 140 | for (int i = -1; i < 2; ++i) { 141 | Location candidate = new Location(pos.getWorld(), pos.getBlockX(), pos.getBlockY() + i, pos.getBlockZ() + 1); 142 | Material m = candidate.getBlock().getType(); 143 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 144 | if (isRail && !history.contains(candidate) && !todo.contains(candidate)) { 145 | updateData(candidate.getChunk()); 146 | todo.add(candidate); 147 | } 148 | } 149 | } 150 | 151 | public void getW(Location pos, HashSet history, Queue todo) { 152 | for (int i = -1; i < 2; ++i) { 153 | Location candidate = new Location(pos.getWorld(), pos.getBlockX() - 1, pos.getBlockY() + i, pos.getBlockZ()); 154 | Material m = candidate.getBlock().getType(); 155 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 156 | if (isRail && !history.contains(candidate) && !todo.contains(candidate)) { 157 | updateData(candidate.getChunk()); 158 | todo.add(candidate); 159 | } 160 | } 161 | } 162 | 163 | public void getAdjacent(Location pos, HashSet history, Queue todo) { 164 | getN(pos, history, todo); 165 | getE(pos, history, todo); 166 | getS(pos, history, todo); 167 | getW(pos, history, todo); 168 | } 169 | 170 | public void updateData(Chunk currentChunk) { 171 | final String world = currentChunk.getWorld().getName(); 172 | for (int i = -1; i < 2; ++i) { 173 | final int x = currentChunk.getX() + i; 174 | for (int j = -1; j < 2; ++j) { 175 | final int z = currentChunk.getZ() + j; 176 | final String chunk = x + "#" + z + "#" + world; 177 | if (Utilities.chunks.contains(chunk)) { 178 | Utilities.chunks.remove(chunk); 179 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, false); 180 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 181 | Utilities.saveDataFile(); 182 | Utilities.reloadDataFile(); 183 | ++totalChunks; 184 | } 185 | } 186 | } 187 | } 188 | 189 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 190 | ArrayList tabs = new ArrayList<>(); 191 | String[] newArgs = CommandWrapper.getArgs(args); 192 | if (newArgs.length == 1) { 193 | tabs.add("current"); 194 | tabs.add("coords"); 195 | } 196 | if (args[1].equals("coords")) { 197 | if (s instanceof Player player) { 198 | Location loc = player.getLocation(); 199 | if (newArgs.length == 2) { 200 | tabs.add(String.valueOf(loc.getBlockX())); 201 | } 202 | if (newArgs.length == 3) { 203 | tabs.add(String.valueOf(loc.getBlockY())); 204 | } 205 | if (newArgs.length == 4) { 206 | tabs.add(String.valueOf(loc.getBlockZ())); 207 | } 208 | if (newArgs.length == 5) { 209 | tabs.add(loc.getWorld().getName()); 210 | } 211 | } else { 212 | if (newArgs.length == 2) { 213 | tabs.add(""); 214 | } 215 | if (newArgs.length == 3) { 216 | tabs.add(""); 217 | } 218 | if (newArgs.length == 4) { 219 | tabs.add(""); 220 | } 221 | if (newArgs.length == 5) { 222 | tabs.add(""); 223 | } 224 | } 225 | } 226 | if (args[1].equals("current")) { 227 | tabs.clear(); 228 | } 229 | return CommandWrapper.filterTabs(tabs, args); 230 | } 231 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/commands/Command_Keeprail.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks.commands; 2 | 3 | import com.geitenijs.keepchunks.Strings; 4 | import com.geitenijs.keepchunks.Utilities; 5 | import org.bukkit.*; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.command.TabCompleter; 10 | import org.bukkit.entity.Player; 11 | 12 | import java.util.*; 13 | 14 | public class Command_Keeprail implements CommandExecutor, TabCompleter { 15 | 16 | int totalRails = 0; 17 | int totalChunks = 0; 18 | 19 | public boolean onCommand(final CommandSender s, final Command c, final String label, final String[] args) { 20 | totalRails = 0; 21 | totalChunks = 0; 22 | if (args.length == 2) { 23 | if (args[1].equalsIgnoreCase("current")) { 24 | if (s instanceof Player) { 25 | Location loc = ((Player) s).getLocation(); 26 | loc.setX(loc.getBlockX()); 27 | loc.setY(loc.getBlockY()); 28 | loc.setZ(loc.getBlockZ()); 29 | loc.setPitch(0.0f); 30 | loc.setYaw(0.0f); 31 | Material m = loc.getBlock().getType(); 32 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 33 | HashSet explored = new HashSet<>(); 34 | Queue agenda = new LinkedList<>(); 35 | if (!isRail) { 36 | Utilities.msg(s, Strings.IGPREFIX + "&cThere don't seem to be rails at your location."); 37 | return true; 38 | } else { 39 | Utilities.msg(s, Strings.IGPREFIX + "&7&oLooking for rails at &9&o(" + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")&7&o in &6&o'" + loc.getWorld().getName() + "'&7&o..."); 40 | agenda.add(loc); 41 | while (!agenda.isEmpty()) { 42 | Location cur = agenda.peek(); 43 | agenda.remove(); 44 | explored.add(cur); 45 | getAdjacent(cur, explored, agenda); 46 | ++totalRails; 47 | } 48 | Utilities.msg(s, Strings.IGPREFIX + "&fFound &c" + totalRails + "&f rails!"); 49 | if (totalChunks == 0) { 50 | Utilities.msg(s, Strings.IGPREFIX + "&cEvery chunk around your railroad was already marked."); 51 | return true; 52 | } 53 | Utilities.msg(s, Strings.IGPREFIX + "&fMarked a total of &9" + totalChunks + "&f chunks in &6'" + loc.getWorld().getName() + "'&f."); 54 | } 55 | } else { 56 | Utilities.msg(s, Strings.ONLYPLAYER); 57 | } 58 | } else { 59 | Utilities.msg(s, Strings.KEEPRAILUSAGE); 60 | } 61 | } else if (args.length == 6) { 62 | if (args[1].equalsIgnoreCase("coords")) { 63 | try { 64 | final int x = Integer.parseInt(args[2]); 65 | final int y = Integer.parseInt(args[3]); 66 | final int z = Integer.parseInt(args[4]); 67 | final String world = args[5]; 68 | if (Bukkit.getWorld(world) == null) { 69 | Utilities.msg(s, Strings.IGPREFIX + "&cWorld &f'" + world + "'&c doesn't exist, or isn't loaded in memory."); 70 | return false; 71 | } 72 | World realWorld = Bukkit.getWorld(world); 73 | Location loc = new Location(realWorld, x, y, z); 74 | loc.setX(loc.getBlockX()); 75 | loc.setY(loc.getBlockY()); 76 | loc.setZ(loc.getBlockZ()); 77 | loc.setPitch(0.0f); 78 | loc.setYaw(0.0f); 79 | Material m = loc.getBlock().getType(); 80 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 81 | HashSet explored = new HashSet<>(); 82 | Queue agenda = new LinkedList<>(); 83 | if (!isRail) { 84 | Utilities.msg(s, Strings.IGPREFIX + "&cThere don't seem to be rails at that location."); 85 | return true; 86 | } else { 87 | Utilities.msg(s, Strings.IGPREFIX + "&7&oLooking for rails at &9&o(" + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")&7&o in &6&o'" + loc.getWorld().getName() + "'&7&o..."); 88 | agenda.add(loc); 89 | while (!agenda.isEmpty()) { 90 | Location cur = agenda.peek(); 91 | agenda.remove(); 92 | explored.add(cur); 93 | getAdjacent(cur, explored, agenda); 94 | ++totalRails; 95 | } 96 | Utilities.msg(s, Strings.IGPREFIX + "&fFound &c" + totalRails + "&f rails!"); 97 | if (totalChunks == 0) { 98 | Utilities.msg(s, Strings.IGPREFIX + "&cEvery chunk around your railroad was already marked."); 99 | return true; 100 | } 101 | Utilities.msg(s, Strings.IGPREFIX + "&fMarked a total of &9" + totalChunks + "&f chunks in &6'" + loc.getWorld().getName() + "'&f."); 102 | } 103 | } catch (NumberFormatException ex) { 104 | Utilities.msg(s, Strings.UNUSABLE); 105 | } 106 | } else { 107 | Utilities.msg(s, Strings.KEEPRAILUSAGE); 108 | } 109 | } else { 110 | Utilities.msg(s, Strings.KEEPRAILUSAGE); 111 | } 112 | return true; 113 | } 114 | 115 | public void getN(Location pos, HashSet history, Queue todo) { 116 | for (int i = -1; i < 2; ++i) { 117 | Location candidate = new Location(pos.getWorld(), pos.getBlockX(), pos.getBlockY() + i, pos.getBlockZ() - 1); 118 | Material m = candidate.getBlock().getType(); 119 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 120 | if (isRail && !history.contains(candidate) && !todo.contains(candidate)) { 121 | updateData(candidate.getChunk()); 122 | todo.add(candidate); 123 | } 124 | } 125 | } 126 | 127 | public void getE(Location pos, HashSet history, Queue todo) { 128 | for (int i = -1; i < 2; ++i) { 129 | Location candidate = new Location(pos.getWorld(), pos.getBlockX() + 1, pos.getBlockY() + i, pos.getBlockZ()); 130 | Material m = candidate.getBlock().getType(); 131 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 132 | if (isRail && !history.contains(candidate) && !todo.contains(candidate)) { 133 | updateData(candidate.getChunk()); 134 | todo.add(candidate); 135 | } 136 | } 137 | } 138 | 139 | public void getS(Location pos, HashSet history, Queue todo) { 140 | for (int i = -1; i < 2; ++i) { 141 | Location candidate = new Location(pos.getWorld(), pos.getBlockX(), pos.getBlockY() + i, pos.getBlockZ() + 1); 142 | Material m = candidate.getBlock().getType(); 143 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 144 | if (isRail && !history.contains(candidate) && !todo.contains(candidate)) { 145 | updateData(candidate.getChunk()); 146 | todo.add(candidate); 147 | } 148 | } 149 | } 150 | 151 | public void getW(Location pos, HashSet history, Queue todo) { 152 | for (int i = -1; i < 2; ++i) { 153 | Location candidate = new Location(pos.getWorld(), pos.getBlockX() - 1, pos.getBlockY() + i, pos.getBlockZ()); 154 | Material m = candidate.getBlock().getType(); 155 | boolean isRail = (m == Material.RAIL || m == Material.POWERED_RAIL || m == Material.ACTIVATOR_RAIL || m == Material.DETECTOR_RAIL); 156 | if (isRail && !history.contains(candidate) && !todo.contains(candidate)) { 157 | updateData(candidate.getChunk()); 158 | todo.add(candidate); 159 | } 160 | } 161 | } 162 | 163 | public void getAdjacent(Location pos, HashSet history, Queue todo) { 164 | getN(pos, history, todo); 165 | getE(pos, history, todo); 166 | getS(pos, history, todo); 167 | getW(pos, history, todo); 168 | } 169 | 170 | public void updateData(Chunk currentChunk) { 171 | final String world = currentChunk.getWorld().getName(); 172 | for (int i = -1; i < 2; ++i) { 173 | final int x = currentChunk.getX() + i; 174 | for (int j = -1; j < 2; ++j) { 175 | final int z = currentChunk.getZ() + j; 176 | final String chunk = x + "#" + z + "#" + world; 177 | if (!Utilities.chunks.contains(chunk)) { 178 | Utilities.chunks.add(chunk); 179 | Bukkit.getServer().getWorld(world).loadChunk(x, z); 180 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, true); 181 | Utilities.data.set("chunks", new ArrayList<>(Utilities.chunks)); 182 | Utilities.saveDataFile(); 183 | Utilities.reloadDataFile(); 184 | ++totalChunks; 185 | } 186 | } 187 | } 188 | } 189 | 190 | public List onTabComplete(CommandSender s, Command c, String label, String[] args) { 191 | ArrayList tabs = new ArrayList<>(); 192 | String[] newArgs = CommandWrapper.getArgs(args); 193 | if (newArgs.length == 1) { 194 | tabs.add("current"); 195 | tabs.add("coords"); 196 | } 197 | if (args[1].equals("coords")) { 198 | if (s instanceof Player player) { 199 | Location loc = player.getLocation(); 200 | if (newArgs.length == 2) { 201 | tabs.add(String.valueOf(loc.getBlockX())); 202 | } 203 | if (newArgs.length == 3) { 204 | tabs.add(String.valueOf(loc.getBlockY())); 205 | } 206 | if (newArgs.length == 4) { 207 | tabs.add(String.valueOf(loc.getBlockZ())); 208 | } 209 | if (newArgs.length == 5) { 210 | tabs.add(loc.getWorld().getName()); 211 | } 212 | } else { 213 | if (newArgs.length == 2) { 214 | tabs.add(""); 215 | } 216 | if (newArgs.length == 3) { 217 | tabs.add(""); 218 | } 219 | if (newArgs.length == 4) { 220 | tabs.add(""); 221 | } 222 | if (newArgs.length == 5) { 223 | tabs.add(""); 224 | } 225 | } 226 | } 227 | if (args[1].equals("current")) { 228 | tabs.clear(); 229 | } 230 | return CommandWrapper.filterTabs(tabs, args); 231 | } 232 | } -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/Utilities.java: -------------------------------------------------------------------------------- 1 | package com.geitenijs.keepchunks; 2 | 3 | import com.geitenijs.keepchunks.commands.CommandWrapper; 4 | import com.geitenijs.keepchunks.updatechecker.UpdateCheck; 5 | import com.sk89q.worldedit.bukkit.WorldEditPlugin; 6 | import com.sk89q.worldguard.bukkit.WorldGuardPlugin; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.ChatColor; 9 | import org.bukkit.command.CommandSender; 10 | import org.bukkit.configuration.file.FileConfiguration; 11 | import org.bukkit.configuration.file.YamlConfiguration; 12 | import org.bukkit.entity.Player; 13 | import org.bukkit.plugin.Plugin; 14 | 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.HashSet; 20 | import java.util.List; 21 | import java.util.logging.Level; 22 | 23 | public class Utilities { 24 | 25 | public static FileConfiguration config; 26 | public static FileConfiguration data; 27 | public static HashSet chunks; 28 | public static HashSet chunkloadAll; 29 | private static File configFile = new File(Main.plugin.getDataFolder(), "config.yml"); 30 | private static File dataFile = new File(Main.plugin.getDataFolder(), "data.yml"); 31 | private static boolean updateAvailable; 32 | private static String updateVersion; 33 | 34 | static { 35 | config = YamlConfiguration.loadConfiguration(new File(Main.plugin.getDataFolder(), "config.yml")); 36 | data = YamlConfiguration.loadConfiguration(new File(Main.plugin.getDataFolder(), "data.yml")); 37 | chunks = new HashSet<>(Utilities.data.getStringList("chunks")); 38 | chunkloadAll = new HashSet<>(); 39 | } 40 | 41 | static void pluginBanner() { 42 | if (config.getBoolean("general.pluginbanner")) { 43 | consoleBanner(""); 44 | consoleBanner("&2 _ _ &8 _______ _ _ "); 45 | consoleBanner("&2(_) | | &8(_______) | | | &2v" + Strings.VERSION); 46 | consoleBanner("&2 _____| |_____ _____ ____ &8 _ | |__ _ _ ____ | | _ ___ "); 47 | consoleBanner("&2| _ _) ___ | ___ | _ \\&8| | | _ \\| | | | _ \\| |_/ )/___)"); 48 | consoleBanner("&2| | \\ \\| ____| ____| |_| &8| |_____| | | | |_| | | | | _ (|___ |"); 49 | consoleBanner("&2|_| \\_)_____)_____) __/&8 \\______)_| |_|____/|_| |_|_| \\_|___/ "); 50 | consoleBanner("&2 |_| &8 "); 51 | consoleBanner(""); 52 | } 53 | } 54 | 55 | static void createConfigs() { 56 | List configComments = new ArrayList<>(); 57 | configComments.addAll(Strings.ASCIILOGO); 58 | configComments.addAll(Arrays.asList( 59 | "Keeping your chunks loaded since 2015! By " + Strings.AUTHOR, 60 | "Information & Support: " + Strings.WEBSITE, 61 | "", 62 | "general:", 63 | " pluginbanner: Whether or not to display the fancy banner in your console on server startup.", 64 | " colourfulconsole: Console messages will be coloured when this is enabled.", 65 | " releaseallprotection: Do you want to restrict the 'release all' command to the console?", 66 | "", 67 | "updates:", 68 | " check: When enabled, the plugin will check for updates. No automatic downloads, just a subtle notification in the console.", 69 | " notify: Would you like to get an in-game reminder of a new update? Requires permission 'keepchunks.notify.update'." 70 | )); 71 | config.options().setHeader(configComments); 72 | config.set("general.debug", null); 73 | config.addDefault("general.pluginbanner", true); 74 | config.addDefault("general.colourfulconsole", true); 75 | config.addDefault("general.releaseallprotection", true); 76 | config.addDefault("updates.check", true); 77 | config.addDefault("updates.notify", true); 78 | List dataComments = new ArrayList<>(); 79 | dataComments.addAll(Strings.ASCIILOGO); 80 | dataComments.addAll(Arrays.asList( 81 | "Keeping your chunks loaded since 2015! By " + Strings.AUTHOR, 82 | "Information & Support: " + Strings.WEBSITE, 83 | "", 84 | "Unless you know what you're doing, it's best not to touch this file. All configurable options can be found in config.yml" 85 | )); 86 | data.options().setHeader(dataComments); 87 | data.addDefault("chunks", new ArrayList<>()); 88 | config.options().parseComments(true); 89 | config.options().copyDefaults(true); 90 | data.options().parseComments(true); 91 | data.options().copyDefaults(true); 92 | saveConfigFile(); 93 | reloadConfigFile(); 94 | saveDataFile(); 95 | reloadDataFile(); 96 | } 97 | 98 | static void registerCommandsAndCompletions() { 99 | Main.plugin.getCommand("keepchunks").setExecutor(new CommandWrapper()); 100 | Main.plugin.getCommand("kc").setExecutor(new CommandWrapper()); 101 | Main.plugin.getCommand("keepchunks").setTabCompleter(new CommandWrapper()); 102 | Main.plugin.getCommand("kc").setTabCompleter(new CommandWrapper()); 103 | } 104 | 105 | static void registerEvents() { 106 | Bukkit.getPluginManager().registerEvents(new Events(), Main.plugin); 107 | } 108 | 109 | public static void loadChunks() { 110 | for (final String chunk : chunks) { 111 | final String[] chunkCoordinates = chunk.split("#"); 112 | final int x = Integer.parseInt(chunkCoordinates[0]); 113 | final int z = Integer.parseInt(chunkCoordinates[1]); 114 | final String world = chunkCoordinates[2]; 115 | try { 116 | Bukkit.getServer().getWorld(world).loadChunk(x, z); 117 | Bukkit.getServer().getWorld(world).setChunkForceLoaded(x, z, true); 118 | } catch (NullPointerException ex) { 119 | consoleMsg(Strings.INTERNALPREFIX + "World '" + world + "' doesn't exist, or isn't loaded in memory."); 120 | } 121 | } 122 | } 123 | 124 | static void startTasks() { 125 | Bukkit.getScheduler().scheduleSyncDelayedTask(Main.plugin, Utilities::checkForUpdates, 190L); 126 | } 127 | 128 | static void stopTasks() { 129 | Bukkit.getScheduler().cancelTasks(Main.plugin); 130 | } 131 | 132 | static void startMetrics() { 133 | Metrics metrics = new Metrics(Main.plugin, 3656); 134 | metrics.addCustomChart(new Metrics.SingleLineChart("loadedChunks", () -> chunks.size())); 135 | metrics.addCustomChart(new Metrics.SimplePie("pluginBannerEnabled", () -> config.getString("general.pluginbanner"))); 136 | metrics.addCustomChart(new Metrics.SimplePie("colourfulConsoleEnabled", () -> config.getString("general.colourfulconsole"))); 137 | metrics.addCustomChart(new Metrics.SimplePie("releaseallProtectionEnabled", () -> config.getString("general.releaseallprotection"))); 138 | metrics.addCustomChart(new Metrics.SimplePie("updateCheckEnabled", () -> config.getString("updates.check"))); 139 | metrics.addCustomChart(new Metrics.SimplePie("updateNotificationEnabled", () -> config.getString("updates.notify"))); 140 | metrics.addCustomChart(new Metrics.SimplePie("worldeditVersion", () -> { 141 | final Plugin p = Bukkit.getPluginManager().getPlugin("WorldEdit"); 142 | if (!(p instanceof WorldEditPlugin)) { 143 | return Strings.NOSTAT; 144 | } else { 145 | return Bukkit.getServer().getPluginManager().getPlugin("WorldEdit").getDescription().getVersion(); 146 | } 147 | })); 148 | metrics.addCustomChart(new Metrics.SimplePie("worldguardVersion", () -> { 149 | final Plugin p = Bukkit.getPluginManager().getPlugin("WorldGuard"); 150 | if (!(p instanceof WorldGuardPlugin)) { 151 | return Strings.NOSTAT; 152 | } else { 153 | return Bukkit.getServer().getPluginManager().getPlugin("WorldGuard").getDescription().getVersion(); 154 | } 155 | })); 156 | } 157 | 158 | static void done() { 159 | consoleMsg(Strings.PLUGIN + " v" + Strings.VERSION + " has been enabled"); 160 | } 161 | 162 | private static void checkForUpdates() { 163 | if (config.getBoolean("updates.check")) { 164 | UpdateCheck 165 | .of(Main.plugin) 166 | .resourceId(Strings.RESOURCEID) 167 | .handleResponse((versionResponse, version) -> { 168 | switch (versionResponse) { 169 | case FOUND_NEW: 170 | consoleMsg("A new release of " + Strings.PLUGIN + ", v" + version + ", is available! You are still on v" + Strings.VERSION + "."); 171 | consoleMsg("To download this update, head over to " + Strings.WEBSITE + "/updates in your browser."); 172 | updateVersion = version; 173 | updateAvailable = true; 174 | break; 175 | case LATEST: 176 | updateAvailable = false; 177 | break; 178 | case UNAVAILABLE: 179 | updateAvailable = false; 180 | } 181 | }).check(); 182 | } 183 | } 184 | 185 | static boolean updateAvailable() { 186 | return updateAvailable; 187 | } 188 | 189 | static String updateVersion() { 190 | return updateVersion; 191 | } 192 | 193 | public static void reloadConfigFile() { 194 | if (configFile == null) { 195 | configFile = new File(Main.plugin.getDataFolder(), "config.yml"); 196 | } 197 | config = YamlConfiguration.loadConfiguration(configFile); 198 | } 199 | 200 | static void saveConfigFile() { 201 | if (config == null || configFile == null) { 202 | return; 203 | } 204 | try { 205 | config.save(configFile); 206 | } catch (IOException ex) { 207 | Bukkit.getLogger().log(Level.SEVERE, "Could not save " + configFile, ex); 208 | } 209 | } 210 | 211 | public static void reloadDataFile() { 212 | if (dataFile == null) { 213 | dataFile = new File(Main.plugin.getDataFolder(), "data.yml"); 214 | } 215 | data = YamlConfiguration.loadConfiguration(dataFile); 216 | } 217 | 218 | public static void saveDataFile() { 219 | if (data == null || dataFile == null) { 220 | return; 221 | } 222 | try { 223 | data.save(dataFile); 224 | } catch (IOException ex) { 225 | Bukkit.getLogger().log(Level.SEVERE, "Could not save " + dataFile, ex); 226 | } 227 | } 228 | 229 | public static void msg(final CommandSender s, String msg) { 230 | msg = ChatColor.translateAlternateColorCodes('&', msg); 231 | if (!(s instanceof Player)) { 232 | if (!config.getBoolean("general.colourfulconsole")) { 233 | msg = ChatColor.stripColor(msg); 234 | } 235 | } 236 | s.sendMessage(msg); 237 | } 238 | 239 | public static void consoleMsg(String msg) { 240 | msg = ChatColor.translateAlternateColorCodes('&', Strings.INTERNALPREFIX + msg); 241 | if (!config.getBoolean("general.colourfulconsole")) { 242 | msg = ChatColor.stripColor(msg); 243 | } 244 | Bukkit.getServer().getConsoleSender().sendMessage(msg); 245 | } 246 | 247 | private static void consoleBanner(final String message) { 248 | Bukkit.getServer().getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', message)); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/com/geitenijs/keepchunks/Metrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Metrics class was auto-generated and can be copied into your project if you are 3 | * not using a build tool like Gradle or Maven for dependency management. 4 | * 5 | * IMPORTANT: You are not allowed to modify this class, except changing the package. 6 | * 7 | * Disallowed modifications include but are not limited to: 8 | * - Remove the option for users to opt-out 9 | * - Change the frequency for data submission 10 | * - Obfuscate the code (every obfuscator should allow you to make an exception for specific files) 11 | * - Reformat the code (if you use a linter, add an exception) 12 | * 13 | * Violations will result in a ban of your plugin and account from bStats. 14 | */ 15 | package com.geitenijs.keepchunks; 16 | 17 | import org.bukkit.Bukkit; 18 | import org.bukkit.configuration.file.YamlConfiguration; 19 | import org.bukkit.entity.Player; 20 | import org.bukkit.plugin.Plugin; 21 | 22 | import javax.net.ssl.HttpsURLConnection; 23 | import java.io.*; 24 | import java.lang.reflect.Method; 25 | import java.net.URL; 26 | import java.nio.charset.StandardCharsets; 27 | import java.util.*; 28 | import java.util.concurrent.Callable; 29 | import java.util.concurrent.ScheduledExecutorService; 30 | import java.util.concurrent.ScheduledThreadPoolExecutor; 31 | import java.util.concurrent.TimeUnit; 32 | import java.util.function.BiConsumer; 33 | import java.util.function.Consumer; 34 | import java.util.function.Supplier; 35 | import java.util.logging.Level; 36 | import java.util.stream.Collectors; 37 | import java.util.zip.GZIPOutputStream; 38 | 39 | public class Metrics { 40 | 41 | private final Plugin plugin; 42 | 43 | private final MetricsBase metricsBase; 44 | 45 | /** 46 | * Creates a new Metrics instance. 47 | * 48 | * @param plugin Your plugin instance. 49 | * @param serviceId The id of the service. It can be found at What is my plugin id? 51 | */ 52 | public Metrics(Plugin plugin, int serviceId) { 53 | this.plugin = plugin; 54 | // Get the config file 55 | File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); 56 | File configFile = new File(bStatsFolder, "config.yml"); 57 | YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); 58 | if (!config.isSet("serverUuid")) { 59 | config.addDefault("enabled", true); 60 | config.addDefault("serverUuid", UUID.randomUUID().toString()); 61 | config.addDefault("logFailedRequests", false); 62 | config.addDefault("logSentData", false); 63 | config.addDefault("logResponseStatusText", false); 64 | // Inform the server owners about bStats 65 | config 66 | .options() 67 | .header( 68 | "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" 69 | + "many people use their plugin and their total player count. It's recommended to keep bStats\n" 70 | + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" 71 | + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" 72 | + "anonymous.") 73 | .copyDefaults(true); 74 | try { 75 | config.save(configFile); 76 | } catch (IOException ignored) { 77 | } 78 | } 79 | // Load the data 80 | boolean enabled = config.getBoolean("enabled", true); 81 | String serverUUID = config.getString("serverUuid"); 82 | boolean logErrors = config.getBoolean("logFailedRequests", false); 83 | boolean logSentData = config.getBoolean("logSentData", false); 84 | boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); 85 | metricsBase = 86 | new MetricsBase( 87 | "bukkit", 88 | serverUUID, 89 | serviceId, 90 | enabled, 91 | this::appendPlatformData, 92 | this::appendServiceData, 93 | submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), 94 | plugin::isEnabled, 95 | (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), 96 | (message) -> this.plugin.getLogger().log(Level.INFO, message), 97 | logErrors, 98 | logSentData, 99 | logResponseStatusText, 100 | false); 101 | } 102 | 103 | /** Shuts down the underlying scheduler service. */ 104 | public void shutdown() { 105 | metricsBase.shutdown(); 106 | } 107 | 108 | /** 109 | * Adds a custom chart. 110 | * 111 | * @param chart The chart to add. 112 | */ 113 | public void addCustomChart(CustomChart chart) { 114 | metricsBase.addCustomChart(chart); 115 | } 116 | 117 | private void appendPlatformData(JsonObjectBuilder builder) { 118 | builder.appendField("playerAmount", getPlayerAmount()); 119 | builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); 120 | builder.appendField("bukkitVersion", Bukkit.getVersion()); 121 | builder.appendField("bukkitName", Bukkit.getName()); 122 | builder.appendField("javaVersion", System.getProperty("java.version")); 123 | builder.appendField("osName", System.getProperty("os.name")); 124 | builder.appendField("osArch", System.getProperty("os.arch")); 125 | builder.appendField("osVersion", System.getProperty("os.version")); 126 | builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); 127 | } 128 | 129 | private void appendServiceData(JsonObjectBuilder builder) { 130 | builder.appendField("pluginVersion", plugin.getDescription().getVersion()); 131 | } 132 | 133 | private int getPlayerAmount() { 134 | try { 135 | // Around MC 1.8 the return type was changed from an array to a collection, 136 | // This fixes java.lang.NoSuchMethodError: 137 | // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; 138 | Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); 139 | return onlinePlayersMethod.getReturnType().equals(Collection.class) 140 | ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() 141 | : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; 142 | } catch (Exception e) { 143 | // Just use the new method if the reflection failed 144 | return Bukkit.getOnlinePlayers().size(); 145 | } 146 | } 147 | 148 | public static class MetricsBase { 149 | 150 | /** The version of the Metrics class. */ 151 | public static final String METRICS_VERSION = "3.0.3"; 152 | 153 | private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; 154 | 155 | private final ScheduledExecutorService scheduler; 156 | 157 | private final String platform; 158 | 159 | private final String serverUuid; 160 | 161 | private final int serviceId; 162 | 163 | private final Consumer appendPlatformDataConsumer; 164 | 165 | private final Consumer appendServiceDataConsumer; 166 | 167 | private final Consumer submitTaskConsumer; 168 | 169 | private final Supplier checkServiceEnabledSupplier; 170 | 171 | private final BiConsumer errorLogger; 172 | 173 | private final Consumer infoLogger; 174 | 175 | private final boolean logErrors; 176 | 177 | private final boolean logSentData; 178 | 179 | private final boolean logResponseStatusText; 180 | 181 | private final Set customCharts = new HashSet<>(); 182 | 183 | private final boolean enabled; 184 | 185 | /** 186 | * Creates a new MetricsBase class instance. 187 | * 188 | * @param platform The platform of the service. 189 | * @param serviceId The id of the service. 190 | * @param serverUuid The server uuid. 191 | * @param enabled Whether or not data sending is enabled. 192 | * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and 193 | * appends all platform-specific data. 194 | * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and 195 | * appends all service-specific data. 196 | * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be 197 | * used to delegate the data collection to a another thread to prevent errors caused by 198 | * concurrency. Can be {@code null}. 199 | * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. 200 | * @param errorLogger A consumer that accepts log message and an error. 201 | * @param infoLogger A consumer that accepts info log messages. 202 | * @param logErrors Whether or not errors should be logged. 203 | * @param logSentData Whether or not the sent data should be logged. 204 | * @param logResponseStatusText Whether or not the response status text should be logged. 205 | * @param skipRelocateCheck Whether or not the relocate check should be skipped. 206 | */ 207 | public MetricsBase( 208 | String platform, 209 | String serverUuid, 210 | int serviceId, 211 | boolean enabled, 212 | Consumer appendPlatformDataConsumer, 213 | Consumer appendServiceDataConsumer, 214 | Consumer submitTaskConsumer, 215 | Supplier checkServiceEnabledSupplier, 216 | BiConsumer errorLogger, 217 | Consumer infoLogger, 218 | boolean logErrors, 219 | boolean logSentData, 220 | boolean logResponseStatusText, 221 | boolean skipRelocateCheck) { 222 | ScheduledThreadPoolExecutor scheduler = 223 | new ScheduledThreadPoolExecutor( 224 | 1, 225 | task -> { 226 | Thread thread = new Thread(task, "bStats-Metrics"); 227 | thread.setDaemon(true); 228 | return thread; 229 | }); 230 | // We want delayed tasks (non-periodic) that will execute in the future to be 231 | // cancelled when the scheduler is shutdown. 232 | // Otherwise, we risk preventing the server from shutting down even when 233 | // MetricsBase#shutdown() is called 234 | scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); 235 | this.scheduler = scheduler; 236 | this.platform = platform; 237 | this.serverUuid = serverUuid; 238 | this.serviceId = serviceId; 239 | this.enabled = enabled; 240 | this.appendPlatformDataConsumer = appendPlatformDataConsumer; 241 | this.appendServiceDataConsumer = appendServiceDataConsumer; 242 | this.submitTaskConsumer = submitTaskConsumer; 243 | this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; 244 | this.errorLogger = errorLogger; 245 | this.infoLogger = infoLogger; 246 | this.logErrors = logErrors; 247 | this.logSentData = logSentData; 248 | this.logResponseStatusText = logResponseStatusText; 249 | if (!skipRelocateCheck) { 250 | checkRelocation(); 251 | } 252 | if (enabled) { 253 | // WARNING: Removing the option to opt-out will get your plugin banned from 254 | // bStats 255 | startSubmitting(); 256 | } 257 | } 258 | 259 | public void addCustomChart(CustomChart chart) { 260 | this.customCharts.add(chart); 261 | } 262 | 263 | public void shutdown() { 264 | scheduler.shutdown(); 265 | } 266 | 267 | private void startSubmitting() { 268 | final Runnable submitTask = 269 | () -> { 270 | if (!enabled || !checkServiceEnabledSupplier.get()) { 271 | // Submitting data or service is disabled 272 | scheduler.shutdown(); 273 | return; 274 | } 275 | if (submitTaskConsumer != null) { 276 | submitTaskConsumer.accept(this::submitData); 277 | } else { 278 | this.submitData(); 279 | } 280 | }; 281 | // Many servers tend to restart at a fixed time at xx:00 which causes an uneven 282 | // distribution of requests on the 283 | // bStats backend. To circumvent this problem, we introduce some randomness into 284 | // the initial and second delay. 285 | // WARNING: You must not modify and part of this Metrics class, including the 286 | // submit delay or frequency! 287 | // WARNING: Modifying this code will get your plugin banned on bStats. Just 288 | // don't do it! 289 | long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); 290 | long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); 291 | scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); 292 | scheduler.scheduleAtFixedRate( 293 | submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); 294 | } 295 | 296 | private void submitData() { 297 | final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); 298 | appendPlatformDataConsumer.accept(baseJsonBuilder); 299 | final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); 300 | appendServiceDataConsumer.accept(serviceJsonBuilder); 301 | JsonObjectBuilder.JsonObject[] chartData = 302 | customCharts.stream() 303 | .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) 304 | .filter(Objects::nonNull) 305 | .toArray(JsonObjectBuilder.JsonObject[]::new); 306 | serviceJsonBuilder.appendField("id", serviceId); 307 | serviceJsonBuilder.appendField("customCharts", chartData); 308 | baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); 309 | baseJsonBuilder.appendField("serverUUID", serverUuid); 310 | baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); 311 | JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); 312 | scheduler.execute( 313 | () -> { 314 | try { 315 | // Send the data 316 | sendData(data); 317 | } catch (Exception e) { 318 | // Something went wrong! :( 319 | if (logErrors) { 320 | errorLogger.accept("Could not submit bStats metrics data", e); 321 | } 322 | } 323 | }); 324 | } 325 | 326 | private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { 327 | if (logSentData) { 328 | infoLogger.accept("Sent bStats metrics data: " + data.toString()); 329 | } 330 | String url = String.format(REPORT_URL, platform); 331 | HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); 332 | // Compress the data to save bandwidth 333 | byte[] compressedData = compress(data.toString()); 334 | connection.setRequestMethod("POST"); 335 | connection.addRequestProperty("Accept", "application/json"); 336 | connection.addRequestProperty("Connection", "close"); 337 | connection.addRequestProperty("Content-Encoding", "gzip"); 338 | connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); 339 | connection.setRequestProperty("Content-Type", "application/json"); 340 | connection.setRequestProperty("User-Agent", "Metrics-Service/1"); 341 | connection.setDoOutput(true); 342 | try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { 343 | outputStream.write(compressedData); 344 | } 345 | StringBuilder builder = new StringBuilder(); 346 | try (BufferedReader bufferedReader = 347 | new BufferedReader(new InputStreamReader(connection.getInputStream()))) { 348 | String line; 349 | while ((line = bufferedReader.readLine()) != null) { 350 | builder.append(line); 351 | } 352 | } 353 | if (logResponseStatusText) { 354 | infoLogger.accept("Sent data to bStats and received response: " + builder); 355 | } 356 | } 357 | 358 | /** Checks that the class was properly relocated. */ 359 | private void checkRelocation() { 360 | // You can use the property to disable the check in your test environment 361 | if (System.getProperty("bstats.relocatecheck") == null 362 | || !System.getProperty("bstats.relocatecheck").equals("false")) { 363 | // Maven's Relocate is clever and changes strings, too. So we have to use this 364 | // little "trick" ... :D 365 | final String defaultPackage = 366 | new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); 367 | final String examplePackage = 368 | new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); 369 | // We want to make sure no one just copy & pastes the example and uses the wrong 370 | // package names 371 | if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) 372 | || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { 373 | throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); 374 | } 375 | } 376 | } 377 | 378 | /** 379 | * Gzips the given string. 380 | * 381 | * @param str The string to gzip. 382 | * @return The gzipped string. 383 | */ 384 | private static byte[] compress(final String str) throws IOException { 385 | if (str == null) { 386 | return null; 387 | } 388 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 389 | try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { 390 | gzip.write(str.getBytes(StandardCharsets.UTF_8)); 391 | } 392 | return outputStream.toByteArray(); 393 | } 394 | } 395 | 396 | public static class AdvancedBarChart extends CustomChart { 397 | 398 | private final Callable> callable; 399 | 400 | /** 401 | * Class constructor. 402 | * 403 | * @param chartId The id of the chart. 404 | * @param callable The callable which is used to request the chart data. 405 | */ 406 | public AdvancedBarChart(String chartId, Callable> callable) { 407 | super(chartId); 408 | this.callable = callable; 409 | } 410 | 411 | @Override 412 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 413 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 414 | Map map = callable.call(); 415 | if (map == null || map.isEmpty()) { 416 | // Null = skip the chart 417 | return null; 418 | } 419 | boolean allSkipped = true; 420 | for (Map.Entry entry : map.entrySet()) { 421 | if (entry.getValue().length == 0) { 422 | // Skip this invalid 423 | continue; 424 | } 425 | allSkipped = false; 426 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 427 | } 428 | if (allSkipped) { 429 | // Null = skip the chart 430 | return null; 431 | } 432 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 433 | } 434 | } 435 | 436 | public abstract static class CustomChart { 437 | 438 | private final String chartId; 439 | 440 | protected CustomChart(String chartId) { 441 | if (chartId == null) { 442 | throw new IllegalArgumentException("chartId must not be null"); 443 | } 444 | this.chartId = chartId; 445 | } 446 | 447 | public JsonObjectBuilder.JsonObject getRequestJsonObject( 448 | BiConsumer errorLogger, boolean logErrors) { 449 | JsonObjectBuilder builder = new JsonObjectBuilder(); 450 | builder.appendField("chartId", chartId); 451 | try { 452 | JsonObjectBuilder.JsonObject data = getChartData(); 453 | if (data == null) { 454 | // If the data is null we don't send the chart. 455 | return null; 456 | } 457 | builder.appendField("data", data); 458 | } catch (Throwable t) { 459 | if (logErrors) { 460 | errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); 461 | } 462 | return null; 463 | } 464 | return builder.build(); 465 | } 466 | 467 | protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; 468 | } 469 | 470 | public static class DrilldownPie extends CustomChart { 471 | 472 | private final Callable>> callable; 473 | 474 | /** 475 | * Class constructor. 476 | * 477 | * @param chartId The id of the chart. 478 | * @param callable The callable which is used to request the chart data. 479 | */ 480 | public DrilldownPie(String chartId, Callable>> callable) { 481 | super(chartId); 482 | this.callable = callable; 483 | } 484 | 485 | @Override 486 | public JsonObjectBuilder.JsonObject getChartData() throws Exception { 487 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 488 | Map> map = callable.call(); 489 | if (map == null || map.isEmpty()) { 490 | // Null = skip the chart 491 | return null; 492 | } 493 | boolean reallyAllSkipped = true; 494 | for (Map.Entry> entryValues : map.entrySet()) { 495 | JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); 496 | boolean allSkipped = true; 497 | for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { 498 | valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); 499 | allSkipped = false; 500 | } 501 | if (!allSkipped) { 502 | reallyAllSkipped = false; 503 | valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); 504 | } 505 | } 506 | if (reallyAllSkipped) { 507 | // Null = skip the chart 508 | return null; 509 | } 510 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 511 | } 512 | } 513 | 514 | public static class AdvancedPie extends CustomChart { 515 | 516 | private final Callable> callable; 517 | 518 | /** 519 | * Class constructor. 520 | * 521 | * @param chartId The id of the chart. 522 | * @param callable The callable which is used to request the chart data. 523 | */ 524 | public AdvancedPie(String chartId, Callable> callable) { 525 | super(chartId); 526 | this.callable = callable; 527 | } 528 | 529 | @Override 530 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 531 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 532 | Map map = callable.call(); 533 | if (map == null || map.isEmpty()) { 534 | // Null = skip the chart 535 | return null; 536 | } 537 | boolean allSkipped = true; 538 | for (Map.Entry entry : map.entrySet()) { 539 | if (entry.getValue() == 0) { 540 | // Skip this invalid 541 | continue; 542 | } 543 | allSkipped = false; 544 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 545 | } 546 | if (allSkipped) { 547 | // Null = skip the chart 548 | return null; 549 | } 550 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 551 | } 552 | } 553 | 554 | public static class SimpleBarChart extends CustomChart { 555 | 556 | private final Callable> callable; 557 | 558 | /** 559 | * Class constructor. 560 | * 561 | * @param chartId The id of the chart. 562 | * @param callable The callable which is used to request the chart data. 563 | */ 564 | public SimpleBarChart(String chartId, Callable> callable) { 565 | super(chartId); 566 | this.callable = callable; 567 | } 568 | 569 | @Override 570 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 571 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 572 | Map map = callable.call(); 573 | if (map == null || map.isEmpty()) { 574 | // Null = skip the chart 575 | return null; 576 | } 577 | for (Map.Entry entry : map.entrySet()) { 578 | valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); 579 | } 580 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 581 | } 582 | } 583 | 584 | public static class SingleLineChart extends CustomChart { 585 | 586 | private final Callable callable; 587 | 588 | /** 589 | * Class constructor. 590 | * 591 | * @param chartId The id of the chart. 592 | * @param callable The callable which is used to request the chart data. 593 | */ 594 | public SingleLineChart(String chartId, Callable callable) { 595 | super(chartId); 596 | this.callable = callable; 597 | } 598 | 599 | @Override 600 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 601 | int value = callable.call(); 602 | if (value == 0) { 603 | // Null = skip the chart 604 | return null; 605 | } 606 | return new JsonObjectBuilder().appendField("value", value).build(); 607 | } 608 | } 609 | 610 | public static class MultiLineChart extends CustomChart { 611 | 612 | private final Callable> callable; 613 | 614 | /** 615 | * Class constructor. 616 | * 617 | * @param chartId The id of the chart. 618 | * @param callable The callable which is used to request the chart data. 619 | */ 620 | public MultiLineChart(String chartId, Callable> callable) { 621 | super(chartId); 622 | this.callable = callable; 623 | } 624 | 625 | @Override 626 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 627 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 628 | Map map = callable.call(); 629 | if (map == null || map.isEmpty()) { 630 | // Null = skip the chart 631 | return null; 632 | } 633 | boolean allSkipped = true; 634 | for (Map.Entry entry : map.entrySet()) { 635 | if (entry.getValue() == 0) { 636 | // Skip this invalid 637 | continue; 638 | } 639 | allSkipped = false; 640 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 641 | } 642 | if (allSkipped) { 643 | // Null = skip the chart 644 | return null; 645 | } 646 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 647 | } 648 | } 649 | 650 | public static class SimplePie extends CustomChart { 651 | 652 | private final Callable callable; 653 | 654 | /** 655 | * Class constructor. 656 | * 657 | * @param chartId The id of the chart. 658 | * @param callable The callable which is used to request the chart data. 659 | */ 660 | public SimplePie(String chartId, Callable callable) { 661 | super(chartId); 662 | this.callable = callable; 663 | } 664 | 665 | @Override 666 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 667 | String value = callable.call(); 668 | if (value == null || value.isEmpty()) { 669 | // Null = skip the chart 670 | return null; 671 | } 672 | return new JsonObjectBuilder().appendField("value", value).build(); 673 | } 674 | } 675 | 676 | /** 677 | * An extremely simple JSON builder. 678 | * 679 | *

While this class is neither feature-rich nor the most performant one, it's sufficient enough 680 | * for its use-case. 681 | */ 682 | public static class JsonObjectBuilder { 683 | 684 | private StringBuilder builder = new StringBuilder(); 685 | 686 | private boolean hasAtLeastOneField = false; 687 | 688 | public JsonObjectBuilder() { 689 | builder.append("{"); 690 | } 691 | 692 | /** 693 | * Appends a null field to the JSON. 694 | * 695 | * @param key The key of the field. 696 | * @return A reference to this object. 697 | */ 698 | public JsonObjectBuilder appendNull(String key) { 699 | appendFieldUnescaped(key, "null"); 700 | return this; 701 | } 702 | 703 | /** 704 | * Appends a string field to the JSON. 705 | * 706 | * @param key The key of the field. 707 | * @param value The value of the field. 708 | * @return A reference to this object. 709 | */ 710 | public JsonObjectBuilder appendField(String key, String value) { 711 | if (value == null) { 712 | throw new IllegalArgumentException("JSON value must not be null"); 713 | } 714 | appendFieldUnescaped(key, "\"" + escape(value) + "\""); 715 | return this; 716 | } 717 | 718 | /** 719 | * Appends an integer field to the JSON. 720 | * 721 | * @param key The key of the field. 722 | * @param value The value of the field. 723 | * @return A reference to this object. 724 | */ 725 | public JsonObjectBuilder appendField(String key, int value) { 726 | appendFieldUnescaped(key, String.valueOf(value)); 727 | return this; 728 | } 729 | 730 | /** 731 | * Appends an object to the JSON. 732 | * 733 | * @param key The key of the field. 734 | * @param object The object. 735 | * @return A reference to this object. 736 | */ 737 | public JsonObjectBuilder appendField(String key, JsonObject object) { 738 | if (object == null) { 739 | throw new IllegalArgumentException("JSON object must not be null"); 740 | } 741 | appendFieldUnescaped(key, object.toString()); 742 | return this; 743 | } 744 | 745 | /** 746 | * Appends a string array to the JSON. 747 | * 748 | * @param key The key of the field. 749 | * @param values The string array. 750 | * @return A reference to this object. 751 | */ 752 | public JsonObjectBuilder appendField(String key, String[] values) { 753 | if (values == null) { 754 | throw new IllegalArgumentException("JSON values must not be null"); 755 | } 756 | String escapedValues = 757 | Arrays.stream(values) 758 | .map(value -> "\"" + escape(value) + "\"") 759 | .collect(Collectors.joining(",")); 760 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 761 | return this; 762 | } 763 | 764 | /** 765 | * Appends an integer array to the JSON. 766 | * 767 | * @param key The key of the field. 768 | * @param values The integer array. 769 | * @return A reference to this object. 770 | */ 771 | public JsonObjectBuilder appendField(String key, int[] values) { 772 | if (values == null) { 773 | throw new IllegalArgumentException("JSON values must not be null"); 774 | } 775 | String escapedValues = 776 | Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); 777 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 778 | return this; 779 | } 780 | 781 | /** 782 | * Appends an object array to the JSON. 783 | * 784 | * @param key The key of the field. 785 | * @param values The integer array. 786 | * @return A reference to this object. 787 | */ 788 | public JsonObjectBuilder appendField(String key, JsonObject[] values) { 789 | if (values == null) { 790 | throw new IllegalArgumentException("JSON values must not be null"); 791 | } 792 | String escapedValues = 793 | Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); 794 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 795 | return this; 796 | } 797 | 798 | /** 799 | * Appends a field to the object. 800 | * 801 | * @param key The key of the field. 802 | * @param escapedValue The escaped value of the field. 803 | */ 804 | private void appendFieldUnescaped(String key, String escapedValue) { 805 | if (builder == null) { 806 | throw new IllegalStateException("JSON has already been built"); 807 | } 808 | if (key == null) { 809 | throw new IllegalArgumentException("JSON key must not be null"); 810 | } 811 | if (hasAtLeastOneField) { 812 | builder.append(","); 813 | } 814 | builder.append("\"").append(escape(key)).append("\":").append(escapedValue); 815 | hasAtLeastOneField = true; 816 | } 817 | 818 | /** 819 | * Builds the JSON string and invalidates this builder. 820 | * 821 | * @return The built JSON string. 822 | */ 823 | public JsonObject build() { 824 | if (builder == null) { 825 | throw new IllegalStateException("JSON has already been built"); 826 | } 827 | JsonObject object = new JsonObject(builder.append("}").toString()); 828 | builder = null; 829 | return object; 830 | } 831 | 832 | /** 833 | * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. 834 | * 835 | *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. 836 | * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). 837 | * 838 | * @param value The value to escape. 839 | * @return The escaped value. 840 | */ 841 | private static String escape(String value) { 842 | final StringBuilder builder = new StringBuilder(); 843 | for (int i = 0; i < value.length(); i++) { 844 | char c = value.charAt(i); 845 | if (c == '"') { 846 | builder.append("\\\""); 847 | } else if (c == '\\') { 848 | builder.append("\\\\"); 849 | } else if (c <= '\u000F') { 850 | builder.append("\\u000").append(Integer.toHexString(c)); 851 | } else if (c <= '\u001F') { 852 | builder.append("\\u00").append(Integer.toHexString(c)); 853 | } else { 854 | builder.append(c); 855 | } 856 | } 857 | return builder.toString(); 858 | } 859 | 860 | /** 861 | * A super simple representation of a JSON object. 862 | * 863 | *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not 864 | * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, 865 | * JsonObject)}. 866 | */ 867 | public static class JsonObject { 868 | 869 | private final String value; 870 | 871 | private JsonObject(String value) { 872 | this.value = value; 873 | } 874 | 875 | @Override 876 | public String toString() { 877 | return value; 878 | } 879 | } 880 | } 881 | } --------------------------------------------------------------------------------