From 248b9aaae546dca2d280619c31bdb3dce9cade65 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 5 May 2026 16:11:53 -0700 Subject: [PATCH 01/26] update javadocs --- build.gradle.kts | 10 +++++++--- src/main/javadoc/overview.html | 10 ++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 src/main/javadoc/overview.html diff --git a/build.gradle.kts b/build.gradle.kts index 426de471..0f9494bb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -98,12 +98,16 @@ tasks { options.compilerArgs.add("-Xlint:deprecation") } javadoc { + val options = options as StandardJavadocDocletOptions + options.docTitle = "HungerGames API - $projectVersion" + options.overview = "src/main/javadoc/overview.html" options.encoding = Charsets.UTF_8.name() + exclude("com/shanebeestudios/hg/plugin/commands") exclude("com/shanebeestudios/hg/plugin/listeners") - (options as StandardJavadocDocletOptions).links( - "https://jd.papermc.io/paper/1.21.5/", - "https://jd.advntr.dev/api/4.17.0/", + options.links( + "https://jd.papermc.io/paper/26.1.2/", + "https://jd.advntr.dev/api/4.25.0/", "https://tr7zw.github.io/Item-NBT-API/v2-api/" ) diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html new file mode 100644 index 00000000..600ce23b --- /dev/null +++ b/src/main/javadoc/overview.html @@ -0,0 +1,10 @@ + +

+ HungerGames is a simple yet fun, and very lightweight PvP arena system. +
It provides advanced kits, spawns, and a very simple arena setup. +
If you've never heard of the books/movies HungerGames, it's basically a free for all. +
You must hunt for food and items, and attempt to kill others before they kill you. The last man standing wins! +
Unlike some HungerGames plugins, this one does not require you to setup a whole server for the game. +
You can just easily setup multiple arenas anywhere in the world. +

+ From c015cdb4533797da7e7faefe194db0d987942f2a Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Tue, 5 May 2026 17:00:58 -0700 Subject: [PATCH 02/26] Update to use WeightedLists (#108) * WeightedList - introducing a weighted list * MobData - update to use weighted list * ItemData - switch to weighted list --- .../shanebeestudios/hg/api/data/ItemData.java | 58 ++++++++------- .../shanebeestudios/hg/api/data/MobData.java | 40 +++++----- .../hg/api/util/WeightedList.java | 73 +++++++++++++++++++ .../hg/plugin/managers/GameManager.java | 8 +- .../hg/plugin/managers/ItemManager.java | 13 +--- .../hg/plugin/managers/MobManager.java | 17 ++--- 6 files changed, 134 insertions(+), 75 deletions(-) create mode 100644 src/main/java/com/shanebeestudios/hg/api/util/WeightedList.java diff --git a/src/main/java/com/shanebeestudios/hg/api/data/ItemData.java b/src/main/java/com/shanebeestudios/hg/api/data/ItemData.java index 3adaa363..d2bd8e17 100644 --- a/src/main/java/com/shanebeestudios/hg/api/data/ItemData.java +++ b/src/main/java/com/shanebeestudios/hg/api/data/ItemData.java @@ -1,66 +1,69 @@ package com.shanebeestudios.hg.api.data; +import com.shanebeestudios.hg.api.game.Game; +import com.shanebeestudios.hg.api.util.WeightedList; import org.bukkit.inventory.ItemStack; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; +/** + * Holder of {@link ItemStack Items} for a {@link Game} + */ public class ItemData { - private final Map> items = new HashMap<>(); - private final Map count = new HashMap<>(); + private final Map> weightedItems = new HashMap<>(); public ItemData() { for (ChestType chestType : ChestType.values()) { - this.items.put(chestType, new ArrayList<>()); + this.weightedItems.put(chestType, new WeightedList<>()); } } - public void setItems(ChestType type, List items) { - this.items.put(type, items); - } - - public List getItems(ChestType type) { - return this.items.get(type); - } - /** - * Set item count + * Add a weighted item to the item data. * - * @param chestType ChestType to count - * @param itemCount Amount of items + * @param type Chest type + * @param item Item to add + * @param weight Weight of item */ - public void setItemCount(ChestType chestType, int itemCount) { - this.count.put(chestType, itemCount); + public void addEntry(ChestType type, ItemStack item, int weight) { + this.weightedItems.get(type).add(item, weight); } /** - * Get item count by ChestType + * Get a random item. * - * @param chestType ChestType to get count from - * @return AMount of items by ChestType + * @param type Type of chest + * @return Random item */ - public int getItemCount(ChestType chestType) { - return this.count.get(chestType); + public ItemStack getRandomItem(ChestType type) { + return this.weightedItems.get(type).nextEntry(); + } + + public void setWeightedItems(ChestType type, WeightedList weightedItems) { + this.weightedItems.put(type, weightedItems); + } + + public WeightedList getWeightedItems(ChestType type) { + return this.weightedItems.get(type); } /** - * Get total item count for all chest types + * Get the total item count for all chest types. * * @return Total item count */ public int getTotalItemCount() { int count = 0; - for (int value : this.count.values()) { - count += value; + for (WeightedList value : this.weightedItems.values()) { + count += value.size(); } return count; } /** - * Represents the type of chests in game + * Represents the type of chests in a game. *

Used for logging and refilling

*/ public enum ChestType { @@ -97,4 +100,5 @@ public String getName() { return this.name; } } + } diff --git a/src/main/java/com/shanebeestudios/hg/api/data/MobData.java b/src/main/java/com/shanebeestudios/hg/api/data/MobData.java index e4eaa1c2..0dde449a 100644 --- a/src/main/java/com/shanebeestudios/hg/api/data/MobData.java +++ b/src/main/java/com/shanebeestudios/hg/api/data/MobData.java @@ -1,7 +1,7 @@ package com.shanebeestudios.hg.api.data; import com.google.common.collect.ImmutableList; -import org.jetbrains.annotations.ApiStatus; +import com.shanebeestudios.hg.api.util.WeightedList; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -12,8 +12,8 @@ public class MobData { private final Random random = new Random(); - private final List dayMobs = new ArrayList<>(); - private final List nightMobs = new ArrayList<>(); + private final WeightedList dayMobs = new WeightedList<>(); + private final WeightedList nightMobs = new WeightedList<>(); private int mobCount; /** @@ -22,7 +22,7 @@ public class MobData { * @return List of MobEntries */ public List getDayMobs() { - return ImmutableList.copyOf(this.dayMobs); + return ImmutableList.copyOf(this.dayMobs.getEntries()); } /** @@ -32,16 +32,17 @@ public List getDayMobs() { */ public @Nullable MobEntry getRandomDayMob() { if (this.dayMobs.isEmpty()) return null; - return this.dayMobs.get(this.random.nextInt(this.dayMobs.size())); + return this.dayMobs.nextEntry(); } /** * Add a new mob entry to the day mobs * * @param mobEntry Mob entry to add + * @param weight Weight of mob entry */ - public void addDayMob(MobEntry mobEntry) { - this.dayMobs.add(mobEntry); + public void addDayMob(MobEntry mobEntry, int weight) { + this.dayMobs.add(mobEntry, weight); } /** @@ -50,7 +51,7 @@ public void addDayMob(MobEntry mobEntry) { * @return List of MobEntries */ public List getNightMobs() { - return ImmutableList.copyOf(this.nightMobs); + return ImmutableList.copyOf(this.nightMobs.getEntries()); } /** @@ -60,24 +61,17 @@ public List getNightMobs() { */ public @Nullable MobEntry getRandomNightMob() { if (this.nightMobs.isEmpty()) return null; - return this.nightMobs.get(this.random.nextInt(this.nightMobs.size())); + return this.nightMobs.nextEntry(); } /** * Add a new mob entry to the night mobs * * @param mobEntry Mob entry to add + * @param weight Weight of mob entry */ - public void addNightMob(MobEntry mobEntry) { - this.nightMobs.add(mobEntry); - } - - /** - * @hidden - */ - @ApiStatus.Internal - public void setMobCount(int mobCount) { - this.mobCount = mobCount; + public void addNightMob(MobEntry mobEntry, int weight) { + this.nightMobs.add(mobEntry, weight); } /** @@ -86,18 +80,18 @@ public void setMobCount(int mobCount) { * @return Count of all mobs */ public int getMobCount() { - return this.mobCount; + return this.dayMobs.size() + this.nightMobs.size(); } /** - * Get list of all MobEntries + * Get a list of all MobEntries * * @return List of MobEntries */ public List getAllMobs() { List mobs = new ArrayList<>(); - mobs.addAll(this.dayMobs); - mobs.addAll(this.nightMobs); + mobs.addAll(this.dayMobs.getEntries()); + mobs.addAll(this.nightMobs.getEntries()); return mobs; } diff --git a/src/main/java/com/shanebeestudios/hg/api/util/WeightedList.java b/src/main/java/com/shanebeestudios/hg/api/util/WeightedList.java new file mode 100644 index 00000000..0dbed94f --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/api/util/WeightedList.java @@ -0,0 +1,73 @@ +package com.shanebeestudios.hg.api.util; + +import java.util.List; +import java.util.Random; +import java.util.TreeMap; + +/** + * A weighted list that allows for random selection based on entry weights. + * + * @param The type of elements in the list. + */ +public class WeightedList { + + private final TreeMap weightMap = new TreeMap<>(); + private final Random random = new Random(); + private double total = 0; + + /** + * Add an entry to the weighted list with a specified weight. + * + * @param entry The entry to add. + * @param weight The weight of the entry. + * @throws IllegalArgumentException if weight is not positive. + */ + public void add(T entry, int weight) { + if (weight <= 0) { + throw new IllegalArgumentException("Weight must be positive"); + } + this.total += weight; + this.weightMap.put(this.total, entry); + } + + /** + * Get the next entry from the weighted list based on weights. + * + * @return The next entry or null if the list is empty. + */ + public T nextEntry() { + if (this.total == 0) { + return null; + } + double randomValue = this.random.nextDouble() * this.total; + return this.weightMap.higherEntry(randomValue).getValue(); + } + + /** + * Get all entries in the weighted list. + * + * @return An unmodifiable list of entries. + */ + public List getEntries() { + return List.copyOf(this.weightMap.values()); + } + + /** + * Check if the weighted list is empty. + * + * @return True if the list is empty, false otherwise. + */ + public boolean isEmpty() { + return this.total == 0; + } + + /** + * Get the number of entries in the weighted list. + * + * @return The number of entries. + */ + public int size() { + return this.weightMap.size(); + } + +} diff --git a/src/main/java/com/shanebeestudios/hg/plugin/managers/GameManager.java b/src/main/java/com/shanebeestudios/hg/plugin/managers/GameManager.java index 17f673cb..5b34327f 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/managers/GameManager.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/managers/GameManager.java @@ -13,7 +13,6 @@ import com.shanebeestudios.hg.plugin.configs.Language; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -234,12 +233,7 @@ public void fillChests(Game game, Block block, ChestType chestType) { * @return Random ItemStack */ public ItemStack randomItem(Game game, ChestType chestType) { - List items = game.getGameItemData().getItemData().getItems(chestType); - int r = items.size(); - if (r == 0) return new ItemStack(Material.AIR); - int i = this.random.nextInt(r); - return items.get(i); - + return game.getGameItemData().getItemData().getRandomItem(chestType); } /** diff --git a/src/main/java/com/shanebeestudios/hg/plugin/managers/ItemManager.java b/src/main/java/com/shanebeestudios/hg/plugin/managers/ItemManager.java index bb2efe3b..1b0fc1c5 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/managers/ItemManager.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/managers/ItemManager.java @@ -66,30 +66,25 @@ private ItemData createItemData(ConfigurationSection itemsSection, @Nullable Gam ItemData itemData = new ItemData(); for (ChestType chestType : ChestType.values()) { - int count = 0; ConfigurationSection chestTypeSection = itemsSection.getConfigurationSection(chestType.getName()); if (chestTypeSection == null) { // If the section does not exist in a game, use defaults if (game != null && this.defaultItemData != null) { - itemData.setItems(chestType, this.defaultItemData.getItems(chestType)); - count += this.defaultItemData.getItemCount(chestType); + itemData.setWeightedItems(chestType, this.defaultItemData.getWeightedItems(chestType)); } } else { - List items = new ArrayList<>(); for (String key : chestTypeSection.getKeys(false)) { ConfigurationSection itemSection = chestTypeSection.getConfigurationSection(key); if (itemSection == null) continue; ItemStack itemStack = ItemParser.parseItem(itemSection); int weight = itemSection.getInt("weight", 1); - for (int i = 0; i < weight; i++) { - items.add(itemStack); + if (weight <= 0) { + continue; } - count++; + itemData.addEntry(chestType, itemStack, weight); } - itemData.setItems(chestType, items); } - itemData.setItemCount(chestType, count); } return itemData; } diff --git a/src/main/java/com/shanebeestudios/hg/plugin/managers/MobManager.java b/src/main/java/com/shanebeestudios/hg/plugin/managers/MobManager.java index 48871c8b..b66dfbff 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/managers/MobManager.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/managers/MobManager.java @@ -88,7 +88,6 @@ public void loadGameMobs(Game game, ConfigurationSection arenaConfig) { private MobData createMobData(ConfigurationSection mobsSection, @Nullable Game game) { MobData mobData = new MobData(); - int count = 0; String gameName = game != null ? game.getGameArenaData().getName() + ":" : ""; for (String time : Arrays.asList("day", "night")) { if (!mobsSection.contains(time)) continue; @@ -212,20 +211,20 @@ private MobData createMobData(ConfigurationSection mobsSection, @Nullable Game g mobEntry.setDeathMessage(deathMessage); } int weight = mobSection.getInt("weight", 1); - count++; - for (int i = 1; i <= weight; i++) { - if (time.equalsIgnoreCase("day")) { - mobData.addDayMob(mobEntry); - } else { - mobData.addNightMob(mobEntry); - } + if (weight <= 0) { + Util.warning("Invalid weight '%d' for mob entry '%s:%s'", weight, time, sectionKey); + continue; + } + if (time.equalsIgnoreCase("day")) { + mobData.addDayMob(mobEntry, weight); + } else { + mobData.addNightMob(mobEntry, weight); } if (Config.SETTINGS_DEBUG) { Util.log("- Loaded mob entry '%s'", mobEntryKey); } } } - mobData.setMobCount(count); return mobData; } From 3e10f158aa702b627a9bd8f0acaf59d434971572 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 5 May 2026 18:35:56 -0700 Subject: [PATCH 03/26] DeleteArenaCommand - add confirmation --- .../plugin/commands/DeleteArenaCommand.java | 75 +++++++++++-------- .../hg/plugin/configs/Language.java | 4 + src/main/resources/language.yml | 3 +- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/plugin/commands/DeleteArenaCommand.java b/src/main/java/com/shanebeestudios/hg/plugin/commands/DeleteArenaCommand.java index 0d40760d..c6239920 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/commands/DeleteArenaCommand.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/commands/DeleteArenaCommand.java @@ -23,44 +23,59 @@ protected Argument register() { return LiteralArgument.literal("delete-arena") .withPermission(Permissions.COMMAND_DELETE.permission()) .then(CustomArg.GAME.get("game") - .executes(info -> { - CommandSender sender = info.sender(); - Game game = info.args().getByClass("game", Game.class); - assert game != null; - GamePlayerData gamePlayerData = game.getGamePlayerData(); - GameArenaData gameArenaData = game.getGameArenaData(); - String name = gameArenaData.getName(); + .then(LiteralArgument.literal("confirm") + .executes(info -> { + CommandSender sender = info.sender(); + Game game = info.args().getByClass("game", Game.class); + assert game != null; + GamePlayerData gamePlayerData = game.getGamePlayerData(); + GameArenaData gameArenaData = game.getGameArenaData(); + String name = gameArenaData.getName(); - try { - Util.sendPrefixedMessage(sender, this.lang.command_delete_attempt.replace("", name)); + try { + Util.sendPrefixedMessage(sender, this.lang.command_delete_attempt.replace("", name)); - switch (gameArenaData.getStatus()) { - case WAITING, COUNTDOWN, FREE_ROAM, RUNNING -> { - Util.sendMessage(sender, this.lang.command_delete_stopping); - game.getGameBlockData().forceRollback(); - game.stop(false); - } - case ROLLBACK -> { - Util.sendMessage(sender, this.lang.command_delete_rollback); - return; + switch (gameArenaData.getStatus()) { + case WAITING, COUNTDOWN, FREE_ROAM, RUNNING -> { + Util.sendMessage(sender, this.lang.command_delete_stopping); + game.getGameBlockData().forceRollback(); + game.stop(false); + } + case ROLLBACK -> { + Util.sendMessage(sender, this.lang.command_delete_rollback); + return; + } } - } - // This shouldn't happen, why is it here? - if (!gamePlayerData.getPlayers().isEmpty()) { - Util.sendMessage(sender, this.lang.command_delete_kicking); - for (Player player : gamePlayerData.getPlayers()) { - gamePlayerData.leaveGame(player, false); + // This shouldn't happen, why is it here? + if (!gamePlayerData.getPlayers().isEmpty()) { + Util.sendMessage(sender, this.lang.command_delete_kicking); + for (Player player : gamePlayerData.getPlayers()) { + gamePlayerData.leaveGame(player, false); + } } + + this.gameManager.deleteGame(game); + Util.sendMessage(sender, this.lang.command_delete_deleted.replace("", name)); + } catch (Exception e) { + Util.sendMessage(sender, this.lang.command_delete_failed); + Util.sendMessage(sender, "Error Message: " + e.getMessage()); } + })) + .executes(info -> { + CommandSender sender = info.sender(); + Game game = info.args().getByClass("game", Game.class); + assert game != null; - this.gameManager.deleteGame(game); - Util.sendMessage(sender, this.lang.command_delete_deleted.replace("", name)); - } catch (Exception e) { - Util.sendMessage(sender, this.lang.command_delete_failed); - Util.sendMessage(sender, "Error Message: " + e.getMessage()); + String name = game.getGameArenaData().getName(); + if (sender instanceof Player player) { + Util.sendMessage(sender, this.lang.command_delete_confirm.replace("", name), + name); + } else { + Util.sendMessage(sender, this.lang.command_delete_confirm_console.replace("", name)); } - })); + }) + ); } } diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/Language.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/Language.java index 11fd3030..1be05907 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/configs/Language.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/Language.java @@ -65,6 +65,8 @@ public class Language { public String command_create_session_sign_invalid; // - Delete public String command_delete_attempt; + public String command_delete_confirm; + public String command_delete_confirm_console; public String command_delete_kicking; public String command_delete_stopping; public String command_delete_deleted; @@ -345,6 +347,8 @@ private void loadLang() { this.command_create_session_done = this.lang.getString("command.create.session-done"); // - Delete this.command_delete_attempt = this.lang.getString("command.delete.attempt"); + this.command_delete_confirm = this.lang.getString("command.delete.confirm"); + this.command_delete_confirm_console = this.lang.getString("command.delete.confirm-console"); this.command_delete_kicking = this.lang.getString("command.delete.kicking"); this.command_delete_stopping = this.lang.getString("command.delete.stopping"); this.command_delete_deleted = this.lang.getString("command.delete.deleted"); diff --git a/src/main/resources/language.yml b/src/main/resources/language.yml index 2bee8c00..7bd59a6b 100644 --- a/src/main/resources/language.yml +++ b/src/main/resources/language.yml @@ -58,6 +58,8 @@ command: session-done: "You're all done, your arena is ready to go!" # Delete delete: + confirm-console: "Please use '/hg delete confirm'" + confirm: 'Are you sure you want to delete the arena ? confirm>Click to confirm' attempt: 'Attempting to delete !' stopping: '- Game running! Stopping..' kicking: '- Players detected! Kicking..' @@ -65,7 +67,6 @@ command: failed: 'Failed to delete arena!' rollback: 'The game is currently rolling back and cannot be deleted right now!' no-exist: "The arena '' does not exist!" - # Edit edit: # ChestRefill From 48e8917aec26fb36977881a8acc80f17b83f2f8d Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 5 May 2026 21:38:43 -0700 Subject: [PATCH 04/26] GameBorderData - add multiple border centers - Ref #105 --- .../com/shanebeestudios/hg/api/game/Game.java | 2 +- .../hg/api/game/GameBorderData.java | 60 ++++++++++++++----- .../hg/plugin/commands/EditCommand.java | 15 +++-- .../hg/plugin/configs/ArenaConfig.java | 29 +++++++-- 4 files changed, 79 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/game/Game.java b/src/main/java/com/shanebeestudios/hg/api/game/Game.java index 55e531c5..7d67ded4 100755 --- a/src/main/java/com/shanebeestudios/hg/api/game/Game.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/Game.java @@ -407,7 +407,7 @@ public void stop() { */ public void stop(boolean death) { if (Config.WORLD_BORDER_ENABLED) { - this.gameBorderData.resetBorder(); + this.gameBorderData.resetBorder(false); } this.gameEntityData.removeEntities(); this.gameScoreboard.resetSidebars(); diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GameBorderData.java b/src/main/java/com/shanebeestudios/hg/api/game/GameBorderData.java index 428d1875..e7f9f3b8 100644 --- a/src/main/java/com/shanebeestudios/hg/api/game/GameBorderData.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GameBorderData.java @@ -1,13 +1,15 @@ package com.shanebeestudios.hg.api.game; +import com.shanebeestudios.hg.api.util.Util; import com.shanebeestudios.hg.plugin.configs.Config; import com.shanebeestudios.hg.plugin.tasks.WorldBorderTask; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.WorldBorder; import org.bukkit.util.BoundingBox; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -18,7 +20,7 @@ public class GameBorderData extends Data { private final Random random = new Random(); private boolean isDefault; - private Location centerLocation; + private final List centerLocations = new ArrayList<>(); private int finalBorderSize; private int borderCountdownStart; private int borderCountdownEnd; @@ -32,11 +34,11 @@ public class GameBorderData extends Data { this.isDefault = true; } - GameBorderData(Game game, Location centerLocation, int finalSize, int start, int end) { + GameBorderData(Game game, Location centerLocations, int finalSize, int start, int end) { super(game); this.gamePlayerData = game.getGamePlayerData(); this.worldBorder = Bukkit.createWorldBorder(); - this.centerLocation = centerLocation; + this.centerLocations.add(centerLocations); this.finalBorderSize = finalSize; this.borderCountdownStart = start; this.borderCountdownEnd = end; @@ -56,26 +58,34 @@ public WorldBorder getWorldBorder() { * Initialize the {@link WorldBorder} of this game */ public void initialize() { - resetBorder(); + resetBorder(true); this.gamePlayerData.getPlayers().forEach(player -> player.setWorldBorder(this.worldBorder)); this.worldBorderTask = new WorldBorderTask(this.game); } /** * Reset the {@link WorldBorder} of this game + * + * @param start Whether this is a game start or end reset */ - public void resetBorder() { + public void resetBorder(boolean start) { Location center; GameArenaData gameArenaData = this.game.getGameArenaData(); List spawns = gameArenaData.getSpawns(); - if (this.centerLocation != null) { - center = this.centerLocation; - } else { + if (this.centerLocations.isEmpty()) { switch (Config.WORLD_BORDER_CENTER) { // 'first-spawn', 'random-spawn' and 'arena-center' case "first-spawn" -> center = spawns.getFirst(); case "random-spawn" -> center = spawns.get(this.random.nextInt(spawns.size())); default -> center = gameArenaData.getGameRegion().getCenter(); } + } else { + if (start) { + // If starting a new game, pick a random center location + center = this.centerLocations.get(this.random.nextInt(this.centerLocations.size())); + } else { + // If game is ending, reset to arena center + center = gameArenaData.getGameRegion().getCenter(); + } } this.worldBorder.setCenter(center); @@ -103,22 +113,40 @@ public void startShrinking(int closingIn) { } /** - * Set the center of the border of this game + * Add a center location to the border of this game * * @param centerLocation Location of the center */ - public void setCenterLocation(Location centerLocation) { - this.centerLocation = centerLocation; + public void addCenterLocation(Location centerLocation) { + this.centerLocations.add(centerLocation); + } + + /** + * Clear all center locations from the border of this game + */ + public void clearCenterLocations() { + this.centerLocations.clear(); + } + + /** + * Set the center of the border of this game + * + * @param centerLocations Location of the center + */ + public void setCenterLocations(List centerLocations) { + Util.log("Setting center locations for game border"); + this.centerLocations.clear(); + this.centerLocations.addAll(centerLocations); this.isDefault = false; } /** - * Get the center location of the border + * Get a list of center locations of the border * - * @return Center location + * @return Center locations */ - public @Nullable Location getCenterLocation() { - return this.centerLocation; + public @NotNull List getCenterLocations() { + return this.centerLocations; } /** diff --git a/src/main/java/com/shanebeestudios/hg/plugin/commands/EditCommand.java b/src/main/java/com/shanebeestudios/hg/plugin/commands/EditCommand.java index f3de01f2..6f703d1b 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/commands/EditCommand.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/commands/EditCommand.java @@ -73,16 +73,23 @@ private Argument border() { saveGame(game); }))) - .then(LiteralArgument.literal("center_location") + .then(LiteralArgument.literal("add_center_location") .then(new Location2DArgument("center_location", LocationType.BLOCK_POSITION) .executes(info -> { Game game = info.args().getByClass("game", Game.class); Location2D centerLocation = info.args().getByClass("center_location", Location2D.class); GameBorderData gameBorderData = game.getGameBorderData(); - gameBorderData.setCenterLocation(convert(centerLocation)); - Util.sendPrefixedMessage(info.sender(), "Border center location set to %s", centerLocation); + gameBorderData.addCenterLocation(convert(centerLocation)); + Util.sendPrefixedMessage(info.sender(), "Border center location added: %s", centerLocation); saveGame(game); - }))); + }))) + .then(LiteralArgument.literal("clear_center_locations") + .executes(info -> { + Game game = info.args().getByClass("game", Game.class); + game.getGameBorderData().clearCenterLocations(); + Util.sendPrefixedMessage(info.sender(), "Border center locations cleared"); + saveGame(game); + })); } @SuppressWarnings("DataFlowIssue") diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/ArenaConfig.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/ArenaConfig.java index 609652d0..0db37f11 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/configs/ArenaConfig.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/ArenaConfig.java @@ -115,6 +115,7 @@ public void loadAllArenas() { @SuppressWarnings("DataFlowIssue") public boolean loadArena(FileConfiguration arenaConfig, String arenaName) { boolean isReady = true; + boolean isDirty = false; List spawns = new ArrayList<>(); Location lobbysign = null; int timer = 0; @@ -192,9 +193,20 @@ public boolean loadArena(FileConfiguration arenaConfig, String arenaName) { if (arenaConfig.isSet("game_border")) { ConfigurationSection borderSection = arenaConfig.getConfigurationSection("game_border"); GameBorderData gameBorderData = game.getGameBorderData(); - if (borderSection.isSet("center_location")) { - Location borderCenter = LocationParser.getBlockLocFromString(borderSection.getString("center_location")); - gameBorderData.setCenterLocation(borderCenter); + + if (borderSection.isSet("center_locations")) { + List centerLocations = borderSection.getStringList("center_locations"); + List centerLocationList = new ArrayList<>(); + for (String locString : centerLocations) { + Location centerLocation = LocationParser.getBlockLocFromString(locString); + centerLocationList.add(centerLocation); + } + gameBorderData.setCenterLocations(centerLocationList); + } else if (borderSection.isSet("center_location")) { // Deprecated (May 5/2026) + String centerLocString = borderSection.getString("center_location"); + Location borderCenter = LocationParser.getBlockLocFromString(centerLocString); + gameBorderData.setCenterLocations(List.of(borderCenter)); + isDirty = true; } if (borderSection.isSet("final_size")) { int borderSize = borderSection.getInt("final_size"); @@ -246,6 +258,9 @@ public boolean loadArena(FileConfiguration arenaConfig, String arenaName) { } else { Util.log("- Loaded arena '%s'", arenaName); } + if (isDirty) { + saveGameToConfig(game); + } return isReady; } @@ -300,11 +315,13 @@ public void saveGameToConfig(Game game) { GameBorderData borderData = game.getGameBorderData(); if (!borderData.isDefault()) { ConfigurationSection borderSection = gameSection.createSection("game_border"); - Location centerLocation = borderData.getCenterLocation(); - if (centerLocation != null) { + List centerLocations = new ArrayList<>(); + for (Location centerLocation : borderData.getCenterLocations()) { String locString = LocationParser.blockLocToString(centerLocation); - borderSection.set("center_location", locString); + centerLocations.add(locString); + } + borderSection.set("center_locations", centerLocations); borderSection.set("final_size", borderData.getFinalBorderSize()); borderSection.set("countdown_start", borderData.getBorderCountdownStart()); borderSection.set("countdown_end", borderData.getBorderCountdownEnd()); From a8896720e9c99eb5f42ebf798414b45a31e15ac0 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 5 May 2026 21:44:05 -0700 Subject: [PATCH 05/26] Registries - cleanup --- .../hg/api/registry/Registries.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/registry/Registries.java b/src/main/java/com/shanebeestudios/hg/api/registry/Registries.java index 384cee4f..3d6180ff 100644 --- a/src/main/java/com/shanebeestudios/hg/api/registry/Registries.java +++ b/src/main/java/com/shanebeestudios/hg/api/registry/Registries.java @@ -19,14 +19,15 @@ @SuppressWarnings({"NullableProblems", "UnstableApiUsage"}) public class Registries { - public static final Registry ATTRIBUTE_REGISTRY = RegistryAccess.registryAccess().getRegistry(RegistryKey.ATTRIBUTE); - public static final Registry BLOCK_TYPE_REGISTRY = RegistryAccess.registryAccess().getRegistry(RegistryKey.BLOCK); - public static final Registry DAMAGE_TYPE_REGISTRY = RegistryAccess.registryAccess().getRegistry(RegistryKey.DAMAGE_TYPE); - public static final Registry DATA_COMPONENT_TYPE_REGISTRY = RegistryAccess.registryAccess().getRegistry(RegistryKey.DATA_COMPONENT_TYPE); - public static final Registry ENCHANTMENT_REGISTRY = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT); - public static final Registry ENTITY_TYPE_REGISTRY = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENTITY_TYPE); - public static final Registry ITEM_TYPE_REGISTRY = RegistryAccess.registryAccess().getRegistry(RegistryKey.ITEM); - public static final Registry POTION_EFFECT_TYPE_REGISTRY = RegistryAccess.registryAccess().getRegistry(RegistryKey.MOB_EFFECT); - public static final Registry POTION_TYPE_REGISTRY = RegistryAccess.registryAccess().getRegistry(RegistryKey.POTION); + private static final RegistryAccess ACCESS = RegistryAccess.registryAccess(); + public static final Registry ATTRIBUTE_REGISTRY = ACCESS.getRegistry(RegistryKey.ATTRIBUTE); + public static final Registry BLOCK_TYPE_REGISTRY = ACCESS.getRegistry(RegistryKey.BLOCK); + public static final Registry DAMAGE_TYPE_REGISTRY = ACCESS.getRegistry(RegistryKey.DAMAGE_TYPE); + public static final Registry DATA_COMPONENT_TYPE_REGISTRY = ACCESS.getRegistry(RegistryKey.DATA_COMPONENT_TYPE); + public static final Registry ENCHANTMENT_REGISTRY = ACCESS.getRegistry(RegistryKey.ENCHANTMENT); + public static final Registry ENTITY_TYPE_REGISTRY = ACCESS.getRegistry(RegistryKey.ENTITY_TYPE); + public static final Registry ITEM_TYPE_REGISTRY = ACCESS.getRegistry(RegistryKey.ITEM); + public static final Registry POTION_EFFECT_TYPE_REGISTRY = ACCESS.getRegistry(RegistryKey.MOB_EFFECT); + public static final Registry POTION_TYPE_REGISTRY = ACCESS.getRegistry(RegistryKey.POTION); } From 684e7ea9762f19192c0d773757d9a03d3782a8a8 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 5 May 2026 21:59:22 -0700 Subject: [PATCH 06/26] Util - remove legacy check --- .../shanebeestudios/hg/api/gui/KitGUI.java | 19 +++++-------------- .../hg/api/parsers/ItemParser.java | 6 +----- .../com/shanebeestudios/hg/api/util/Util.java | 2 -- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/gui/KitGUI.java b/src/main/java/com/shanebeestudios/hg/api/gui/KitGUI.java index 36a9d19e..89f9a6a6 100644 --- a/src/main/java/com/shanebeestudios/hg/api/gui/KitGUI.java +++ b/src/main/java/com/shanebeestudios/hg/api/gui/KitGUI.java @@ -12,7 +12,6 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemType; import org.bukkit.potion.PotionEffect; @@ -41,12 +40,9 @@ public KitGUI(KitsGUI kitsGUI, Player player, KitEntry kitEntry) { // SETUP INVENTORY // divider ItemStack divider = ItemType.BLACK_STAINED_GLASS_PANE.createItemStack(); - if (Util.RUNNING_1_21_5) { - divider.setData(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay() - .hideTooltip(true)); - } else { - divider.setData(DataComponentTypes.CUSTOM_NAME, Component.text(" ")); - } + divider.setData(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay() + .hideTooltip(true)); + for (int i = 9; i < 18; i++) { this.inventory.setItem(i, divider); } @@ -96,13 +92,8 @@ public KitGUI(KitsGUI kitsGUI, Player player, KitEntry kitEntry) { // Potions ItemStack potion = ItemType.POTION.createItemStack(); potion.setData(DataComponentTypes.CUSTOM_NAME, Util.getMini(this.lang.kits_kit_gui_potion_effects)); - if (Util.RUNNING_1_21_5) { - potion.setData(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay() - .hideTooltip(true)); - } else { - //noinspection deprecation - potion.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP); - } + potion.setData(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay() + .hideTooltip(true)); List potionEffects = kitEntry.getPotionEffects(); List lore = new ArrayList<>(); diff --git a/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java b/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java index de088cb7..867f5fdc 100644 --- a/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java +++ b/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java @@ -14,7 +14,6 @@ import org.bukkit.NamespacedKey; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemType; import org.bukkit.potion.PotionEffect; @@ -156,13 +155,10 @@ public class ItemParser { if (config.contains("dyed_color")) { int color = config.getInt("dyed_color"); itemStack.setData(DataComponentTypes.DYED_COLOR, DyedItemColor.dyedItemColor().color(Color.fromRGB(color))); - if (!Util.RUNNING_1_21_5) { - itemStack.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP); - } } // HIDDEN COMPONENTS - if (Util.RUNNING_1_21_5 && config.isList("hidden_components")) { + if (config.isList("hidden_components")) { TooltipDisplay.Builder builder = TooltipDisplay.tooltipDisplay(); for (String compKey : config.getStringList("hidden_components")) { NamespacedKey namespacedKey = NamespacedKey.fromString(compKey); diff --git a/src/main/java/com/shanebeestudios/hg/api/util/Util.java b/src/main/java/com/shanebeestudios/hg/api/util/Util.java index a80f27bf..840b4eae 100755 --- a/src/main/java/com/shanebeestudios/hg/api/util/Util.java +++ b/src/main/java/com/shanebeestudios/hg/api/util/Util.java @@ -26,10 +26,8 @@ public class Util { // PUBLIC // Quick link to help for removing legacy stuff later - public static final boolean RUNNING_1_21_5 = isRunningMinecraft(1, 21, 5); public static final boolean IS_RUNNING_FOLIA = classExists("io.papermc.paper.threadedregions.FoliaWatchdogThread"); - // PRIVATE private static final Pattern HEX_PATTERN = Pattern.compile("<#([A-Fa-f0-9]){6}>"); private static final CommandSender CONSOLE = Bukkit.getConsoleSender(); From 3799c073bfbb67d6dcbe4b7872139a49821b5cab Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 14 May 2026 19:39:29 -0700 Subject: [PATCH 07/26] GameSidebar - update javadocs --- .../java/com/shanebeestudios/hg/api/game/GameSidebar.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GameSidebar.java b/src/main/java/com/shanebeestudios/hg/api/game/GameSidebar.java index 96ad8981..10574bb9 100644 --- a/src/main/java/com/shanebeestudios/hg/api/game/GameSidebar.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GameSidebar.java @@ -30,7 +30,7 @@ public GameSidebar(Game game) { } /** - * Add a player to this scoreboard + * Add a player to this sidebar * * @param player Player to add */ @@ -49,7 +49,7 @@ public void removePlayer(Player player) { } /** - * Set the title of this scoreboard + * Set the title of this sidebar * * @param title Title to set */ @@ -58,7 +58,7 @@ public void setTitle(String title) { } /** - * Update this scoreboard + * Update this sidebar */ public void updateBoard() { String alive = " " + this.lang.scoreboard_sidebar_players_alive_num.replace("", String.valueOf(this.game.getGamePlayerData().getPlayers().size())); From b46d070eb7463e9977a1fd46705419af9b3619ae Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 14 May 2026 19:42:16 -0700 Subject: [PATCH 08/26] build.gradle.kts - updates --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0f9494bb..40c23ca0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation("org.bstats:bstats-bukkit:3.2.0") // MythicMobs - compileOnly("io.lumine:Mythic-Dist:5.6.1") + compileOnly("io.lumine:Mythic-Dist:5.12.0") // Papi compileOnly("me.clip:placeholderapi:2.12.2") @@ -80,7 +80,7 @@ tasks { dependsOn("shadowJar") from("build/libs") { include("HungerGames-*.jar") - destinationDir = file("/Users/ShaneBee/Desktop/Server/Minecraft/${serverLocation}/plugins/") + destinationDir = file("/Users/ShaneBee/Desktop/Server/Minecraft/Skript/${serverLocation}/plugins/") } } From c5997e9437eb2b95810ecdf181ea2f4f6b259b44 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 14 May 2026 19:42:31 -0700 Subject: [PATCH 09/26] other javadoc updates --- .../com/shanebeestudios/hg/api/parsers/ItemParser.java | 3 +++ .../com/shanebeestudios/hg/api/status/PlayerStatus.java | 3 +++ src/main/java/com/shanebeestudios/hg/api/util/Pair.java | 8 ++++++++ .../com/shanebeestudios/hg/plugin/configs/Config.java | 3 +-- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java b/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java index 867f5fdc..1efaf5ee 100644 --- a/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java +++ b/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java @@ -25,6 +25,9 @@ import java.util.List; import java.util.Locale; +/** + * Parser for items from configs + */ public class ItemParser { @SuppressWarnings({"UnstableApiUsage"}) diff --git a/src/main/java/com/shanebeestudios/hg/api/status/PlayerStatus.java b/src/main/java/com/shanebeestudios/hg/api/status/PlayerStatus.java index b7835e7c..354c48f9 100644 --- a/src/main/java/com/shanebeestudios/hg/api/status/PlayerStatus.java +++ b/src/main/java/com/shanebeestudios/hg/api/status/PlayerStatus.java @@ -5,6 +5,9 @@ import com.shanebeestudios.hg.plugin.configs.Language; import net.kyori.adventure.text.Component; +/** + * Status of a player in and out of games + */ public enum PlayerStatus { IN_GAME, diff --git a/src/main/java/com/shanebeestudios/hg/api/util/Pair.java b/src/main/java/com/shanebeestudios/hg/api/util/Pair.java index 9234a7fc..ccfec3eb 100644 --- a/src/main/java/com/shanebeestudios/hg/api/util/Pair.java +++ b/src/main/java/com/shanebeestudios/hg/api/util/Pair.java @@ -1,5 +1,13 @@ package com.shanebeestudios.hg.api.util; +/** + * A pair of 2 objects. + * + * @param first First object + * @param second Second object + * @param Class type of the first object + * @param Class type of the second object + */ public record Pair(F first, S second) { public static Pair of(F first, S second) { diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java index b5dcc9cd..4e7149ba 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java @@ -209,8 +209,7 @@ private void loadConfig() { COMMANDS_ALLOWED_IN_GAME = config.getStringList("commands.allowed-in-game"); try { - Vault.setupEconomy(); - if (Vault.ECONOMY == null) { + if (!Vault.setupEconomy()) { Util.log("Unable to setup vault!"); Util.log(" - Economy provider is missing."); Util.log(" - Cash rewards will not be given out.."); From 1188ef5308b872458a79a4873ade140d384ff05d Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 14 May 2026 20:33:24 -0700 Subject: [PATCH 10/26] build.gradle.kts - exclude sources for server task --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index 40c23ca0..b902b165 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -80,6 +80,7 @@ tasks { dependsOn("shadowJar") from("build/libs") { include("HungerGames-*.jar") + exclude("*-sources.jar") destinationDir = file("/Users/ShaneBee/Desktop/Server/Minecraft/Skript/${serverLocation}/plugins/") } From b532758c05268373dc1006543b1c6bc8f62ce8e0 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 14 May 2026 20:39:26 -0700 Subject: [PATCH 11/26] Data - add more data --- .../com/shanebeestudios/hg/api/game/Data.java | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/game/Data.java b/src/main/java/com/shanebeestudios/hg/api/game/Data.java index 787c3b33..d406f202 100644 --- a/src/main/java/com/shanebeestudios/hg/api/game/Data.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/Data.java @@ -2,6 +2,7 @@ import com.shanebeestudios.hg.plugin.HungerGames; import com.shanebeestudios.hg.plugin.configs.Language; +import com.shanebeestudios.hg.plugin.managers.GameManager; /** * General class for storing different aspects of data for {@link Game Games} @@ -10,30 +11,50 @@ public abstract class Data { final Game game; final HungerGames plugin; + final GameManager gameManager; final Language lang; Data(Game game) { this.game = game; this.plugin = game.plugin; + this.gameManager = this.plugin.getGameManager(); this.lang = game.lang; } /** - * Get the {@link Game} this data belongs to + * Get the {@link Game} this data belongs to. * * @return Game this data belongs to */ public Game getGame() { - return game; + return this.game; } /** - * Quick method to access the main plugin + * Quick method to access the main plugin. * * @return Instance of {@link HungerGames plugin} */ public HungerGames getPlugin() { - return plugin; + return this.plugin; + } + + /** + * Get the {@link GameManager} for the plugin. + * + * @return Instance of {@link GameManager} + */ + public GameManager getGameManager() { + return this.gameManager; + } + + /** + * Get the {@link Language} for the plugin. + * + * @return Instance of {@link Language} + */ + public Language getLang() { + return this.lang; } } From f45699fbb60b16e34861dc2ead1eeb3606ae9c81 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 14 May 2026 20:40:24 -0700 Subject: [PATCH 12/26] Game - fix exit location of arena not working --- .../hg/api/game/GameArenaData.java | 23 +++++++++++++++++-- .../hg/api/game/GamePlayerData.java | 5 +--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GameArenaData.java b/src/main/java/com/shanebeestudios/hg/api/game/GameArenaData.java index c8c00cd5..cbde3839 100644 --- a/src/main/java/com/shanebeestudios/hg/api/game/GameArenaData.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GameArenaData.java @@ -5,6 +5,7 @@ import com.shanebeestudios.hg.plugin.HungerGames; import net.kyori.adventure.text.Component; import org.bukkit.Location; +import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; @@ -80,7 +81,8 @@ public Game checkOverlap() { for (Game toCheck : HungerGames.getPlugin().getGameManager().getGames()) { if (this.game.equals(toCheck)) continue; - if (toCheck.getGameArenaData().getGameRegion().getBoundingBox().overlaps(this.gameRegion.getBoundingBox())) return toCheck; + if (toCheck.getGameArenaData().getGameRegion().getBoundingBox().overlaps(this.gameRegion.getBoundingBox())) + return toCheck; } return null; } @@ -231,7 +233,24 @@ public Location getExitLocation() { } /** - * Set exit location for this game + * Get the exit location for a player. + *

+ * If the game has a specific exit location, it will be returned. + * Otherwise, the global exit location for the player will be returned. + *

+ * + * @param player Player to get exit location for + * @return Exit location for the player + */ + public Location getExitForPlayer(Player player) { + if (this.exit != null) { + return this.exit; + } + return this.gameManager.getGlobalExitLocation(player); + } + + /** + * Set the exit location for this game. * * @param location Location where players will exit */ diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java b/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java index defe6b1f..f9594128 100644 --- a/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java @@ -6,7 +6,6 @@ import com.shanebeestudios.hg.api.status.Status; import com.shanebeestudios.hg.api.util.Util; import com.shanebeestudios.hg.plugin.configs.Config; -import com.shanebeestudios.hg.plugin.managers.GameManager; import com.shanebeestudios.hg.plugin.managers.PlayerManager; import com.shanebeestudios.hg.plugin.permission.Permissions; import net.kyori.adventure.title.Title; @@ -43,7 +42,6 @@ public class GamePlayerData extends Data { private static final @NotNull NamespacedKey MOVE_KEY = NamespacedKey.fromString("hg:freeze_move"); private final PlayerManager playerManager; - private final GameManager gameManager; // Player Lists private final Map players = new HashMap<>(); @@ -59,7 +57,6 @@ public class GamePlayerData extends Data { protected GamePlayerData(Game game) { super(game); this.playerManager = this.plugin.getPlayerManager(); - this.gameManager = this.plugin.getGameManager(); } /** @@ -308,7 +305,7 @@ void exit(Player player, @Nullable Location exitLocation) { player.setInvulnerable(false); if (gameArenaData.getStatus() == Status.RUNNING) this.game.getGameBarData().removePlayer(player); - Location location = exitLocation != null ? exitLocation : this.gameManager.getGlobalExitLocation(player); + Location location = exitLocation != null ? exitLocation : this.game.getGameArenaData().getExitForPlayer(player); PlayerData playerData = this.playerManager.getData(player); if (playerData == null || playerData.isOnline()) { player.teleportAsync(location); From 0315726c429b17c2a4b78c8c8b00c0c9381206b4 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Fri, 15 May 2026 06:50:51 -0700 Subject: [PATCH 13/26] Util - add debug with format --- .../com/shanebeestudios/hg/api/util/Util.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/util/Util.java b/src/main/java/com/shanebeestudios/hg/api/util/Util.java index 840b4eae..2a65ec8c 100755 --- a/src/main/java/com/shanebeestudios/hg/api/util/Util.java +++ b/src/main/java/com/shanebeestudios/hg/api/util/Util.java @@ -99,7 +99,7 @@ public static void warning(String format, Object... objects) { } /** - * Send a debug message to console + * Send a debug message to console. *

This will only send if 'debug' is enabled in config.yml

* * @param debug Debug message to log @@ -110,6 +110,19 @@ public static void debug(String debug) { } } + /** + * Send a formatted debug message to the console. + *

This will only send if 'debug' is enabled in config.yml

+ * + * @param format Message format + * @param args Arguments for message format + */ + public static void debug(String format, Object... args) { + if (Config.SETTINGS_DEBUG) { + log(format, args); + } + } + /** * Send a debug exception to console *

This will only send if 'debug' is enabled in config.yml

From 8b4fedf777a3261e47d71bc5c0d6072f7febc46d Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Fri, 15 May 2026 08:10:27 -0700 Subject: [PATCH 14/26] Add player tracking config options --- .../hg/api/data/PlayerData.java | 9 +++ .../hg/api/game/GameEntityData.java | 8 +++ .../hg/api/game/GamePlayerData.java | 13 +++- .../hg/api/util/ItemUtils.java | 4 +- .../hg/plugin/configs/Config.java | 61 +++++++++++++++++-- .../hg/plugin/managers/KillManager.java | 2 +- 6 files changed, 87 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/data/PlayerData.java b/src/main/java/com/shanebeestudios/hg/api/data/PlayerData.java index fe7ac45d..a816f207 100755 --- a/src/main/java/com/shanebeestudios/hg/api/data/PlayerData.java +++ b/src/main/java/com/shanebeestudios/hg/api/data/PlayerData.java @@ -5,6 +5,7 @@ import com.shanebeestudios.hg.api.util.Util; import com.shanebeestudios.hg.plugin.HungerGames; import org.bukkit.Bukkit; +import org.bukkit.Color; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.attribute.Attribute; @@ -41,6 +42,8 @@ public class PlayerData implements Cloneable { private Location previousLocation = null; private boolean online; private boolean hasGameStarted; + private double waypointReceiveRange; + private Color trackingColor; //InGame data private GameTeam gameTeam; @@ -59,6 +62,7 @@ public PlayerData(Player player, Game game) { this.online = true; } + @SuppressWarnings("DataFlowIssue") public void backup() { this.hasGameStarted = true; this.inv = this.player.getInventory().getStorageContents(); @@ -73,6 +77,8 @@ public void backup() { this.player.setLevel(0); this.player.setExp(0); this.scoreboard = this.player.getScoreboard(); + this.waypointReceiveRange = this.player.getAttribute(Attribute.WAYPOINT_RECEIVE_RANGE).getValue(); + this.trackingColor = this.player.getWaypointColor(); } /** @@ -80,6 +86,7 @@ public void backup() { * * @param player Player to restore data to */ + @SuppressWarnings("DataFlowIssue") public void restore(Player player) { if (player == null || !this.hasGameStarted) return; Util.clearInv(player); @@ -97,6 +104,8 @@ public void restore(Player player) { // Force back their original scoreboard player.setScoreboard(DUMMY); player.setScoreboard(this.scoreboard); + player.getAttribute(Attribute.WAYPOINT_RECEIVE_RANGE).setBaseValue(Math.max(0, this.waypointReceiveRange)); + player.setWaypointColor(this.trackingColor); } // Restores later if player has an item in their inventory which changes their max health value diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GameEntityData.java b/src/main/java/com/shanebeestudios/hg/api/game/GameEntityData.java index 5a5a7052..f087a7b1 100644 --- a/src/main/java/com/shanebeestudios/hg/api/game/GameEntityData.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GameEntityData.java @@ -3,7 +3,10 @@ import com.shanebeestudios.hg.api.data.MobData; import com.shanebeestudios.hg.api.data.MobEntry; import com.shanebeestudios.hg.plugin.HungerGames; +import com.shanebeestudios.hg.plugin.configs.Config; import org.bukkit.Location; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.Enemy; import org.bukkit.entity.Entity; import org.bukkit.metadata.FixedMetadataValue; import org.jetbrains.annotations.NotNull; @@ -52,6 +55,7 @@ public void setMobData(MobData mobData) { * @param dayTime Whether it's daytime/nighttime * @return Whether an entity spawned */ + @SuppressWarnings("DataFlowIssue") public boolean spawnMob(Location location, boolean dayTime) { MobEntry mobEntry; if (dayTime) { @@ -63,6 +67,10 @@ public boolean spawnMob(Location location, boolean dayTime) { Entity spawn = mobEntry.spawn(location); if (spawn != null) { logEntity(spawn); + if (spawn instanceof Enemy enemy && Config.PLAYER_TRACKING_DISTANCE > 0 && Config.PLAYER_TRACKING_ENEMY_ENTITY_COLOR != null) { + enemy.getAttribute(Attribute.WAYPOINT_TRANSMIT_RANGE).setBaseValue(Math.max(0, Config.PLAYER_TRACKING_DISTANCE)); + enemy.setWaypointColor(Config.PLAYER_TRACKING_ENEMY_ENTITY_COLOR); + } return true; } } diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java b/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java index f9594128..fa2e80b0 100644 --- a/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java @@ -217,6 +217,7 @@ public void putAllPlayersIntoArena() { this.players.keySet().forEach(this::putPlayerIntoArena); } + @SuppressWarnings("DataFlowIssue") void putPlayerIntoArena(Player player) { boolean savePreviousLocation = this.players.get(player); Location loc = pickRandomSpawn(); @@ -234,13 +235,23 @@ void putPlayerIntoArena(Player player) { // Teleport async into the arena so it loads a little more smoothly player.teleportAsync(loc).thenAccept(a -> { + Util.debug("Putting player %s into arena", player.getName()); PlayerData playerData = this.playerManager.getPlayerData(player); - assert playerData != null; + assert playerData != null : "This should never happen, player data should always exist"; playerData.backup(); if (savePreviousLocation && Config.SETTINGS_SAVE_PREVIOUS_LOCATION) { playerData.setPreviousLocation(previousLocation); } this.game.getGameScoreboard().setupBoard(player); + Util.debug("Player %s has been teleported into arena", player.getName()); + + // Setup Tracking + if (Config.PLAYER_TRACKING_DISTANCE >= 0) { + player.getAttribute(Attribute.WAYPOINT_RECEIVE_RANGE).setBaseValue(Math.max(0, Config.PLAYER_TRACKING_DISTANCE)); + } + if (Config.PLAYER_TRACKING_ENEMY_PLAYER_COLOR != null) { + player.setWaypointColor(Config.PLAYER_TRACKING_ENEMY_PLAYER_COLOR); + } heal(player); freeze(player); diff --git a/src/main/java/com/shanebeestudios/hg/api/util/ItemUtils.java b/src/main/java/com/shanebeestudios/hg/api/util/ItemUtils.java index a8c0817d..37b2194c 100644 --- a/src/main/java/com/shanebeestudios/hg/api/util/ItemUtils.java +++ b/src/main/java/com/shanebeestudios/hg/api/util/ItemUtils.java @@ -10,8 +10,6 @@ import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemType; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.Nullable; @@ -38,7 +36,7 @@ public static ItemStack getTrackingStick() { itemStack.setData(DataComponentTypes.ITEM_NAME, Util.getMini(LANG.item_tracking_stick_name)); itemStack.setData(DataComponentTypes.MAX_STACK_SIZE, 1); - itemStack.setData(DataComponentTypes.MAX_DAMAGE, Config.SETTINGS_TRACKING_STICK_USES); + itemStack.setData(DataComponentTypes.MAX_DAMAGE, Config.PLAYER_TRACKING_TRACKING_STICK_USES); itemStack.setData(DataComponentTypes.DAMAGE, 0); List lore = new ArrayList<>(); LANG.item_tracking_stick_lore.forEach(line -> lore.add(Util.getMini(line))); diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java index 4e7149ba..5afa818a 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java @@ -4,10 +4,13 @@ import com.shanebeestudios.hg.api.util.Util; import com.shanebeestudios.hg.api.util.Vault; import com.shanebeestudios.hg.plugin.HungerGames; +import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.configuration.Configuration; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -22,18 +25,23 @@ public class Config { public static boolean SETTINGS_DEBUG; - //Basic settings + // Basic settings public static boolean SETTINGS_BROADCAST_JOIN_MESSAGES; public static boolean SETTINGS_BROADCAST_WIN_MESSAGES; public static boolean HAS_ECONOMY = true; public static boolean SETTINGS_BOSSBAR_COUNTDOWN; - public static int SETTINGS_TRACKING_STICK_USES; - public static int SETTINGS_PLAYERS_FOR_TRACKING_STICK; public static int SETTINGS_TELEPORT_AT_END_TIME; public static boolean SETTINGS_SAVE_PREVIOUS_LOCATION; public static int SETTINGS_FREE_ROAM_TIME; public static Location SETTINGS_GLOBAL_EXIT_LOCATION; + // Player Tracking + public static int PLAYER_TRACKING_DISTANCE; + public static Color PLAYER_TRACKING_ENEMY_PLAYER_COLOR; + public static Color PLAYER_TRACKING_ENEMY_ENTITY_COLOR; + public static int PLAYER_TRACKING_TRACKING_STICK_USES; + public static int PLAYER_TRACKING_PLAYERS_FOR_TRACKING_STICK; + // Scoreboard public static boolean SCOREBOARD_HIDE_NAMETAGS; public static boolean SCOREBOARD_SHOW_HEALTH_ENABLED; @@ -136,8 +144,6 @@ private void loadConfig() { SETTINGS_BROADCAST_JOIN_MESSAGES = config.getBoolean("settings.broadcast-join-messages"); SETTINGS_BROADCAST_WIN_MESSAGES = config.getBoolean("settings.broadcast-win-messages"); SETTINGS_BOSSBAR_COUNTDOWN = config.getBoolean("settings.bossbar-countdown"); - SETTINGS_TRACKING_STICK_USES = config.getInt("settings.tracking-stick-uses"); - SETTINGS_PLAYERS_FOR_TRACKING_STICK = config.getInt("settings.players-for-tracking-stick"); SETTINGS_SAVE_PREVIOUS_LOCATION = config.getBoolean("settings.save-previous-location"); SETTINGS_TELEPORT_AT_END_TIME = config.getInt("settings.teleport-at-end-time"); SETTINGS_FREE_ROAM_TIME = config.getInt("settings.free-room-time"); @@ -146,6 +152,13 @@ private void loadConfig() { SETTINGS_GLOBAL_EXIT_LOCATION = LocationParser.getLocFromString(locString); } + // Player Tracking + PLAYER_TRACKING_DISTANCE = this.config.getInt("player-tracking.distance"); + PLAYER_TRACKING_ENEMY_PLAYER_COLOR = getColor("player-tracking.enemy-player-color"); + PLAYER_TRACKING_ENEMY_ENTITY_COLOR = getColor("player-tracking.enemy-entity-color"); + PLAYER_TRACKING_TRACKING_STICK_USES = config.getInt("player-tracking.tracking-stick-uses"); + PLAYER_TRACKING_PLAYERS_FOR_TRACKING_STICK = config.getInt("player-tracking.players-for-tracking-stick"); + // Scoreboard SCOREBOARD_HIDE_NAMETAGS = config.getBoolean("scoreboard.hide-nametags"); SCOREBOARD_SHOW_HEALTH_ENABLED = config.getBoolean("scoreboard.show-health.enabled"); @@ -276,4 +289,42 @@ public void setGlobalExitLocation(Location location) { save(); } + private @Nullable Color getColor(@NotNull String setting) { + String string = this.config.getString(setting); + if (string == null) { + Util.log("Invalid color for setting '%s'", setting); + return null; + } + if (string.equalsIgnoreCase("disable") || string.equalsIgnoreCase("disabled")) { + return null; + } + if (string.startsWith("#")) { + // parse from hex + int i = Integer.parseInt(string.substring(1), 16); + return Color.fromRGB(i); + } else if (string.contains(":")) { + String[] split = string.split(":"); + if (split.length == 3) { + try { + int r = Integer.parseInt(split[0]); + int g = Integer.parseInt(split[1]); + int b = Integer.parseInt(split[2]); + return Color.fromRGB(r,g,b); + } catch (NumberFormatException ignore) { + Util.log("Invalid color '%s' for setting '%s'", string, setting); + return null; + } + } + } else { + try { + return Color.fromRGB(Integer.parseInt(string)); + } catch (NumberFormatException ignore) { + Util.log("Invalid color '%s' for setting '%s'", string, setting); + return null; + } + } + Util.log("Invalid color '%s' for setting '%s'", string, setting); + return null; + } + } diff --git a/src/main/java/com/shanebeestudios/hg/plugin/managers/KillManager.java b/src/main/java/com/shanebeestudios/hg/plugin/managers/KillManager.java index 6f29a5d2..c20dedce 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/managers/KillManager.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/managers/KillManager.java @@ -271,7 +271,7 @@ private List dropInventoryOfPlayer(Player player) { } private void checkStick(Game game) { - if (Config.SETTINGS_PLAYERS_FOR_TRACKING_STICK == game.getGamePlayerData().getPlayers().size()) { + if (Config.PLAYER_TRACKING_PLAYERS_FOR_TRACKING_STICK == game.getGamePlayerData().getPlayers().size()) { for (Player player : game.getGamePlayerData().getPlayers()) { Util.sendMessage(player, this.lang.item_tracking_stick_bar); Util.sendMessage(player, this.lang.item_tracking_stick_new1); From 6a144a17d04d84af853dc7e275ee2c6318ee29af Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Fri, 15 May 2026 08:11:00 -0700 Subject: [PATCH 15/26] Add player tracking config options - part 2 - forgot to add config --- src/main/resources/config.yml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 8386bb17..c46e5b27 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -26,10 +26,6 @@ settings: free-roam-time: 25 # Whether or not to show a countdown bossbar in game bossbar-countdown: true - # The amount of uses a tracking stick will have - tracking-stick-uses: 8 - # Minimum amount of players required in a game for a tracking stick to work - players-for-tracking-stick: 5 # Maximum/Minimum amount of items that will spawn in chests max-chestcontent: 5 min-chestcontent: 1 @@ -49,6 +45,28 @@ settings: # If 'none', will use the player's respawn location or main world's spawn location global-exit-location: 'none' +## PLAYER TRACKING +player-tracking: + # How far in blocks players can see each other on the tracking bar + # Set to -1 to keep default value (whatever the player's current tracking range is) + # Set to 0 to disable + distance: 100 + + # Represents the color of enemy players + # Set to 'disable' to keep default color the player is assigned + # Accepts int color, `#rrggbb` or `r:g:b` (0-255 for r:g:b) formats + enemy-player-color: '#EB2D07' + + # Represents the color of enemy entities + # Set to 'disable' to disable + enemy-entity-color: '#EBD807' + + # The number of uses a tracking stick will have + tracking-stick-uses: 8 + + # Minimum number of players required in a game for a tracking stick to work + players-for-tracking-stick: 5 + # Settings for different scoreboard options # This doesn't necessarily mean the sidebar scoreboard: From f4e530ba9258cf7914f88a18cbea76154bc0141e Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Fri, 15 May 2026 12:20:43 -0700 Subject: [PATCH 16/26] PlayerManager - fix spectators data not backing up --- .../java/com/shanebeestudios/hg/api/game/GamePlayerData.java | 2 +- .../com/shanebeestudios/hg/plugin/managers/PlayerManager.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java b/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java index fa2e80b0..c3e51e72 100644 --- a/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GamePlayerData.java @@ -337,7 +337,7 @@ public void spectate(Player spectator) { } else { this.playerManager.createSpectatorData(spectator, this.game); } - this.spectators.put(spectator, true); // TODO should we handle location saving? + this.spectators.put(spectator, true); spectator.setGameMode(GameMode.SURVIVAL); spectator.setCollidable(false); if (Config.SPECTATE_FLY) diff --git a/src/main/java/com/shanebeestudios/hg/plugin/managers/PlayerManager.java b/src/main/java/com/shanebeestudios/hg/plugin/managers/PlayerManager.java index c2ba4bef..dc00ec3f 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/managers/PlayerManager.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/managers/PlayerManager.java @@ -112,6 +112,7 @@ public void createPlayerData(Player player, Game game) { */ public void createSpectatorData(Player spectator, Game game) { PlayerData playerData = new PlayerData(spectator, game); + playerData.backup(); this.spectatorMap.put(spectator.getUniqueId(), playerData); } From 3c8bc94860594b8f6ab92bf09a8b74556ca1fc4c Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 19 May 2026 05:44:48 -0700 Subject: [PATCH 17/26] Game - dont try to stop it if its already stopped --- .../java/com/shanebeestudios/hg/api/game/Game.java | 4 ++++ .../com/shanebeestudios/hg/api/status/Status.java | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/com/shanebeestudios/hg/api/game/Game.java b/src/main/java/com/shanebeestudios/hg/api/game/Game.java index 7d67ded4..af0aa717 100755 --- a/src/main/java/com/shanebeestudios/hg/api/game/Game.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/Game.java @@ -406,6 +406,10 @@ public void stop() { * @param death Whether the game stopped after the result of a death (false = no winnings paid out) */ public void stop(boolean death) { + if (!this.gameArenaData.getStatus().isRunning()) { + // Don't attempt to stop if the game isn't running + return; + } if (Config.WORLD_BORDER_ENABLED) { this.gameBorderData.resetBorder(false); } diff --git a/src/main/java/com/shanebeestudios/hg/api/status/Status.java b/src/main/java/com/shanebeestudios/hg/api/status/Status.java index dde0b858..912c8655 100755 --- a/src/main/java/com/shanebeestudios/hg/api/status/Status.java +++ b/src/main/java/com/shanebeestudios/hg/api/status/Status.java @@ -56,6 +56,18 @@ public boolean isActive() { }; } + /** + * Whether the game is currently running. + * + * @return Is currently running + */ + public boolean isRunning() { + return switch (this) { + case WAITING, COUNTDOWN, FREE_ROAM, RUNNING -> true; + default -> false; + }; + } + public Component getName() { return switch (this) { case READY -> Util.getMini(this.lang.game_status_ready); From c4691dab7bd6e36aa4b1fa9388d2a7a9eea64e56 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 19 May 2026 08:01:35 -0700 Subject: [PATCH 18/26] update Rollback system --- .../com/shanebeestudios/hg/api/game/Game.java | 33 ++++--- .../hg/api/game/GameBlockData.java | 39 +++------ .../hg/api/game/GameRegion.java | 5 +- .../shanebeestudios/hg/api/status/Status.java | 7 +- .../hg/plugin/configs/Config.java | 2 + .../hg/plugin/configs/Language.java | 2 + .../hg/plugin/tasks/PrepareArenaTask.java | 86 +++++++++++++++++++ .../hg/plugin/tasks/RollbackTask.java | 2 +- src/main/resources/config.yml | 4 +- src/main/resources/language.yml | 1 + 10 files changed, 141 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/shanebeestudios/hg/plugin/tasks/PrepareArenaTask.java diff --git a/src/main/java/com/shanebeestudios/hg/api/game/Game.java b/src/main/java/com/shanebeestudios/hg/api/game/Game.java index af0aa717..b30993da 100755 --- a/src/main/java/com/shanebeestudios/hg/api/game/Game.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/Game.java @@ -20,6 +20,7 @@ import com.shanebeestudios.hg.plugin.tasks.GameTimerTask; import com.shanebeestudios.hg.plugin.tasks.MobSpawnerTask; import com.shanebeestudios.hg.plugin.tasks.NearestPlayerCompassTask; +import com.shanebeestudios.hg.plugin.tasks.PrepareArenaTask; import com.shanebeestudios.hg.plugin.tasks.RollbackTask; import com.shanebeestudios.hg.plugin.tasks.StartingTask; import org.bukkit.Bukkit; @@ -45,6 +46,7 @@ public class Game { // Tasks here! private MobSpawnerTask mobSpawnerTask; + private PrepareArenaTask prepareArenaTask; private FreeRoamTask freeRoamTask; private StartingTask startingTask; private GameTimerTask gameTimerTask; @@ -222,17 +224,21 @@ public int getRemainingTime() { } /** - * Initialize the waiting period of the game - *

This will be called when a player first joins

+ * Prepare the arena for the game. + */ + public void prepareArena() { + this.gameArenaData.setStatus(Status.PREPARING); + this.prepareArenaTask = new PrepareArenaTask(this); + } + + /** + * Initialize the waiting period of the game. + *

This will be called when a player first joins.

*/ public void startWaitingPeriod() { this.gameArenaData.setStatus(Status.WAITING); - long start = System.currentTimeMillis(); - int count = this.gameBlockData.logBlocksForRollback(); - this.gameBlockData.setupRandomizedBonusChests(); - long fin = System.currentTimeMillis() - start; - if (Config.SETTINGS_DEBUG) { - Util.log("Logged %,d blocks in %sms for arena %s", count, fin, getGameArenaData().getName()); + if (this.gamePlayerData.getPlayers().size() >= this.gameArenaData.getMinPlayers()) { + startPreGameCountdown(); } } @@ -286,6 +292,7 @@ public void startRunningGame() { * Cancel all active tasks */ public void cancelTasks() { + if (this.prepareArenaTask != null) this.prepareArenaTask.stop(); if (this.startingTask != null) this.startingTask.stop(); if (this.freeRoamTask != null) this.freeRoamTask.stop(); if (this.gameTimerTask != null) this.gameTimerTask.stop(); @@ -369,7 +376,13 @@ public boolean joinGame(Player player, boolean savePreviousLocation) { case READY -> { if (!canJoin(player)) return false; this.gamePlayerData.addPlayerData(player, savePreviousLocation); - startWaitingPeriod(); + prepareArena(); + broadcastJoin(player); + Util.sendPrefixedMessage(player, this.lang.game_joined_waiting_to_teleport.replace("", arenaName)); + } + case PREPARING -> { + if (!canJoin(player)) return false; + this.gamePlayerData.addPlayerData(player, savePreviousLocation); broadcastJoin(player); Util.sendPrefixedMessage(player, this.lang.game_joined_waiting_to_teleport.replace("", arenaName)); } @@ -543,7 +556,7 @@ void updateAfterDeath(Player player, boolean death) { .replace("", this.gameArenaData.getName()) .replace("", player.getName()) + (this.gameArenaData.getMinPlayers() - this.gamePlayerData.getPlayers().size() <= 0 ? "!" : ": " + this.lang.game_waiting_players_to_start - .replace("", String.valueOf((this.gameArenaData.getMinPlayers() - this.gamePlayerData.getPlayers().size()))))); + .replace("", String.valueOf((this.gameArenaData.getMinPlayers() - this.gamePlayerData.getPlayers().size()))))); } this.gameBlockData.updateLobbyBlock(); this.gameScoreboard.updateBoards(); diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GameBlockData.java b/src/main/java/com/shanebeestudios/hg/api/game/GameBlockData.java index 2c1de92b..1d8ef077 100644 --- a/src/main/java/com/shanebeestudios/hg/api/game/GameBlockData.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GameBlockData.java @@ -5,7 +5,6 @@ import com.shanebeestudios.hg.api.util.BlockUtils; import com.shanebeestudios.hg.plugin.configs.Config; import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.BlockType; @@ -32,7 +31,7 @@ public class GameBlockData extends Data { private final Random random = new Random(); private final Map> chests = new HashMap<>(); - private final List blocks = new ArrayList<>(); + private final List blocksToRollback = new ArrayList<>(); private final List randomBonusChests = new ArrayList<>(); private final Map itemFrameData = new HashMap<>(); private final GameLobbyWall gameLobbyWall; @@ -164,24 +163,14 @@ public boolean canBeFilled(Location location) { return true; } - /** - * Log all blocks in an arena for rollback - */ - public int logBlocksForRollback() { - int count = 0; - for (Location location : this.getGame().getGameArenaData().getGameRegion().getBlocks(null)) { - Block block = location.getBlock(); - this.blocks.add(block.getState()); - count++; - if (Config.CHESTS_BONUS_RANDOMIZE_ENABLED && BlockUtils.isBonusBlockReplacement(block)) { - this.randomBonusChests.add(block); - block.setType(Material.AIR); - } - } - return count; + public void logBlockForRollback(Block block) { + this.blocksToRollback.add(block.getState()); + } + + public void logRandomBonusChest(Block block) { + this.randomBonusChests.add(block); } - @SuppressWarnings("UnstableApiUsage") public void setupRandomizedBonusChests() { if (!Config.CHESTS_BONUS_RANDOMIZE_ENABLED) return; @@ -207,14 +196,14 @@ public void setupRandomizedBonusChests() { * rollback at once, which can cause heavy amounts of lag.

*/ public void forceRollback() { - Collections.reverse(blocks); - for (BlockState state : blocks) { + Collections.reverse(this.blocksToRollback); + for (BlockState state : this.blocksToRollback) { state.update(true); } } boolean requiresRollback() { - return !this.blocks.isEmpty() || !this.itemFrameData.isEmpty(); + return !this.blocksToRollback.isEmpty() || !this.itemFrameData.isEmpty(); } /** @@ -241,16 +230,16 @@ public Collection getItemFrameData() { * * @return List of all recorded blocks */ - public List getBlocks() { - Collections.reverse(this.blocks); - return this.blocks; + public List getBlocksToRollback() { + Collections.reverse(this.blocksToRollback); + return this.blocksToRollback; } /** * Clear the current block list */ public void resetBlocks() { - this.blocks.clear(); + this.blocksToRollback.clear(); this.randomBonusChests.clear(); } diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java b/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java index 56f6728b..12cd349d 100755 --- a/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.function.Predicate; /** * Data holder for a {@link Game Game's} bounding box @@ -76,7 +77,7 @@ public boolean isInRegion(Location loc) { * @param type Material type to check * @return ArrayList of locations of all blocks of this type in this bound */ - public List getBlocks(@Nullable Material type) { + public List getBlocks(@Nullable Predicate predicate) { World world = Bukkit.getWorld(this.world); assert world != null; List blockList = new ArrayList<>(); @@ -86,7 +87,7 @@ public List getBlocks(@Nullable Material type) { for (int z = (int) this.boundingBox.getMinZ(); z < this.boundingBox.getMaxZ(); z++) { Block block = world.getBlockAt(x, y, z); - if (type == null || block.getType() == type) { + if (predicate == null || predicate.test(block)) { blockList.add(block.getLocation()); } } diff --git a/src/main/java/com/shanebeestudios/hg/api/status/Status.java b/src/main/java/com/shanebeestudios/hg/api/status/Status.java index 912c8655..28527155 100755 --- a/src/main/java/com/shanebeestudios/hg/api/status/Status.java +++ b/src/main/java/com/shanebeestudios/hg/api/status/Status.java @@ -34,6 +34,10 @@ public enum Status { * Game has stopped */ STOPPED, + /** + * Game is preparing to start + */ + PREPARING, /** * Game is currently rolling back blocks */ @@ -63,7 +67,7 @@ public boolean isActive() { */ public boolean isRunning() { return switch (this) { - case WAITING, COUNTDOWN, FREE_ROAM, RUNNING -> true; + case PREPARING, WAITING, COUNTDOWN, FREE_ROAM, RUNNING -> true; default -> false; }; } @@ -76,6 +80,7 @@ public Component getName() { case FREE_ROAM -> Util.getMini(this.lang.game_status_free_roam); case RUNNING -> Util.getMini(this.lang.game_status_running); case STOPPED -> Util.getMini(this.lang.game_status_stopped); + case PREPARING -> Util.getMini(this.lang.game_status_preparing); case ROLLBACK -> Util.getMini(this.lang.game_status_rollback); case BROKEN -> Util.getMini(this.lang.game_status_broken); case NOT_READY -> Util.getMini(this.lang.game_status_not_ready); diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java index 5afa818a..20b83d36 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java @@ -64,6 +64,7 @@ public class Config { public static List REWARD_MESSAGES; //Rollback + public static boolean ROLLBACK_ENABLED; public static boolean ROLLBACK_ALLOW_BREAK_BLOCKS; public static int ROLLBACK_BLOCKS_PER_SECOND; public static boolean ROLLBACK_PROTECT_DURING_FREE_ROAM; @@ -180,6 +181,7 @@ private void loadConfig() { REWARD_MESSAGES = config.getStringList("reward.messages"); // Rollback + ROLLBACK_ENABLED = config.getBoolean("rollback.enabled"); ROLLBACK_ALLOW_BREAK_BLOCKS = config.getBoolean("rollback.allow-block-break"); ROLLBACK_BLOCKS_PER_SECOND = config.getInt("rollback.blocks-per-second"); ROLLBACK_PROTECT_DURING_FREE_ROAM = config.getBoolean("rollback.protect-during-free-roam"); diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/Language.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/Language.java index 1be05907..d18219ad 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/configs/Language.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/Language.java @@ -237,6 +237,7 @@ public class Language { public String game_status_not_ready; public String game_status_free_roam; public String game_status_countdown; + public String game_status_preparing; public String player_status_in_game; public String player_status_spectator; public String player_status_not_in_game; @@ -529,6 +530,7 @@ private void loadLang() { this.game_status_not_ready = this.lang.getString("status.game-status.not-ready"); this.game_status_free_roam = this.lang.getString("status.game-status.free-roam"); this.game_status_countdown = this.lang.getString("status.game-status.countdown"); + this.game_status_preparing = this.lang.getString("status.game-status.preparing"); this.player_status_in_game = this.lang.getString("status.player-status.in-game"); this.player_status_spectator = this.lang.getString("status.player-status.spectator"); this.player_status_not_in_game = this.lang.getString("status.player-status.not-in-game"); diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/PrepareArenaTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/PrepareArenaTask.java new file mode 100644 index 00000000..c67b68fa --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/PrepareArenaTask.java @@ -0,0 +1,86 @@ +package com.shanebeestudios.hg.plugin.tasks; + +import com.shanebeestudios.hg.api.game.Game; +import com.shanebeestudios.hg.api.util.BlockUtils; +import com.shanebeestudios.hg.api.util.Util; +import com.shanebeestudios.hg.plugin.HungerGames; +import com.shanebeestudios.hg.plugin.configs.Config; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; + +import java.util.Iterator; +import java.util.List; + +public class PrepareArenaTask implements Runnable { + + private final Game game; + private final int blocksPerTick; + private Iterator blocksToLog; + private final int countToLog; + private int totalLogged = 0; + private int taskId; + + public PrepareArenaTask(Game game) { + this.game = game; + this.blocksPerTick = Config.ROLLBACK_BLOCKS_PER_SECOND / 20; + + Util.debug("Starting prepare task, grabbing blocks to log..."); + List blocks; + if (Config.ROLLBACK_ENABLED) { + blocks = this.game.getGameArenaData().getGameRegion().getBlocks(null); + } else if (Config.CHESTS_BONUS_RANDOMIZE_ENABLED) { + blocks = this.game.getGameArenaData().getGameRegion().getBlocks(BlockUtils::isBonusBlockReplacement); + } else { + blocks = List.of(); + } + this.countToLog = blocks.size(); + this.blocksToLog = blocks.iterator(); + Util.debug("Found %s blocks to log", this.countToLog); + this.taskId = schedule(); + } + + @Override + public void run() { + int logged = 0; + + while (logged < this.blocksPerTick && this.blocksToLog.hasNext()) { + if (this.taskId == -1) { + // Task has been stopped + return; + } + Block block = this.blocksToLog.next().getBlock(); + this.game.getGameBlockData().logBlockForRollback(block); + logged++; + this.totalLogged++; + + if (Config.CHESTS_BONUS_RANDOMIZE_ENABLED && BlockUtils.isBonusBlockReplacement(block)) { + this.game.getGameBlockData().logRandomBonusChest(block); + block.setType(Material.AIR); + } + } + if (this.blocksToLog.hasNext()) { + Util.debug("Logging blocks... %,d/%,d", this.totalLogged, this.countToLog); + this.taskId = this.schedule(); + return; + } + + Util.debug("Finished logging blocks and setting up bonus chests"); + this.blocksToLog = null; + this.game.getGameBlockData().setupRandomizedBonusChests(); + Bukkit.getScheduler().cancelTask(this.taskId); + this.game.startWaitingPeriod(); + } + + public void stop() { + Util.debug("Cancelling prepare task"); + Bukkit.getScheduler().cancelTask(this.taskId); + this.taskId = -1; + } + + private int schedule() { + return Bukkit.getScheduler().scheduleSyncDelayedTask(HungerGames.getPlugin(), this, 1); + } + +} diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/RollbackTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/RollbackTask.java index a01f22ad..f12c8f2f 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/tasks/RollbackTask.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/RollbackTask.java @@ -28,7 +28,7 @@ public RollbackTask(Game game) { this.gameBlockData = game.getGameBlockData(); this.blocks_per_tick = Config.ROLLBACK_BLOCKS_PER_SECOND / 20; game.getGameArenaData().setStatus(Status.ROLLBACK); - this.blockRollbackSession = this.gameBlockData.getBlocks().iterator(); + this.blockRollbackSession = this.gameBlockData.getBlocksToRollback().iterator(); this.itemFrameDataIterator = this.gameBlockData.getItemFrameData().iterator(); this.taskId = Bukkit.getScheduler().scheduleSyncDelayedTask(HungerGames.getPlugin(), this, 20); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c46e5b27..c30df3e8 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -157,12 +157,14 @@ chests: min-content: 1 max-content: 5 rollback: + # Whether to enable the rollback system + enabled: true # Let players break blocks in the arena, and restore when the game is done allow-block-break: false # Allow players to take items out of item frames (will be rolled back) allow-itemframe-take: false # The amount of blocks that will be restored during rollback per second (this number is actually divided by 20 and restored in 1 tick intervals) - blocks-per-second: 500 + blocks-per-second: 500000 # When enabled, will not allow players to break blocks during the free-roam time protect-during-free-roam: true # Prevent players from trampling crops in your arenas diff --git a/src/main/resources/language.yml b/src/main/resources/language.yml index 7bd59a6b..03fe8de2 100644 --- a/src/main/resources/language.yml +++ b/src/main/resources/language.yml @@ -341,6 +341,7 @@ status: running: 'Running' stopped: 'Stopped' rollback: 'Restoring...' + preparing: 'Preparing...' broken: 'BROKEN' not-ready: 'NotReady' player-status: From f021f3ed47392f44f9ee337dec41b892b4e6eee8 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 19 May 2026 08:03:51 -0700 Subject: [PATCH 19/26] config - update some links --- src/main/resources/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c30df3e8..c8a788d8 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,13 +2,13 @@ # Revamped by: ShaneBee # Issues: Please report issues on the GitHub issue tracker @ https://github.com/ShaneBeeStudios/HungerGames/issues -# Updates: Can be found on SpigotMC @ --- https://www.spigotmc.org/resources/hungergames.65942/ +# Updates: Can be found on Modrinth @ --- https://modrinth.com/plugin/hungergames-sb # More Info: See the wiki for more info on this config @ https://github.com/ShaneBeeStudios/HungerGames/wiki/Config.yml # Helpful Links: -# Bukkit Material Enums: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html -# Bukkit Tags: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Tag.html +# Bukkit Material Enums: https://jd.papermc.io/paper/26.1.2/org/bukkit/Material.html +# Bukkit Tags: https://jd.papermc.io/paper/26.1.2/org/bukkit/Tag.html settings: # When enabled, more informative debug messages will be sent when errors occur From abfa0c9a79c342c67e1a333b4e80fb1cd49ff544 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 19 May 2026 09:21:06 -0700 Subject: [PATCH 20/26] ItemParser - clamp stack size --- .../java/com/shanebeestudios/hg/api/parsers/ItemParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java b/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java index 1efaf5ee..ceb8ec6c 100644 --- a/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java +++ b/src/main/java/com/shanebeestudios/hg/api/parsers/ItemParser.java @@ -47,7 +47,7 @@ public class ItemParser { // COUNT int count = 1; if (config.contains("count")) { - count = config.getInt("count"); + count = Math.clamp(config.getInt("count"), 1, 99); } ItemStack itemStack = itemType.createItemStack(count); From 15fbe45121bbcbc63c311d86624cc57e40ee5c93 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 19 May 2026 10:25:28 -0700 Subject: [PATCH 21/26] Location handling - now properly using locations and world keys --- .../hg/api/game/GameRegion.java | 54 ++++++++++---- .../hg/api/parsers/LocationParser.java | 26 +------ .../hg/plugin/configs/ArenaConfig.java | 74 +++++++++++++------ .../hg/plugin/configs/Config.java | 14 ++-- 4 files changed, 102 insertions(+), 66 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java b/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java index 12cd349d..a7208040 100755 --- a/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java @@ -3,7 +3,7 @@ import org.bukkit.Bukkit; import org.bukkit.HeightMap; import org.bukkit.Location; -import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.util.BoundingBox; @@ -22,25 +22,43 @@ public class GameRegion { private final BoundingBox boundingBox; - private final String world; + private NamespacedKey key; + private String name; public static GameRegion createNew(@NotNull Block corner1, @NotNull Block corner2) { BoundingBox boundingBox = BoundingBox.of(corner1, corner2); - return new GameRegion(corner1.getWorld().getName(), boundingBox); + return new GameRegion(corner1.getWorld().getKey(), boundingBox); } - public static GameRegion loadFromConfig(String world, BoundingBox boundingBox) { - return new GameRegion(world, boundingBox); + public static GameRegion loadFromConfig(NamespacedKey key, BoundingBox boundingBox) { + return new GameRegion(key, boundingBox); + } + + + @Deprecated(forRemoval = true, since = "INSERT VERSION") + public static GameRegion loadFromConfig(String name, BoundingBox boundingBox) { + World world = Bukkit.getWorld(name); + if (world == null) { + return new GameRegion(name, boundingBox); + } + return new GameRegion(world.getKey(), boundingBox); } /** * Create a new bounding box between 2 sets of coordinates * - * @param world World this bound is in + * @param key World this bound is in * @param boundingBox BoundingBox for this bound */ - private GameRegion(String world, BoundingBox boundingBox) { - this.world = world; + private GameRegion(NamespacedKey key, BoundingBox boundingBox) { + this.key = key; + this.boundingBox = boundingBox; + } + + @Deprecated(forRemoval = true, since = "INSERT VERSION") + private GameRegion(String name, BoundingBox boundingBox) { + this.key = null; + this.name = name; this.boundingBox = boundingBox; } @@ -74,11 +92,11 @@ public boolean isInRegion(Location loc) { /** * Get location of all blocks of a type within a bound * - * @param type Material type to check + * @param predicate Material predicate to check * @return ArrayList of locations of all blocks of this type in this bound */ public List getBlocks(@Nullable Predicate predicate) { - World world = Bukkit.getWorld(this.world); + World world = getWorld(); assert world != null; List blockList = new ArrayList<>(); @@ -101,8 +119,17 @@ public List getBlocks(@Nullable Predicate predicate) { * * @return World of this bound */ - public World getWorld() { - return Bukkit.getWorld(this.world); + public @Nullable World getWorld() { + if (this.key != null) { + return Bukkit.getWorld(this.key); + } else if (this.name != null) { + World world = Bukkit.getWorld(this.name); + if (world != null) { + this.key = world.getKey(); + return world; + } + } + return null; } public BoundingBox getBoundingBox() { @@ -138,9 +165,10 @@ public Location getCenter() { @Override public String toString() { + String name = this.key != null ? this.key.toString() : this.name != null ? this.name : "unavailable"; return "Bound{" + "boundingBox=" + boundingBox + - ", world='" + world + '\'' + + ", world='" + name + '\'' + '}'; } diff --git a/src/main/java/com/shanebeestudios/hg/api/parsers/LocationParser.java b/src/main/java/com/shanebeestudios/hg/api/parsers/LocationParser.java index df8bff70..a19f4d41 100644 --- a/src/main/java/com/shanebeestudios/hg/api/parsers/LocationParser.java +++ b/src/main/java/com/shanebeestudios/hg/api/parsers/LocationParser.java @@ -8,30 +8,9 @@ /** * Parser for {@link Location Locations} */ +@Deprecated(forRemoval = true, since = "INSERT VERSION") public class LocationParser { - /** - * Serialize a block location to a string - * - * @param location Block location to serialize - * @return Serialized block location - */ - public static String blockLocToString(Location location) { - return location.getWorld().getName() + ":" + location.getBlockX() + ":" + location.getBlockY() + ":" + location.getBlockZ(); - } - - /** - * Serialize a location to a string - * - * @param location Location to serialize - * @return Serialized location - */ - public static String locToString(Location location) { - float yaw = (float) Math.floor(location.getYaw()); - float pitch = (float) Math.floor(location.getPitch()); - return location.getWorld().getName() + ":" + location.getX() + ":" + location.getY() + ":" + location.getZ() + ":" + yaw + ":" + pitch; - } - /** * Deserialize a block location from a string * @@ -49,7 +28,8 @@ public static Location getBlockLocFromString(String stringLocation) { * @param stringLocation String to deserialize * @return Location from string */ - public static @Nullable Location getLocFromString(String stringLocation) { + public static @Nullable Location getLocFromString(@Nullable String stringLocation) { + if (stringLocation == null) return null; String[] split = stringLocation.split(":"); if (split.length < 4) return null; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/ArenaConfig.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/ArenaConfig.java index 0db37f11..2598301e 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/configs/ArenaConfig.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/ArenaConfig.java @@ -12,6 +12,7 @@ import com.shanebeestudios.hg.plugin.managers.GameManager; import org.bukkit.Difficulty; import org.bukkit.Location; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; @@ -145,7 +146,12 @@ public boolean loadArena(FileConfiguration arenaConfig, String arenaName) { // LOCATIONS ConfigurationSection locationsSection = arenaConfig.getConfigurationSection("locations"); try { - lobbysign = LocationParser.getBlockLocFromString(locationsSection.getString("lobby_sign")); + if (locationsSection.isString("lobby_sign")) { + lobbysign = LocationParser.getBlockLocFromString(locationsSection.getString("lobby_sign")); + isDirty = true; + } else { + lobbysign = locationsSection.getLocation("lobby_sign"); + } } catch (Exception e) { Util.warning("Unable to load lobby sign for arena '" + arenaName + "'!"); Util.debug(e); @@ -153,8 +159,16 @@ public boolean loadArena(FileConfiguration arenaConfig, String arenaName) { } try { - for (String location : locationsSection.getStringList("spawns")) { - spawns.add(LocationParser.getLocFromString(location)); + for (Object object : locationsSection.getList("spawns")) { + if (object instanceof String s) { + Location locFromString = LocationParser.getLocFromString(s); + if (locFromString != null) { + spawns.add(locFromString); + isDirty = true; + } + } else if (object instanceof Location location) { + spawns.add(location); + } } } catch (Exception e) { Util.warning("Unable to load random spawns for arena '" + arenaName + "'!"); @@ -164,9 +178,16 @@ public boolean loadArena(FileConfiguration arenaConfig, String arenaName) { // REGION try { ConfigurationSection regionSection = arenaConfig.getConfigurationSection("region"); - String world = regionSection.getString("world"); BoundingBox boundingBox = regionSection.getObject("bounding_box", BoundingBox.class); - gameRegion = GameRegion.loadFromConfig(world, boundingBox); + + if (regionSection.isSet("world")) { + String world = regionSection.getString("world"); + gameRegion = GameRegion.loadFromConfig(world, boundingBox); + isDirty = true; + } else if (regionSection.isSet("world_key")) { + String worldKey = regionSection.getString("world_key"); + gameRegion = GameRegion.loadFromConfig(NamespacedKey.fromString(worldKey), boundingBox); + } } catch (Exception e) { Util.warning("Unable to load region bounds for arena " + arenaName + "!"); isReady = false; @@ -195,11 +216,16 @@ public boolean loadArena(FileConfiguration arenaConfig, String arenaName) { GameBorderData gameBorderData = game.getGameBorderData(); if (borderSection.isSet("center_locations")) { - List centerLocations = borderSection.getStringList("center_locations"); + List centerLocations = borderSection.getList("center_locations"); List centerLocationList = new ArrayList<>(); - for (String locString : centerLocations) { - Location centerLocation = LocationParser.getBlockLocFromString(locString); - centerLocationList.add(centerLocation); + for (Object object : centerLocations) { + if (object instanceof String locString) { + Location centerLocation = LocationParser.getBlockLocFromString(locString); + centerLocationList.add(centerLocation); + isDirty = true; + } else if (object instanceof Location location) { + centerLocationList.add(location); + } } gameBorderData.setCenterLocations(centerLocationList); } else if (borderSection.isSet("center_location")) { // Deprecated (May 5/2026) @@ -242,8 +268,16 @@ public boolean loadArena(FileConfiguration arenaConfig, String arenaName) { } try { if (locationsSection.isSet("exit")) { - Location exitLocation = LocationParser.getLocFromString(locationsSection.getString("exit")); - gameArenaData.setExitLocation(exitLocation); + if (locationsSection.isString("exit")) { + Location exitLocation = LocationParser.getLocFromString(locationsSection.getString("exit")); + if (exitLocation != null) { + gameArenaData.setExitLocation(exitLocation); + isDirty = true; + } + } else if (locationsSection.isLocation("exit")) { + Location exitLocation = locationsSection.getLocation("exit"); + gameArenaData.setExitLocation(exitLocation); + } } } catch (Exception exception) { @@ -293,7 +327,7 @@ public void saveGameToConfig(Game game) { // REGION ConfigurationSection regionSection = gameSection.createSection("region"); - regionSection.set("world", gameArenaData.getGameRegion().getWorld().getName()); + regionSection.set("world_key", gameArenaData.getGameRegion().getWorld().getKey().toString()); regionSection.set("bounding_box", gameArenaData.getGameRegion().getBoundingBox()); // COMMANDS @@ -301,27 +335,19 @@ public void saveGameToConfig(Game game) { // LOCATIONS ConfigurationSection locationsSection = gameSection.createSection("locations"); - List spawns = new ArrayList<>(); - gameArenaData.getSpawns().forEach(spawn -> spawns.add(LocationParser.locToString(spawn))); - locationsSection.set("spawns", spawns); + locationsSection.set("spawns", gameArenaData.getSpawns()); Location exit = gameArenaData.getExitLocation(); if (exit != null) { - locationsSection.set("exit", LocationParser.blockLocToString(exit)); + locationsSection.set("exit", exit); } - locationsSection.set("lobby_sign", LocationParser.blockLocToString(game.getLobbyLocation())); + locationsSection.set("lobby_sign", game.getLobbyLocation()); // BORDER GameBorderData borderData = game.getGameBorderData(); if (!borderData.isDefault()) { ConfigurationSection borderSection = gameSection.createSection("game_border"); - List centerLocations = new ArrayList<>(); - for (Location centerLocation : borderData.getCenterLocations()) { - String locString = LocationParser.blockLocToString(centerLocation); - centerLocations.add(locString); - - } - borderSection.set("center_locations", centerLocations); + borderSection.set("center_locations", borderData.getCenterLocations()); borderSection.set("final_size", borderData.getFinalBorderSize()); borderSection.set("countdown_start", borderData.getBorderCountdownStart()); borderSection.set("countdown_end", borderData.getBorderCountdownEnd()); diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java index 20b83d36..abd239a0 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java @@ -148,9 +148,12 @@ private void loadConfig() { SETTINGS_SAVE_PREVIOUS_LOCATION = config.getBoolean("settings.save-previous-location"); SETTINGS_TELEPORT_AT_END_TIME = config.getInt("settings.teleport-at-end-time"); SETTINGS_FREE_ROAM_TIME = config.getInt("settings.free-room-time"); - String locString = config.getString("settings.global-exit-location"); - if (locString != null && locString.contains(":")) { - SETTINGS_GLOBAL_EXIT_LOCATION = LocationParser.getLocFromString(locString); + + if (this.config.isString("settings.global-exit-location")) { + SETTINGS_GLOBAL_EXIT_LOCATION = LocationParser.getLocFromString(this.config.getString("settings.global-exit-location")); + setGlobalExitLocation(SETTINGS_GLOBAL_EXIT_LOCATION); + } else if (this.config.isLocation("settings.global-exit-location")) { + SETTINGS_GLOBAL_EXIT_LOCATION = this.config.getLocation("settings.global-exit-location"); } // Player Tracking @@ -286,8 +289,7 @@ public void save() { * @param location Global exit location */ public void setGlobalExitLocation(Location location) { - String locString = LocationParser.locToString(location); - this.config.set("settings.global-exit-location", locString); + this.config.set("settings.global-exit-location", location); save(); } @@ -311,7 +313,7 @@ public void setGlobalExitLocation(Location location) { int r = Integer.parseInt(split[0]); int g = Integer.parseInt(split[1]); int b = Integer.parseInt(split[2]); - return Color.fromRGB(r,g,b); + return Color.fromRGB(r, g, b); } catch (NumberFormatException ignore) { Util.log("Invalid color '%s' for setting '%s'", string, setting); return null; From 9da353f177e22a22a941d342a5185db8d987b083 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Wed, 20 May 2026 14:44:44 -0700 Subject: [PATCH 22/26] Util - remove old deprecated method --- .../com/shanebeestudios/hg/api/util/Util.java | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/util/Util.java b/src/main/java/com/shanebeestudios/hg/api/util/Util.java index 2a65ec8c..dbdea06f 100755 --- a/src/main/java/com/shanebeestudios/hg/api/util/Util.java +++ b/src/main/java/com/shanebeestudios/hg/api/util/Util.java @@ -6,7 +6,6 @@ import dev.jorel.commandapi.arguments.CustomArgument.CustomArgumentException; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; -import net.md_5.bungee.api.ChatColor; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.block.BlockFace; @@ -15,9 +14,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * Generalized utility class for shortcut methods */ @@ -29,7 +25,6 @@ public class Util { public static final boolean IS_RUNNING_FOLIA = classExists("io.papermc.paper.threadedregions.FoliaWatchdogThread"); // PRIVATE - private static final Pattern HEX_PATTERN = Pattern.compile("<#([A-Fa-f0-9]){6}>"); private static final CommandSender CONSOLE = Bukkit.getConsoleSender(); private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage(); private static String PREFIX; @@ -150,27 +145,6 @@ public static void broadcast(String s) { } } - /** - * Shortcut for adding color to a string - * - * @param string String including color codes - * @return Formatted string - */ - @Deprecated(forRemoval = true) - public static String getColString(String string) { - if (isRunningMinecraft(1, 16)) { - Matcher matcher = HEX_PATTERN.matcher(string); - while (matcher.find()) { - final ChatColor hexColor = ChatColor.of(matcher.group().substring(1, matcher.group().length() - 1)); - final String before = string.substring(0, matcher.start()); - final String after = string.substring(matcher.end()); - string = before + hexColor + after; - matcher = HEX_PATTERN.matcher(string); - } - } - return ChatColor.translateAlternateColorCodes('&', string); - } - /** * Convert text to MiniMessage * From d1495de29b4d46449a38c3c35a31235ed61c1897 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Wed, 20 May 2026 15:24:47 -0700 Subject: [PATCH 23/26] Util - version check updates --- .../com/shanebeestudios/hg/api/util/Util.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/shanebeestudios/hg/api/util/Util.java b/src/main/java/com/shanebeestudios/hg/api/util/Util.java index dbdea06f..788907bf 100755 --- a/src/main/java/com/shanebeestudios/hg/api/util/Util.java +++ b/src/main/java/com/shanebeestudios/hg/api/util/Util.java @@ -215,26 +215,26 @@ public static void clearInv(Player player) { } /** - * Check if server is running a minimum Minecraft version + * Check if the server is running a minimum Minecraft version. * - * @param major Major version to check (Most likely just going to be 1) - * @param minor Minor version to check - * @return True if running this version or higher + * @param year Year version to check + * @param drop Drop version to check + * @return True if running this version or higher, otherwise false */ - public static boolean isRunningMinecraft(int major, int minor) { - return isRunningMinecraft(major, minor, 0); + public static boolean isRunningMinecraft(int year, int drop) { + return isRunningMinecraft(year, drop, 0); } /** - * Check if server is running a minimum Minecraft version + * Check if the server is running a minimum Minecraft version. * - * @param major Major version to check (Most likely just going to be 1) - * @param minor Minor version to check + * @param year Year version to check + * @param drop Drop version to check * @param revision Revision to check - * @return True if running this version or higher + * @return True if running this version or higher, otherwise false */ - public static boolean isRunningMinecraft(int major, int minor, int revision) { - String[] version = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\."); + public static boolean isRunningMinecraft(int year, int drop, int revision) { + String[] version = Bukkit.getServer().getMinecraftVersion().split("\\."); int maj = Integer.parseInt(version[0]); int min = Integer.parseInt(version[1]); int rev; @@ -243,7 +243,13 @@ public static boolean isRunningMinecraft(int major, int minor, int revision) { } catch (Exception ignore) { rev = 0; } - return maj > major || min > minor || (min == minor && rev >= revision); + if (maj > year) { + return true; + } else if (maj == year && min > drop) { + return true; + } else { + return maj == year && min == drop && rev >= revision; + } } /** From 20f336e84440004a4994042925af2d071b16d27d Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Wed, 20 May 2026 15:25:05 -0700 Subject: [PATCH 24/26] Javadoc updates --- build.gradle.kts | 1 + .../hg/api/game/GameRegion.java | 10 +++- .../hg/api/parsers/package-info.java | 4 ++ .../hg/api/region/package-info.java | 4 ++ .../hg/api/region/scheduler/package-info.java | 2 +- .../region/scheduler/task/package-info.java | 4 ++ .../hg/plugin/HungerGames.java | 52 +++++++++++++------ .../hg/plugin/configs/package-info.java | 4 ++ .../hg/plugin/permission/Permissions.java | 3 ++ .../hg/plugin/permission/package-info.java | 4 ++ .../plugin/tasks/ChestRefillRepeatTask.java | 3 ++ .../hg/plugin/tasks/FreeRoamTask.java | 3 ++ .../hg/plugin/tasks/GameTimerTask.java | 3 ++ .../hg/plugin/tasks/MobSpawnerTask.java | 3 ++ .../tasks/NearestPlayerCompassTask.java | 3 ++ .../hg/plugin/tasks/PrepareArenaTask.java | 3 ++ .../hg/plugin/tasks/StartingTask.java | 3 ++ .../hg/plugin/tasks/WorldBorderTask.java | 4 +- .../hg/plugin/tasks/package-info.java | 4 ++ 19 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/shanebeestudios/hg/api/parsers/package-info.java create mode 100644 src/main/java/com/shanebeestudios/hg/api/region/package-info.java create mode 100644 src/main/java/com/shanebeestudios/hg/api/region/scheduler/task/package-info.java create mode 100644 src/main/java/com/shanebeestudios/hg/plugin/configs/package-info.java create mode 100644 src/main/java/com/shanebeestudios/hg/plugin/permission/package-info.java create mode 100644 src/main/java/com/shanebeestudios/hg/plugin/tasks/package-info.java diff --git a/build.gradle.kts b/build.gradle.kts index b902b165..ba9eecb6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -107,6 +107,7 @@ tasks { exclude("com/shanebeestudios/hg/plugin/commands") exclude("com/shanebeestudios/hg/plugin/listeners") options.links( + "https://javadoc.io/doc/org.jetbrains/annotations/latest/", "https://jd.papermc.io/paper/26.1.2/", "https://jd.advntr.dev/api/4.25.0/", "https://tr7zw.github.io/Item-NBT-API/v2-api/" diff --git a/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java b/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java index a7208040..2994235b 100755 --- a/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java +++ b/src/main/java/com/shanebeestudios/hg/api/game/GameRegion.java @@ -7,6 +7,7 @@ import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.util.BoundingBox; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,11 +31,18 @@ public static GameRegion createNew(@NotNull Block corner1, @NotNull Block corner return new GameRegion(corner1.getWorld().getKey(), boundingBox); } + /** + * @hidden + */ + @ApiStatus.Internal public static GameRegion loadFromConfig(NamespacedKey key, BoundingBox boundingBox) { return new GameRegion(key, boundingBox); } - + /** + * @hidden + */ + @ApiStatus.Internal @Deprecated(forRemoval = true, since = "INSERT VERSION") public static GameRegion loadFromConfig(String name, BoundingBox boundingBox) { World world = Bukkit.getWorld(name); diff --git a/src/main/java/com/shanebeestudios/hg/api/parsers/package-info.java b/src/main/java/com/shanebeestudios/hg/api/parsers/package-info.java new file mode 100644 index 00000000..f44303f4 --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/api/parsers/package-info.java @@ -0,0 +1,4 @@ +/** + * Utility classes for parsing data + */ +package com.shanebeestudios.hg.api.parsers; diff --git a/src/main/java/com/shanebeestudios/hg/api/region/package-info.java b/src/main/java/com/shanebeestudios/hg/api/region/package-info.java new file mode 100644 index 00000000..38470ce4 --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/api/region/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes related to region based scheduling + */ +package com.shanebeestudios.hg.api.region; diff --git a/src/main/java/com/shanebeestudios/hg/api/region/scheduler/package-info.java b/src/main/java/com/shanebeestudios/hg/api/region/scheduler/package-info.java index 233997b9..5c3fb248 100644 --- a/src/main/java/com/shanebeestudios/hg/api/region/scheduler/package-info.java +++ b/src/main/java/com/shanebeestudios/hg/api/region/scheduler/package-info.java @@ -1,5 +1,5 @@ /** - * Utility classes for Schedulers + * Schedulers for scheduling tasks *

Things will differ based on Spigot/Paper vs. Folia servers

*/ package com.shanebeestudios.hg.api.region.scheduler; diff --git a/src/main/java/com/shanebeestudios/hg/api/region/scheduler/task/package-info.java b/src/main/java/com/shanebeestudios/hg/api/region/scheduler/task/package-info.java new file mode 100644 index 00000000..fc64f1e8 --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/api/region/scheduler/task/package-info.java @@ -0,0 +1,4 @@ +/** + * Tasks based on server software + */ +package com.shanebeestudios.hg.api.region.scheduler.task; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/HungerGames.java b/src/main/java/com/shanebeestudios/hg/plugin/HungerGames.java index 90854089..f205b940 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/HungerGames.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/HungerGames.java @@ -38,12 +38,13 @@ import org.bukkit.event.HandlerList; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.ApiStatus; import java.util.HashMap; import java.util.Map; /** - * Main class for HungerGames + * Main class for HungerGames */ public class HungerGames extends JavaPlugin { @@ -83,6 +84,9 @@ public void onLoad() { } } + /** + * @hidden + */ @Override public void onEnable() { if (!Util.isRunningMinecraft(1, 21, 10)) { @@ -96,6 +100,9 @@ public void onEnable() { loadPlugin(true); } + /** + * @hidden + */ @SuppressWarnings("deprecation") public void loadPlugin(boolean load) { long start = System.currentTimeMillis(); @@ -142,6 +149,13 @@ public void loadPlugin(boolean load) { Util.log("HungerGames has been enabled in %.2f seconds!", (float) (System.currentTimeMillis() - start) / 1000); } + /** + * Reload the plugin, unloading and reloading all components. + *

+ * This should not be used externally. + *

+ */ + @ApiStatus.Internal public void reloadPlugin() { unloadPlugin(true); } @@ -168,6 +182,9 @@ private void unloadPlugin(boolean reload) { } } + /** + * @hidden + */ @Override public void onDisable() { // I know this seems odd, but this method just @@ -216,7 +233,7 @@ private void loadCommands() { } /** - * Get the instance of this plugin + * Get the instance of this plugin. * * @return This plugin */ @@ -225,7 +242,7 @@ public static HungerGames getPlugin() { } /** - * Get an instance of the KillManager + * Get an instance of the KillManager. * * @return KillManager */ @@ -234,7 +251,7 @@ public KillManager getKillManager() { } /** - * Get an instance of the plugins main item manager + * Get an instance of the ItemManager. * * @return The item manager */ @@ -243,7 +260,7 @@ public ItemManager getItemManager() { } /** - * Get an instance of the plugins main kit manager + * Get an instance of the KitManager. * * @return The kit manager */ @@ -252,7 +269,7 @@ public KitManager getKitManager() { } /** - * Get the instance of the game manager + * Get the instance of the game manager. * * @return The game manager */ @@ -261,7 +278,7 @@ public GameManager getGameManager() { } /** - * Get an instance of the PlayerManager + * Get an instance of the PlayerManager. * * @return PlayerManager */ @@ -270,7 +287,7 @@ public PlayerManager getPlayerManager() { } /** - * Get an instance of the ArenaConfig + * Get an instance of the ArenaConfig. * * @return ArenaConfig */ @@ -279,7 +296,7 @@ public ArenaConfig getArenaConfig() { } /** - * Get an instance of HG's leaderboards + * Get an instance of HG's leaderboards. * * @return Leaderboard */ @@ -288,7 +305,7 @@ public Leaderboard getLeaderboard() { } /** - * Get an instance of the language file + * Get an instance of the language file. * * @return Language file */ @@ -297,7 +314,7 @@ public Language getLang() { } /** - * Get an instance of {@link Config} + * Get an instance of {@link Config}. * * @return Config file */ @@ -306,7 +323,7 @@ public Config getHGConfig() { } /** - * Get an instance of the MobManager + * Get an instance of the MobManager. * * @return MobManager */ @@ -314,12 +331,8 @@ public MobManager getMobManager() { return this.mobManager; } - public Metrics getMetrics() { - return this.metrics; - } - /** - * Get an instance of the MythicMobs MobManager + * Get an instance of the MythicMobs MobManager. * * @return MythicMobs MobManager */ @@ -327,6 +340,11 @@ public io.lumine.mythic.api.mobs.MobManager getMythicMobManager() { return this.mythicMobManager; } + /** + * Get an instance of the SessionManager. + * + * @return Instance of SessionManager + */ // Managers public SessionManager getSessionManager() { return this.sessionManager; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/package-info.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/package-info.java new file mode 100644 index 00000000..b794bc82 --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/package-info.java @@ -0,0 +1,4 @@ +/** + * Configs related to the plugin + */ +package com.shanebeestudios.hg.plugin.configs; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/permission/Permissions.java b/src/main/java/com/shanebeestudios/hg/plugin/permission/Permissions.java index f8c0bba9..d6a5f47c 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/permission/Permissions.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/permission/Permissions.java @@ -9,6 +9,9 @@ import java.util.LinkedHashMap; import java.util.Map; +/** + * Permissions for players + */ public class Permissions { public record Permission(String permission, org.bukkit.permissions.Permission bukkitPermission) { diff --git a/src/main/java/com/shanebeestudios/hg/plugin/permission/package-info.java b/src/main/java/com/shanebeestudios/hg/plugin/permission/package-info.java new file mode 100644 index 00000000..d0236cb7 --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/plugin/permission/package-info.java @@ -0,0 +1,4 @@ +/** + * Permission related classes for the plugin + */ +package com.shanebeestudios.hg.plugin.permission; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/ChestRefillRepeatTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/ChestRefillRepeatTask.java index 3da31896..cec4a840 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/tasks/ChestRefillRepeatTask.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/ChestRefillRepeatTask.java @@ -8,6 +8,9 @@ import com.shanebeestudios.hg.plugin.configs.Language; import org.bukkit.Bukkit; +/** + * Task for handling refills of chests in a game + */ public class ChestRefillRepeatTask implements Runnable { private final Language lang; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/FreeRoamTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/FreeRoamTask.java index 63ccf862..c14c15a4 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/tasks/FreeRoamTask.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/FreeRoamTask.java @@ -8,6 +8,9 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; +/** + * Task for allowing game players to free roam + */ public class FreeRoamTask implements Runnable { private final Game game; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/GameTimerTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/GameTimerTask.java index efe7cf72..122c3449 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/tasks/GameTimerTask.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/GameTimerTask.java @@ -10,6 +10,9 @@ import com.shanebeestudios.hg.plugin.configs.Language; import org.bukkit.Bukkit; +/** + * Timer task for games + */ public class GameTimerTask implements Runnable { private int remainingTime; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/MobSpawnerTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/MobSpawnerTask.java index acada2aa..6c4ffc14 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/tasks/MobSpawnerTask.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/MobSpawnerTask.java @@ -14,6 +14,9 @@ import java.util.Random; +/** + * Task for spawning mobs in the game arena + */ public class MobSpawnerTask implements Runnable { private final GamePlayerData gamePlayerData; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/NearestPlayerCompassTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/NearestPlayerCompassTask.java index e14766d4..2b89a11f 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/tasks/NearestPlayerCompassTask.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/NearestPlayerCompassTask.java @@ -11,6 +11,9 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; +/** + * Task for handling nearest player compass functionality + */ public class NearestPlayerCompassTask implements Runnable { private final Game game; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/PrepareArenaTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/PrepareArenaTask.java index c67b68fa..877e6f02 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/tasks/PrepareArenaTask.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/PrepareArenaTask.java @@ -13,6 +13,9 @@ import java.util.Iterator; import java.util.List; +/** + * Task for preparing an arena by logging blocks for rollback + */ public class PrepareArenaTask implements Runnable { private final Game game; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/StartingTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/StartingTask.java index 1b412e99..5e95932c 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/tasks/StartingTask.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/StartingTask.java @@ -6,6 +6,9 @@ import com.shanebeestudios.hg.plugin.configs.Language; import org.bukkit.Bukkit; +/** + * Task for counting down before game starts + */ public class StartingTask implements Runnable { private int timer; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/WorldBorderTask.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/WorldBorderTask.java index 507bb10c..cc3fe0e1 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/tasks/WorldBorderTask.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/WorldBorderTask.java @@ -11,7 +11,9 @@ import org.bukkit.damage.DamageSource; import org.bukkit.damage.DamageType; -@SuppressWarnings("UnstableApiUsage") +/** + * Task for handing a world border in a game + */ public class WorldBorderTask implements Runnable { private final Game game; diff --git a/src/main/java/com/shanebeestudios/hg/plugin/tasks/package-info.java b/src/main/java/com/shanebeestudios/hg/plugin/tasks/package-info.java new file mode 100644 index 00000000..b3ad354b --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/plugin/tasks/package-info.java @@ -0,0 +1,4 @@ +/** + * Task management for games + */ +package com.shanebeestudios.hg.plugin.tasks; From d5b796878ccaed3e9659289c997ef04d00a3e68f Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 21 May 2026 06:34:27 -0700 Subject: [PATCH 25/26] Add update checker --- .../hg/plugin/HungerGames.java | 2 + .../hg/plugin/configs/Config.java | 4 + .../hg/plugin/permission/Permissions.java | 1 + .../hg/plugin/update/ModrinthVersion.java | 38 +++++ .../hg/plugin/update/UpdateChecker.java | 133 ++++++++++++++++++ src/main/resources/config.yml | 13 +- 6 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/shanebeestudios/hg/plugin/update/ModrinthVersion.java create mode 100644 src/main/java/com/shanebeestudios/hg/plugin/update/UpdateChecker.java diff --git a/src/main/java/com/shanebeestudios/hg/plugin/HungerGames.java b/src/main/java/com/shanebeestudios/hg/plugin/HungerGames.java index f205b940..6bc2bf90 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/HungerGames.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/HungerGames.java @@ -27,6 +27,7 @@ import com.shanebeestudios.hg.plugin.managers.Placeholders; import com.shanebeestudios.hg.plugin.managers.PlayerManager; import com.shanebeestudios.hg.plugin.managers.SessionManager; +import com.shanebeestudios.hg.plugin.update.UpdateChecker; import dev.jorel.commandapi.CommandAPI; import dev.jorel.commandapi.CommandAPIPaperConfig; import dev.jorel.commandapi.exceptions.UnsupportedVersionException; @@ -146,6 +147,7 @@ public void loadPlugin(boolean load) { setupMetrics(); + new UpdateChecker(this); Util.log("HungerGames has been enabled in %.2f seconds!", (float) (System.currentTimeMillis() - start) / 1000); } diff --git a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java index abd239a0..d889fe75 100755 --- a/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/configs/Config.java @@ -26,6 +26,8 @@ public class Config { public static boolean SETTINGS_DEBUG; // Basic settings + public static boolean SETTINGS_UPDATE_CHECKER_ENABLED; + public static boolean SETTINGS_UPDATE_CHECKER_ASYNC; public static boolean SETTINGS_BROADCAST_JOIN_MESSAGES; public static boolean SETTINGS_BROADCAST_WIN_MESSAGES; public static boolean HAS_ECONOMY = true; @@ -142,6 +144,8 @@ private void loadConfigFile() { private void loadConfig() { // Settings SETTINGS_DEBUG = config.getBoolean("settings.debug"); + SETTINGS_UPDATE_CHECKER_ENABLED = config.getBoolean("settings.update-checker.enabled"); + SETTINGS_UPDATE_CHECKER_ASYNC = config.getBoolean("settings.update-checker.async"); SETTINGS_BROADCAST_JOIN_MESSAGES = config.getBoolean("settings.broadcast-join-messages"); SETTINGS_BROADCAST_WIN_MESSAGES = config.getBoolean("settings.broadcast-win-messages"); SETTINGS_BOSSBAR_COUNTDOWN = config.getBoolean("settings.bossbar-countdown"); diff --git a/src/main/java/com/shanebeestudios/hg/plugin/permission/Permissions.java b/src/main/java/com/shanebeestudios/hg/plugin/permission/Permissions.java index d6a5f47c..cbc2d5f8 100644 --- a/src/main/java/com/shanebeestudios/hg/plugin/permission/Permissions.java +++ b/src/main/java/com/shanebeestudios/hg/plugin/permission/Permissions.java @@ -51,6 +51,7 @@ public boolean has(CommandSender sender) { // Other Permissions public static final Permission BYPASS_COMMAND_RESTRICTION = getBase("bypass.command.restriction", "Bypass command restriction while in games", PermissionDefault.OP); + public static final Permission UPDATE_CHECKER = getBase("update_checker", "Receive update messages on join", PermissionDefault.OP); private static Permission getCommand(String perm, String description, PermissionDefault defaultPermission) { return getBase("command." + perm, description, defaultPermission); diff --git a/src/main/java/com/shanebeestudios/hg/plugin/update/ModrinthVersion.java b/src/main/java/com/shanebeestudios/hg/plugin/update/ModrinthVersion.java new file mode 100644 index 00000000..485ee763 --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/plugin/update/ModrinthVersion.java @@ -0,0 +1,38 @@ +package com.shanebeestudios.hg.plugin.update; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.List; + +public class ModrinthVersion { + + private final String updateVersion; + private final List supportedVersions = new ArrayList<>(); + + public ModrinthVersion(JsonElement jsonElement) { + JsonObject json = jsonElement.getAsJsonObject(); + this.updateVersion = json.get("version_number").getAsString(); + JsonArray gameVersions = json.getAsJsonArray("game_versions"); + gameVersions.forEach(version -> this.supportedVersions.add(version.getAsString())); + } + + public String getUpdateVersion() { + return this.updateVersion; + } + + public String getUpdateLink() { + return "https://modrinth.com/plugin/hungergames-sb/version/" + this.updateVersion; + } + + public List getSupportedVersions() { + return this.supportedVersions; + } + + public boolean isServerSupported(String serverVersion) { + return this.supportedVersions.contains(serverVersion); + } + +} diff --git a/src/main/java/com/shanebeestudios/hg/plugin/update/UpdateChecker.java b/src/main/java/com/shanebeestudios/hg/plugin/update/UpdateChecker.java new file mode 100644 index 00000000..fa2e7c2e --- /dev/null +++ b/src/main/java/com/shanebeestudios/hg/plugin/update/UpdateChecker.java @@ -0,0 +1,133 @@ +package com.shanebeestudios.hg.plugin.update; + + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.shanebeestudios.hg.api.region.TaskUtils; +import com.shanebeestudios.hg.api.util.Util; +import com.shanebeestudios.hg.plugin.HungerGames; +import com.shanebeestudios.hg.plugin.configs.Config; +import com.shanebeestudios.hg.plugin.permission.Permissions; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +/** + * Utility class to check for plugin updates + */ +@SuppressWarnings("deprecation") +public class UpdateChecker implements Listener { + + private final HungerGames plugin; + private final String pluginVersion; + private final String serverVersion = Bukkit.getMinecraftVersion(); + private ModrinthVersion currentUpdateVersion; + + public UpdateChecker(HungerGames plugin) { + this.plugin = plugin; + this.pluginVersion = plugin.getPluginMeta().getVersion(); + + if (Config.SETTINGS_UPDATE_CHECKER_ENABLED) { + setupJoinListener(); + checkUpdate(Config.SETTINGS_UPDATE_CHECKER_ASYNC); + } else { + Util.log("Update checker disabled!"); + } + } + + private void setupJoinListener() { + Bukkit.getPluginManager().registerEvents(new Listener() { + @EventHandler + private void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if (!Permissions.UPDATE_CHECKER.has(player)) return; + + TaskUtils.getEntityScheduler(player).runTaskLater(() -> getUpdateVersion(true).thenApply(version -> { + Util.sendPrefixedMessage(player, "Update available: " + version.getUpdateVersion()); + Util.sendMessage(player, "Download at: " + version.getUpdateLink()); + return true; + }), 30); + } + }, this.plugin); + } + + private void checkUpdate(boolean async) { + Util.log("Checking for update..."); + getUpdateVersion(async).thenApply(modrinthVersion -> { + Util.log("Plugin is not up to date!"); + Util.log(" - Current version: v%s", this.pluginVersion); + Util.log(" - Available update: v%s", modrinthVersion.getUpdateVersion()); + if (modrinthVersion.isServerSupported(this.serverVersion)) { + Util.log(" - Download at: " + modrinthVersion.getUpdateLink()); + } else { + Util.log(" - Your server version (%s) does not support this update.", this.serverVersion); + Util.log(" - Supported Versions:"); + for (String supportedVersion : modrinthVersion.getSupportedVersions()) { + Util.log(" - %s", supportedVersion); + } + } + return true; + }).exceptionally(throwable -> { + Util.log("Plugin is up to date!"); + return true; + }); + } + + private CompletableFuture getUpdateVersion(boolean async) { + CompletableFuture updateVersionFuture = new CompletableFuture<>(); + if (this.currentUpdateVersion != null) { + updateVersionFuture.complete(this.currentUpdateVersion); + } else { + CompletableFuture latestReleaseFuture = new CompletableFuture<>(); + if (async) { + TaskUtils.getGlobalScheduler().runTaskAsync(() -> { + ModrinthVersion lastest = getLatestVersionFromModrinth(); + if (lastest == null) latestReleaseFuture.cancel(true); + latestReleaseFuture.complete(lastest); + }); + } else { + ModrinthVersion latest = getLatestVersionFromModrinth(); + if (latest == null) latestReleaseFuture.cancel(true); + latestReleaseFuture.complete(latest); + } + latestReleaseFuture.thenApply(version -> { + if (version.getUpdateVersion().compareTo(this.pluginVersion) <= 0) { + updateVersionFuture.cancel(true); + } else { + this.currentUpdateVersion = version; + updateVersionFuture.complete(this.currentUpdateVersion); + } + return true; + }); + } + return updateVersionFuture; + } + + @SuppressWarnings("CallToPrintStackTrace") + private ModrinthVersion getLatestVersionFromModrinth() { + try { + URL url = new URL("https://api.modrinth.com/v2/project/UPP8KYnj/version"); + BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); + JsonArray elements = new Gson().fromJson(reader, JsonArray.class); + JsonElement latestVersion = elements.get(0); + return new ModrinthVersion(latestVersion); + } catch (IOException e) { + if (Config.SETTINGS_DEBUG) { + e.printStackTrace(); + } else { + Util.log("Checking for update failed!"); + } + } + return null; + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c8a788d8..21338d4e 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -6,13 +6,18 @@ # More Info: See the wiki for more info on this config @ https://github.com/ShaneBeeStudios/HungerGames/wiki/Config.yml -# Helpful Links: -# Bukkit Material Enums: https://jd.papermc.io/paper/26.1.2/org/bukkit/Material.html -# Bukkit Tags: https://jd.papermc.io/paper/26.1.2/org/bukkit/Tag.html - settings: # When enabled, more informative debug messages will be sent when errors occur debug: false + + # Enable this to automatically check for HungerGames updates + # Disable this if you do not want to check for updates, or it takes too long/has issues checking + update-checker: + # Whether to check for updates + enabled: true + # Whether the update check should be done on another thread + async: false + # When enabled, if a player joins an arena, a message will broadcast to all players on the server that a game is available to # join and how many players til it can start. # A message will broadcast that a game has started, and that you can still join. From 14fead9e08f481e6745576321845e7d68994cefa Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 21 May 2026 06:44:24 -0700 Subject: [PATCH 26/26] Update issue templates --- .github/ISSUE_TEMPLATE/config.yml | 6 +++--- .github/ISSUE_TEMPLATE/report-a-bug.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 75d6011e..b427c22d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,6 +3,6 @@ contact_links: - name: 💻 ShaneBee's Support Discord url: https://discord.gg/UzBCFSQJyM about: For general help with HungerGames ask in the HungerGames channel - - name: 💿 Modrinth Resource - url: todo - about: Read about HungerGames on Modrinth! + - name: 🛠️Modrinth Resource + url: https://modrinth.com/plugin/hungergames-sb + about: Read about and download HungerGames on Modrinth! diff --git a/.github/ISSUE_TEMPLATE/report-a-bug.yml b/.github/ISSUE_TEMPLATE/report-a-bug.yml index 5299dc88..89af0efb 100644 --- a/.github/ISSUE_TEMPLATE/report-a-bug.yml +++ b/.github/ISSUE_TEMPLATE/report-a-bug.yml @@ -47,7 +47,7 @@ body: label: Server Version description: Which version of the server and MC are you using? placeholder: | - Ex: "Paper 1.21.4" + Ex: "Paper 26.1.2" validations: required: true