xTournament Summary

  1. This plugin links 9 depends to enable players to enter player vs. player tournaments on the server.
  2. The tournament handler accounts for dozens of server-side factors to ensure player stats and other player data is conserved upon entering the temporary tournament gamestate.
  3. These tournaments went on to serve players opportunities to win powerful rewards to aid them on their journey through the overarching MMO.

Below is the plugin's configuration, showcasing all of its core features.

# Duration (in seconds) of party invitations
party-invitation-duration: 60
# Announcements that will be made during a tournament's pre start countdown.
# Each number represents the second of the countdown at which the announcement will be made.
tournament-countdown-announcements:
  60: '&bA &l%tournament_type%&b tournament will start in &l%timer% seconds&b. Type &b&n&l%clickable_join_tournament%&b to participate.'
  30: '&bA &l%tournament_type%&b tournament will start in &l%timer% seconds&b. Type &b&n&l%clickable_join_tournament%&b to participate.'
  10: '&bA &l%tournament_type%&b tournament will start in &l%timer% seconds&b. Type &b&n&l%clickable_join_tournament%&b to participate.'
  5: '&eThe &l%tournament_type%&e tournament will start in &l%timer% seconds&b.'
  4: '&eThe &l%tournament_type%&e tournament will start in &l%timer% seconds&b.'
  3: '&eThe &l%tournament_type%&e tournament will start in &l%timer% seconds&b.'
  2: '&eThe &l%tournament_type%&e tournament will start in &l%timer% seconds&b.'
  1: '&eThe &l%tournament_type%&e tournament will start in &l%timer% seconds&b.'
# Announcements that will be made during a tournament's grace period countdown.
grace-period-countdown-announcements:
  30: '&eThe tournament needs &l%missing_players%&e more players to start | Run &l%clickable_join_tournament%&e to take part.'
  10: '&eThe tournament needs &l%missing_players%&e more players to start | Run &l%clickable_join_tournament%&e to take part.'
  5: '&eThe tournament needs &l%missing_players%&e more players to start | Run &l%clickable_join_tournament%&e to take part.'
  4: '&eThe tournament needs &l%missing_players%&e more players to start | Run &l%clickable_join_tournament%&e to take part.'
  3: '&eThe tournament needs &l%missing_players%&e more players to start | Run &l%clickable_join_tournament%&e to take part.'
  2: '&eThe tournament needs &l%missing_players%&e more players to start | Run &l%clickable_join_tournament%&e to take part.'
  1: '&eThe tournament needs &l%missing_players%&e more players to start | Run &l%clickable_join_tournament%&e to take part.'
# Texts that will be clickable and their attributes.
clickable-texts:
  join-tournament:
    text: '/xtournament join'
    hover: '&9Click here to join the tournament'
    command: '/xtournament join'
  join-party:
    text: '/xtournament part join %player%'
    hover: '&9Click here to join %player%''s party.'
    command: '/xtournament party join %player%'
# Amount of time (in seconds) before tournament matches will start.
pre-match-countdown: 5
# Amount of time (in seconds) matches will last.
match-timer: 300
# Announcements that will be made during the time between brackets.
bracket-countdown-announcements:
  60: '&bThe next bracket matches will start in &l%timer% seconds&b.'
  30: '&bThe next bracket matches will start in &l%timer% seconds&b.'
  10: '&bThe next bracket matches will start in &l%timer% seconds&b.'
  1: '&bThe next bracket matches are starting...'
  
# Arenas that will be used for each tournament type.
arena-lists:
  ONEVSONE:
    - 1v1-1
    - 1v1-2
    - 1v1-3
    - 1v1-4
    - 1v1-5
    - 1v1-6
    - 1v1-7
    - 1v1-8
  TWOVSTWO:
    - 2v2Test1
    - 2v2Test2
    - 2v2Test3
  THREEVSTHREE:
    - 3v3Test1
    - 3v3Test2
    - 3v3Test3
  FOURVSFOUR:
    - 4v4Test1
    - 4v4Test2
    - 4v4Test3

# Commands that will be blocked during a tournament.
# It doesn't need to have the '/' character.
tournament-blocked-commands:
  - /spawn
  - /lobby
  - /warp
  - /suicide
  - /tiers
  - /thevoid
  - /spartan
  - /commander
  - /overlord
  - /decrypter
  - /ranks
  - /gigabeast
  - /voidwalker
  - /exoknight
  - /atomsmasher
  - /back
  - /heal
  - /mine
  - /minewarps
  - /tutorials
  
  
# Regions where players will receive tournament messages.
tournament-regions:
  - '*'

# Symbols assigned for each team.
team-symbols:
  ALLY: '&a&l[&2&l◆&a&l]'
  OPPONENT: '&c&l[&4&l◆&c&l]'
# Text that will display the player's health during a tournament.
health-tag: '&c♥ &f&l%health%'



# Announcements that will be made after a tournament ends.
# The delay is specified in seconds, it accepts decimals.
# The delay is the time from the moment the tournament ends.
#
# PAPI placeholders can be used in the options.
# You can also format party members with %member{number}%. Ex: %member1% shows the leader of the party.
tournament-end-announcements:
  ONEVSONE:
    1:
      delay: 5
      options:
        - '&8&l(&b&l!&8&l) &bWhat a dub &a%player_name%&b! First place secured! > 1v1 Tournament'
        - '&8&l(&b&l!&8&l) &bAbsolutely DOMINANT Victory for 1st place &a%player_name%&b! > 1v1 Tournament'
        - '&8&l(&b&l!&8&l) &bYou did it &a%player_name%&b! First place in the bag! > 1v1 Tournament'
    2:
      delay: 10
      options:
        - '&8&l(&b&l!&8&l) &bGood fight &a%player_name%&b! Second place secured! > 1v1 Tournament'
        - '&8&l(&b&l!&8&l) &bYou faught hard &a%player_name%&b! Second placed earned. > 1v1 Tournament'
        - '&8&l(&b&l!&8&l) &bYou put up a good fight &a%player_name%&b! 2nd place achieved. > 1v1 Tournament'
    3:
      delay: 15
      options:
        - '&8&l(&b&l!&8&l) &bNice try &a%player_name%&b! Third place for you this time. > 1v1 Tournament'
        - '&8&l(&b&l!&8&l) &bGet em next time &a%player_name%&b! You placed third. > 1v1 Tournament'
        - '&8&l(&b&l!&8&l) &bTough defeat, &a%player_name%&b! 3rd place today! > 1v1 Tournament'
  TWOVSTWO:
    1:
      delay: 0
      options:
        - '&8&l(&b&l!&8&l) &bWhat a dub &a%member1% &b and %member2%&b! First place secured! > 2v2 Tournament'
        - '&8&l(&b&l!&8&l) &bAbsolutely DOMINANT Victory for 1st place &a%member1% &b and %member2%&b! > 2v2 Tournament'
        - '&8&l(&b&l!&8&l) &bYou did it &a%member1% &b and %member2%&b! First place in the bag! > 2v2 Tournament'
    2:
      delay: 5
      options:
        - '&8&l(&b&l!&8&l) &bGood fight &a%member1% &b and %member2%&b! Second place secured! > 2v2 Tournament'
        - '&8&l(&b&l!&8&l) &bYou faught hard &a%member1% &b and %member2%&b! Second placed earned. > 2v2 Tournament'
        - '&8&l(&b&l!&8&l) &bYou put up a good fight &a%member1% &b and %member2%&b! 2nd place achieved. > 2v2 Tournament'
    3:
      delay: 10
      options:
        - '&8&l(&b&l!&8&l) &bNice try &a%member1% &b and %member2%&b! Third place for you this time. > 2v2 Tournament'
        - '&8&l(&b&l!&8&l) &bGet em next time &a%member1% &b and %member2%&b! You placed third. > 2v2 Tournament'
        - '&8&l(&b&l!&8&l) &bTough defeat, &a%member1% &b and %member2%&b! 3rd place today! > 2v2 Tournament'
  THREEVSTHREE:
    1:
      delay: 0
      options:
        - '&8&l(&b&l!&8&l) &bWhat a dub &a%member1%&b, &a%member2%&b, and &a%member3%! First place secured! > 3v3 Tournament'
        - '&8&l(&b&l!&8&l) &bAbsolutely DOMINANT Victory for 1st place &a%member1%&b, &a%member2%&b, and &a%member3%&b! > 3v3 Tournament'
        - '&8&l(&b&l!&8&l) &bYou did it &a%member1%&b, &a%member2%&b, and &a%member3%&b! First place in the bag! > 3v3 Tournament'
    2:
      delay: 5
      options:
        - '&8&l(&b&l!&8&l) &bGood fight &a%member1%&b, &a%member2%&b, and %member3%&b! Second place secured! > 3v3 Tournament'
        - '&8&l(&b&l!&8&l) &bYou faught hard &a%member1%&b, &a%member2%&b, and %member3%&b! Second placed earned. > 3v3 Tournament'
        - '&8&l(&b&l!&8&l) &bYou put up a good fight &a%member1%&b, &a%member2%&b, and %member3%&b! 2nd place achieved. > 3v3 Tournament'
    3:
      delay: 10
      options:
        - '&8&l(&b&l!&8&l) &bNice try &a%member1%&b, &a%member2%&b, and %member3%&b! Third place for you this time. > 4v4 Tournament'
        - '&8&l(&b&l!&8&l) &bGet em next time &a%member1%&b, &a%member2%&b, and %member3%&b! You placed third. > 4v4 Tournament'
        - '&8&l(&b&l!&8&l) &bTough defeat, &a%member1%&b, &a%member2%&b, and %member3%&b! 3rd place today! > 4v4 Tournament'
  FOURVSFOUR:
    1:
      delay: 0
      options:
        - '&8&l(&b&l!&8&l) &bWhat a dub &a%member1%&b, &a%member2%&b, &a%member3%&b, and &a%member4%! First place secured! > 3v3 Tournament'
        - '&8&l(&b&l!&8&l) &bAbsolutely DOMINANT Victory for 1st place &a%member1%&b, &a%member2%&b, &a%member3%&b, and &a%member4%! > 4v4 Tournament'
        - '&8&l(&b&l!&8&l) &bYou did it &a%member1%&b, &a%member2%&b, &a%member3%&b, and &a%member4%! First place in the bag! > 4v4 Tournament'
    2:
      delay: 5
      options:
        - '&8&l(&b&l!&8&l) &bGood fight &a%member1%&b, &a%member2%&b, &a%member3%&b, and &a%member4%! Second place secured! > 4v4 Tournament'
        - '&8&l(&b&l!&8&l) &bYou faught hard &a%member1%&b, &a%member2%&b, &a%member3%&b, and &a%member4%! Second placed earned. > 4v4 Tournament'
        - '&8&l(&b&l!&8&l) &bYou put up a good fight &a%member1%&b, &a%member2%&b, &a%member3%&b, and &a%member4%! 2nd place achieved. > 4v4 Tournament'
    3:
      delay: 10
      options:
        - '&8&l(&b&l!&8&l) &bNice try &a%member1%&b, &a%member2%&b, &a%member3%&b, and &a%member4%! Third place for you this time. > 4v4 Tournament'
        - '&8&l(&b&l!&8&l) &bGet em next time &a%member1%&b, &a%member2%&b, &a%member3%&b, and &a%member4%! You placed third. > 4v4 Tournament'
        - '&8&l(&b&l!&8&l) &bTough defeat, &a%member1%&b, &a%member2%&b, &a%member3%&b, and &a%member4%! 3rd place today! > 4v4 Tournament'

# Commands that will be ran for each party member based on their party's score at the end of the tournament.
# Ex.
# 1: Commands that will be ran for the winners.
# 2: Commands that will be ran for the party that lost the last match.
# eliminated: Commands that will be ran for the rest of parties.

# Commands that will be ran for each party member based on their party's score at the end of the tournament.
# Ex.
# 1: Commands that will be ran for the winners.
# 2: Commands that will be ran for the party that lost the last match.
# eliminated: Commands that will be ran for the rest of parties.
tournament-end-commands:
  1:
    - warp xtWinner1 %player_name%
  2:
    - warp xtWinner2 %player_name%
  3:
    - warp xtWinner3 %player_name%
  eliminated:
    - warp xtLoser %player_name%

messages:
  unknown-command: '&cUnknown command. Type &l/xtournament help &cto see the full command list.'
  player-command: '&cNot available for consoles.'
  no-permission: '&cYou don''t have permission to do this.'
  player-not-found: '&cPlayer &l%player%&c not found.'
  invalid-numeric-input: '&cYou must enter a numeric value with no decimals.'
  invalid-numeric-input-non-zero: '&cAmount must be higher than 0.'
  invalid-numeric-input-less-than-zero: '&cAmount must be 0 or higher.'
  arena-not-found: '&cArena &l%arena%&c not found.'
  unknown-tournament-type: '&cUnknown tournament type &l%tournament_type%&c.'

  arena-command-info: 'Allows you to create, modify and delete arenas.'
  arena-command-usage: '/xtournament arena'
  arena-help-text: |
    &b&lArena Commands
    &a/xtournament arena <Name> create &b- &eCreates an arena with the specified name.
    &a/xtournament arena <Arena> viewspawnpoints &b- &eDisplays a list with all the spawnpoints of the arena.
    &a/xtournament arena <Arena> addspawnpoint <TournamentType> <TeamNumber> &b- &eCreates a spawnpoint for the arena.
    &a/xtournament arena <Arena> removespawnpoint <TournamentType> <Index> &b- &eRemoves the specified spawnpoint from the arena.
    &a/xtournament arena <Arena> setspawnpoint <TournamentType> <Index> <TeamNumber> &b- &eSets the specified spawnpoint to your current location.
    &a/xtournament arena <Arena> delete &b- &eDeletes the arena under the specified name.

  arena-already-exists: '&cArena &l%arena%&c already exists.'
  arena-create-command-success: '&aArena &l%arena%&a successfully created.'

  no-arena-spawnpoints: '&c&l%arena%&c doesn''t have any spawnpoints yet.'
  spawnpoints-format: |
    &9%index%.
      &eTeam Number: &3%team_number%
      &eCoordinates: &3X: %x% &9| &3Y: %y% &9| &3Z: %z% &9| &3World: %world%
  viewspawnpoints-text: |
    &b&l1v1 Spawnpoints:
    %1v1_spawnpoints%
    &b&l2v2 Spawnpoints:
    %2v2_spawnpoints%
    &b&l3v3 Spawnpoints:
    %3v3_spawnpoints%
    &b&l4v4 Spawnpoints:
    %4v4_spawnpoints%

  arena-addspawnpoint-command-usage: '/xtournament arena <Arena> addspawnpoint <TournamentType> <TeamNumber>'
  invalid-team-number: '&cTeam number must be &l1&c or &l2&c.'
  arena-addspawnpoint-command-success: '&aSpawnpoint for &l%tournament_type%&a successfully added to &l%arena%&a.'

  arena-removespawnpoint-command-usage: '/xtournament arena <Arena> addspawnpoint <TournamentType> <Index>'
  invalid-index: '&c&l%arena%&c doesn''t have a spawnpoint with index &l%index%&c.'
  arena-removespawnpoint-command-success: '&aSpawnpoint for &l%tournament_type%&a successfully deleted from &l%arena%&a.'

  arena-setspawnpoint-command-usage: '/xtournament arena <Arena> setspawnpoint <TournamentType> <Index> <TeamNumber>'
  arena-setspawnpoint-command-success: '&aSpawnpoint for &l%tournament_type%&a successfully set to index &l%index%&a and team number &l%team_number% for arena &l%arena%&a.'

  arena-setbeacon-command-success: '&a&l%arena%''s &abeacon location successfully set to your location'

  arena-delete-command-success: '&aArena &l%arena%&a successfully deleted.'

  setlobby-command-info: 'Sets the lobby tournaments will use.'
  setlobby-command-usage: '/xtournament setlobby <TournamentType>'
  setlobby-command-success: '&a&l%tournament_type%&a tournament lobby successfully set to your current location.'

  start-command-info: 'Starts a tournament with the specified settings.'
  start-command-usage: '/xtournament start <TournamentType> <MaxPoolSize> <StartTimer> <Kit> [MinimumParticipants] [GracePeriod]'
  already-started-tournament: '&cThere''s a tournament already running.'
  no-lobby-location: '&cThere is no lobby location configured yet. Set one with &l/xtournament setlobby'
  invalid-pool-size: '&cPool size must be at least 2.'
  invalid-minimum-participants: '&cMinimum participants must be at least 2.'
  invalid-minimum-participants-bracket-size: '&c&l%minimum_participants%&c participants don''t result in a perfect bracket.'
  invalid-grace-period: '&cGrace period can''t be lower than 0.'
  not-enough-arenas: '&cThere are not enough arenas to have all matches at the same time.'
  not-enough-spawnpoints: '&c&l%arena%&c doesn''t have enough spawnpoints for team number &l%team_number%&c.'
  start-command-success: '&aA new tournament has started.'

  cancel-command-info: 'Cancels the current tournament for the specified reason.'
  cancel-command-usage: '/xtournament cancel <Reason>'
  cancel-broadcast: '&cThe current tournament has been cancelled. Reason: %reason%'

  join-command-info: 'Joins the current tournament.'
  join-command-usage: '/xtournament join'
  no-tournament: '&cThere are no running tournaments at the moment.'
  #  too-big-party: '&cThe size of your party doesn''t allow you to join the tournament.'
  filled-pool: '&cThe tournament isn''t accepting any more participants.'
  already-participant: '&cYou already joined the tournament.'
  join-command-success: '&aYour party is now participating in the tournament.'

  leave-command-info: 'Leaves the current tournament.'
  leave-command-usage: '/xtournament leave'
  not-in-tournament: '&cYou are not participating in any tournaments right now.'
  leave-command-success: '&aYou successfully left the tournament.'

  party-command-info: 'Handles all operations regarding parties.'
  party-command-usage: '/xtournament party'
  no-party-tournaments: '&cYou can only use party commands during a tournament.'
  party-help-text: |
    &b&lParty Commands
    &a/xtournament party invite <Player> &b- &eInvites the specified player to your party.
    &a/xtournament party join <Player> &b- &eJoins the party of the passed player.

  not-party-owner: '&cOnly party creators can invite other members.'
  party-limit-reached: '&cYour party can''t invite any more members.'
  already-member: '&c&l%target%&c is already in the party.'
  party-invite-success: '&aYou''ve successfully invited &l%targets%&a to join your party.'
  party-invitation: '&eYou''ve been invited to join &l%creator%''s&e party. Type &l%clickable_join_party%&e to join.'

  no-invitation: '&cYou don''t have any party invitations from &l%target%&c.'
  full-party: '&c&l%player%''s&c party is already full.'
  party-join-success: '&aYou successfully joined &l%player%''s&a party.'
  party-join-message: '&8[&a+&8] &7&l%player%&7 joined the party.'

  party-leave-message: '&8[&c-&8] &7&l%player%&7 left the party.'

  progress-command-info: 'Displays the progress of the current tournament.'
  progress-command-usage: '/xtournament progress'
  progress-status-translations:
    NOT_STARTED: 'Not Started'
    IN_PROGRESS: 'In Progress'
    FINISHED: 'Finished'
  progress-status-colors:
    NOT_STARTED: RED
    IN_PROGRESS: AQUA
    FINISHED: GREEN
  progress-text-format: |
    &aR%round% &b- %status%
        &e%completed_matches%&3/&3%total_matches%

  tournamentplayers-command-info: 'Displays a list of the players in the current tournament.'
  tournamentplayers-command-usage: '/xtournament tournamentplayers'
  tournamentplayers-list-entry: '&9- &a%player%'
  tournamentplayers-party-divider: '&d---------------------'

  help-command-info: 'Displays this list.'
  help-command-usage: '/xtournament help'
  help-text-header: '&d&lCommands'
  help-text-command-format: '&e%usage% &d- &a%info%'

  reload-command-info: 'Reloads the plugin''s config.'
  reload-command-usage: '/xtournament reload'
  reload-command-success: '&aConfig reloaded.'

  impossible-party-fill: '&cYour party couldn''t be filled with more players, so it was left out of the tournament.'
  imperfect-bracket-kick: '&cYour party would have caused an imperfect tournament bracket, so you''ve been removed from the tournament. Next time, try joining the tournament earlier.'
  buy-and-advance: '&eYour opponent for this round couldn''t be found, so you receive a buy and advance to the next round.'
  matchup-text: '&eYou''ve been matched with &l%player%&e.'
  party-matchup-text: '&eYou''ve been matched with &l%player%''s&e party.'

  not-enough-participants: '&cThe tournament has been cancelled due to lack of participants.'
  not-complete-party: '&cYou can''t participate on the tournament because you weren''t able to join a complete party.'
  pre-match-text: '&cTournament matches will start in %time%&c.'
  round-win: '&eYou won this round!'
  round-lose: '&cYou lost this round and were eliminated from the tournament.'
  round-tie: '&cYou couldn''t finish the round on time and were eliminated from the tournament.'
  no-winner-tournament: '&cThe tournament has ended with no clear winner.'
  winner-text: '&e&l%player%&e is the winner of the tournament.'
  party-winner-text: '&e&l%player%''s &eparty is the winner of the tournament.'
  blocked-tournament-command: '&cYou can''t run this command during a tournament.'
  block-tournament-jail: '&cTournament participants can''t be jailed.'
  death-broadcast: '&c☠ %team_symbol% &e%player% &c☠'
  match-gamemode-change: '&cYou can''t switch to spectator mode during a match.'

  # Scoreboard used during the pre-tournament countdown
  starting-lobby-scoreboard-title: '&bxTournament'
  starting-lobby-scoreboard-content:
    - '    &e&l%tournament_type%'
    - '&3Required Participants: &b%required_participants%'
    - '&3Current Participants: &b%current_participants%'
    - '&7--------------'
    - '&eStarting in &l%timer%&e seconds'

  # Scoreboard used during the pre-match lobby.
  lobby-scoreboard-title: '&bxTournament'
  lobby-scoreboard-content:
    - '&3Round: &b%round%'
    - '&3Matches: &b%current_match%&3/&b%total_matches%'
    - '%party%'
    - '&7-------------------'
    - '&3Round Opponent: &c%opponent%'
  # These lines will only show if the party is bigger than 1 and they will replace the %party% placeholder.
  party-lines:
    - '&7--------------'
    - '&3Your Party:'
    - '%members%'
  # Line that will show the member of a party.
  party-member-line: '&f%player%'

  # Scoreboard used during a match.
  match-scoreboard-title: '&bxTournament Match'
  # Timer has mm:ss format.
  match-scoreboard-content:
    - 'Match Timer: &e&l%timer%'
    - '&4Opponent Party:'
    - '%opponent_members%'
    - '&8---------------'
    - '&2Your Party:'
    - '%ally_members%'
  # Line that will show the member of the opponent party.
  opponent-party-member: '&c%player% &c♥ &f&l%health%'
  # Line that will show the member of ally party.
  ally-party-member: '&a%player% &c♥ &f&l%health%'
  # Symbol that will be displayed instead of the health when a player is dead.
  death-symbol: '&4☠'

Bracket Handler Code

To ensure sound, error free bracket handling, I led our team in manual QA to hunt out bugs and account for all possible scenarios that would break the bracket.

    Some examples included:

    - A player leaving the server while in the tournament.
    - A player being banned from the server while in the tournament.
    - A player disconnecting then rejoining the server while in a tournament.

import com.atomix.xtournament.files.ConfigManager;
import com.atomix.xtournament.files.TournamentLog;
import com.atomix.xtournament.util.*;
import com.atomix.xtournament.xTournament;
import me.clip.placeholderapi.PlaceholderAPI;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.Sound;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Tournament {
    public static final Map&lt;TournamentType, List&lt;Integer&gt;&gt; VALID_TOURNAMENT_POOL_SIZES = new HashMap&lt;&gt;();

    static {
        // Generate the valid tournament pools for each tournament type
        for (TournamentType tournamentType : TournamentType.values()) {
            int doubledPartySized = tournamentType.getPartySize() * 2;
            List&lt;Integer&gt; validTournamentPoolSizes = new ArrayList&lt;&gt;();
            for (int tournamentPoolSize = doubledPartySized; tournamentPoolSize &lt; 128; tournamentPoolSize *= 2) {
                validTournamentPoolSizes.add(tournamentPoolSize);
            }
            VALID_TOURNAMENT_POOL_SIZES.put(tournamentType, validTournamentPoolSizes);
        }
    }

    private final Map&lt;Party, Integer&gt; partyScores = new LinkedHashMap&lt;&gt;();
    /**
     * This list will contain all the parties that are eliminated from the tournament, the item of index 0 being the
     * first eliminated party, and hence, the party with the lowest score.
     */
    private final List&lt;Party&gt; tournamentLeaderboard = new ArrayList&lt;&gt;();
    private final List&lt;TournamentMatch&gt; pendingMatches = new ArrayList&lt;&gt;();
    private final List&lt;TournamentMatch&gt; currentMatches = new ArrayList&lt;&gt;();
    private final List&lt;TournamentMatch&gt; finishedMatches = new ArrayList&lt;&gt;();

    private final TournamentType tournamentType;
    private final int maximumParticipants;
    private final int minimumParticipants;
    private int startTimerSeconds;
    private final int gracePeriodSeconds;
    private final String kitName;
    private final List&lt;Arena&gt; arenas;
    private final Set&lt;BukkitTask&gt; preStartMatchTasks = new HashSet&lt;&gt;();
    private final TournamentLog tournamentLog;

    private int currentRound = 0;
    private BukkitTask preStartTask;
    private int tournamentPool;
    private Scoreboard startingScoreboard;

    public Tournament(TournamentType tournamentType, int maximumParticipants, int minimumParticipants,
                      int startTimerSeconds, int gracePeriodSeconds, String kitName, List&lt;Arena&gt; arenas) {
        this.tournamentType = tournamentType;
        this.maximumParticipants = maximumParticipants;
        this.minimumParticipants = minimumParticipants;
        this.startTimerSeconds = startTimerSeconds;
        this.gracePeriodSeconds = gracePeriodSeconds;
        this.kitName = kitName;
        this.arenas = arenas;
        this.tournamentLog = new TournamentLog(xTournament.getInstance());
    }

    /**
     * Starts the countdown before the tournament starts.
     */
    public void preStart() {
        xTournament.getInstance().setCurrentTournament(this);
        this.preStartTask = new BukkitRunnable() {
            private String announcementPath = "tournament-countdown-announcements";

            @Override
            public void run() {
                if (startTimerSeconds &lt;= 0) {
                    if (gracePeriodSeconds &gt; 0 && countParticipants() &lt; minimumParticipants
                            && !announcementPath.equals("grace-period-countdown-announcements")) {
                        // A grace period was specified, start grace period
                        startTimerSeconds = gracePeriodSeconds;
                        announcementPath = "grace-period-countdown-announcements";
                        return;
                    }
                    cancel();
                    setupInitialBracket();
                    if (countParticipants() &lt; minimumParticipants) {
                        cancelTournament("Not enough participants.");
                        return;
                    }
                    startInitialRound();
                    BungeeUtil.sendTournamentStatus(TournamentStatus.IN_PROGRESS);
                    return;
                }
                String announcement = ConfigManager.getTimerAnnouncement(announcementPath, startTimerSeconds);
                if (announcement == null) {
                    updateStartingScoreboard();
                    startTimerSeconds--;
                    return;
                }
                announcement = announcement.replace("%tournament_type%", tournamentType.getConfigName());
                announcement = announcement.replace("%timer%", startTimerSeconds + "");
                announcement = announcement.replace("%missing_players%", (minimumParticipants - countParticipants()) + "");

                TextComponent clickableAnnouncement = StringUtil.insertClickableText(announcement,
                        ConfigManager.getClickableJoinTournamentText(), "%clickable_join_tournament%");
                Messager.sendGlobalMessage(clickableAnnouncement, true);
                SFXManager.playGlobalSound(Sound.BLOCK_NOTE_BLOCK_BELL, 0.6F, 1.3F);
                updateStartingScoreboard();
                startTimerSeconds--;
            }
        }.runTaskTimer(xTournament.getInstance(), 0L, 20L);
        BungeeUtil.sendTournamentStatus(TournamentStatus.STARTING);
    }

    /**
     * Updates the pre starting global scoreboard.
     */
    private void updateStartingScoreboard() {
        // Get or create starting scoreboard
        if (startingScoreboard == null) {
            startingScoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
        }
        // Register sidebar objective
        Objective sidebarObjective;
        if (startingScoreboard.getObjective("tournamentPreStart") == null) {
            sidebarObjective = startingScoreboard.registerNewObjective("tournamentPreStart", "dummy", "");
            sidebarObjective.setDisplaySlot(DisplaySlot.SIDEBAR);
        } else {
            sidebarObjective = startingScoreboard.getObjective("tournamentPreStart");
        }
        sidebarObjective.setDisplayName(ConfigManager.getMessage("starting-lobby-scoreboard-title"));
        // Get and format the lines of the scoreboard
        List&lt;String&gt; scoreboardContent = formatLines(ConfigManager.getScoreboardContent("starting-lobby-scoreboard" +
                "-content"));
        ScoreboardUtil.setScoreboardLines(sidebarObjective, scoreboardContent);
    }

    /**
     * Formats the passed list of lines with the tournament's data.
     *
     * @param lines Lines that will be formatted.
     * @return List of lines with the tournament's data.
     */
    private List&lt;String&gt; formatLines(List&lt;String&gt; lines) {
        ListIterator&lt;String&gt; listIterator = lines.listIterator();
        while (listIterator.hasNext()) {
            String line = StringUtil.formatColor(listIterator.next());
            line = line.replace("%tournament_type%", tournamentType.getConfigName());
            line = line.replace("%required_participants%", minimumParticipants + "");
            line = line.replace("%current_participants%", countParticipants() + "");
            line = line.replace("%timer%", (startTimerSeconds &gt; 0 ? startTimerSeconds : gracePeriodSeconds) + "");
            line = line.replace("%round%", currentRound + "");
            line = line.replace("%current_match%", getCurrentRoundFinishedMatchCount() + "");
            line = line.replace("%total_matches%", getCurrentRoundTotalMatchCount() + "");
            listIterator.set(line);
        }
        return lines;
    }

    /**
     * Starts the tournament on its first round.
     */
    public void startInitialRound() {
        clearInventories();
        computeInitialMatches();
        preStartMatches();
    }

    /**
     * Sets the initial bracket, filling incomplete parties if necessary.
     */
    private void setupInitialBracket() {
        // Determine the amount of players the tournament can hold in order to have perfect tournament brackets
        List&lt;Integer&gt; validTournamentPoolSizes = VALID_TOURNAMENT_POOL_SIZES.get(tournamentType);
        int currentParticipants = countParticipants();
        for (Integer validTournamentPoolSize : validTournamentPoolSizes) {
            if (validTournamentPoolSize &gt; currentParticipants) break;

            this.tournamentPool = validTournamentPoolSize;
        }
        // Get the needed parties
        int maxPartySize = tournamentType.getPartySize();
        int neededParties = this.tournamentPool / maxPartySize;
        if (maxPartySize &gt; 1) {
            fillIncompleteParties();
        }
        // Remove parties that generate an imperfect bracket
        while (partyScores.size() &gt; neededParties) {
            Party lastJoinedParty = new ArrayList&lt;&gt;(partyScores.keySet()).get(partyScores.size() - 1);
            lastJoinedParty.unregister();
            lastJoinedParty.broadcastMessage(ConfigManager.getMessage("imperfect-bracket-kick"));
        }
    }

    /**
     * Fills the incomplete parties and removes the participants that are left on an incomplete party.
     */
    private void fillIncompleteParties() {
        // Find the parties that have more than one member, and the ones that have only one member
        int maxPartySize = tournamentType.getPartySize();
        List&lt;Party&gt; squadParties = new ArrayList&lt;&gt;();
        List&lt;Party&gt; soloParties = new ArrayList&lt;&gt;();
        for (Party party : partyScores.keySet()) {
            int partySize = party.getSize();
            if (partySize == maxPartySize) continue;
            if (partySize == 1) {
                soloParties.add(party);
            } else {
                squadParties.add(party);
            }
        }
        if (!squadParties.isEmpty()) {
            // Complete squad parties with solo party members
            Iterator&lt;Party&gt; soloPartyIterator = soloParties.iterator();
            while (soloPartyIterator.hasNext()) {
                Party soloParty = soloPartyIterator.next();
                if (squadParties.isEmpty()) break;

                soloPartyIterator.remove();
                partyScores.remove(soloParty);
                OfflinePlayer soloPartyMember = soloParty.getPlayerMembers().get(0);
                Party squadParty = squadParties.get(0);
                squadParty.addMember(soloPartyMember);
                if (squadParty.getSize() == maxPartySize) {
                    // Party already has maximum size, delete from squad parties list
                    squadParties.remove(0);
                }
            }
        }
        if (soloParties.size() &gt;= maxPartySize) {
            // Create parties with the remaining solo parties
            Map&lt;UUID, Party&gt; partyMembers = new HashMap&lt;&gt;();
            List&lt;Party&gt; mergedSoloParties = new ArrayList&lt;&gt;();
            for (Party soloParty : soloParties) {
                partyScores.remove(soloParty);
                partyMembers.put(soloParty.getPlayerMembers().get(0).getUniqueId(), soloParty);
                if (partyMembers.size() &lt; maxPartySize) continue;

                partyScores.put(new Party(new ArrayList&lt;&gt;(partyMembers.keySet())), 0);
                mergedSoloParties.addAll(partyMembers.values());
                partyMembers.clear();
            }
            soloParties.removeAll(mergedSoloParties);
        }
        // If the amount of parties is odd, remove the last party that joined
        // Remove parties that were unable to be filled
        for (Party squadParty : squadParties) {
            squadParty.broadcastMessage(ConfigManager.getMessage("impossible-party-fill"));
            squadParty.unregister();
        }
        for (Party soloParty : soloParties) {
            soloParty.broadcastMessage(ConfigManager.getMessage("impossible-party-fill"));
            soloParty.unregister();
        }
    }

    /**
     * Saves and clears the inventories of all participants.
     */
    private void clearInventories() {
        for (Party party : partyScores.keySet()) {
            for (TournamentPlayer tournamentPlayerMember : party.getTournamentPlayerMembers()) {
                tournamentPlayerMember.clearInventory();
            }
        }
    }

    /**
     * Computes the PvP matches between parties with the same score. If there is an odd amount of parties, the last
     * party gets a buy to the next round.
     */
    private void computeInitialMatches() {
        pendingMatches.clear();

        List&lt;Party&gt; parties = new ArrayList&lt;&gt;(partyScores.keySet());
        for (int index = 0; index &lt; partyScores.size(); index += 2) {
            Party partyA = parties.get(index);
            Party partyB = parties.get(index + 1);
            TournamentMatch match = new TournamentMatch(partyA, partyB, tournamentType, kitName, false);
            match.announceMatch();
            pendingMatches.add(match);
        }
        this.currentRound += 1;
    }

    /**
     * Computes the PvP matches using the already existent matches.
     */
    private void computeBracketMatches() {
        if (finishedMatches.size() == 2) {
            startThirdPlaceMatch();
            return;
        }
        if (finishedMatches.size() == 1 && !finishedMatches.get(0).isThirdPlaceMatch()) {
            // There's only one party left, declare it the winner
            endTournament(finishedMatches.get(0).getWinner());
            return;
        }
        // If the last match was the third place match, remove it
        if (finishedMatches.get(finishedMatches.size() - 1).isThirdPlaceMatch()) {
            finishedMatches.remove(finishedMatches.size() - 1);
        }
        List&lt;TournamentMatch&gt; newMatches = new ArrayList&lt;&gt;();
        for (int index = 0; index &lt; finishedMatches.size(); index += 2) {
            TournamentMatch matchA = finishedMatches.get(index);
            TournamentMatch matchB = finishedMatches.get(index + 1);
            Party winnerA = matchA.getWinner();
            Party winnerB = matchB.getWinner();
            if (winnerA == null && winnerB == null) continue;
            if (winnerA == null) {
                winnerB.giveBuyAndAdvance();
                continue;
            }
            if (winnerB == null) {
                winnerA.giveBuyAndAdvance();
                continue;
            }
            TournamentMatch newMatch = new TournamentMatch(winnerA, winnerB, tournamentType, kitName, false);
            newMatch.announceMatch();
            newMatches.add(newMatch);
        }
        finishedMatches.clear();
        pendingMatches.addAll(newMatches);
        this.currentRound += 1;
    }

    /**
     * Starts the match that will decide the third place of the tournament.
     */
    private void startThirdPlaceMatch() {
        // Remove both losers from the leaderboard list
        Party loserA = tournamentLeaderboard.remove(tournamentLeaderboard.size() - 1);
        Party loserB = tournamentLeaderboard.remove(tournamentLeaderboard.size() - 1);
        if (!loserA.isOnline() && !loserB.isOnline()) {
            // Select third place by coin toss
            Party thirdPlace = ThreadLocalRandom.current().nextBoolean() ? loserA : loserB;
            // Select fourth place as the opposite of third place
            Party fourthPlace = thirdPlace.equals(loserA) ? loserB : loserA;
            addLoser(fourthPlace);
            addLoser(thirdPlace);
            return;
        }
//        if (!loserA.isOnline()) {
//            // Loser B is third, Loser A is fourth
//            addLoser(loserA);
//            addLoser(loserB);
//        }
//        if (!loserB.isOnline()) {
//            // Loser A is third, Loser B is fourth
//            addLoser(loserB);
//            addLoser(loserA);
//        }
        TournamentMatch newMatch = new TournamentMatch(loserA, loserB, tournamentType, kitName, true);
        newMatch.announceMatch();
        pendingMatches.add(newMatch);
    }

    /**
     * Attempts to pre start the current match queue.
     */
    private void preStartMatches() {
        if (pendingMatches.isEmpty()) return;
        List&lt;Arena&gt; availableArenas = getAvailableArenas();
        Random random = ThreadLocalRandom.current();
        while (!pendingMatches.isEmpty() && !availableArenas.isEmpty()) {
            Arena arena = availableArenas.remove(random.nextInt(availableArenas.size()));
            TournamentMatch match = pendingMatches.remove(0);
            match.register(arena);
            preStartMatch(match);
        }
        updateLobbyScoreboards();
    }

    /**
     * Goes through all the tournament participants and updates their lobby scoreboards.
     */
    private void updateLobbyScoreboards() {
        for (Party party : partyScores.keySet()) {
            if (party.isInMatch()) continue;

            updateLobbyScoreboard(party);
        }
    }

    /**
     * Updates the lobby scoreboard of the passed party.
     *
     * @param party Party whose lobby scoreboard will be updated.
     */
    private void updateLobbyScoreboard(Party party) {
        // Create and name scoreboard
        Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
        Objective sidebarObjective = scoreboard.registerNewObjective("lobbyScoreboard", "dummy", "");
        sidebarObjective.setDisplayName(ConfigManager.getMessage("lobby-scoreboard-title"));
        sidebarObjective.setDisplaySlot(DisplaySlot.SIDEBAR);
        // Set scoreboard data
        List&lt;String&gt; scoreboardContent = formatPartyLines(party,
                ConfigManager.getScoreboardContent("lobby-scoreboard-content"));
        ScoreboardUtil.setScoreboardLines(sidebarObjective, scoreboardContent);
        for (Player player : party.getPlayerMembers()) {
            player.setScoreboard(scoreboard);
        }
    }

    /**
     * Formats the passed line using the passed party's data.
     *
     * @param party Party whose data will be used to format the passed lines.
     * @param lines Lines that will be formatted.
     * @return Formatted lines.
     */
    private List&lt;String&gt; formatPartyLines(Party party, List&lt;String&gt; lines) {
        formatLines(lines);
        formatPartyMembersPlaceholder(party, lines);
        String opponentName = getPartyOpponentName(party);
        // Replace lines
        ListIterator&lt;String&gt; listIterator = lines.listIterator();
        while (listIterator.hasNext()) {
            listIterator.set(listIterator.next().replace("%opponent%", opponentName));
        }
        return lines;
    }

    /**
     * Formats the %party% placeholder from the passed list of lines using the passed party.
     *
     * @param party Party whose members will be formatted into the placeholder.
     * @param lines Lines that will be formatted.
     */
    private void formatPartyMembersPlaceholder(Party party, List&lt;String&gt; lines) {
        if (party.getSize() == 1) {
            // The party has no members, don't display party lines
            StringUtil.formatListPlaceholder("%party%", lines, new ArrayList&lt;&gt;());
            return;
        }
        // Format party lines
        List&lt;String&gt; memberNames = createMemberNameLines(party);
        List&lt;String&gt; partyLines = ConfigManager.getScoreboardContent("party-lines");
        StringUtil.formatListPlaceholder("%members%", partyLines, memberNames);
        StringUtil.formatListPlaceholder("%party%", lines, partyLines);
        formatLines(lines);
    }

    /**
     * Gets the name of the passed party's opponent.
     *
     * @param party Party whose opponent's name will be fetched.
     * @return Name of the passed party's opponent.
     */
    private String getPartyOpponentName(Party party) {
        Party opponent = party.findOponent();
        if (opponent == null || opponent.getSize() == 0) {
            return "";
        }
        String opponentName = Bukkit.getOfflinePlayer(opponent.getCreatorUuid()).getName();
        if (opponent.getSize() == 1) return opponentName;

        return opponentName + "'s Party";
    }

    /**
     * Creates a list with the names of all the members of the passed party.
     *
     * @param party Party whose member names will be added to the list.
     * @return List with the names of all the members of the passed party.
     */
    private List&lt;String&gt; createMemberNameLines(Party party) {
        List&lt;String&gt; memberNames = new ArrayList&lt;&gt;();
        for (UUID memberUuid : party.getMembers()) {
            memberNames.add(Bukkit.getOfflinePlayer(memberUuid).getName());
        }
        return memberNames;
    }

    /**
     * Pre starts the passed match with the passed arena.
     *
     * @param match Math that will be pre started.
     */
    private void preStartMatch(TournamentMatch match) {
        BukkitTask preStartMatchTask = new BukkitRunnable() {
            private int timer = ConfigManager.getPreMatchCountdown();

            @Override
            public void run() {
                if (timer == 0) {
                    cancel();
                    match.start();
                    return;
                }
                match.broadcastMessage(ConfigManager.getMessage("pre-match-text").replace("%time%", timer + ""));
                match.broadcastSound(Sound.BLOCK_NOTE_BLOCK_BELL, 0.6F, 1.0F + (float) timer / 10);
                timer--;
            }
        }.runTaskTimer(xTournament.getInstance(), 0L, 20L);
        preStartMatchTasks.add(preStartMatchTask);
    }

    /**
     * Attempts to finish the current bracket and start the new one if all matches already ended.
     */
    public void attemptBracketEnd() {
        // Check if all matches have ended
        updateLobbyScoreboards();
        if (!pendingMatches.isEmpty()) {
            preStartMatches();
            return;
        }
        for (TournamentMatch match : currentMatches) {
            if (match.isStarting()) return;
            if (match.isRunning()) return;
        }
        computeBracketMatches();
        preStartMatches();
    }

    /**
     * Ends the tournament with the passed winner.
     *
     * @param winner Winner of the tournament.
     */
    private void endTournament(Party winner) {
        if (winner == null) {
            Messager.sendGlobalMessage(ConfigManager.getMessage("no-winner-announcement"));
            return;
        }
        tournamentLeaderboard.add(winner);
        tournamentLog.logWinners(winner);
        String winBroadcast = winner.getSize() == 1 ?
                ConfigManager.getMessage("winner-text") : ConfigManager.getMessage("party-winner-text");
        winBroadcast = winBroadcast.replace("%player%", winner.getPlayerMembers().get(0).getName());
        clearTournamentData();
        runTournamentEndActionsCommands();
        tournamentLeaderboard.clear();
        BungeeUtil.sendTournamentStatus(TournamentStatus.NO_TOURNAMENT);
        Messager.sendGlobalMessage(new ComponentBuilder(winBroadcast).create()[0], true);
        SFXManager.playGlobalSound(Sound.UI_TOAST_CHALLENGE_COMPLETE, 0.3F, 1.2F);
    }

    /**
     * Runs all the commands for each party on the leaderboard and runs the end tournament announcements.
     */
    private void runTournamentEndActionsCommands() {
        int position = 1;
        for (int index = tournamentLeaderboard.size() - 1; index &gt;= 0; index--) {
            Party party = tournamentLeaderboard.get(index);
            TournamentEndAnnouncement tournamentEndAnnouncement = ConfigManager.fetchTournamentEndAnnouncement(tournamentType, position);
            if (tournamentEndAnnouncement != null) {
                sendTournamentEndAnnouncement(party, tournamentEndAnnouncement);
            }
            runTournamentEndActionsCommands(party, ConfigManager.getTournamentEndCommands(position));
            position += 1;
        }
    }

    /**
     * Sends the passed tournament end announcement.
     *
     * @param party               Party the announcement will be formatted with.
     * @param tournamentEndAnnouncement TournamentEndAnnouncement that will be sent.
     */
    private void sendTournamentEndAnnouncement(Party party, TournamentEndAnnouncement tournamentEndAnnouncement) {
        if (tournamentEndAnnouncement == null) return;

        String message = tournamentEndAnnouncement.getMessage();
        if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
            message = PlaceholderAPI.setPlaceholders(party.getCreator(), message);
        }
        List&lt;UUID&gt; partyMembers = party.getMembers();
        for (int index = 0; index &lt; partyMembers.size(); index++) {
            int memberNumber = index + 1;
            message = message.replace("%member" + memberNumber + "%", Bukkit.getOfflinePlayer(partyMembers.get(index)).getName());
        }
        String finalMessage = message;
        Bukkit.getScheduler().runTaskLater(xTournament.getInstance(),
                () -&gt; Messager.sendGlobalMessage(new ComponentBuilder(finalMessage).create()[0], true),
                tournamentEndAnnouncement.getDelayTicks());
    }

    /**
     * Runs the passed list of commands for each member of the party from the console.
     *
     * @param party Party the commands will be ran for.
     * @param commands Commands that will be ran.
     */
    private void runTournamentEndActionsCommands(Party party, List&lt;String&gt; commands) {
        ConsoleCommandSender console = Bukkit.getConsoleSender();
        boolean isPlaceholderApiEnabled = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
        for (String command : commands) {
            for (Player playerMember : party.getPlayerMembers()) {
                if (isPlaceholderApiEnabled) {
                    command = PlaceholderAPI.setPlaceholders(playerMember, command);
                }
                Bukkit.dispatchCommand(console, command);
            }
        }
    }

    /**
     * Cancels the tournament, leaving no winner.
     *
     * @param reason Reason the tournament was cancelled.
     */
    public void cancelTournament(String reason) {
        clearTournamentData();
        Messager.sendGlobalMessage(new ComponentBuilder(ConfigManager.getMessage("cancel-broadcast")
                .replace("%reason%", reason)).create()[0], true);
        SFXManager.playGlobalSound(Sound.ENTITY_VILLAGER_NO, 0.6F, 1.2F);
    }

    /**
     * Clears all the tournament data.
     */
    private void clearTournamentData() {
        pendingMatches.clear();
        // Set the available status of reserved arenas
        Iterator&lt;TournamentMatch&gt; matchIterator = currentMatches.iterator();
        while (matchIterator.hasNext()) {
            TournamentMatch match = matchIterator.next();
            matchIterator.remove();
            match.unregister();
        }
        finishedMatches.clear();
        cancelTasks();
        teleportParticipantsToLobby();
        unregisterParties();

        xTournament plugin = xTournament.getInstance();
        plugin.getDataManager().clearPlayerItemData();
        plugin.setCurrentTournament(null);
    }

    /**
     * Unregisters all the parties that joined the tournament
     */
    private void unregisterParties() {
        Iterator&lt;Party&gt; partyIterator = partyScores.keySet().iterator();
        while (partyIterator.hasNext()) {
            Party party = partyIterator.next();
            partyIterator.remove();
            party.unregister();
        }
    }

    /**
     * Cancels all potentially active tasks.
     */
    private void cancelTasks() {
        if (preStartTask != null) {
            preStartTask.cancel();
        }
        for (BukkitTask preStartMatchTask : preStartMatchTasks) {
            preStartMatchTask.cancel();
        }
    }

    /**
     * Teleports all the participants to the lobby.
     */
    private void teleportParticipantsToLobby() {
        Location lobbyLocation = xTournament.getInstance().getDataManager().getLobbyLocation(tournamentType);
        for (Party party : partyScores.keySet()) {
            party.teleport(lobbyLocation);
        }
    }

    /**
     * Gets the score of the passed party.
     *
     * @param party Party whose score will be returned.
     * @return Score of the passed party.
     */
    public int getPartyScore(Party party) {
        return partyScores.getOrDefault(party, 0);
    }

    /**
     * Increases the passed party score by 1.
     *
     * @param party Party whose score will be increased.
     */
    public void increasePartyScore(Party party) {
        partyScores.put(party, getPartyScore(party) + 1);
    }

    public List&lt;Party&gt; getParticipants() {
        return new ArrayList&lt;&gt;(partyScores.keySet());
    }

    public boolean isParticipant(Party party) {
        return partyScores.containsKey(party);
    }

    public void addParticipant(Party party) {
        for (Player playerMember : party.getPlayerMembers()) {
            playerMember.setScoreboard(startingScoreboard);
        }
        partyScores.put(party, 0);
        party.teleport(xTournament.getInstance().getDataManager().getLobbyLocation(tournamentType));
    }

    public void removeParticipant(Party party) {
        partyScores.remove(party);
    }

    public void addLoser(Party party) {
        tournamentLeaderboard.add(party);
    }

    /**
     * Checks if the tournament already met the maximum participant pool.
     *
     * @return True if the tournament already met the maximum participant pool.
     */
    public boolean isFull() {
        return countParticipants() &gt;= maximumParticipants;
    }

    /**
     * Counts and returns the number of participants between all parties.
     *
     * @return Number of participants between all parties.
     */
    public int countParticipants() {
        int participants = 0;
        for (Party party : partyScores.keySet()) {
            participants += party.getSize();
        }
        return participants;
    }

    public TournamentType getTournamentType() {
        return tournamentType;
    }

    public boolean isRunning() {
        return getCurrentRound() &gt; 0;
    }

    public int getCurrentRound() {
        return currentRound;
    }

    /**
     * Returns the total amount of rounds the tournament will have.
     *
     * @return Total amount of rounds the tournament will have.
     */
    public int getTotalRounds() {
        int totalRounds = 1;
        int matchSize = tournamentType.getPartySize() * 2;

        for (int tournamentPool = matchSize; tournamentPool &lt; this.tournamentPool; tournamentPool *= 2) {
            totalRounds += 1;
        }
        return totalRounds;
    }

    public int getCurrentRoundFinishedMatchCount() {
        return finishedMatches.size();
    }

    public int getCurrentRoundTotalMatchCount() {
        return finishedMatches.size() + pendingMatches.size() + currentMatches.size();
    }

    /**
     * Calculates the total match count for the passed round.
     *
     * @param round Round whose total matches will be calculated.
     * @return Total match count for the passed round.
     */
    public int calculateTotalRoundMatchCount(int round) {
        int roundDifference = Math.abs(round - currentRound);
        int currentTotalMatches = getCurrentRoundTotalMatchCount();
        if (round &gt; currentRound) {
            // Iterate through round difference and reduce current total matches
            for (int i = 0; i &lt; roundDifference; i++) {
                currentTotalMatches /= 2;
            }
        } else {
            // Iterate through round difference and increase current total matches
            for (int i = 0; i &lt; roundDifference; i++) {
                currentTotalMatches *= 2;
            }
        }
        return currentTotalMatches;
    }

    /**
     * Returns all the arenas that aren't being used for matches.
     *
     * @return Arenas that aren't being used for matches.
     */
    private List&lt;Arena&gt; getAvailableArenas() {
        return arenas.stream().filter(arena -&gt; !arena.isBeingUsed()).collect(Collectors.toList());
    }

    public List&lt;TournamentMatch&gt; getPendingMatches() {
        return pendingMatches;
    }

    public List&lt;TournamentMatch&gt; getCurrentMatches() {
        return currentMatches;
    }

    public List&lt;TournamentMatch&gt; getFinishedMatches() {
        return finishedMatches;
    }

    public List&lt;TournamentMatch&gt; getAllMatches() {
        return Stream.concat(pendingMatches.stream(), Stream.concat(currentMatches.stream(), finishedMatches.stream())).toList();
    }

    public TournamentLog getTournamentLog() {
        return tournamentLog;
    }
}

Bracket Chart

Tournaments in this build are single elimination, offering team sizes of 1v1's, 2v2's, 3v3's, and 4v4's, with bracket sizes of up to 128 teams.

A visual representation of the tournament brackets that xTournament created and ran.