From 5b7f7b6f837d3513ba21ff7cc64742feddf75df4 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 20 Jul 2024 20:34:43 -0500 Subject: [PATCH] Fix a few event bugs, add hunger-deplete option and top-team-stat scoreboard line creator --- .../module/scoreboard/ScoreboardTemplate.java | 3 +- .../line/ScoreboardLineCreator.java | 3 +- .../line/TopTeamStatLineCreator.java | 93 +++++++++++++++++++ .../src/main/resources/scoreboards.yml | 18 ++++ .../org/battleplugins/arena/BattleArena.java | 13 +-- .../arena/competition/LiveCompetition.java | 2 +- .../arena/competition/OptionsListener.java | 8 ++ .../arena/competition/team/TeamManager.java | 21 ++++- .../competition/team/TeamStatHolder.java | 17 +++- .../arena/config/ArenaConfigParser.java | 2 +- .../editor/context/MapCreateContext.java | 25 ++++- .../arena/event/ArenaEventManager.java | 2 +- .../arena/options/ArenaOptionType.java | 1 + .../battleplugins/arena/team/ArenaTeams.java | 17 ++++ 14 files changed, 210 insertions(+), 15 deletions(-) create mode 100644 module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/line/TopTeamStatLineCreator.java diff --git a/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/ScoreboardTemplate.java b/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/ScoreboardTemplate.java index ba0326ac..357e13f5 100644 --- a/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/ScoreboardTemplate.java +++ b/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/ScoreboardTemplate.java @@ -22,7 +22,8 @@ public class ScoreboardTemplate { name = "lines", description = "The lines to display on the scoreboard.", contextProvider = ScoreboardLineCreatorContextProvider.class, - required = true) + required = true + ) private List lines; public Component getTitle() { diff --git a/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/line/ScoreboardLineCreator.java b/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/line/ScoreboardLineCreator.java index a4ff18e9..fe61cd63 100644 --- a/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/line/ScoreboardLineCreator.java +++ b/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/line/ScoreboardLineCreator.java @@ -10,7 +10,8 @@ public interface ScoreboardLineCreator { Map> LINE_CREATORS = Map.of( "simple", SimpleLineCreator.class, "player-list", PlayerListLineCreator.class, - "top-stat", TopStatLineCreator.class + "top-stat", TopStatLineCreator.class, + "top-team-stat", TopTeamStatLineCreator.class ); List createLines(ArenaPlayer player); diff --git a/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/line/TopTeamStatLineCreator.java b/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/line/TopTeamStatLineCreator.java new file mode 100644 index 00000000..32aa18db --- /dev/null +++ b/module/scoreboards/src/main/java/org/battleplugins/arena/module/scoreboard/line/TopTeamStatLineCreator.java @@ -0,0 +1,93 @@ +package org.battleplugins.arena.module.scoreboard.line; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.battleplugins.arena.ArenaPlayer; +import org.battleplugins.arena.BattleArena; +import org.battleplugins.arena.competition.team.TeamManager; +import org.battleplugins.arena.config.ArenaOption; +import org.battleplugins.arena.stat.ArenaStat; +import org.battleplugins.arena.stat.ArenaStats; +import org.battleplugins.arena.stat.StatHolder; +import org.battleplugins.arena.team.ArenaTeam; +import org.battleplugins.arena.util.Version; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class TopTeamStatLineCreator implements ScoreboardLineCreator { + + @ArenaOption(name = "max-entries", description = "The maximum number of entries to display on the scoreboard.", required = true) + private int maxEntries; + + @ArenaOption(name = "stat", description = "The stat to display on the scoreboard.", required = true) + private String stat; + + @ArenaOption(name = "stat-color", description = "The color of the stat.") + private Color color; + + @ArenaOption(name = "ascending", description = "Whether to display the stat in ascending order.") + private boolean ascending; + + @SuppressWarnings("unchecked") + @Override + public List createLines(ArenaPlayer player) { + ArenaStat stat = ArenaStats.get(this.stat); + if (stat == null) { + return List.of(); + } + + if (!(stat.getDefaultValue() instanceof Number)) { + BattleArena.getInstance().warn("Stat {} is not a number. Unsure how to sort players in top stat line type.", stat.getName()); + return List.of(); + } + + List lines = new ArrayList<>(this.maxEntries); + TeamManager teamManager = player.getCompetition().getTeamManager(); + List teams = teamManager.getTeams() + .stream() + .sorted((team1, team2) -> { + int value1 = statOrDefault(teamManager, team1, (ArenaStat) stat).intValue(); + int value2 = statOrDefault(teamManager, team2, (ArenaStat) stat).intValue(); + return this.ascending ? Integer.compare(value1, value2) : Integer.compare(value2, value1); + }) + .toList(); + + for (ArenaTeam team : teams) { + if (teamManager.getPlayersOnTeam(team).isEmpty()) { + continue; + } + + Component component; + TextColor color = team.getTextColor(); + if (Version.getServerVersion().isLessThan("1.20.4")) { + component = Component.text(team.getName(), color); + } else { + component = team.getFormattedName(); + } + + TextColor statColor = this.color == null ? NamedTextColor.WHITE : TextColor.color(this.color.getRGB()); + lines.add(Component.text("(" + statOrDefault(teamManager, team, (ArenaStat) stat) + ") ", statColor).append(component)); + } + + return lines; + } + + private static Number statOrDefault(TeamManager manager, ArenaTeam team, ArenaStat stat) { + StatHolder stats = manager.getStats(team); + if (stats.stat(stat).isEmpty()) { + Set players = manager.getPlayersOnTeam(team); + int score = 0; + for (ArenaPlayer teamPlayer : players) { + score += teamPlayer.stat(stat).orElse(stat.getDefaultValue()).intValue(); + } + + return score; + } else { + return stats.stat(stat).orElse(stat.getDefaultValue()); + } + } +} diff --git a/module/scoreboards/src/main/resources/scoreboards.yml b/module/scoreboards/src/main/resources/scoreboards.yml index 36cd59bf..02a0393d 100644 --- a/module/scoreboards/src/main/resources/scoreboards.yml +++ b/module/scoreboards/src/main/resources/scoreboards.yml @@ -83,6 +83,24 @@ templates: - top-stat: stat: kills max-entries: 8 + - simple: + lines: + - " " + - "battleplugins.org" + ingame-top-team-kills: + title: "%arena%" + refresh-time: 1s + lines: + - simple: + lines: + - " " + - "Map: %map%" + - "Time remaining: %time_remaining_short%" + - " " + - "Top teams:" + - top-team-stat: + stat: kills + max-entries: 8 - simple: lines: - " " diff --git a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java index bc1fbb7f..9f4e5146 100644 --- a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java +++ b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java @@ -87,7 +87,7 @@ public void onLoad() { this.info("Loading BattleArena {} for {}", this.getPluginMeta().getVersion(), Version.getServerVersion()); - this.loadConfig(); + this.loadConfig(false); Path dataFolder = this.getDataFolder().toPath(); this.arenasPath = dataFolder.resolve("arenas"); @@ -245,7 +245,7 @@ public void reload() { this.disable(); // Reload the config - this.loadConfig(); + this.loadConfig(true); this.enable(); @@ -730,7 +730,7 @@ private void clearDynamicMaps() { } } - private void loadConfig() { + private void loadConfig(boolean reload) { this.saveDefaultConfig(); File configFile = new File(this.getDataFolder(), "config.yml"); @@ -740,9 +740,10 @@ private void loadConfig() { } catch (ParseException e) { ParseException.handle(e); - this.error("Failed to load BattleArena configuration! Disabling plugin."); - this.getServer().getPluginManager().disablePlugin(this); - return; + this.error("Failed to load BattleArena configuration!"); + if (!reload) { + this.getServer().getPluginManager().disablePlugin(this); + } } } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java b/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java index d9e36612..78e5503c 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java @@ -356,7 +356,7 @@ private ArenaPlayer createPlayer(Player player) { private int calculateMaxPlayers() { Teams teams = this.arena.getTeams(); - int teamAmount = teams.isNonTeamGame() ? teams.getTeamAmount().getMax() : teams.getAvailableTeams().size(); + int teamAmount = teams.isNonTeamGame() ? teams.getTeamAmount().getMax() : this.teamManager.getTeams().size(); int teamSizeMax = teams.getTeamSize().getMax(); int maxPlayers; if (teamSizeMax == Integer.MAX_VALUE) { diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/OptionsListener.java b/plugin/src/main/java/org/battleplugins/arena/competition/OptionsListener.java index ff3bc084..ff625f33 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/OptionsListener.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/OptionsListener.java @@ -14,6 +14,7 @@ import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerInteractEvent; @@ -146,6 +147,13 @@ public void onEntityDamage(EntityDamageByEntityEvent event) { } } + @ArenaEventHandler(priority = EventPriority.LOWEST) + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if (!this.competition.option(ArenaOptionType.HUNGER_DEPLETE).map(BooleanArenaOption::isEnabled).orElse(true)) { + event.setCancelled(true); + } + } + @SuppressWarnings("unchecked") @Override public T getCompetition() { diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java b/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java index cdecb7ab..6ee978f3 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java @@ -1,9 +1,11 @@ package org.battleplugins.arena.competition.team; import org.battleplugins.arena.ArenaPlayer; +import org.battleplugins.arena.BattleArena; import org.battleplugins.arena.competition.LiveCompetition; import org.battleplugins.arena.competition.map.LiveCompetitionMap; import org.battleplugins.arena.competition.map.options.Spawns; +import org.battleplugins.arena.competition.map.options.TeamSpawns; import org.battleplugins.arena.options.Teams; import org.battleplugins.arena.stat.StatHolder; import org.battleplugins.arena.team.ArenaTeam; @@ -28,8 +30,23 @@ public class TeamManager { public TeamManager(LiveCompetition competition) { this.competition = competition; - for (ArenaTeam availableTeam : competition.getArena().getTeams().getAvailableTeams()) { - this.teams.put(availableTeam, new HashSet<>()); + if (competition.getArena().getTeams().isNonTeamGame() || (competition.getMap().getSpawns() == null || competition.getMap().getSpawns().getTeamSpawns() == null)) { + for (ArenaTeam availableTeam : competition.getArena().getTeams().getAvailableTeams()) { + this.teams.put(availableTeam, new HashSet<>()); + } + + return; + } + + for (Map.Entry entry : competition.getMap().getSpawns().getTeamSpawns().entrySet()) { + String teamName = entry.getKey(); + ArenaTeam team = BattleArena.getInstance().getTeams().getTeam(teamName); + if (team == null) { + BattleArena.getInstance().warn("Could not find team with name {} when loading {} for {}!", teamName, competition.getMap().getName(), competition.getArena().getName()); + continue; + } + + this.teams.put(team, new HashSet<>()); } } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamStatHolder.java b/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamStatHolder.java index 91af1bea..2d02b8d7 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamStatHolder.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamStatHolder.java @@ -50,7 +50,22 @@ public T getStat(ArenaStat stat) { if (total == null) { total = (Number) playerStat; } else { - total = (Number) stat.getType().cast(total.doubleValue() + ((Number) playerStat).doubleValue()); + Class type = stat.getType(); + if (type.equals(Integer.class)) { + total = total.intValue() + (Integer) playerStat; + } else if (type.equals(Double.class)) { + total = total.doubleValue() + (Double) playerStat; + } else if (type.equals(Float.class)) { + total = total.floatValue() + (Float) playerStat; + } else if (type.equals(Long.class)) { + total = total.longValue() + (Long) playerStat; + } else if (type.equals(Short.class)) { + total = total.shortValue() + (Short) playerStat; + } else if (type.equals(Byte.class)) { + total = total.byteValue() + (Byte) playerStat; + } else { + throw new IllegalArgumentException("Don't know how to accumulate type " + stat.getType()); + } } } diff --git a/plugin/src/main/java/org/battleplugins/arena/config/ArenaConfigParser.java b/plugin/src/main/java/org/battleplugins/arena/config/ArenaConfigParser.java index 21891ce2..b7f7d67b 100644 --- a/plugin/src/main/java/org/battleplugins/arena/config/ArenaConfigParser.java +++ b/plugin/src/main/java/org/battleplugins/arena/config/ArenaConfigParser.java @@ -176,7 +176,7 @@ private static void populateType(@Nullable Path sourceFile, Field field, ArenaOp } } else { // Value is not a primitive, let's check to see if we have a provider for it - if (OBJECT_PROVIDERS.containsKey(type)) { + if (OBJECT_PROVIDERS.containsKey(type) && configuration.contains(name)) { try { field.set(instance, OBJECT_PROVIDERS.get(type).parse(configuration.get(name))); } catch (ParseException e) { diff --git a/plugin/src/main/java/org/battleplugins/arena/editor/context/MapCreateContext.java b/plugin/src/main/java/org/battleplugins/arena/editor/context/MapCreateContext.java index e0f175ec..5b8e2c54 100644 --- a/plugin/src/main/java/org/battleplugins/arena/editor/context/MapCreateContext.java +++ b/plugin/src/main/java/org/battleplugins/arena/editor/context/MapCreateContext.java @@ -10,6 +10,7 @@ import org.battleplugins.arena.editor.ArenaEditorWizard; import org.battleplugins.arena.editor.EditorContext; import org.battleplugins.arena.team.ArenaTeam; +import org.battleplugins.arena.util.IntRange; import org.battleplugins.arena.util.PositionWithRotation; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -91,7 +92,29 @@ public void addSpawn(String team, PositionWithRotation spawns) { } public boolean hasValidTeamSpawns() { - return this.getMissingTeams().isEmpty(); + List missingTeams = this.getMissingTeams(); + + // No missing teams - we don't need to check for anything further + if (missingTeams.isEmpty()) { + return true; + } + + IntRange teamAmount = this.arena.getTeams().getTeamAmount(); + if (teamAmount.getMax() == Integer.MAX_VALUE) { + // Check if we have the spawns for the minimum amount + int teamsWithSpawns = 0; + for (ArenaTeam availableTeam : this.arena.getTeams().getAvailableTeams()) { + if (this.spawns.containsKey(availableTeam.getName()) && !this.spawns.get(availableTeam.getName()).isEmpty()) { + teamsWithSpawns++; + } + } + + return teamsWithSpawns >= teamAmount.getMin(); + } + + // We are not bounded by the maximum value, so + // each team must have a spawnpoint + return false; } public List getMissingTeams() { diff --git a/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventManager.java b/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventManager.java index 68079ad6..4fe77673 100644 --- a/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventManager.java +++ b/plugin/src/main/java/org/battleplugins/arena/event/ArenaEventManager.java @@ -156,7 +156,7 @@ public T callEvent(T event) { private void pollActions(T event, Competition competition, Iterator iterator, Collection players) { while (iterator.hasNext()) { EventAction action = iterator.next(); - if (action instanceof DelayAction delayAction) { + if (!Bukkit.isStopping() && action instanceof DelayAction delayAction) { Bukkit.getScheduler().runTaskLater(BattleArena.getInstance(), () -> this.pollActions(event, competition, iterator, players), delayAction.getTicks()); return; } diff --git a/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java b/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java index c4d56edb..5595ee32 100644 --- a/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java +++ b/plugin/src/main/java/org/battleplugins/arena/options/ArenaOptionType.java @@ -26,6 +26,7 @@ public final class ArenaOptionType { public static final ArenaOptionType ITEM_DROPS = new ArenaOptionType<>("item-drops", BooleanArenaOption::new); public static final ArenaOptionType KEEP_INVENTORY = new ArenaOptionType<>("keep-inventory", BooleanArenaOption::new); public static final ArenaOptionType KEEP_EXPERIENCE = new ArenaOptionType<>("keep-experience", BooleanArenaOption::new); + public static final ArenaOptionType HUNGER_DEPLETE = new ArenaOptionType<>("hunger-deplete", BooleanArenaOption::new); public static final ArenaOptionType TEAM_SELECTION = new ArenaOptionType<>("team-selection", BooleanArenaOption::new); public static final ArenaOptionType> DAMAGE_PLAYERS = new ArenaOptionType<>("damage-players", params -> new EnumArenaOption<>(params, DamageOption.class, "option")); diff --git a/plugin/src/main/java/org/battleplugins/arena/team/ArenaTeams.java b/plugin/src/main/java/org/battleplugins/arena/team/ArenaTeams.java index 5463e3ed..c8de5eca 100644 --- a/plugin/src/main/java/org/battleplugins/arena/team/ArenaTeams.java +++ b/plugin/src/main/java/org/battleplugins/arena/team/ArenaTeams.java @@ -5,10 +5,12 @@ import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.Color; import java.util.Iterator; import java.util.List; +import java.util.Optional; @DocumentationSource("https://docs.battleplugins.org/books/user-guide/page/teams") public final class ArenaTeams implements Iterable { @@ -19,6 +21,21 @@ public final class ArenaTeams implements Iterable { @ArenaOption(name = "teams", description = "All of the registered teams.", required = true) private List teams; + public Optional team(String name) { + return Optional.ofNullable(this.getTeam(name)); + } + + @Nullable + public ArenaTeam getTeam(String name) { + for (ArenaTeam team : this.teams) { + if (team.getName().equalsIgnoreCase(name)) { + return team; + } + } + + return null; + } + @NotNull @Override public Iterator iterator() {