Skip to content

Commit

Permalink
Rework match cancel reason logic (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pugzy authored Jun 1, 2022
1 parent eb79608 commit c7f7c74
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 77 deletions.
20 changes: 2 additions & 18 deletions src/main/java/rip/bolt/ingame/commands/RankedAdminCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static tc.oc.pgm.lib.net.kyori.adventure.text.Component.text;

import java.time.Duration;
import javax.annotation.Nullable;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
Expand All @@ -14,6 +13,7 @@
import rip.bolt.ingame.config.AppData;
import rip.bolt.ingame.ranked.MatchStatus;
import rip.bolt.ingame.ranked.RankedManager;
import rip.bolt.ingame.utils.CancelReason;
import rip.bolt.ingame.utils.Messages;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.match.MatchPhase;
Expand All @@ -22,7 +22,6 @@
import tc.oc.pgm.lib.app.ashcon.intake.parametric.annotation.Switch;
import tc.oc.pgm.lib.app.ashcon.intake.parametric.annotation.Text;
import tc.oc.pgm.lib.net.kyori.adventure.text.format.NamedTextColor;
import tc.oc.pgm.result.TieVictoryCondition;
import tc.oc.pgm.util.Audience;

public class RankedAdminCommands {
Expand Down Expand Up @@ -113,29 +112,14 @@ public void cancel(CommandSender sender, Match match) throws CommandException {
throw new CommandException(ChatColor.RED + "Unable to transition to the cancelled state.");
}

ranked.manualCancel(match);

if (match.getPhase().equals(MatchPhase.STARTING)) {
match.getCountdown().cancelAll();
}

boolean running = match.getPhase().canTransitionTo(MatchPhase.FINISHED);
if (running) {
match.addVictoryCondition(new TieVictoryCondition());
match.finish();
}
ranked.cancel(match, CancelReason.MANUAL_CANCEL);

Audience.get(sender)
.sendMessage(
text(
"Match " + boltMatch.getId() + " has been reported as cancelled.",
NamedTextColor.GRAY));
match.sendMessage(text("Match has been cancelled by an admin.", NamedTextColor.RED));

if (!running) {
Audience.get(match.getCompetitors()).sendMessage(Messages.requeue());
ranked.getPoll().startIn(Duration.ofSeconds(15));
}
}

@Command(aliases = "ban", desc = "Manually queue bans a player", perms = "ingame.staff.ban")
Expand Down
73 changes: 55 additions & 18 deletions src/main/java/rip/bolt/ingame/ranked/CancelManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

import com.google.common.collect.Ordering;
import java.time.Duration;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import rip.bolt.ingame.Ingame;
import javax.annotation.Nullable;
import rip.bolt.ingame.config.AppData;
import rip.bolt.ingame.utils.CancelReason;
import rip.bolt.ingame.utils.Messages;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.match.MatchScope;
import tc.oc.pgm.api.player.MatchPlayer;
Expand All @@ -27,17 +31,40 @@ public CancelManager(PlayerWatcher playerWatcher) {
this.playerWatcher = playerWatcher;
}

protected void cancelMatch(Match match, List<UUID> players) {
playerWatcher.playersAbandoned(players);
playerWatcher.getRankedManager().cancel(match, CancelReason.AUTOMATED_CANCEL);
match.sendMessage(Messages.participationBan());
}

public void clearCountdown() {
if (countdown != null) countdown.cancelCountdown();
this.countdown = null;
}

public void playerJoined(MatchPlayer player) {
if (countdown != null && countdown.player.equals(player.getId())) clearCountdown();
if (canCancel(player.getMatch())) startCountdownIfRequired(player.getMatch());
if (countdown == null || !countdown.contains(player.getId())) return;

// Remove player from countdown
countdown.removePlayer(player.getId());
if (countdown.isEmpty()) {
clearCountdown();
startCountdownIfRequired(player.getMatch());
}
}

public void startCountdownIfRequired(Match match) {
public void startCountdown(Match match, List<UUID> players, @Nullable Duration duration) {
clearCountdown();

countdown =
new LeaverCountdown(
this, match, players, duration == null ? CANCEL_ABSENCE_LENGTH : duration);
}

private void startCountdownIfRequired(Match match) {
// Check if countdown can be started
if (!canCancel(match)) return;

// Check if countdown required for any participants
PlayerWatcher.MatchParticipation participation =
playerWatcher.getParticipations().values().stream()
Expand All @@ -48,24 +75,19 @@ public void startCountdownIfRequired(Match match) {
.max(Comparator.comparing(PlayerWatcher.MatchParticipation::currentAbsentDuration))
.orElse(null);

if (participation == null || (countdown != null && countdown.player == participation.getUUID()))
if (participation == null || (countdown != null && countdown.contains(participation.getUUID())))
return;

// Cancel and clear existing countdown
clearCountdown();

Duration duration =
Ordering.natural()
.max(CANCEL_ABSENCE_LENGTH.minus(participation.absentDuration()), Duration.ZERO);

countdown = new LeaverCountdown(match, participation.getUUID(), duration);
// Start countdown with single player
startCountdown(match, Collections.singletonList(participation.getUUID()), duration);
}

public void playerLeft(MatchPlayer player) {
Match match = player.getMatch();
if (canCancel(match)) {
startCountdownIfRequired(match);
}
startCountdownIfRequired(player.getMatch());
}

private boolean canCancel(Match match) {
Expand All @@ -74,15 +96,18 @@ private boolean canCancel(Match match) {

public static class LeaverCountdown {

private final CancelManager manager;
private final Match match;
private final UUID player;
private final List<UUID> players;
private long duration;

private final ScheduledFuture<?> scheduledFuture;

public LeaverCountdown(Match match, UUID player, Duration duration) {
public LeaverCountdown(
CancelManager cancelManager, Match match, List<UUID> players, Duration duration) {
this.manager = cancelManager;
this.match = match;
this.player = player;
this.players = players;
this.duration = (duration.toMillis() + 999) / 1000;

scheduledFuture =
Expand All @@ -101,21 +126,33 @@ private void broadcast() {
private void tick() {
if (duration <= 0) {
// Cancel match
Ingame.get().getRankedManager().getPlayerWatcher().cancelMatch(match);
this.manager.cancelMatch(match, players);
// Cancel scheduler
cancelCountdown();
} else if (duration % 5 == 0 || duration <= 3) broadcast();

duration--;
}

public boolean contains(UUID player) {
return players.contains(player);
}

public boolean isEmpty() {
return players.isEmpty();
}

public void removePlayer(UUID player) {
players.remove(player);
}

public void cancelCountdown() {
this.scheduledFuture.cancel(false);
}

@Override
public String toString() {
return "LeaverCountdown{" + "player=" + player + ", duration=" + duration + '}';
return "LeaverCountdown{" + "players=" + players + ", duration=" + duration + '}';
}
}
}
59 changes: 26 additions & 33 deletions src/main/java/rip/bolt/ingame/ranked/PlayerWatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import rip.bolt.ingame.Ingame;
import rip.bolt.ingame.api.definitions.Punishment;
import rip.bolt.ingame.config.AppData;
import rip.bolt.ingame.utils.CancelReason;
import rip.bolt.ingame.utils.Messages;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.match.event.MatchFinishEvent;
Expand All @@ -22,7 +23,6 @@
import tc.oc.pgm.api.player.MatchPlayer;
import tc.oc.pgm.events.PlayerJoinMatchEvent;
import tc.oc.pgm.events.PlayerPartyChangeEvent;
import tc.oc.pgm.result.TieVictoryCondition;

public class PlayerWatcher implements Listener {

Expand All @@ -33,20 +33,22 @@ public class PlayerWatcher implements Listener {
private final CancelManager cancelManager;

private final Map<UUID, MatchParticipation> players = new HashMap<>();
private boolean automaticallyCancelled;

public PlayerWatcher(RankedManager rankedManager) {
this.rankedManager = rankedManager;
this.forfeitManager = new ForfeitManager(this);
this.cancelManager = new CancelManager(this);
}

public RankedManager getRankedManager() {
return rankedManager;
}

public ForfeitManager getForfeitManager() {
return forfeitManager;
}

public void addPlayers(List<UUID> uuids) {
automaticallyCancelled = false;
players.clear();
forfeitManager.clearPolls();
cancelManager.clearCountdown();
Expand Down Expand Up @@ -105,22 +107,13 @@ public void onLeave(PlayerPartyChangeEvent event) {

@EventHandler(priority = EventPriority.LOW)
public void onMatchEnd(MatchFinishEvent event) {
if (rankedManager.isManuallyCanceled()) return;

// If a regular match end and duration less than max absent period no bans to check
if (!automaticallyCancelled && event.getMatch().getDuration().compareTo(ABSENT_MAX) > 0) return;
// If match was cancelled, don't bother with the rest
if (rankedManager.getCancelReason() != null) return;

Duration maxAbsenceDuration =
(automaticallyCancelled) ? CancelManager.CANCEL_ABSENCE_LENGTH : ABSENT_MAX;
// Duration less than max absent period no bans to check
if (event.getMatch().getDuration().compareTo(ABSENT_MAX) > 0) return;

List<UUID> abandonedPlayers =
players.values().stream()
.filter(
participation -> participation.absentDuration().compareTo(maxAbsenceDuration) > 0)
.map(MatchParticipation::getUUID)
.collect(Collectors.toList());

if (playersAbandoned(abandonedPlayers)) {
if (playersAbandoned(getParticipationsBelowDuration(ABSENT_MAX))) {
event.getMatch().sendMessage(Messages.participationBan());
}
}
Expand All @@ -136,29 +129,22 @@ public void onMatchStart(MatchStartEvent event) {
if (!AppData.fullTeamsRequired()) return;

// No players are currently missing
if (getOfflinePlayers(event.getMatch()).isEmpty()) return;
List<UUID> offlinePlayers = getOfflinePlayers(event.getMatch());
if (offlinePlayers.isEmpty()) return;

// If a player never joined mark as abandoned
if (playersAbandoned(getNonJoinedPlayers())) {
cancelMatch(event.getMatch());
rankedManager.cancel(event.getMatch(), CancelReason.AUTOMATED_CANCEL);
event.getMatch().sendMessage(Messages.matchStartCancelled());
return;
}

// Set everyone who is not online as "absent"
players.forEach(
(uuid, matchParticipation) -> {
if (event.getMatch().getPlayer(uuid) == null) matchParticipation.playerLeft();
});
players.values().stream()
.filter(participation -> offlinePlayers.contains(participation.getUUID()))
.forEach(MatchParticipation::playerLeft);

cancelManager.startCountdownIfRequired(event.getMatch());
}

public void cancelMatch(Match match) {
automaticallyCancelled = true;
// the order of these two lines should not be changed
rankedManager.postMatchStatus(match, MatchStatus.CANCELLED);
match.addVictoryCondition(new TieVictoryCondition());
match.finish();
cancelManager.startCountdown(event.getMatch(), offlinePlayers, null);
}

public List<UUID> getNonJoinedPlayers() {
Expand All @@ -174,7 +160,14 @@ public List<UUID> getOfflinePlayers(Match match) {
.collect(Collectors.toList());
}

private boolean playersAbandoned(List<UUID> players) {
private List<UUID> getParticipationsBelowDuration(Duration minimumDuration) {
return players.values().stream()
.filter(participation -> participation.absentDuration().compareTo(minimumDuration) >= 0)
.map(MatchParticipation::getUUID)
.collect(Collectors.toList());
}

public boolean playersAbandoned(List<UUID> players) {
if (players.size() <= 5) {
Integer seriesId = Ingame.get().getRankedManager().getMatch().getSeries().getId();
players.forEach(player -> playerAbandoned(player, seriesId));
Expand Down
Loading

0 comments on commit c7f7c74

Please sign in to comment.