package tc.oc.pgm.victory; import java.util.Comparator; import java.util.Set; import javax.inject.Inject; import com.google.common.collect.ImmutableList; import org.bukkit.event.EventBus; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import tc.oc.commons.core.util.RankedSet; import tc.oc.pgm.events.CompetitorAddEvent; import tc.oc.pgm.events.CompetitorRemoveEvent; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.events.MatchEndEvent; import tc.oc.pgm.match.Competitor; import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchScope; /** * Decides when the match should end and who should win * * Regression tests: * * - In a goals + blitz match, a team that completes all their goals should win, * even if they have fewer players than other teams. * - In a goals + blitz match, a team that runs out of players should lose, * even if they have more goals completed. * - When a time limit elapses with a default result, it should end the match, * but not affect the final ranking of competitors. * - When a time limit elapses with a tie result, no team should win, even if * some teams are leading according to other conditions. */ @ListenerScope(MatchScope.LOADED) public class VictoryMatchModule extends MatchModule implements Listener { @Inject private EventBus eventBus; private final VictoryCalculator calculator = new VictoryCalculator(); @Override public void load() { super.load(); calculator().addCompetitors(match.getCompetitors()); invalidateCompetitorRanking(); } @Override public void enable() { super.enable(); match.getScheduler(MatchScope.RUNNING) .createTask(this::invalidateAndCheckEnd); } public VictoryCalculator calculator() { return calculator; } public Comparator<Competitor> victoryOrder() { return calculator().victoryOrder(); } public void setImmediateWinner(Competitor competitor) { calculator().setImmediateWinner(competitor); } public void setVictoryCondition(VictoryCondition condition) { calculator().setVictoryCondition(condition); } /** * Re-sort all {@link Competitor}s from scratch, according to the current {@link VictoryCondition}. * This should be called when the state of the match changes in a way that affects the * rank of any competitors. */ public void invalidateCompetitorRanking() { final ImmutableList<Competitor> before = ImmutableList.copyOf(calculator().rankedCompetitors()); calculator().invalidateRanking(); final ImmutableList<Competitor> after = ImmutableList.copyOf(calculator().rankedCompetitors()); if(!before.equals(after)) { eventBus.callEvent(new RankingsChangeEvent(match, before, after)); } } /** * Return all currently active competitors, ordered by closeness to winning the match. * Competitors that are tied are returned in arbitrary, and inconsistent order. */ public RankedSet<Competitor> rankedCompetitors() { return calculator().rankedCompetitors(); } /** * Return all {@link Competitor}s that are as close as, or closer to winning * the match than any other competitor. Unless the match is empty, the returned * set will always contain at least one competitor. If all competitors are tied * for the lead, the returned set will contain all of them. */ public Set<Competitor> leaders() { return calculator().leaders(); } /** * If some competitors finished ahead of others, return the winners, * otherwise return an empty set. */ public Set<Competitor> winners() { return calculator().winners(); } public boolean shouldMatchEnd() { return match.isFinished() || (match.hasStarted() && calculator().shouldMatchEnd()); } public boolean checkMatchEnd() { if(match.hasStarted() && !match.isFinished() && shouldMatchEnd()) { match.end(); return true; } return false; } public boolean invalidateAndCheckEnd() { invalidateCompetitorRanking(); return checkMatchEnd(); } @EventHandler(priority = EventPriority.LOWEST) public void onCompetitorAddEarly(CompetitorAddEvent event) { calculator().addCompetitor(event.getCompetitor()); } @EventHandler(priority = EventPriority.MONITOR) public void onCompetitorAddLate(CompetitorAddEvent event) { invalidateCompetitorRanking(); } @EventHandler(priority = EventPriority.MONITOR) public void onCompetitorRemove(CompetitorRemoveEvent event) { calculator().removeCompetitor(event.getCompetitor()); invalidateCompetitorRanking(); } @EventHandler(priority = EventPriority.LOWEST) public void onMatchEnd(MatchEndEvent event) { invalidateCompetitorRanking(); } }