diff --git a/core/src/main/java/tc/oc/pgm/ffa/Tribute.java b/core/src/main/java/tc/oc/pgm/ffa/Tribute.java index 55fefb6eea..43ac9a376d 100644 --- a/core/src/main/java/tc/oc/pgm/ffa/Tribute.java +++ b/core/src/main/java/tc/oc/pgm/ffa/Tribute.java @@ -148,6 +148,11 @@ public Collection getPlayers() { return this.players; } + @Nullable + public MatchPlayer getPlayer() { + return this.player; + } + @Override public @Nullable MatchPlayer getPlayer(final UUID playerId) { return player != null && player.getId().equals(playerId) ? player : null; diff --git a/core/src/main/java/tc/oc/pgm/menu/InventoryMenu.java b/core/src/main/java/tc/oc/pgm/menu/InventoryMenu.java index 2f91a3cdc2..4c031a7859 100644 --- a/core/src/main/java/tc/oc/pgm/menu/InventoryMenu.java +++ b/core/src/main/java/tc/oc/pgm/menu/InventoryMenu.java @@ -40,12 +40,11 @@ public InventoryMenu( Component title, int rows, MatchPlayer viewer, @Nullable SmartInventory parent) { this.viewer = viewer; - SmartInventory.Builder builder = - SmartInventory.builder() - .manager(PGM.get().getInventoryManager()) - .title(translateLegacy(title, getBukkit())) - .size(rows, 9) - .provider(this); + SmartInventory.Builder builder = SmartInventory.builder() + .manager(PGM.get().getInventoryManager()) + .title(translateLegacy(title, getBukkit())) + .size(rows, 9) + .provider(this); if (parent != null) { builder.parent(parent); @@ -70,6 +69,10 @@ public SmartInventory getInventory() { return inventory; } + public int lastRow() { + return inventory.getRows() - 1; + } + public void addBackButton( InventoryContents contents, Component parentMenuTitle, int row, int col) { Component back = translatable("menu.page.return", NamedTextColor.GRAY, parentMenuTitle); @@ -78,18 +81,8 @@ public void addBackButton( row, col, ClickableItem.of( - new ItemBuilder() - .material(Material.BARRIER) - .name(translateLegacy(back, getBukkit())) - .build(), - c -> { - getInventory() - .getParent() - .ifPresent( - parent -> { - parent.open(getBukkit()); - }); - })); + new ItemBuilder().material(Material.BARRIER).name(getBukkit(), back).build(), + c -> getInventory().getParent().ifPresent(parent -> parent.open(getBukkit())))); } @Override diff --git a/core/src/main/java/tc/oc/pgm/menu/PagedInventoryMenu.java b/core/src/main/java/tc/oc/pgm/menu/PagedInventoryMenu.java index f9d2abbf9c..fbc0cccb51 100644 --- a/core/src/main/java/tc/oc/pgm/menu/PagedInventoryMenu.java +++ b/core/src/main/java/tc/oc/pgm/menu/PagedInventoryMenu.java @@ -84,14 +84,18 @@ public void setupPageContents(Player player, InventoryContents contents) { * * @return a {@link SlotPos} where previous page button will be located. */ - public abstract SlotPos getPreviousPageSlot(); + public SlotPos getPreviousPageSlot() { + return SlotPos.of(lastRow(), 0); + } /** * The position of the next page button. * * @return a {@link SlotPos} where next page button will be located. */ - public abstract SlotPos getNextPageSlot(); + public SlotPos getNextPageSlot() { + return SlotPos.of(lastRow(), 8); + } /** * The position of the no page contents button, only displayed when page contents is empty or @@ -112,8 +116,10 @@ public void setupPageContents(Player player, InventoryContents contents) { protected ClickableItem getEmptyContentsButton(Player viewer) { Component name = translatable("menu.page.empty", NamedTextColor.DARK_RED, TextDecoration.BOLD); - return ClickableItem.empty( - new ItemBuilder().material(Material.BARRIER).name(translateLegacy(name, viewer)).build()); + return ClickableItem.empty(new ItemBuilder() + .material(Material.BARRIER) + .name(translateLegacy(name, viewer)) + .build()); } protected ClickableItem getPageItem(Player player, int page, String key) { diff --git a/core/src/main/java/tc/oc/pgm/stats/StatsMatchModule.java b/core/src/main/java/tc/oc/pgm/stats/StatsMatchModule.java index 86021748aa..a2ce93b7dd 100644 --- a/core/src/main/java/tc/oc/pgm/stats/StatsMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/stats/StatsMatchModule.java @@ -11,7 +11,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Table; import com.google.common.collect.Tables; -import java.text.DecimalFormat; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; @@ -28,7 +27,6 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; import org.bukkit.Material; -import org.bukkit.entity.Arrow; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -38,6 +36,8 @@ import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; @@ -62,17 +62,20 @@ import tc.oc.pgm.events.PlayerJoinPartyEvent; import tc.oc.pgm.events.PlayerLeavePartyEvent; import tc.oc.pgm.events.PlayerParticipationStopEvent; +import tc.oc.pgm.ffa.Tribute; import tc.oc.pgm.flag.Flag; import tc.oc.pgm.flag.event.FlagCaptureEvent; import tc.oc.pgm.flag.event.FlagStateChangeEvent; import tc.oc.pgm.flag.state.Carried; import tc.oc.pgm.goals.events.GoalTouchEvent; +import tc.oc.pgm.menu.MenuItem; import tc.oc.pgm.stats.menu.StatsMainMenu; import tc.oc.pgm.stats.menu.items.PlayerStatsMenuItem; import tc.oc.pgm.stats.menu.items.TeamStatsMenuItem; import tc.oc.pgm.teams.Team; import tc.oc.pgm.tracker.TrackerMatchModule; import tc.oc.pgm.tracker.info.ProjectileInfo; +import tc.oc.pgm.util.Pair; import tc.oc.pgm.util.named.NameStyle; import tc.oc.pgm.util.text.TextFormatter; import tc.oc.pgm.util.usernames.UsernameResolvers; @@ -81,6 +84,10 @@ @ListenerScope(MatchScope.LOADED) public class StatsMatchModule implements MatchModule, Listener { + private static final Component HEART_SYMBOL = text("\u2764"); // ❤ + + private static final String BOW_KEY = "bow"; + private static final MetadataValue TRUE = new FixedMetadataValue(PGM.get(), true); private final Match match; private final Map allPlayerStats = new HashMap<>(); @@ -92,18 +99,7 @@ public class StatsMatchModule implements MatchModule, Listener { private final boolean ownStats = PGM.get().getConfiguration().showOwnStats(); private final int verboseItemSlot = PGM.get().getConfiguration().getVerboseItemSlot(); - /** Common formats used by stats with decimals */ - public static final DecimalFormat FORMATTER = new DecimalFormat("#.00"); - - public static final DecimalFormat THOUSANDS_FORMATTER = new DecimalFormat("#.00"); - - static { - THOUSANDS_FORMATTER.setMultiplier(1000); - THOUSANDS_FORMATTER.setPositiveSuffix("k"); - THOUSANDS_FORMATTER.setNegativeSuffix("k"); - } - - public static final Component HEART_SYMBOL = text("\u2764"); // ❤ + private List teams; public StatsMatchModule(Match match) { this.match = match; @@ -119,10 +115,8 @@ public Table getParticipationStats() { @EventHandler public void onMatchStart(final MatchStartEvent event) { - event - .getMatch() - .getParticipants() - .forEach(player -> getPlayerStat(player).startParticipation()); + event.getMatch().getParticipants().forEach(player -> getPlayerStat(player) + .startParticipation()); } @EventHandler(priority = EventPriority.LOWEST) @@ -162,12 +156,12 @@ public void onDamage(EntityDamageByEntityEvent event) { // Prevent tracking damage to entities or self if (damaged == null || (damager != null && damaged.getId() == damager.getId())) return; - boolean bow = event.getDamager() instanceof Arrow; + boolean bow = event.getDamager().hasMetadata(BOW_KEY); // Absorbed damage gets removed so we add it back - double absorptionHearts = -event.getDamage(EntityDamageEvent.DamageModifier.ABSORPTION); + double absHearts = -event.getDamage(EntityDamageEvent.DamageModifier.ABSORPTION); double realFinalDamage = - Math.min(event.getFinalDamage(), ((Player) event.getEntity()).getHealth()) - + absorptionHearts; + Math.min(event.getFinalDamage(), ((Player) event.getEntity()).getHealth()) + absHearts; + if (realFinalDamage <= 0) return; if (damager != null) getPlayerStat(damager).onDamage(realFinalDamage, bow); getPlayerStat(damaged).onDamaged(realFinalDamage, bow); @@ -178,6 +172,7 @@ public void onShoot(EntityShootBowEvent event) { if (event.getEntity() instanceof Player) { MatchPlayer player = match.getPlayer(event.getEntity()); if (player != null) getPlayerStat(player).onBowShoot(); + event.getProjectile().setMetadata(BOW_KEY, TRUE); } } @@ -191,28 +186,20 @@ public void onDestroyableBreak(DestroyableHealthChangeEvent event) { @EventHandler(priority = EventPriority.MONITOR) public void onMonumentDestroy(DestroyableDestroyedEvent event) { - event - .getDestroyable() - .getContributions() - .forEach( - destroyer -> { - if (destroyer.getPlayerState() != null) { - getPlayerStat(destroyer.getPlayerState()).onMonumentDestroyed(); - } - }); + event.getDestroyable().getContributions().forEach(destroyer -> { + if (destroyer.getPlayerState() != null) { + getPlayerStat(destroyer.getPlayerState()).onMonumentDestroyed(); + } + }); } @EventHandler(priority = EventPriority.MONITOR) public void onCoreLeak(CoreLeakEvent event) { - event - .getCore() - .getContributions() - .forEach( - leaker -> { - if (leaker.getPlayerState() != null) { - getPlayerStat(leaker.getPlayerState()).onCoreLeak(); - } - }); + event.getCore().getContributions().forEach(leaker -> { + if (leaker.getPlayerState() != null) { + getPlayerStat(leaker.getPlayerState()).onCoreLeak(); + } + }); } @EventHandler(priority = EventPriority.MONITOR) @@ -269,11 +256,10 @@ public void onPlayerDeath(MatchPlayerDeathEvent event) { PlayerStats murdererStats = getPlayerStat(murderer); if (event.getDamageInfo() instanceof ProjectileInfo) { - murdererStats.setLongestBowKill( - victim - .getState() - .getLocation() - .distance(((ProjectileInfo) event.getDamageInfo()).getOrigin())); + murdererStats.setLongestBowKill(victim + .getState() + .getLocation() + .distance(((ProjectileInfo) event.getDamageInfo()).getOrigin())); } murdererStats.onMurder(); @@ -296,10 +282,9 @@ private void sendPlayerStats(MatchPlayer player, PlayerStats stats) { } private Future sendLongHotbarMessage(MatchPlayer player, Component message) { - Future task = - match - .getExecutor(MatchScope.LOADED) - .scheduleWithFixedDelay(() -> player.sendActionBar(message), 0, 1, TimeUnit.SECONDS); + Future task = match + .getExecutor(MatchScope.LOADED) + .scheduleWithFixedDelay(() -> player.sendActionBar(message), 0, 1, TimeUnit.SECONDS); match.getExecutor(MatchScope.LOADED).schedule(() -> task.cancel(true), 4, TimeUnit.SECONDS); @@ -331,71 +316,62 @@ public void onStatsDisplay(MatchStatsEvent event) { if (allPlayerStats.isEmpty()) return; // Gather all player stats from this match - Map allKills = new HashMap<>(); - Map allStreaks = new HashMap<>(); - Map allDeaths = new HashMap<>(); - Map allBowShots = new HashMap<>(); - Map allDamage = new HashMap<>(); + Pair bestKills = null; + Pair bestStreaks = null; + Pair bestDeaths = null; + Pair bestBowShots = null; + Pair bestDamage = null; for (Map.Entry mapEntry : allPlayerStats.entrySet()) { - UUID playerUUID = mapEntry.getKey(); - PlayerStats playerStats = mapEntry.getValue(); - - allKills.put(playerUUID, playerStats.getKills()); - allStreaks.put(playerUUID, playerStats.getMaxKillstreak()); - allDeaths.put(playerUUID, playerStats.getDeaths()); - allBowShots.put(playerUUID, playerStats.getLongestBowKill()); - allDamage.put(playerUUID, playerStats.getDamageDone()); + UUID uuid = mapEntry.getKey(); + PlayerStats s = mapEntry.getValue(); + bestKills = getBest(bestKills, uuid, s.getKills()); + bestStreaks = getBest(bestStreaks, uuid, s.getMaxKillstreak()); + bestDeaths = getBest(bestDeaths, uuid, s.getDeaths()); + bestBowShots = getBest(bestBowShots, uuid, s.getLongestBowKill()); + bestDamage = getBest(bestDamage, uuid, s.getDamageDone()); } List best = new ArrayList<>(); if (event.isShowBest()) { - best.add(getMessage("match.stats.kills", sortStats(allKills), NamedTextColor.GREEN)); - best.add(getMessage("match.stats.killstreak", sortStats(allStreaks), NamedTextColor.GREEN)); - best.add(getMessage("match.stats.deaths", sortStats(allDeaths), NamedTextColor.RED)); + best.add(getMessage("match.stats.kills", bestKills, NamedTextColor.GREEN)); + best.add(getMessage("match.stats.killstreak", bestStreaks, NamedTextColor.GREEN)); + best.add(getMessage("match.stats.deaths", bestDeaths, NamedTextColor.RED)); - Map.Entry bestBowshot = sortStats(allBowShots); - if (bestBowshot.getValue() > 1) - best.add(getMessage("match.stats.bowshot", bestBowshot, NamedTextColor.YELLOW)); + if (bestBowShots.getRight() > 0) + best.add(getMessage("match.stats.bowshot", bestBowShots, NamedTextColor.YELLOW)); if (verboseStats) { - Map.Entry bestDamage = sortStatsDouble(allDamage); - best.add( - translatable( - "match.stats.damage", - player(bestDamage.getKey(), NameStyle.VERBOSE), - damageComponent(bestDamage.getValue(), NamedTextColor.GREEN))); + best.add(translatable( + "match.stats.damage", + player(bestDamage.getLeft(), NameStyle.VERBOSE), + damageComponent(bestDamage.getRight(), NamedTextColor.GREEN))); } } for (MatchPlayer viewer : match.getPlayers()) { if (viewer.getSettings().getValue(SettingKey.STATS) == SettingValue.STATS_OFF) continue; - viewer.sendMessage( - TextFormatter.horizontalLineHeading( - viewer.getBukkit(), - translatable("match.stats.title", NamedTextColor.YELLOW), - NamedTextColor.WHITE)); + viewer.sendMessage(TextFormatter.horizontalLineHeading( + viewer.getBukkit(), + translatable("match.stats.title", NamedTextColor.YELLOW), + NamedTextColor.WHITE)); best.forEach(viewer::sendMessage); PlayerStats stats = getPlayerStat(viewer); if (event.isShowOwn() && stats != null) { - Component ksHover = - translatable( - "match.stats.killstreak.concise", - number(stats.getKillstreak(), NamedTextColor.GREEN)); - - viewer.sendMessage( - translatable( - "match.stats.own", - number(stats.getKills(), NamedTextColor.GREEN), - number(stats.getMaxKillstreak(), NamedTextColor.GREEN) - .hoverEvent(showText(ksHover)), - number(stats.getDeaths(), NamedTextColor.RED), - number(stats.getKD(), NamedTextColor.GREEN), - damageComponent(stats.getDamageDone(), NamedTextColor.GREEN))); + Component ksHover = translatable( + "match.stats.killstreak.concise", number(stats.getKillstreak(), NamedTextColor.GREEN)); + + viewer.sendMessage(translatable( + "match.stats.own", + number(stats.getKills(), NamedTextColor.GREEN), + number(stats.getMaxKillstreak(), NamedTextColor.GREEN).hoverEvent(showText(ksHover)), + number(stats.getDeaths(), NamedTextColor.RED), + number(stats.getKD(), NamedTextColor.GREEN), + damageComponent(stats.getDamageDone(), NamedTextColor.GREEN))); } giveVerboseStatsItem(viewer, false); @@ -405,14 +381,11 @@ public void onStatsDisplay(MatchStatsEvent event) { @EventHandler public void onToolClick(PlayerInteractEvent event) { if (event.getPlayer().getItemInHand().getType() != Material.PAPER) return; - if (!match.isFinished() - || !verboseStats - || !match.getCompetitors().stream().allMatch(c -> c instanceof Team)) return; + if (!match.isFinished() || !verboseStats) return; Action action = event.getAction(); - if ((action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK)) { + if (action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK) { MatchPlayer player = match.getPlayer(event.getPlayer()); - if (player == null) return; - giveVerboseStatsItem(player, true); + if (player != null) giveVerboseStatsItem(player, true); } } @@ -423,46 +396,36 @@ public PlayerStatsMenuItem getPlayerStatsItem(MatchPlayer player) { PLAYER_UTILS.getPlayerSkin(player.getBukkit())); } - private List teams; - public void giveVerboseStatsItem(MatchPlayer player, boolean forceOpen) { + if (!verboseStats) return; final Collection competitors = match.getSortedCompetitors(); - boolean showAllVerboseStats = - verboseStats && competitors.stream().allMatch(c -> c instanceof Team); - if (!showAllVerboseStats) return; if (teams == null) { teams = Lists.newArrayList(); for (Competitor competitor : competitors) { - Map playerStats = stats.row((Team) competitor); - teams.add(new TeamStatsMenuItem(match, competitor, playerStats)); + if (competitor instanceof Team t) { + teams.add(new TeamStatsMenuItem(match, competitor, stats.row(t))); + } else if (competitor instanceof Tribute t) { + MatchPlayer tribute = t.getPlayer(); + if (tribute != null) teams.add(getPlayerStatsItem(tribute)); + } } } StatsMainMenu menu = new StatsMainMenu(player, teams, this); player.getInventory().setItem(verboseItemSlot, menu.getItem()); - - if (forceOpen) { - menu.open(); - } + if (forceOpen) menu.open(); } - private Map.Entry sortStats(Map map) { - return map.entrySet().stream().max(Comparator.comparingInt(Map.Entry::getValue)).orElse(null); + private > Pair getBest(Pair curr, UUID uuid, T alt) { + return curr != null && curr.getRight().compareTo(alt) >= 0 ? curr : Pair.of(uuid, alt); } - private Map.Entry sortStatsDouble(Map map) { - return map.entrySet().stream() - .max(Comparator.comparingDouble(Map.Entry::getValue)) - .orElse(null); - } - - Component getMessage( - String messageKey, Map.Entry mapEntry, TextColor color) { + Component getMessage(String messageKey, Pair mapEntry, TextColor color) { return translatable( messageKey, - player(mapEntry.getKey(), NameStyle.VERBOSE), - number(mapEntry.getValue(), color)); + player(mapEntry.getLeft(), NameStyle.VERBOSE), + number(mapEntry.getRight(), color)); } /** Formats raw damage to damage relative to the amount of hearths the player would have broken */ @@ -487,9 +450,8 @@ private void putNewPlayer(UUID player) { private PlayerStats computeTeamStatsIfAbsent(UUID id, Party party) { // Only players on a team have team specific stats PlayerStats globalStats = getGlobalPlayerStat(id); - if (!(party instanceof Team)) return globalStats; + if (!(party instanceof Team team)) return globalStats; - Team team = (Team) party; PlayerStats playerStats = stats.get(team, id); if (playerStats != null) return playerStats; @@ -541,10 +503,9 @@ public Component getBasicStatsMessage(UUID player) { * @return Primary team of the player, null if no team found or observer time exceeds playtime */ public Team getPrimaryTeam(UUID uuid, boolean includeObservers) { - Map.Entry primaryTeam = - stats.column(uuid).entrySet().stream() - .max(Comparator.comparing(entry -> entry.getValue().getTimePlayed())) - .orElse(null); + Map.Entry primaryTeam = stats.column(uuid).entrySet().stream() + .max(Comparator.comparing(entry -> entry.getValue().getTimePlayed())) + .orElse(null); if (primaryTeam == null) return null; diff --git a/core/src/main/java/tc/oc/pgm/stats/menu/StatsMainMenu.java b/core/src/main/java/tc/oc/pgm/stats/menu/StatsMainMenu.java index 2745bc6ac5..5c6f5b6680 100644 --- a/core/src/main/java/tc/oc/pgm/stats/menu/StatsMainMenu.java +++ b/core/src/main/java/tc/oc/pgm/stats/menu/StatsMainMenu.java @@ -10,6 +10,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.menu.MenuItem; import tc.oc.pgm.menu.PagedInventoryMenu; import tc.oc.pgm.stats.StatsMatchModule; import tc.oc.pgm.stats.menu.items.TeamStatsMenuItem; @@ -17,29 +18,29 @@ /** * Menu overview of match stats - populated with {@link TeamStatsMenuItem} which lead to more - * detailed team stats * + * detailed team stats */ public class StatsMainMenu extends PagedInventoryMenu { // GUI values - private static final int TOTAL_ROWS = 4; - private static final int PER_PAGE = 18; + private static final int MIN_TEAM_ROWS = 2; + private static final int MAX_TEAM_ROWS = 4; // How to populate the inventory slots when within fancy slot max - private static final int MAX_FANCY_SLOTS = 13; - private static final int[][] FANCY_SLOTS = {{3, 5, 1, 7}, {4, 0, 8, 2, 6}}; + private static final int[][] SLOTS = {{}, {4}, {3, 5}, {2, 4, 6}, {1, 3, 5, 7}, {0, 2, 4, 6, 8}}; + private static final int MAX_FANCY_SLOTS = SLOTS[SLOTS.length - 1].length * 2; private final StatsMatchModule stats; private final VerboseStatsMenuItem item; - private final List teams; + private final List teams; - public StatsMainMenu(MatchPlayer viewer, List teams, StatsMatchModule stats) { + public StatsMainMenu(MatchPlayer viewer, List teams, StatsMatchModule stats) { super( translatable("match.stats.title", NamedTextColor.GOLD), - TOTAL_ROWS, + 2 + teamRows(teams.size()), viewer, null, - PER_PAGE, + teamRows(teams.size()) * 9, 1, 0); this.stats = stats; @@ -47,14 +48,18 @@ public StatsMainMenu(MatchPlayer viewer, List teams, StatsMat this.item = new VerboseStatsMenuItem(); } + private static int teamRows(int teams) { + int requiredRows = (teams + 8) / 9; + return Math.max(Math.min(requiredRows, MAX_TEAM_ROWS), MIN_TEAM_ROWS); + } + public ItemStack getItem() { return item.createItem(getBukkit()); } @Override public void init(Player player, InventoryContents contents) { - contents.set( - 0, 4, ClickableItem.empty(stats.getPlayerStatsItem(getViewer()).createItem(getBukkit()))); + contents.set(0, 4, stats.getPlayerStatsItem(getViewer()).getClickableItem(getBukkit())); // Use pagination when too many teams are present if (teams.size() > MAX_FANCY_SLOTS) { @@ -62,35 +67,17 @@ public void init(Player player, InventoryContents contents) { return; } - // Fancy Slots layout supports up to 13 teams. If a map contains more than this - // menu will default to a paginated style with 18 teams per page. - int slotCol = 0; - int slotRow = 0; - int row = 1; - for (TeamStatsMenuItem team : teams) { - contents.set(row, FANCY_SLOTS[slotRow][slotCol], team.getClickableItem(player)); - - slotCol++; - if (slotCol >= FANCY_SLOTS[slotRow].length) { - slotCol = 0; - slotRow++; - row++; - } - - if (slotRow >= FANCY_SLOTS.length) { - slotRow = 0; - } - } - } - - @Override - public SlotPos getPreviousPageSlot() { - return SlotPos.of(3, 0); + int splitAt = teams.size() <= 4 ? teams.size() : (teams.size() + 1) >> 1; + fancyLayoutRow(1, contents, teams.subList(0, splitAt), player); + fancyLayoutRow(2, contents, teams.subList(splitAt, teams.size()), player); } - @Override - public SlotPos getNextPageSlot() { - return SlotPos.of(3, 8); + private void fancyLayoutRow( + int row, InventoryContents contents, List items, Player player) { + int[] slots = SLOTS[items.size()]; + for (int i = 0; i < items.size(); i++) { + contents.set(row, slots[i], items.get(i).getClickableItem(player)); + } } @Override @@ -100,7 +87,6 @@ public SlotPos getEmptyPageSlot() { @Override public ClickableItem[] getPageContents(Player viewer) { - if (teams.isEmpty() || teams == null) return null; return teams.stream().map(team -> team.getClickableItem(viewer)).toArray(ClickableItem[]::new); } } diff --git a/core/src/main/java/tc/oc/pgm/stats/menu/TeamStatsMenu.java b/core/src/main/java/tc/oc/pgm/stats/menu/TeamStatsMenu.java index 98abb333a9..a19de75dc8 100644 --- a/core/src/main/java/tc/oc/pgm/stats/menu/TeamStatsMenu.java +++ b/core/src/main/java/tc/oc/pgm/stats/menu/TeamStatsMenu.java @@ -7,15 +7,12 @@ import fr.minuskube.inv.content.InventoryContents; import fr.minuskube.inv.content.SlotPos; import java.util.List; -import java.util.stream.Collectors; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; import tc.oc.pgm.api.party.Competitor; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.menu.PagedInventoryMenu; -import tc.oc.pgm.stats.TeamStats; import tc.oc.pgm.stats.menu.items.PlayerStatsMenuItem; import tc.oc.pgm.util.text.TextFormatter; @@ -28,16 +25,14 @@ public class TeamStatsMenu extends PagedInventoryMenu { private static final int STARTING_COL = 0; private final Competitor team; - private final TeamStats stats; private final List members; - private ItemStack teamItem; + private final ClickableItem teamItem; public TeamStatsMenu( Competitor team, - TeamStats stats, List members, MatchPlayer viewer, - ItemStack teamItem, + ClickableItem teamItem, SmartInventory parent) { super( translatable("match.stats.team", TextFormatter.convert(team.getColor()), team.getName()), @@ -48,10 +43,8 @@ public TeamStatsMenu( STARTING_ROW, STARTING_COL); this.team = team; - this.stats = stats; this.members = members; this.teamItem = teamItem; - open(); } public Competitor getTeam() { @@ -60,31 +53,18 @@ public Competitor getTeam() { @Override public void init(Player player, InventoryContents contents) { - contents.set(0, 4, ClickableItem.empty(teamItem)); + contents.set(0, 4, teamItem); this.setupPageContents(player, contents); this.addBackButton( contents, translatable("match.stats.title", NamedTextColor.GOLD, TextDecoration.BOLD), - 5, + lastRow(), 4); } @Override public ClickableItem[] getPageContents(Player viewer) { - List items = - members.stream().map(ps -> ps.getClickableItem(viewer)).collect(Collectors.toList()); - - return items.isEmpty() ? null : items.toArray(new ClickableItem[items.size()]); - } - - @Override - public SlotPos getPreviousPageSlot() { - return SlotPos.of(4, 0); - } - - @Override - public SlotPos getNextPageSlot() { - return SlotPos.of(4, 8); + return members.stream().map(p -> p.getClickableItem(viewer)).toArray(ClickableItem[]::new); } @Override diff --git a/core/src/main/java/tc/oc/pgm/stats/menu/items/PlayerStatsMenuItem.java b/core/src/main/java/tc/oc/pgm/stats/menu/items/PlayerStatsMenuItem.java index bd3c948fa2..fac0aeeaf6 100644 --- a/core/src/main/java/tc/oc/pgm/stats/menu/items/PlayerStatsMenuItem.java +++ b/core/src/main/java/tc/oc/pgm/stats/menu/items/PlayerStatsMenuItem.java @@ -2,6 +2,7 @@ import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.Component.translatable; +import static net.kyori.adventure.text.format.NamedTextColor.GRAY; import static tc.oc.pgm.stats.StatsMatchModule.damageComponent; import static tc.oc.pgm.util.nms.NMSHacks.NMS_HACKS; import static tc.oc.pgm.util.nms.PlayerUtils.PLAYER_UTILS; @@ -14,7 +15,6 @@ import java.util.UUID; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -33,7 +33,6 @@ /** Represents a player's stats via player head & lore * */ public class PlayerStatsMenuItem implements MenuItem { - private final TextColor RESET = NamedTextColor.GRAY; private final UUID uuid; private final PlayerStats stats; private final Skin skin; @@ -57,27 +56,27 @@ public List getLore(Player player) { Component statLore = translatable( "match.stats.concise", - RESET, + GRAY, number(stats.getKills(), NamedTextColor.GREEN), number(stats.getDeaths(), NamedTextColor.RED), number(stats.getKD(), NamedTextColor.GREEN)); Component killstreakLore = translatable( "match.stats.killstreak.concise", - RESET, + GRAY, number(stats.getMaxKillstreak(), NamedTextColor.GREEN)); Component damageDealtLore = translatable( "match.stats.damage.dealt", - RESET, + GRAY, damageComponent(stats.getDamageDone(), NamedTextColor.GREEN), damageComponent(stats.getBowDamage(), NamedTextColor.YELLOW)); Component damageReceivedLore = translatable( "match.stats.damage.received", - RESET, + GRAY, damageComponent(stats.getDamageTaken(), NamedTextColor.RED), damageComponent(stats.getBowDamageTaken(), NamedTextColor.GOLD)); Component bowLore = translatable( "match.stats.bow", - RESET, + GRAY, number(stats.getShotsHit(), NamedTextColor.YELLOW), number(stats.getShotsTaken(), NamedTextColor.YELLOW), number(stats.getArrowAccuracy(), NamedTextColor.YELLOW).append(text('%'))); @@ -95,7 +94,7 @@ public List getLore(Player player) { lore.add(TextTranslations.translateLegacy( translatable( "match.stats.flaghold.concise", - RESET, + GRAY, TemporalComponent.briefNaturalApproximate(stats.getLongestFlagHold()) .color(NamedTextColor.AQUA) .decoration(TextDecoration.BOLD, true)), @@ -110,7 +109,7 @@ public List getLore(Player player) { private boolean optionalStat(List lore, Number stat, String key, Player player) { if (stat.doubleValue() > 0) { lore.add(null); - Component loreComponent = translatable(key, RESET, number(stat, NamedTextColor.AQUA)); + Component loreComponent = translatable(key, GRAY, number(stat, NamedTextColor.AQUA)); lore.add(TextTranslations.translateLegacy(loreComponent, player)); return true; } diff --git a/core/src/main/java/tc/oc/pgm/stats/menu/items/TeamStatsMenuItem.java b/core/src/main/java/tc/oc/pgm/stats/menu/items/TeamStatsMenuItem.java index b4595b5f9b..ad88f5d21f 100644 --- a/core/src/main/java/tc/oc/pgm/stats/menu/items/TeamStatsMenuItem.java +++ b/core/src/main/java/tc/oc/pgm/stats/menu/items/TeamStatsMenuItem.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Material; @@ -37,34 +36,22 @@ public class TeamStatsMenuItem implements MenuItem { private final Competitor team; private final Match match; private final TeamStats stats; - private List members; - - private final NamedTextColor RESET = NamedTextColor.GRAY; + private final List members; public TeamStatsMenuItem(Match match, Competitor team, Map playerStats) { - this.team = team; - this.members = Lists.newArrayList(); this.stats = new TeamStats(playerStats.values()); Datastore datastore = PGM.get().getDatastore(); - this.members = - playerStats.entrySet().stream() - .map( - entry -> { - UUID uuid = entry.getKey(); - PlayerStats stats = entry.getValue(); - - MatchPlayer p = match.getPlayer(uuid); - Skin skin = - (p != null) - ? PLAYER_UTILS.getPlayerSkin(p.getBukkit()) - : datastore.getSkin(uuid); - - return new PlayerStatsMenuItem(uuid, stats, skin); - }) - .collect(Collectors.toList()); + this.members = playerStats.entrySet().stream() + .map(entry -> { + UUID id = entry.getKey(); + MatchPlayer p = match.getPlayer(id); + Skin skin = p != null ? PLAYER_UTILS.getPlayerSkin(p.getBukkit()) : datastore.getSkin(id); + return new PlayerStatsMenuItem(id, entry.getValue(), skin); + }) + .toList(); this.match = match; } @@ -78,33 +65,29 @@ public Component getDisplayName() { public List getLore(Player player) { List lore = Lists.newArrayList(); - Component statLore = - translatable( - "match.stats.concise", - RESET, - number(stats.getTeamKills(), NamedTextColor.GREEN), - number(stats.getTeamDeaths(), NamedTextColor.RED), - number(stats.getTeamKD(), NamedTextColor.GREEN)); - - Component damageDealtLore = - translatable( - "match.stats.damage.dealt", - RESET, - damageComponent(stats.getDamageDone(), NamedTextColor.GREEN), - damageComponent(stats.getBowDamage(), NamedTextColor.YELLOW)); - Component damageReceivedLore = - translatable( - "match.stats.damage.received", - RESET, - damageComponent(stats.getDamageTaken(), NamedTextColor.RED), - damageComponent(stats.getBowDamageTaken(), NamedTextColor.GOLD)); - Component bowLore = - translatable( - "match.stats.bow", - RESET, - number(stats.getShotsHit(), NamedTextColor.YELLOW), - number(stats.getShotsTaken(), NamedTextColor.YELLOW), - number(stats.getTeamBowAcc(), NamedTextColor.YELLOW).append(text('%'))); + Component statLore = translatable( + "match.stats.concise", + NamedTextColor.GRAY, + number(stats.getTeamKills(), NamedTextColor.GREEN), + number(stats.getTeamDeaths(), NamedTextColor.RED), + number(stats.getTeamKD(), NamedTextColor.GREEN)); + + Component damageDealtLore = translatable( + "match.stats.damage.dealt", + NamedTextColor.GRAY, + damageComponent(stats.getDamageDone(), NamedTextColor.GREEN), + damageComponent(stats.getBowDamage(), NamedTextColor.YELLOW)); + Component damageReceivedLore = translatable( + "match.stats.damage.received", + NamedTextColor.GRAY, + damageComponent(stats.getDamageTaken(), NamedTextColor.RED), + damageComponent(stats.getBowDamageTaken(), NamedTextColor.GOLD)); + Component bowLore = translatable( + "match.stats.bow", + NamedTextColor.GRAY, + number(stats.getShotsHit(), NamedTextColor.YELLOW), + number(stats.getShotsTaken(), NamedTextColor.YELLOW), + number(stats.getTeamBowAcc(), NamedTextColor.YELLOW).append(text('%'))); lore.add(TextTranslations.translateLegacy(statLore, player)); lore.add(TextTranslations.translateLegacy(damageDealtLore, player)); @@ -122,20 +105,18 @@ public Material getMaterial(Player player) { @Override public void onClick(Player player, ClickType clickType) { new TeamStatsMenu( - team, - stats, - members, - match.getPlayer(player), - createItem(player), - PGM.get().getInventoryManager().getInventory(player).orElse(null)); + team, + members, + match.getPlayer(player), + getClickableItem(player), + PGM.get().getInventoryManager().getInventory(player).orElse(null)) + .open(); } @Override public ItemMeta modifyMeta(ItemMeta meta) { LeatherArmorMeta leatherArmorMeta = (LeatherArmorMeta) meta; - leatherArmorMeta.setColor(team.getFullColor()); - return leatherArmorMeta; } } diff --git a/core/src/main/java/tc/oc/pgm/stats/menu/items/VerboseStatsMenuItem.java b/core/src/main/java/tc/oc/pgm/stats/menu/items/VerboseStatsMenuItem.java index c271ceeaee..1c31ad5602 100644 --- a/core/src/main/java/tc/oc/pgm/stats/menu/items/VerboseStatsMenuItem.java +++ b/core/src/main/java/tc/oc/pgm/stats/menu/items/VerboseStatsMenuItem.java @@ -2,7 +2,6 @@ import static net.kyori.adventure.text.Component.translatable; -import com.google.common.collect.Lists; import java.util.List; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -23,9 +22,8 @@ public Component getDisplayName() { @Override public List getLore(Player player) { - return Lists.newArrayList( - TextTranslations.translateLegacy( - translatable("setting.lore", NamedTextColor.GRAY), player)); + return List.of(TextTranslations.translateLegacy( + translatable("setting.lore", NamedTextColor.GRAY), player)); } @Override