ProjectAres/PGM/src/main/java/tc/oc/pgm/tablist/MatchTabView.java

273 lines
11 KiB
Java

package tc.oc.pgm.tablist;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import tc.oc.commons.bukkit.nick.PlayerIdentityChangeEvent;
import tc.oc.commons.bukkit.tablist.TabEntry;
import tc.oc.commons.bukkit.tablist.TabManager;
import tc.oc.commons.bukkit.tablist.TabView;
import tc.oc.commons.bukkit.util.PermissionUtils;
import tc.oc.commons.core.util.DefaultProvider;
import tc.oc.commons.core.util.Numbers;
import tc.oc.pgm.Config;
import tc.oc.pgm.events.PlayerJoinMatchEvent;
import tc.oc.pgm.events.PlayerPartyChangeEvent;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.Party;
import tc.oc.pgm.teams.Team;
import tc.oc.pgm.teams.TeamMatchModule;
public class MatchTabView extends TabView implements Listener {
public static class Factory implements DefaultProvider<Player, MatchTabView> {
@Override
public MatchTabView get(Player key) {
return new MatchTabView(key);
}
}
private final MatchPlayerOrder playerOrder;
private final ListMultimap<Party.Type, MatchPlayer> players = ArrayListMultimap.create();
private final ListMultimap<Team, MatchPlayer> teamPlayers = ArrayListMultimap.create();
private Match match;
private @Nullable TeamMatchModule tmm;
private MatchPlayer matchPlayer;
private TeamOrder teamOrder;
public MatchTabView(Player viewer) {
super(viewer);
this.playerOrder = new MatchPlayerOrder(viewer);
}
@Override
public void enable(TabManager manager) {
super.enable(manager);
manager.getPlugin().eventRegistry().registerListener(this);
}
@Override
public void disable() {
HandlerList.unregisterAll(this);
super.disable();
}
protected MatchTabManager getManager() {
return (MatchTabManager) this.manager;
}
@Override
public void render() {
if(this.manager == null) return;
if(this.match != null && this.isLayoutDirty()) {
this.setHeader(this.getManager().getMapEntry(this.match));
this.setFooter(this.getManager().getFooterEntry(this.match));
Set<MatchPlayer> observers = this.match.getObservingPlayers();
// Number of players/staff on observers
int observingPlayers = 0;
int observingStaff = 0;
if(Config.PlayerList.playersSeeObservers() || matchPlayer.isObservingType()) {
observingPlayers = observers.size();
for(MatchPlayer player : observers) {
if(PermissionUtils.isStaff(player.getBukkit())) observingStaff++;
}
}
int availableRows = this.getHeight();
// Minimum rows required to show all staff observers
int observerRows = Math.min(availableRows, Numbers.divideRoundingUp(observingStaff, this.getWidth()));
if(tmm != null) {
// Render participating teams
Iterator<Team> iter = tmm.teams().sorted(this.teamOrder).iterator();
// Current row to print teams in, will increase by 1 (head) + team player size + 1 (space row after teams)
int currentTeamRow = 0;
do { // Always render the first team row, even if observers could fill up the whole list
// Store all the teams that fit in in a row
List<Team> row = new ArrayList<>(this.getWidth());
for (int i = 0; i < this.getWidth() && iter.hasNext(); i++) row.add(iter.next());
// Max rows allowed: max rows - (current row + reserved rows for obs + 1 (reserved for blank space)
currentTeamRow += renderTeams(currentTeamRow, availableRows - (currentTeamRow + observerRows + 1), row);
renderBlank(currentTeamRow++);
// Need 3 rows for team headers, show someone in the teams, and the extra space row,
// so stop if we don't have that space left, or we ran out of teams
} while (iter.hasNext() && currentTeamRow + observerRows + 3 <= availableRows);
// Expand observer rows until all observers are showing
observerRows = Math.min(availableRows - currentTeamRow, Numbers.divideRoundingUp(observingPlayers, this.getWidth()));
renderBlank(currentTeamRow, availableRows - observerRows);
} else {
List<MatchPlayer> participants = players.get(Party.Type.Participating);
// Minimum rows required by participating players
int participantRows = Math.min(availableRows - addRowIfAny(observerRows), 1 + Numbers.divideRoundingUp(participants.size(), this.getWidth()));
// Expand observer rows until all observers are showing
observerRows = Math.min(availableRows - participantRows, Numbers.divideRoundingUp(observingPlayers, this.getWidth()));
// Expand participant rows to fill whatever if left
participantRows = availableRows - addRowIfAny(observerRows);
this.renderTeam(participants, getManager().getFreeForAllEntry(match), true, 0, this.getWidth(), 0, participantRows);
// Render 1 line space before observers
if (observerRows > 0) renderBlank(availableRows - observerRows + 1);
}
if(observerRows > 0) {
// Render observers
this.renderTeam(players.get(Party.Type.Observing), null, false, 0, this.getWidth(), this.getHeight() - observerRows, this.getHeight());
}
}
super.render();
}
/* --- Render utils --- */
// Add a blank row, but only if there is any obs to show
private int addRowIfAny(int rows) {
return rows <= 0 ? 0 : rows + 1;
}
// Render a blank row at y
private void renderBlank(int y) {
renderBlank(0, this.getWidth(), y, y + 1);
}
// Render blank rows from y1 to y2
private void renderBlank(int y1, int y2) {
renderBlank(0, this.getWidth(), y1, y2);
}
// Fill with blank spaces from x1,x2 to y1,y2
private void renderBlank(int x1, int x2, int y1, int y2) {
for(int y = y1; y < y2; y++) {
for(int x = x1; x < x2; x++) {
this.setSlot(x, y, null);
}
}
}
// Will render a row of teams, max is tab list width, will return the number of rows used
private int renderTeams(int y, int maxHeight, List<Team> teams) {
if (maxHeight <= 1) return 0;
int columnsPerTeam = Math.max(1, this.getWidth() / Math.max(1, teams.size()));
int maxRows = Numbers.divideRoundingUp(teams.stream().mapToInt(Team::getSize).max().orElse(0), columnsPerTeam);
maxRows = Math.min(maxRows + 1, maxHeight);
int x1 = 0;
for (Team team : teams) {
int x2 = Math.min(x1 + columnsPerTeam, this.getWidth());
this.renderTeam(teamPlayers.get(team), getManager().getTeamEntry(team), true, x1, x2, y, y + maxRows);
x1 = x2;
}
// Render remaining spaces to the side of the teams, for example if the row has only 3 teams
renderBlank(x1, this.getWidth(), y, y + maxRows);
return maxRows;
}
// Will render a team and fill all the remaining spaces
private void renderTeam(List<MatchPlayer> players, @Nullable TabEntry header, boolean vertical, final int x1, final int x2, int y1, final int y2) {
if(header != null) {
// Render the header row
for(int x = x1; x < x2; x++) {
this.setSlot(x, y1, x == x1 ? header : null);
}
y1++;
}
// Re-sort team members and render them
Collections.sort(players, this.playerOrder);
Iterator<MatchPlayer> iter = players.iterator();
if(vertical) {
// Fill columns first
for(int x = x1; x < x2; x++) {
for(int y = y1; y < y2; y++) {
this.setSlot(x, y, iter.hasNext() ? this.getManager().getPlayerEntry(iter.next()) : null);
}
}
} else {
// Fill rows first
for(int y = y1; y < y2; y++) {
for(int x = x1; x < x2; x++) {
this.setSlot(x, y, iter.hasNext() ? this.getManager().getPlayerEntry(iter.next()) : null);
}
}
}
}
/* --- End rendering utils --- */
public void onViewerJoinMatch(PlayerJoinMatchEvent event) {
if(this.getViewer() == event.getPlayer().getBukkit()) {
this.match = event.getMatch();
this.matchPlayer = event.getPlayer();
this.teamOrder = new TeamOrder(this.matchPlayer);
this.players.replaceValues(Party.Type.Observing, this.match.getObservingPlayers());
this.players.replaceValues(Party.Type.Participating, this.match.getParticipatingPlayers());
this.tmm = this.match.getMatchModule(TeamMatchModule.class);
if(this.tmm != null) {
for(Team team : this.tmm.getTeams()) {
this.teamPlayers.replaceValues(team, team.getPlayers());
}
}
this.invalidateLayout();
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTeamChange(PlayerPartyChangeEvent event) {
if(this.match != event.getMatch()) return;
if(event.getOldParty() != null) {
this.players.get(event.getOldParty().getType()).removeAll(Collections.singleton(event.getPlayer()));
}
if(event.getNewParty() != null && !this.players.containsEntry(event.getNewParty().getType(), event.getPlayer())) {
this.players.put(event.getNewParty().getType(), event.getPlayer());
}
if(event.getOldParty() instanceof Team) {
this.teamPlayers.get((Team) event.getOldParty()).removeAll(Collections.singleton(event.getPlayer()));
}
if(event.getNewParty() instanceof Team && !this.teamPlayers.containsEntry(event.getNewParty(), event.getPlayer())) {
this.teamPlayers.put((Team) event.getNewParty(), event.getPlayer());
}
this.invalidateLayout();
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onNickChange(PlayerIdentityChangeEvent event) {
this.invalidateLayout();
}
}