feat(admin): add game lifecycle controls
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package games.dmg.spigottyrant;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class GameLifecycleService {
|
||||
public LifecycleState start(
|
||||
PersistentState state,
|
||||
UUID tyrantId,
|
||||
Optional<UUID> vigilanteId,
|
||||
Instant now
|
||||
) {
|
||||
if (state.game().lifecycle() != GameLifecycle.UNSTARTED) {
|
||||
throw new IllegalStateException("game has already been started");
|
||||
}
|
||||
if (vigilanteId.filter(tyrantId::equals).isPresent()) {
|
||||
throw new IllegalArgumentException("Tyrant and Vigilante must be different players");
|
||||
}
|
||||
GameState game = new GameState(
|
||||
GameLifecycle.RUNNING,
|
||||
Optional.of(tyrantId),
|
||||
vigilanteId,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Duration.ZERO,
|
||||
0,
|
||||
1,
|
||||
Set.of()
|
||||
);
|
||||
return new LifecycleState(game, state.players());
|
||||
}
|
||||
|
||||
public LifecycleState pause(PersistentState state, Instant now) {
|
||||
if (state.game().lifecycle() != GameLifecycle.RUNNING) {
|
||||
throw new IllegalStateException("only a running game can be paused");
|
||||
}
|
||||
GameState current = state.game();
|
||||
GameState paused = new GameState(
|
||||
GameLifecycle.PAUSED,
|
||||
current.tyrantId(),
|
||||
current.vigilanteId(),
|
||||
current.pendingTyrant(),
|
||||
current.pendingVigilante(),
|
||||
Optional.of(now),
|
||||
current.accumulatedPausedTime(),
|
||||
current.tyrantLevel(),
|
||||
current.unspentChoices(),
|
||||
current.purchases()
|
||||
);
|
||||
return new LifecycleState(paused, state.players());
|
||||
}
|
||||
|
||||
public LifecycleState resume(PersistentState state, Instant now) {
|
||||
GameState current = state.game();
|
||||
if (current.lifecycle() != GameLifecycle.PAUSED || current.pausedAt().isEmpty()) {
|
||||
throw new IllegalStateException("only a paused game can be resumed");
|
||||
}
|
||||
Instant pausedAt = current.pausedAt().orElseThrow();
|
||||
Duration pausedFor = Duration.between(pausedAt, now);
|
||||
if (pausedFor.isNegative()) {
|
||||
throw new IllegalArgumentException("resume time must not precede pause time");
|
||||
}
|
||||
GameState resumed = new GameState(
|
||||
GameLifecycle.RUNNING,
|
||||
current.tyrantId(),
|
||||
current.vigilanteId(),
|
||||
shiftSelection(current.pendingTyrant(), pausedFor),
|
||||
shiftSelection(current.pendingVigilante(), pausedFor),
|
||||
Optional.empty(),
|
||||
current.accumulatedPausedTime().plus(pausedFor),
|
||||
current.tyrantLevel(),
|
||||
current.unspentChoices(),
|
||||
current.purchases()
|
||||
);
|
||||
Map<UUID, PlayerState> players = new HashMap<>();
|
||||
for (PlayerState player : state.players().values()) {
|
||||
players.put(player.playerId(), shift(player, pausedFor));
|
||||
}
|
||||
return new LifecycleState(resumed, players);
|
||||
}
|
||||
|
||||
public LifecycleState reset(PersistentState state) {
|
||||
Map<UUID, PlayerState> players = new HashMap<>();
|
||||
for (PlayerState player : state.players().values()) {
|
||||
players.put(player.playerId(), new PlayerState(
|
||||
player.playerId(),
|
||||
player.latestName(),
|
||||
player.lastLogin(),
|
||||
player.optedOutUntil(),
|
||||
TyrantClass.NONE,
|
||||
Optional.empty(),
|
||||
Map.of(),
|
||||
Set.of(),
|
||||
player.capturedMobs()
|
||||
));
|
||||
}
|
||||
return new LifecycleState(GameState.empty(), players);
|
||||
}
|
||||
|
||||
private static Optional<PendingSelection> shiftSelection(
|
||||
Optional<PendingSelection> selection,
|
||||
Duration pausedFor
|
||||
) {
|
||||
return selection.map(value -> new PendingSelection(
|
||||
value.candidateId(), value.expiresAt().plus(pausedFor)
|
||||
));
|
||||
}
|
||||
|
||||
private static PlayerState shift(PlayerState player, Duration pausedFor) {
|
||||
Map<Ability, Instant> cooldowns = new EnumMap<>(Ability.class);
|
||||
player.cooldownEnds().forEach((ability, deadline) ->
|
||||
cooldowns.put(ability, deadline.plus(pausedFor))
|
||||
);
|
||||
return new PlayerState(
|
||||
player.playerId(),
|
||||
player.latestName(),
|
||||
shiftInstant(player.lastLogin(), pausedFor),
|
||||
shiftInstant(player.optedOutUntil(), pausedFor),
|
||||
player.tyrantClass(),
|
||||
player.followerOf(),
|
||||
cooldowns,
|
||||
player.readyAbilityItems(),
|
||||
player.capturedMobs()
|
||||
);
|
||||
}
|
||||
|
||||
private static Optional<Instant> shiftInstant(Optional<Instant> value, Duration duration) {
|
||||
return value.map(instant -> instant.plus(duration));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user