Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow smoother restarts with third party integration #1484

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/src/main/java/tc/oc/pgm/PGMConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public final class PGMConfig implements Config {
// restart.*
private final Duration uptimeLimit;
private final long matchLimit;
private final boolean pollMapOnRestart;

// gameplay.*
private final boolean woolRefill;
Expand Down Expand Up @@ -179,6 +180,7 @@ public final class PGMConfig implements Config {

this.uptimeLimit = parseDuration(config.getString("restart.uptime", "1d"));
this.matchLimit = parseInteger(config.getString("restart.match-limit", "30"));
this.pollMapOnRestart = parseBoolean(config.getString("restart.poll-map-on-restart", "false"));

this.woolRefill = parseBoolean(config.getString("gameplay.refill-wool", "true"));
this.griefScore =
Expand Down Expand Up @@ -533,6 +535,10 @@ public long getMatchLimit() {
return matchLimit;
}

public boolean shouldPollMapOnRestart() {
return pollMapOnRestart;
}

@Override
public long getMinimumPlayers() {
return minPlayers;
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ public interface Config {
*/
long getMatchLimit();

/**
* If a map pool vote should be started even when the server restarts.
*
* @return If a vote should be started.
*/
boolean shouldPollMapOnRestart();

/**
* Gets the minimum number of players for a match to start.
*
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/Modules.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import tc.oc.pgm.crafting.CraftingMatchModule;
import tc.oc.pgm.crafting.CraftingModule;
import tc.oc.pgm.cycle.CycleMatchModule;
import tc.oc.pgm.cycle.SmoothRestartModule;
import tc.oc.pgm.damage.DamageMatchModule;
import tc.oc.pgm.damage.DamageModule;
import tc.oc.pgm.damage.DisableDamageMatchModule;
Expand Down Expand Up @@ -225,6 +226,7 @@ void registerAll() {
register(PlayerTimeMatchModule.class, PlayerTimeMatchModule::new);
register(SpectateMatchModule.class, SpectateMatchModule::new);
register(DamageHistoryMatchModule.class, DamageHistoryMatchModule::new);
register(SmoothRestartModule.class, SmoothRestartModule::new);

register(ProjectileTrailMatchModule.class, ProjectileTrailMatchModule::new);

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/tc/oc/pgm/command/CancelCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public final class CancelCommand {
@CommandDescription("Cancels all countdowns")
@Permission(Permissions.STOP)
public void cancel(CommandSender sender, Audience audience, Match match) {
if (RestartManager.isQueued()) {
if (RestartManager.getInstance().isQueued()) {
match.callEvent(new CancelRestartEvent());
audience.sendMessage(translatable("admin.cancelRestart.restartUnqueued", NamedTextColor.RED));
return;
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/java/tc/oc/pgm/command/MapOrderCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void setNext(
@Flag(value = "force", aliases = "f") boolean force,
@Flag(value = "reset", aliases = "r") boolean reset,
@Argument("map") @Default(CURRENT) @FlagYielding MapInfo map) {
if (RestartManager.isQueued() && !force) {
if (RestartManager.getInstance().isQueued() && !force) {
throw exception("map.setNext.confirm");
}

Expand All @@ -77,8 +77,8 @@ public void setNext(

mapOrder.setNextMap(map);

if (RestartManager.isQueued()) {
RestartManager.cancelRestart();
if (RestartManager.getInstance().isQueued()) {
RestartManager.getInstance().cancelRestart();
viewer.sendWarning(translatable("admin.cancelRestart.restartUnqueued", NamedTextColor.GREEN));
}

Expand Down
6 changes: 2 additions & 4 deletions core/src/main/java/tc/oc/pgm/command/RestartCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import org.incendo.cloud.annotations.Permission;
import tc.oc.pgm.api.Permissions;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.restart.RequestRestartEvent;
import tc.oc.pgm.restart.RestartManager;
import tc.oc.pgm.util.Audience;

Expand All @@ -25,8 +24,6 @@ public void restart(
Match match,
@Argument("duration") Duration duration,
@Flag(value = "force", aliases = "f") boolean force) {
RestartManager.queueRestart("Restart requested via /queuerestart command", duration);

if (force && match.isRunning()) {
match.finish();
}
Expand All @@ -37,6 +34,7 @@ public void restart(
audience.sendMessage(translatable("admin.queueRestart.restartingNow", NamedTextColor.GREEN));
}

match.callEvent(new RequestRestartEvent());
RestartManager.getInstance()
.queueRestart("Restart requested via /queuerestart command", duration);
}
}
28 changes: 13 additions & 15 deletions core/src/main/java/tc/oc/pgm/cycle/CycleCountdown.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,22 @@ public class CycleCountdown extends MatchCountdown {

// Number of seconds before a cycle occurs to start loading the next match.
// This eases stress on the main thread when handling lots of players.
private int preloadSecs;
protected int preloadSecs;

private MapInfo nextMap;
protected MapInfo nextMap;
private MatchFactory nextMatch;

public CycleCountdown(Match match) {
super(match, BossBar.Color.BLUE);

try {
this.preloadSecs =
TextParser.parseInteger(
PGM.get()
.getConfiguration()
.getExperiments()
.getOrDefault("match-preload-seconds", "")
.toString(),
Range.atLeast(0));
this.preloadSecs = TextParser.parseInteger(
PGM.get()
.getConfiguration()
.getExperiments()
.getOrDefault("match-preload-seconds", "")
.toString(),
Range.atLeast(0));
} catch (TextException t) {
// No-op, since this is experimental
}
Expand All @@ -54,16 +53,15 @@ protected Component formatText() {
mapName != null ? translatable("map.cycledMap", mapName) : translatable("map.cycled");
} else {
Component secs = secondsRemaining(NamedTextColor.DARK_RED);
cycleComponent =
mapName != null
? translatable("map.cycleMap", mapName, secs)
: translatable("map.cycle", secs);
cycleComponent = mapName != null
? translatable("map.cycleMap", mapName, secs)
: translatable("map.cycle", secs);
}

return cycleComponent.color(NamedTextColor.DARK_AQUA);
}

private void checkSetNext() {
protected void checkSetNext() {
final MapOrder mapOrder = PGM.get().getMapOrder();
if (remaining.getSeconds() <= preloadSecs) {
if (nextMatch != null) return;
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/tc/oc/pgm/cycle/CycleMatchModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void onMatchEnd(MatchFinishEvent event) {
final Match match = event.getMatch();
mapOrder.matchEnded(match);

if (!RestartManager.isQueued()) {
if (!RestartManager.getInstance().isQueued()) {
Duration duration = mapOrder.getCycleTime();

if (!duration.isNegative()) {
Expand Down
56 changes: 56 additions & 0 deletions core/src/main/java/tc/oc/pgm/cycle/FakeCycleCountdown.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package tc.oc.pgm.cycle;

import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.translatable;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import tc.oc.pgm.api.PGM;
import tc.oc.pgm.api.map.MapOrder;
import tc.oc.pgm.api.match.Match;

public class FakeCycleCountdown extends CycleCountdown {

private final SmoothRestartModule smoothRestartModule;

public FakeCycleCountdown(Match match, SmoothRestartModule smoothRestartModule) {
super(match);
this.smoothRestartModule = smoothRestartModule;
}

@Override
protected Component formatText() {
Component mapName = nextMap == null ? null : text(nextMap.getName(), NamedTextColor.AQUA);

TranslatableComponent cycleComponent;
if (remaining.isZero()) {
cycleComponent =
mapName != null ? translatable("map.cycledMap", mapName) : translatable("map.cycled");
} else {
Component secs = secondsRemaining(NamedTextColor.DARK_RED);
cycleComponent = mapName != null
? translatable("map.cycleMap", mapName, secs)
: translatable("map.cycle", secs);
}

return cycleComponent.color(NamedTextColor.GOLD);
}

@Override
protected void checkSetNext() {
final MapOrder mapOrder = PGM.get().getMapOrder();
if (remaining.getSeconds() <= preloadSecs) {
if (nextMap != null) return;
nextMap = mapOrder.popNextMap();
smoothRestartModule.updateSelectedMap(nextMap);
Bukkit.broadcastMessage(nextMap.toString());
} else {
nextMap = mapOrder.getNextMap();
if (nextMap != null) {
smoothRestartModule.updateSelectedMap(nextMap);
}
}
}
}
107 changes: 107 additions & 0 deletions core/src/main/java/tc/oc/pgm/cycle/SmoothRestartModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package tc.oc.pgm.cycle;

import static net.kyori.adventure.text.Component.text;

import java.util.concurrent.TimeUnit;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import tc.oc.pgm.api.PGM;
import tc.oc.pgm.api.map.MapInfo;
import tc.oc.pgm.api.map.MapOrder;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.match.MatchModule;
import tc.oc.pgm.api.match.MatchScope;
import tc.oc.pgm.events.CountdownEndEvent;
import tc.oc.pgm.events.CountdownStartEvent;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.restart.CancelRestartEvent;
import tc.oc.pgm.restart.RequestRestartEvent;
import tc.oc.pgm.rotation.MapPoolManager;
import tc.oc.pgm.rotation.pools.MapPool;
import tc.oc.pgm.rotation.pools.VotingPool;
import tc.oc.pgm.rotation.vote.MapPoll;

@ListenerScope(MatchScope.LOADED)
public class SmoothRestartModule implements MatchModule, Listener {

private final Match match;
private FakeCycleCountdown countdown;
private MapPoll currentPoll;

public SmoothRestartModule(Match match) {
this.match = match;
}

public void updateSelectedMap(MapInfo nextMap) {
if (nextMap == null) return;
Bukkit.broadcastMessage(nextMap.toString());
}

@EventHandler(priority = EventPriority.LOW)
public void onRequestRestart(RequestRestartEvent event) {
// Randomly allow for regular restarts
if (match.getRandom().nextBoolean()) {
match.sendMessage(text("real restart"));
return;
}

match.sendMessage(text("fake cycle"));

countdown = new FakeCycleCountdown(match, this);
event.setRestartCountdown(countdown);
}

public void tryStartCycleCountdown() {
countdown = new FakeCycleCountdown(match, this);
match.getCountdown().start(countdown, PGM.get().getConfiguration().getCycleTime());
}

@EventHandler
public void onRestartCancel(CancelRestartEvent event) {
if (match.getCountdown().isRunning(countdown)) {
match.getCountdown().cancel(countdown);
}
}

@EventHandler(priority = EventPriority.MONITOR)
public void onCountdownStart(CountdownStartEvent event) {
if (!(event.getCountdown() instanceof FakeCycleCountdown)) return;

// // TODO: check that we are on a voted pool
MapOrder mapOrder = PGM.get().getMapOrder();

MapInfo nextMap = mapOrder.getNextMap();
if (nextMap != null) Bukkit.broadcastMessage("next map: " + nextMap.getName());

if (mapOrder instanceof MapPoolManager) {

MapPool pool = ((MapPoolManager) mapOrder).getActiveMapPool();

if (pool instanceof VotingPool votingPool) {

match
.getExecutor(MatchScope.LOADED)
.schedule(
() -> {
votingPool.startMapPoll(match);
},
5,
TimeUnit.SECONDS);
}
}
}

@EventHandler(priority = EventPriority.MONITOR)
public void onCountdownEnd(CountdownEndEvent event) {
if (!(event.getCountdown() instanceof FakeCycleCountdown)) return;

Bukkit.getServer().broadcastMessage("next map: " + countdown.nextMap);

event.getMatch().sendMessage(text("get the map"));

// TODO: how to actual restart?
// RestartListener.getInstance().startRestartCountdown(event.getMatch());
}
}
21 changes: 16 additions & 5 deletions core/src/main/java/tc/oc/pgm/restart/RequestRestartEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.Plugin;
import tc.oc.pgm.countdowns.Countdown;

public class RequestRestartEvent extends Event {

private Countdown restartCountdown;

public Countdown getRestartCountdown() {
return restartCountdown;
}

public void setRestartCountdown(Countdown restartCountdown) {
this.restartCountdown = restartCountdown;
}

public class Deferral {
private final Plugin plugin;

Expand All @@ -22,22 +33,22 @@ public Plugin getPlugin() {
* and can be discarded.
*/
public void remove() {
RestartManager.removeDeferral(this);
RestartManager.getInstance().removeDeferral(this);
}

public boolean isDeferring() {
return RestartManager.isDeferredBy(this);
return RestartManager.getInstance().isDeferredBy(this);
}
}

private static final HandlerList handlers = new HandlerList();

public Deferral defer(Plugin plugin) {
Deferral deferral = new Deferral(plugin);
RestartManager.addDeferral(deferral);
RestartManager.getInstance().addDeferral(deferral);
return deferral;
}

private static final HandlerList handlers = new HandlerList();

@Override
public HandlerList getHandlers() {
return handlers;
Expand Down
Loading
Loading