From d1c8d29cde9bdaf56b49193bb2a4731c839353d9 Mon Sep 17 00:00:00 2001 From: Stijn <38841986+Teriuihi@users.noreply.github.com> Date: Sun, 18 Feb 2024 14:22:17 +0100 Subject: [PATCH] Add IslandTop command with refresh cooldown (#1) * Add IslandTop command with refresh cooldown The "IslandTop" command has been added. This provides a sorted list of top-performing islands. A configuration for refresh cooldown was also added. The sorted island list is updated once the specified minutes in the configuration have passed to improve performance. The ranking display for the player's island is highlighted in green text. * Update IslandTop command On plugin startup the IslandData is now loaded in. IslandData was moved to its own file and gets updated when a new island is created or when one levels up. The IslandData gets deleted when the owner leaves the island, destroying it. --- .../cometskyblock/CometSkyBlockPlugin.java | 4 + .../commands/island/IslandCommand.java | 1 + .../commands/island/IslandTop.java | 45 ++++++++++- .../configuration/PluginConfiguration.java | 1 + .../alttd/cometskyblock/island/Island.java | 19 ++++- .../cometskyblock/island/IslandData.java | 79 +++++++++++++++++++ .../cometskyblock/request/LeaveRequest.java | 5 +- 7 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 plugin/src/main/java/com/alttd/cometskyblock/island/IslandData.java diff --git a/plugin/src/main/java/com/alttd/cometskyblock/CometSkyBlockPlugin.java b/plugin/src/main/java/com/alttd/cometskyblock/CometSkyBlockPlugin.java index 8a39cae..98b4b2b 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/CometSkyBlockPlugin.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/CometSkyBlockPlugin.java @@ -4,6 +4,7 @@ import com.alttd.cometskyblock.commands.challenges.ChallengeCommand; import com.alttd.cometskyblock.commands.island.IslandCommand; import com.alttd.cometskyblock.configuration.*; import com.alttd.cometskyblock.gui.GUIListener; +import com.alttd.cometskyblock.island.IslandData; import com.alttd.cometskyblock.listeners.BedListener; import com.alttd.cometskyblock.listeners.CobbestoneGeneratorListener; import com.alttd.cometskyblock.listeners.PlayerJoinListener; @@ -48,6 +49,9 @@ public class CometSkyBlockPlugin extends JavaPlugin implements CometSkyBlockAPI // Load event listeners loadEventListeners(); + // Reload island data for top list + IslandData.reloadAllIslandData(); + // load data from storage // run cleanup tasks diff --git a/plugin/src/main/java/com/alttd/cometskyblock/commands/island/IslandCommand.java b/plugin/src/main/java/com/alttd/cometskyblock/commands/island/IslandCommand.java index 7713fa1..c5337b9 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/commands/island/IslandCommand.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/commands/island/IslandCommand.java @@ -16,6 +16,7 @@ public class IslandCommand extends PlayerSubCommand { this.plugin = plugin; registerSubCommand(new IslandGo(plugin)); // TODO -- Add some more output + registerSubCommand(new IslandTop(plugin)); registerSubCommand(new IslandRestart(plugin)); // TODO -- Add IslandRestartCommand registerSubCommand(new IslandAccept(plugin)); registerSubCommand(new IslandDeny(plugin)); diff --git a/plugin/src/main/java/com/alttd/cometskyblock/commands/island/IslandTop.java b/plugin/src/main/java/com/alttd/cometskyblock/commands/island/IslandTop.java index 78e84cc..26a19ee 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/commands/island/IslandTop.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/commands/island/IslandTop.java @@ -2,19 +2,58 @@ package com.alttd.cometskyblock.commands.island; import com.alttd.cometskyblock.CometSkyBlockPlugin; import com.alttd.cometskyblock.commands.PlayerSubCommand; +import com.alttd.cometskyblock.configuration.PluginConfiguration; +import com.alttd.cometskyblock.island.IslandData; import com.alttd.cometskyblock.island.IslandPlayer; import org.bukkit.entity.Player; +import java.util.List; +import java.util.OptionalInt; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + public class IslandTop extends PlayerSubCommand { + private final PluginConfiguration pluginConfiguration; + public IslandTop(CometSkyBlockPlugin plugin) { super(plugin, "top"); + pluginConfiguration = plugin.pluginConfiguration().get(); } - // TODO - Finish TOP command + @Override public boolean execute(Player player, IslandPlayer islandPlayer, String[] args) { - // TODO -- Implement - player.sendRichMessage("Not implemented yet, please wait for a future update."); + int playerIslandId = islandPlayer.islandId(); + + //TODO allow players to iterate through the list + List islandData = IslandData.getIslandData(pluginConfiguration.topRefreshMinutesCoolDown()); + String islandRankings = IntStream.range(0, 10) + .mapToObj(i -> (i + 1) + ". " + islandData.get(i).format(playerIslandId)) + .collect(Collectors.joining("\n")); + + islandRankings += getFormattedPlayerIslandRanking(islandData, playerIslandId, 0, 10); + + player.sendRichMessage("Island Top:\n" + islandRankings); return true; } + + /** + * Retrieves the formatted ranking of a player's island based on their island ID. + * + * @param playerIslandId The ID of the player's island. + * @return The formatted ranking of the player's island. Returns an empty string if the player's island is not ranked in the top 10. + */ + private String getFormattedPlayerIslandRanking(List islandDataList, int playerIslandId, int minPos, int maxPos) { + OptionalInt position = IntStream.range(0, islandDataList.size()) + .filter(i -> playerIslandId == islandDataList.get(i).islandId()) + .findFirst(); + if (position.isPresent()) { + int playerIslandPosition = position.getAsInt(); + if (playerIslandId < minPos || playerIslandPosition > maxPos) { + IslandData islandData = islandDataList.get(playerIslandPosition); + return "\n" + playerIslandPosition + ". " + islandData.format() + ""; + } + } + return ""; + } } diff --git a/plugin/src/main/java/com/alttd/cometskyblock/configuration/PluginConfiguration.java b/plugin/src/main/java/com/alttd/cometskyblock/configuration/PluginConfiguration.java index 6fab54b..652b906 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/configuration/PluginConfiguration.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/configuration/PluginConfiguration.java @@ -29,5 +29,6 @@ public class PluginConfiguration implements Configuration { } private int requestTimeOut = 30; + private int topRefreshMinutesCoolDown = 10; } diff --git a/plugin/src/main/java/com/alttd/cometskyblock/island/Island.java b/plugin/src/main/java/com/alttd/cometskyblock/island/Island.java index 2950eba..deeb4f9 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/island/Island.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/island/Island.java @@ -30,6 +30,10 @@ public class Island extends YamlConfiguration { } } + public static Island loadIslandFromFile(File file) { + return new Island(file); + } + public static void remove(UUID uuid) { synchronized (configs) { configs.remove(uuid); @@ -54,6 +58,16 @@ public class Island extends YamlConfiguration { reload(); } + private Island(File file) { + this.islandUUID = Island.NILL_UUID; + this.file = file; + reload(); + } + + public static Collection getIslands() { + return configs.values(); + } + private void reload() { synchronized (saveLock) { try { @@ -103,8 +117,9 @@ public class Island extends YamlConfiguration { return getInt("island.level", 0); } - public void level(int id) { - set("island.level", id); + public void level(int level) { + IslandData.updateIsland(new IslandData(islandId(), islandName(), level)); + set("island.level", level); save(); } diff --git a/plugin/src/main/java/com/alttd/cometskyblock/island/IslandData.java b/plugin/src/main/java/com/alttd/cometskyblock/island/IslandData.java new file mode 100644 index 0000000..5567b0c --- /dev/null +++ b/plugin/src/main/java/com/alttd/cometskyblock/island/IslandData.java @@ -0,0 +1,79 @@ +package com.alttd.cometskyblock.island; + +import com.alttd.cometskyblock.CometSkyBlockPlugin; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Stream; + +public record IslandData(int islandId, String name, int level) { + + private static final Int2ObjectOpenHashMap islandDataMap = new Int2ObjectOpenHashMap<>(); + private static List sortedIslandData = new ArrayList<>(); + private static Instant lastUpdated = Instant.MIN; + + public synchronized static void updateIsland(IslandData islandData) { + IslandData.islandDataMap.put(islandData.islandId, islandData); + } + + public synchronized static void removeIsland(int islandId) { + IslandData.islandDataMap.remove(islandId); + } + + public static List getIslandData(int maxAgeMinutes) { + updateIslandData(maxAgeMinutes); + return IslandData.sortedIslandData; + } + + private static synchronized void updateIslandData(int maxAgeMinutes) { + if (Duration.between(lastUpdated, Instant.now()).toMinutes() <= maxAgeMinutes) + return; + lastUpdated = Instant.now(); + sortedIslandData = islandDataMap.values().stream() + .sorted(Comparator.comparingInt(IslandData::level).reversed()) + .toList(); + lastUpdated = Instant.now(); + } + + public static synchronized void reloadAllIslandData() { + Logger logger = CometSkyBlockPlugin.instance().getLogger(); + File islandDataDir = new File(CometSkyBlockPlugin.instance().getDataFolder(), "IslandData"); + if (!islandDataDir.isDirectory()) { + logger.warning("No data folder found for IslandData, unable to load files"); + return; + } + islandDataMap.clear(); + try (Stream paths = Files.walk(islandDataDir.toPath(), 1)) { + paths.filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".yml")) + .map(Path::toFile) + .map(Island::loadIslandFromFile) + .map(island -> new IslandData(island.islandId(), island.islandName(), island.level())) + .forEach(island -> islandDataMap.put(island.islandId, island)); + } catch (IOException e) { + logger.severe("Encountered exception while reloading IslandData"); + logger.throwing(IslandData.class.getName(), "reloadAllIslandData", e); + } + } + + public String format(int playerIslandId) { + if (playerIslandId == islandId) { + return "" + format() + ""; + } else { + return format(); + } + } + + public String format() { + return name + " - " + level; + } +} diff --git a/plugin/src/main/java/com/alttd/cometskyblock/request/LeaveRequest.java b/plugin/src/main/java/com/alttd/cometskyblock/request/LeaveRequest.java index bf290a5..8788329 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/request/LeaveRequest.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/request/LeaveRequest.java @@ -2,6 +2,7 @@ package com.alttd.cometskyblock.request; import com.alttd.cometskyblock.CometSkyBlockPlugin; import com.alttd.cometskyblock.island.Island; +import com.alttd.cometskyblock.island.IslandData; import com.alttd.cometskyblock.island.IslandPlayer; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -25,8 +26,10 @@ public class LeaveRequest extends Request { requester().sendRichMessage(requests().leave().accept(), placeholders()); IslandPlayer islandPlayer = IslandPlayer.getIslandPlayer(requester().getUniqueId()); Island island = Island.getIsland(islandPlayer.islandUUID()); - if (islandPlayer.islandOwner()) + if (islandPlayer.islandOwner()) { + IslandData.removeIsland(island.islandId()); island.owner(Island.NILL_UUID); + } islandPlayer.islandId(0); islandPlayer.islandUUID(null); World world = Bukkit.getWorlds().get(0);