Implement BossBarService for countdowns and finale updates, integrating dynamic boss bar messages and real-time player progress tracking.
This commit is contained in:
parent
dd060bce48
commit
233cffd2df
|
|
@ -8,12 +8,7 @@ import com.alttd.hunger_games.event_listeners.ChestListener;
|
||||||
import com.alttd.hunger_games.event_listeners.PlayerDamageListener;
|
import com.alttd.hunger_games.event_listeners.PlayerDamageListener;
|
||||||
import com.alttd.hunger_games.event_listeners.PlayerDisconnectListener;
|
import com.alttd.hunger_games.event_listeners.PlayerDisconnectListener;
|
||||||
import com.alttd.hunger_games.event_listeners.PlayerJoinListener;
|
import com.alttd.hunger_games.event_listeners.PlayerJoinListener;
|
||||||
import com.alttd.hunger_games.services.LootService;
|
import com.alttd.hunger_games.services.*;
|
||||||
import com.alttd.hunger_games.services.PlayerService;
|
|
||||||
import com.alttd.hunger_games.services.PlayerTeleporterService;
|
|
||||||
import com.alttd.hunger_games.services.Round;
|
|
||||||
import com.alttd.hunger_games.services.RoundService;
|
|
||||||
import com.alttd.hunger_games.services.StatService;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
@ -27,6 +22,7 @@ public final class Main extends JavaPlugin {
|
||||||
private PlayerTeleporterService playerTeleporterService;
|
private PlayerTeleporterService playerTeleporterService;
|
||||||
private LootService lootService;
|
private LootService lootService;
|
||||||
private StatService statService;
|
private StatService statService;
|
||||||
|
private BossBarService bossBarService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
|
@ -45,6 +41,10 @@ public final class Main extends JavaPlugin {
|
||||||
roundService = RoundService.createSingletonInstance(round, this, lootService, statService);
|
roundService = RoundService.createSingletonInstance(round, this, lootService, statService);
|
||||||
playerTeleporterService = PlayerTeleporterService.createSingletonInstance();
|
playerTeleporterService = PlayerTeleporterService.createSingletonInstance();
|
||||||
playerService = PlayerService.createSingletonInstance(round, roundService, playerTeleporterService);
|
playerService = PlayerService.createSingletonInstance(round, roundService, playerTeleporterService);
|
||||||
|
|
||||||
|
bossBarService = BossBarService.createSingletonInstance(round, roundService);
|
||||||
|
round.setBossBarService(bossBarService);
|
||||||
|
roundService.setBossBarService(bossBarService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public class LootItems extends AbstractConfig {
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static void load() {
|
private static void load() {
|
||||||
for (RARITY rarity : RARITY.values()) {
|
for (RARITY rarity : RARITY.values()) {
|
||||||
List<String> materialNameList = ITEMS.get(rarity).stream().map(Material::name).toList();
|
List<String> materialNameList = ITEMS.getOrDefault(rarity, List.of()).stream().map(Material::name).toList();
|
||||||
List<String> materialList = config.getList(prefix, rarity.getConfigName(), materialNameList);
|
List<String> materialList = config.getList(prefix, rarity.getConfigName(), materialNameList);
|
||||||
ITEMS.put(rarity, materialList.stream().map(Material::getMaterial).toList());
|
ITEMS.put(rarity, materialList.stream().map(Material::getMaterial).toList());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,11 @@ public class Messages extends AbstractConfig {
|
||||||
public static String WINNER = "<gold><player> has won the Hunger Games!</gold>";
|
public static String WINNER = "<gold><player> has won the Hunger Games!</gold>";
|
||||||
public static String PLAYER_DEATH = "<red><player> has died! <gold><remaining></gold> players remaining.</red>";
|
public static String PLAYER_DEATH = "<red><player> has died! <gold><remaining></gold> players remaining.</red>";
|
||||||
|
|
||||||
|
public static String BOSSBAR_COUNTDOWN = "<gold>Loot phase in <time></gold>";
|
||||||
|
public static String BOSSBAR_SAFE_PHASE = "<green>Kill phase in <time></green>";
|
||||||
|
public static String BOSSBAR_KILL_PHASE = "<red>Border shrink in <time></red>";
|
||||||
|
public static String BOSSBAR_FINALE = "<gold>Remaining Players: <remaining></gold>";
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static void load() {
|
private static void load() {
|
||||||
WARMUP = config.getString(prefix, "warmup", WARMUP);
|
WARMUP = config.getString(prefix, "warmup", WARMUP);
|
||||||
|
|
@ -92,6 +97,11 @@ public class Messages extends AbstractConfig {
|
||||||
CHEST_EMPTY = config.getString(prefix, "chest-empty", CHEST_EMPTY);
|
CHEST_EMPTY = config.getString(prefix, "chest-empty", CHEST_EMPTY);
|
||||||
WINNER = config.getString(prefix, "winner", WINNER);
|
WINNER = config.getString(prefix, "winner", WINNER);
|
||||||
PLAYER_DEATH = config.getString(prefix, "player-death", PLAYER_DEATH);
|
PLAYER_DEATH = config.getString(prefix, "player-death", PLAYER_DEATH);
|
||||||
|
|
||||||
|
BOSSBAR_COUNTDOWN = config.getString(prefix, "bossbar-countdown", BOSSBAR_COUNTDOWN);
|
||||||
|
BOSSBAR_SAFE_PHASE = config.getString(prefix, "bossbar-safe-phase", BOSSBAR_SAFE_PHASE);
|
||||||
|
BOSSBAR_KILL_PHASE = config.getString(prefix, "bossbar-kill-phase", BOSSBAR_KILL_PHASE);
|
||||||
|
BOSSBAR_FINALE = config.getString(prefix, "bossbar-finale", BOSSBAR_FINALE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
package com.alttd.hunger_games.services;
|
||||||
|
|
||||||
|
import com.alttd.hunger_games.config.Messages;
|
||||||
|
import com.alttd.hunger_games.data_objects.PLAYER_STATE;
|
||||||
|
import com.alttd.hunger_games.data_objects.ROUND_STATE;
|
||||||
|
import net.kyori.adventure.bossbar.BossBar;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class BossBarService implements RoundListener {
|
||||||
|
|
||||||
|
private static BossBarService instance = null;
|
||||||
|
private final RoundService roundService;
|
||||||
|
private final BossBar bossBar;
|
||||||
|
private final Set<UUID> viewers = new HashSet<>();
|
||||||
|
private int maxPlayers = 0;
|
||||||
|
|
||||||
|
public BossBarService(Round round, RoundService roundService) {
|
||||||
|
this.roundService = roundService;
|
||||||
|
this.bossBar = BossBar.bossBar(Component.empty(), 1.0f, BossBar.Color.YELLOW, BossBar.Overlay.PROGRESS);
|
||||||
|
round.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BossBarService createSingletonInstance(Round round, RoundService roundService) {
|
||||||
|
if (instance != null) {
|
||||||
|
throw new IllegalStateException("BossBarService is already initialized.");
|
||||||
|
}
|
||||||
|
instance = new BossBarService(round, roundService);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCountdown(Duration timeLeft, Duration totalTime, ROUND_STATE state) {
|
||||||
|
String message = switch (state) {
|
||||||
|
case COUNTDOWN -> Messages.GAME.BOSSBAR_COUNTDOWN;
|
||||||
|
case SAFE_PHASE -> Messages.GAME.BOSSBAR_SAFE_PHASE;
|
||||||
|
case KILL_PHASE -> Messages.GAME.BOSSBAR_KILL_PHASE;
|
||||||
|
default -> throw new IllegalArgumentException("Invalid round state: " + state);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (message.isEmpty()) {
|
||||||
|
bossBar.name(Component.empty());
|
||||||
|
bossBar.progress(0.0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bossBar.name(MiniMessage.miniMessage().deserialize(message, Placeholder.unparsed("time", formatTime(timeLeft))));
|
||||||
|
bossBar.progress(Math.clamp((float) timeLeft.toMillis() / totalTime.toMillis(), 0.0f, 1.0f));
|
||||||
|
|
||||||
|
updateBossBarColor(state);
|
||||||
|
updateViewers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBossBarColor(ROUND_STATE state) {
|
||||||
|
switch (state) {
|
||||||
|
case COUNTDOWN -> bossBar.color(BossBar.Color.YELLOW);
|
||||||
|
case SAFE_PHASE -> bossBar.color(BossBar.Color.GREEN);
|
||||||
|
case KILL_PHASE -> bossBar.color(BossBar.Color.RED);
|
||||||
|
case FINALE -> bossBar.color(BossBar.Color.PURPLE);
|
||||||
|
default -> bossBar.color(BossBar.Color.WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateFinale(int remainingPlayers) {
|
||||||
|
if (maxPlayers == 0) {
|
||||||
|
maxPlayers = remainingPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingPlayers <= 1) {
|
||||||
|
hideAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bossBar.name(MiniMessage.miniMessage().deserialize(Messages.GAME.BOSSBAR_FINALE, Placeholder.unparsed("remaining", String.valueOf(remainingPlayers))));
|
||||||
|
bossBar.progress(Math.clamp((float) remainingPlayers / maxPlayers, 0.0f, 1.0f));
|
||||||
|
bossBar.color(BossBar.Color.PURPLE);
|
||||||
|
updateViewers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateViewers() {
|
||||||
|
Set<UUID> registeredPlayers = roundService.getPlayers(PLAYER_STATE.REGISTERED);
|
||||||
|
|
||||||
|
// Add new viewers
|
||||||
|
registeredPlayers.stream()
|
||||||
|
.filter(viewers::add)
|
||||||
|
.map(Bukkit::getPlayer)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(player -> player.showBossBar(bossBar));
|
||||||
|
|
||||||
|
// Remove old viewers
|
||||||
|
Set<UUID> toRemove = viewers.stream()
|
||||||
|
.filter(uuid -> !registeredPlayers.contains(uuid))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
for (UUID uuid : toRemove) {
|
||||||
|
viewers.remove(uuid);
|
||||||
|
Player player = Bukkit.getPlayer(uuid);
|
||||||
|
if (player != null) {
|
||||||
|
player.hideBossBar(bossBar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideAll() {
|
||||||
|
viewers.stream()
|
||||||
|
.map(Bukkit::getPlayer)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(player -> player.hideBossBar(bossBar));
|
||||||
|
viewers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatTime(Duration duration) {
|
||||||
|
if (duration.isNegative()) {
|
||||||
|
return "0s";
|
||||||
|
}
|
||||||
|
|
||||||
|
long minutes = duration.toMinutesPart();
|
||||||
|
int seconds = duration.toSecondsPart();
|
||||||
|
|
||||||
|
if (minutes > 0) {
|
||||||
|
return "%dm %ds".formatted(minutes, seconds);
|
||||||
|
}
|
||||||
|
return "%ds".formatted(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stateChange(ROUND_STATE roundState) {
|
||||||
|
if (roundState == ROUND_STATE.PLAYER_REGISTRATION || roundState == ROUND_STATE.ENDED) {
|
||||||
|
hideAll();
|
||||||
|
maxPlayers = 0;
|
||||||
|
} else if (roundState == ROUND_STATE.FINALE) {
|
||||||
|
updateFinale(roundService.getPlayers(PLAYER_STATE.REGISTERED).size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,11 +6,13 @@ import com.alttd.hunger_games.data_objects.ROUND_STATE;
|
||||||
import com.alttd.hunger_games.game.GameStageHandler;
|
import com.alttd.hunger_games.game.GameStageHandler;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class Round {
|
public class Round {
|
||||||
|
|
@ -19,6 +21,8 @@ public class Round {
|
||||||
private final List<RoundListener> listeners = new ArrayList<>();
|
private final List<RoundListener> listeners = new ArrayList<>();
|
||||||
private ROUND_STATE roundState = ROUND_STATE.PLAYER_REGISTRATION;
|
private ROUND_STATE roundState = ROUND_STATE.PLAYER_REGISTRATION;
|
||||||
private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
private ScheduledFuture<?> countdownFuture = null;
|
||||||
|
private BossBarService bossBarService;
|
||||||
|
|
||||||
private Round() {
|
private Round() {
|
||||||
|
|
||||||
|
|
@ -42,11 +46,17 @@ public class Round {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
if (countdownFuture != null) {
|
||||||
|
countdownFuture.cancel(true);
|
||||||
|
}
|
||||||
roundState = ROUND_STATE.PLAYER_REGISTRATION;
|
roundState = ROUND_STATE.PLAYER_REGISTRATION;
|
||||||
listeners.forEach(roundListener -> roundListener.stateChange(roundState));
|
listeners.forEach(roundListener -> roundListener.stateChange(roundState));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void endRound() {
|
public void endRound() {
|
||||||
|
if (countdownFuture != null) {
|
||||||
|
countdownFuture.cancel(true);
|
||||||
|
}
|
||||||
roundState = ROUND_STATE.ENDED;
|
roundState = ROUND_STATE.ENDED;
|
||||||
listeners.forEach(roundListener -> roundListener.stateChange(roundState));
|
listeners.forEach(roundListener -> roundListener.stateChange(roundState));
|
||||||
}
|
}
|
||||||
|
|
@ -70,11 +80,29 @@ public class Round {
|
||||||
listeners.forEach(roundListener -> roundListener.stateChange(roundState));
|
listeners.forEach(roundListener -> roundListener.stateChange(roundState));
|
||||||
GameStageHandler.handleWarmup();
|
GameStageHandler.handleWarmup();
|
||||||
Duration warmupDuration = Config.ROUND.COUNTDOWN;
|
Duration warmupDuration = Config.ROUND.COUNTDOWN;
|
||||||
scheduledExecutorService.schedule(() -> {
|
|
||||||
|
startCountdown(warmupDuration, () -> {
|
||||||
GameStageHandler.handleCountdownEnd();
|
GameStageHandler.handleCountdownEnd();
|
||||||
nextStage();
|
nextStage();
|
||||||
scheduleNextStage(0);
|
scheduleNextStage(0);
|
||||||
}, warmupDuration.toSeconds(), TimeUnit.SECONDS);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startCountdown(Duration duration, Runnable onEnd) {
|
||||||
|
if (countdownFuture != null) {
|
||||||
|
countdownFuture.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Instant endTime = Instant.now().plus(duration);
|
||||||
|
countdownFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
|
||||||
|
Duration timeLeft = Duration.between(Instant.now(), endTime);
|
||||||
|
if (bossBarService != null) {
|
||||||
|
bossBarService.updateCountdown(timeLeft, duration, roundState);
|
||||||
|
}
|
||||||
|
if (timeLeft.isZero() || timeLeft.isNegative()) {
|
||||||
|
onEnd.run();
|
||||||
|
}
|
||||||
|
}, 0, 1, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleNextStage(int index) {
|
private void scheduleNextStage(int index) {
|
||||||
|
|
@ -86,9 +114,14 @@ public class Round {
|
||||||
|
|
||||||
GameStage gameStage = gameStageList.get(index);
|
GameStage gameStage = gameStageList.get(index);
|
||||||
Duration duration = gameStage.getDuration();
|
Duration duration = gameStage.getDuration();
|
||||||
scheduledExecutorService.schedule(() -> {
|
|
||||||
|
startCountdown(duration, () -> {
|
||||||
GameStageHandler.handleStageChange(gameStage.getWorldBorderSize());
|
GameStageHandler.handleStageChange(gameStage.getWorldBorderSize());
|
||||||
scheduleNextStage(index + 1);
|
scheduleNextStage(index + 1);
|
||||||
}, duration.toSeconds(), TimeUnit.SECONDS);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBossBarService(BossBarService bossBarService) {
|
||||||
|
this.bossBarService = bossBarService;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ public class RoundService implements RoundListener {
|
||||||
private final HashMap<UUID, PLAYER_STATE> players = new HashMap<>();
|
private final HashMap<UUID, PLAYER_STATE> players = new HashMap<>();
|
||||||
|
|
||||||
private final Round round;
|
private final Round round;
|
||||||
|
private BossBarService bossBarService;
|
||||||
|
|
||||||
public static RoundService createSingletonInstance(Round round, Main main, LootService lootService, StatService statService) {
|
public static RoundService createSingletonInstance(Round round, Main main, LootService lootService, StatService statService) {
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
|
|
@ -59,6 +60,9 @@ public class RoundService implements RoundListener {
|
||||||
if (playerState == PLAYER_STATE.SPECTATING && oldState == PLAYER_STATE.REGISTERED) {
|
if (playerState == PLAYER_STATE.SPECTATING && oldState == PLAYER_STATE.REGISTERED) {
|
||||||
int remaining = getPlayers(PLAYER_STATE.REGISTERED).size();
|
int remaining = getPlayers(PLAYER_STATE.REGISTERED).size();
|
||||||
statService.setPlacement(uuid, remaining + 1);
|
statService.setPlacement(uuid, remaining + 1);
|
||||||
|
if (roundState == ROUND_STATE.FINALE && bossBarService != null) {
|
||||||
|
bossBarService.updateFinale(remaining);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!roundState.equals(ROUND_STATE.KILL_PHASE) && !roundState.equals(ROUND_STATE.FINALE)) {
|
if (!roundState.equals(ROUND_STATE.KILL_PHASE) && !roundState.equals(ROUND_STATE.FINALE)) {
|
||||||
|
|
@ -121,4 +125,8 @@ public class RoundService implements RoundListener {
|
||||||
playerMovementListener = new PlayerMovementListener(uuidSet);
|
playerMovementListener = new PlayerMovementListener(uuidSet);
|
||||||
main.getServer().getPluginManager().registerEvents(playerMovementListener, main);
|
main.getServer().getPluginManager().registerEvents(playerMovementListener, main);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBossBarService(BossBarService bossBarService) {
|
||||||
|
this.bossBarService = bossBarService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user