From c832a6648baa183528b0b984caa1ca055b89d964 Mon Sep 17 00:00:00 2001 From: akastijn Date: Sun, 24 May 2026 01:30:11 +0200 Subject: [PATCH] Implement round progression system with countdown, stages, and `/hg start` command --- .../commands/subcommands/StartRound.java | 36 +++++++++++ .../com/alttd/hunger_games/config/Config.java | 62 +++++++++++++++++++ .../alttd/hunger_games/config/Messages.java | 2 + .../hunger_games/data_objects/GameStage.java | 15 +++++ .../hunger_games/game/GameStageHandler.java | 19 ++++++ .../hunger_games/services/PlayerService.java | 5 -- .../alttd/hunger_games/services/Round.java | 51 ++++++++++++--- .../hunger_games/services/RoundListener.java | 2 - .../hunger_games/services/RoundService.java | 9 +-- 9 files changed, 179 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/alttd/hunger_games/commands/subcommands/StartRound.java create mode 100644 src/main/java/com/alttd/hunger_games/data_objects/GameStage.java create mode 100644 src/main/java/com/alttd/hunger_games/game/GameStageHandler.java diff --git a/src/main/java/com/alttd/hunger_games/commands/subcommands/StartRound.java b/src/main/java/com/alttd/hunger_games/commands/subcommands/StartRound.java new file mode 100644 index 0000000..17fdb69 --- /dev/null +++ b/src/main/java/com/alttd/hunger_games/commands/subcommands/StartRound.java @@ -0,0 +1,36 @@ +package com.alttd.hunger_games.commands.subcommands; + +import com.alttd.hunger_games.commands.SubCommand; +import com.alttd.hunger_games.config.Messages; +import com.alttd.hunger_games.services.Round; +import lombok.RequiredArgsConstructor; +import org.bukkit.command.CommandSender; + +import java.util.List; + +@RequiredArgsConstructor +public class StartRound extends SubCommand { + + private final Round round; + + @Override + public boolean onCommand(CommandSender commandSender, String[] args) { + round.startRound(); + return true; + } + + @Override + public String getName() { + return "start"; + } + + @Override + public List getTabComplete(CommandSender commandSender, String[] args) { + return List.of(); + } + + @Override + public String getHelpMessage() { + return Messages.HELP.START_ROUND; + } +} diff --git a/src/main/java/com/alttd/hunger_games/config/Config.java b/src/main/java/com/alttd/hunger_games/config/Config.java index 295b736..83638fe 100644 --- a/src/main/java/com/alttd/hunger_games/config/Config.java +++ b/src/main/java/com/alttd/hunger_games/config/Config.java @@ -1,8 +1,14 @@ package com.alttd.hunger_games.config; +import com.alttd.hunger_games.data_objects.GameStage; import lombok.extern.slf4j.Slf4j; +import org.bukkit.configuration.ConfigurationSection; import java.io.File; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; @Slf4j public class Config extends AbstractConfig { @@ -31,4 +37,60 @@ public class Config extends AbstractConfig { private static void load() { } } + + public static class ROUND { + private static final String prefix = "round."; + + public static Duration COUNTDOWN = Duration.ofSeconds(10); + public static List STAGES = List.of( + GameStage.builder().duration(Duration.ofMinutes(5)).worldBorderSize(1000).build(), + GameStage.builder().duration(Duration.ofMinutes(5)).worldBorderSize(750).build(), + GameStage.builder().duration(Duration.ofMinutes(5)).worldBorderSize(500).build() + ); + + @SuppressWarnings("unused") + private static void load() { + int countdownSeconds = config.getInt(prefix, "countdown-seconds", Math.toIntExact(COUNTDOWN.toSeconds())); + COUNTDOWN = Duration.ofSeconds(countdownSeconds); + + ConfigurationSection configurationSection = config.getConfigurationSection(prefix + "stages"); + Set keys = configurationSection.getKeys(false); + STAGES = getGameStages(keys, configurationSection); + } + + private static List getGameStages(Set keys, ConfigurationSection configurationSection) { + if (keys.isEmpty()) { + int key = 0; + for (GameStage stage : STAGES) { + ConfigurationSection section = configurationSection.createSection(String.valueOf(key++)); + section.set("duration-seconds", stage.getDuration().toSeconds()); + section.set("world-border-size", stage.getWorldBorderSize()); + } + return STAGES; + } + List gameStageList = new ArrayList<>(); + + for (String key : keys) { + ConfigurationSection section = configurationSection.getConfigurationSection(key); + if (section == null) { + throw new IllegalStateException("Stage section is null for key [" + key + "] but is in the list of retrieved keys"); + } + if (!section.contains("duration-seconds")) { + log.error("Missing duration-seconds in stage {}.", key); + continue; + } + if (!section.contains("world-border-size")) { + log.error("Missing world-border-size in stage {}.", key); + continue; + } + int durationSeconds = section.getInt("duration-seconds"); + int worldBorderSize = section.getInt("world-border-size"); + gameStageList.add(GameStage.builder() + .duration(Duration.ofSeconds(durationSeconds)) + .worldBorderSize(worldBorderSize) + .build()); + } + return gameStageList; + } + } } diff --git a/src/main/java/com/alttd/hunger_games/config/Messages.java b/src/main/java/com/alttd/hunger_games/config/Messages.java index 6c7489b..c81048f 100644 --- a/src/main/java/com/alttd/hunger_games/config/Messages.java +++ b/src/main/java/com/alttd/hunger_games/config/Messages.java @@ -28,6 +28,7 @@ public class Messages extends AbstractConfig { public static String HELP_MESSAGE = "Show this menu: /hg help"; public static String ROUND_STATE = "Show the current round state: /hg roundstate"; public static String REGISTER = "Register a player for the game: /hg register "; + public static String START_ROUND = "Start the game: /hg start"; @SuppressWarnings("unused") private static void load() { @@ -35,6 +36,7 @@ public class Messages extends AbstractConfig { HELP_MESSAGE = config.getString(prefix, "help", HELP_MESSAGE); ROUND_STATE = config.getString(prefix, "round-state", ROUND_STATE); REGISTER = config.getString(prefix, "register", REGISTER); + START_ROUND = config.getString(prefix, "start", START_ROUND); } } diff --git a/src/main/java/com/alttd/hunger_games/data_objects/GameStage.java b/src/main/java/com/alttd/hunger_games/data_objects/GameStage.java new file mode 100644 index 0000000..3879237 --- /dev/null +++ b/src/main/java/com/alttd/hunger_games/data_objects/GameStage.java @@ -0,0 +1,15 @@ +package com.alttd.hunger_games.data_objects; + +import lombok.Builder; +import lombok.Getter; + +import java.time.Duration; + +@Builder +@Getter +public class GameStage { + + private final Duration duration; + private final int worldBorderSize; + +} diff --git a/src/main/java/com/alttd/hunger_games/game/GameStageHandler.java b/src/main/java/com/alttd/hunger_games/game/GameStageHandler.java new file mode 100644 index 0000000..80b1d31 --- /dev/null +++ b/src/main/java/com/alttd/hunger_games/game/GameStageHandler.java @@ -0,0 +1,19 @@ +package com.alttd.hunger_games.game; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class GameStageHandler { + + public static void handleStageChange(int worldBorderSize) { + //TODO change world border size and handle stage change + } + + public static void handleCountdownEnd() { + //TODO free players and handle stage change + } + + public static void handleWarmup() { + //TODO tp players to start area etc (might be handled by state change already, so maybe just messages) + } +} diff --git a/src/main/java/com/alttd/hunger_games/services/PlayerService.java b/src/main/java/com/alttd/hunger_games/services/PlayerService.java index 85f4bcb..9180938 100644 --- a/src/main/java/com/alttd/hunger_games/services/PlayerService.java +++ b/src/main/java/com/alttd/hunger_games/services/PlayerService.java @@ -42,11 +42,6 @@ public class PlayerService implements RoundListener { unregisteredPlayers.forEach(player -> roundService.setPlayerState(player.getUniqueId(), PLAYER_STATE.SPECTATING)); } - @Override - public void roundReset() { - //TODO: teleport everyone to spawn (or spectator?) location (from where they can join the game) - } - public Optional registerPlayer(Player player) { if (roundState == null) { return Optional.empty(); diff --git a/src/main/java/com/alttd/hunger_games/services/Round.java b/src/main/java/com/alttd/hunger_games/services/Round.java index 5993ed4..9040c29 100644 --- a/src/main/java/com/alttd/hunger_games/services/Round.java +++ b/src/main/java/com/alttd/hunger_games/services/Round.java @@ -1,16 +1,24 @@ package com.alttd.hunger_games.services; +import com.alttd.hunger_games.config.Config; +import com.alttd.hunger_games.data_objects.GameStage; import com.alttd.hunger_games.data_objects.ROUND_STATE; +import com.alttd.hunger_games.game.GameStageHandler; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; public class Round { private static Round instance = null; private final List listeners = new ArrayList<>(); - private ROUND_STATE roundState = null; + private ROUND_STATE roundState = ROUND_STATE.PLAYER_REGISTRATION; + private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); private Round() { @@ -33,24 +41,49 @@ public class Round { listeners.remove(listener); } - public void reset() { - listeners.forEach(RoundListener::roundReset); - } - - public void start() { + public void stop() { roundState = ROUND_STATE.PLAYER_REGISTRATION; listeners.forEach(roundListener -> roundListener.stateChange(roundState)); } - public void nextStage() { + private void nextStage() { Optional optionalNextRoundState = roundState.next(); if (optionalNextRoundState.isEmpty()) { - roundState = null; - reset(); + roundState = ROUND_STATE.PLAYER_REGISTRATION; + listeners.forEach(roundListener -> roundListener.stateChange(roundState)); return; } roundState = optionalNextRoundState.get(); listeners.forEach(roundListener -> roundListener.stateChange(roundState)); } + public void startRound() { + if (roundState.equals(ROUND_STATE.PLAYER_REGISTRATION)) { + throw new IllegalStateException("Round can not be started before player registration."); + } + roundState = ROUND_STATE.COUNTDOWN; + listeners.forEach(roundListener -> roundListener.stateChange(roundState)); + GameStageHandler.handleWarmup(); + Duration warmupDuration = Config.ROUND.COUNTDOWN; + scheduledExecutorService.schedule(() -> { + GameStageHandler.handleCountdownEnd(); + nextStage(); + scheduleNextStage(0); + }, warmupDuration.toSeconds(), TimeUnit.SECONDS); + } + + private void scheduleNextStage(int index) { + List gameStageList = Config.ROUND.STAGES; + if (gameStageList.size() <= index) { + nextStage(); + return; + } + + GameStage gameStage = gameStageList.get(index); + Duration duration = gameStage.getDuration(); + scheduledExecutorService.schedule(() -> { + GameStageHandler.handleStageChange(gameStage.getWorldBorderSize()); + scheduleNextStage(index + 1); + }, duration.toSeconds(), TimeUnit.SECONDS); + } } diff --git a/src/main/java/com/alttd/hunger_games/services/RoundListener.java b/src/main/java/com/alttd/hunger_games/services/RoundListener.java index d8d2727..d44eeb3 100644 --- a/src/main/java/com/alttd/hunger_games/services/RoundListener.java +++ b/src/main/java/com/alttd/hunger_games/services/RoundListener.java @@ -6,6 +6,4 @@ public interface RoundListener { void stateChange(ROUND_STATE roundState); - void roundReset(); - } diff --git a/src/main/java/com/alttd/hunger_games/services/RoundService.java b/src/main/java/com/alttd/hunger_games/services/RoundService.java index c4f499e..a5cc05b 100644 --- a/src/main/java/com/alttd/hunger_games/services/RoundService.java +++ b/src/main/java/com/alttd/hunger_games/services/RoundService.java @@ -40,11 +40,8 @@ public class RoundService implements RoundListener { @Override public void stateChange(ROUND_STATE roundState) { this.roundState = roundState; - } - - @Override - public void roundReset() { - clear(); - roundState = null; + if (roundState.equals(ROUND_STATE.PLAYER_REGISTRATION)) { + clear(); + } } }