Compare commits

..
34 Commits
Author SHA1 Message Date
stijn 7dce9d9f30 Integrate TabListService to update player colors during the game 2026-07-12 21:41:26 +02:00
stijn 95219c17bc Add Multiverse-Core as a soft dependency to ensure teleport locations are found 2026-07-11 22:22:09 +02:00
stijn d412812dcb Add /hg list command to display players and spectators in the game 2026-07-11 22:20:09 +02:00
stijn 21f12904ac Update stateChange to handle ROUND_STATE.ENDED instead of ROUND_STATE.FINALE. 2026-07-11 22:20:03 +02:00
stijn 416c44a8f3 show participant status in tab using colors
announce killer if present
show everyone the bossbar
drop items on death
give players participation price permission
2026-07-11 22:13:52 +02:00
stijn fc7b5c76a6 Handle ROUND_STATE.ENDED in stateChange to clear resources and reset loot. 2026-07-11 21:53:40 +02:00
stijn 9198af6c29 Register online players without auto-join-bypass permission during ROUND_STATE.PLAYER_REGISTRATION or ROUND_STATE.ENDED. 2026-07-11 19:38:59 +02:00
stijn d5266b659b Register online players without auto-join-bypass permission during ROUND_STATE.PLAYER_REGISTRATION or ROUND_STATE.ENDED. 2026-07-11 19:38:26 +02:00
stijn e9e7c1cc80 Allow round to start from ROUND_STATE.ENDED and update related state handling 2026-07-11 19:35:33 +02:00
stijn 40f52a4a45 Update PlayerService to adjust PLAYER_STATE during ROUND_STATE.ENDED phase 2026-07-11 19:14:37 +02:00
stijn 3906bd747b Handle asynchronous player teleportation in PlayerTeleporterService with error handling and logging. 2026-07-11 17:14:32 +02:00
stijn 0d757aa2ab Remove redundant teleportation logging in PlayerService during finale phase. 2026-07-11 17:14:25 +02:00
stijn 5f35866e7a Make finale border size configurable via Config and apply it dynamically in RoundService. 2026-07-11 17:14:07 +02:00
stijn 491e6a6050 Make finale border size configurable via Config and apply it dynamically in RoundService. 2026-07-11 16:52:15 +02:00
stijn 244f8f6df7 Add logging to PlayerService for teleports, adjust ROUND_STATE handling, and use @Slf4j annotation for consistency. Update countdown logic in Round and refactor configuration keys for finale timing. 2026-07-11 16:51:04 +02:00
stijn fe10d57978 Show title messages during broadcasts to enhance player visibility. 2026-07-11 16:28:03 +02:00
stijn 8d05658214 Introduce LootItem record and refactor loot handling logic to support material-specific max amounts 2026-07-06 00:04:53 +02:00
stijn 7324177250 Change player game mode from SURVIVAL to ADVENTURE during round reset in RoundService 2026-07-05 23:59:49 +02:00
stijn c7161f707c Refactor Round phase transitions and countdown logic; consolidate messages, adjust state handling, and add configurable durations for safe and kill phases. 2026-07-05 23:42:20 +02:00
stijn 5ce26782f2 Add CommandListener to enforce command whitelist and block restricted commands 2026-07-05 23:19:49 +02:00
stijn a137ef4cc6 Handle empty stage list in Round and adjust state transitions in RoundService 2026-06-28 02:19:28 +02:00
stijn 62c9661c82 Adjust finale border size to 60 in RoundService 2026-06-28 00:35:39 +02:00
stijn 936cabfd42 Update boss bar messages to reflect accurate border shrinking phases and align config keys 2026-06-28 00:32:32 +02:00
stijn 38f8ffd4a3 Prioritize finale center when searching for safe locations in /stuck command 2026-06-27 23:58:54 +02:00
stijn 1a9f4cbd2d Refactor respawn handling: streamline spectator teleportation, reset player experience, adjust finale border size, and improve game stage countdown logic. 2026-06-27 23:57:07 +02:00
stijn 99bff5446d Adjust Round logic to handle last game stage countdown gracefully 2026-06-27 23:00:13 +02:00
stijn f9e00b04cc Update BORDER_TRANSITION_SECONDS in WorldBorderUtil to 45 2026-06-27 23:00:05 +02:00
stijn a7ce376abb Add ItemSpawnListener to control item despawn delays based on round phases 2026-06-27 22:55:09 +02:00
stijn 897c8ab14d Refactor world border handling: centralize logic in WorldBorderUtil and integrate into RoundService and GameStageHandler. Add logging for player teleportation and improve player reset process. 2026-06-27 22:32:43 +02:00
stijn a3085ad4b4 Prevent duplicate game end announcements and update RoundService initialization to include PlayerTeleporterService. 2026-06-27 22:17:50 +02:00
stijn 29c46ba485 Fix typo in getHumanReadableName across commands and services 2026-06-27 22:04:17 +02:00
stijn ca7b6f1152 Enhance death handling and round logic: suppress death messages, clear player inventories, reset health and food levels, and teleport players to spectator area or prepare them for countdown phase. Integrate PlayerTeleporterService into RoundService. 2026-06-27 22:02:05 +02:00
stijn 4bf6ca9dc4 Improve RoundState command to handle null ROUND_STATE values gracefully 2026-06-22 02:26:37 +02:00
stijn f0d3a20283 Add /hg nextphase and /hg end commands to manage game phases and rounds 2026-06-22 02:22:02 +02:00
29 changed files with 681 additions and 125 deletions
@@ -4,10 +4,7 @@ import com.alttd.hunger_games.commands.BaseCommand;
import com.alttd.hunger_games.config.Config; import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.config.LootItems; import com.alttd.hunger_games.config.LootItems;
import com.alttd.hunger_games.config.Messages; import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.event_listeners.ChestListener; import com.alttd.hunger_games.event_listeners.*;
import com.alttd.hunger_games.event_listeners.PlayerDamageListener;
import com.alttd.hunger_games.event_listeners.PlayerDisconnectListener;
import com.alttd.hunger_games.event_listeners.PlayerJoinListener;
import com.alttd.hunger_games.services.*; import com.alttd.hunger_games.services.*;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
@@ -23,6 +20,7 @@ public final class Main extends JavaPlugin {
private LootService lootService; private LootService lootService;
private StatService statService; private StatService statService;
private BossBarService bossBarService; private BossBarService bossBarService;
private TabListService tabListService;
@Override @Override
public void onEnable() { public void onEnable() {
@@ -38,8 +36,10 @@ public final class Main extends JavaPlugin {
round = Round.createSingletonInstance(); round = Round.createSingletonInstance();
lootService = new LootService(); lootService = new LootService();
statService = new StatService(this, round); statService = new StatService(this, round);
roundService = RoundService.createSingletonInstance(round, this, lootService, statService);
playerTeleporterService = PlayerTeleporterService.createSingletonInstance(); playerTeleporterService = PlayerTeleporterService.createSingletonInstance();
roundService = RoundService.createSingletonInstance(round, this, lootService, statService, playerTeleporterService);
tabListService = new TabListService(round, roundService);
roundService.setTabListService(tabListService);
playerService = PlayerService.createSingletonInstance(round, roundService, playerTeleporterService); playerService = PlayerService.createSingletonInstance(round, roundService, playerTeleporterService);
bossBarService = BossBarService.createSingletonInstance(round, roundService); bossBarService = BossBarService.createSingletonInstance(round, roundService);
@@ -53,15 +53,17 @@ public final class Main extends JavaPlugin {
} }
private void registerCommands() { private void registerCommands() {
BaseCommand command = new BaseCommand(this, roundService, playerService, round, statService); BaseCommand command = new BaseCommand(this, roundService, playerService, round, statService, tabListService);
} }
private void registerEvents() { private void registerEvents() {
PluginManager pluginManager = getServer().getPluginManager(); PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new PlayerDisconnectListener(playerService), this); pluginManager.registerEvents(new PlayerDisconnectListener(playerService), this);
pluginManager.registerEvents(new PlayerJoinListener(playerService), this); pluginManager.registerEvents(new PlayerJoinListener(playerService, tabListService), this);
pluginManager.registerEvents(new PlayerDamageListener(roundService, playerService, statService), this); pluginManager.registerEvents(new PlayerDamageListener(roundService, playerService, statService), this);
pluginManager.registerEvents(new ChestListener(roundService, lootService), this); pluginManager.registerEvents(new ChestListener(roundService, lootService), this);
pluginManager.registerEvents(new ItemSpawnListener(roundService), this);
pluginManager.registerEvents(new CommandListener(), this);
} }
public void reloadConfigs() { public void reloadConfigs() {
@@ -3,10 +3,7 @@ package com.alttd.hunger_games.commands;
import com.alttd.hunger_games.Main; import com.alttd.hunger_games.Main;
import com.alttd.hunger_games.commands.subcommands.*; import com.alttd.hunger_games.commands.subcommands.*;
import com.alttd.hunger_games.config.Messages; import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.services.PlayerService; import com.alttd.hunger_games.services.*;
import com.alttd.hunger_games.services.Round;
import com.alttd.hunger_games.services.RoundService;
import com.alttd.hunger_games.services.StatService;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
@@ -23,7 +20,7 @@ import java.util.stream.Collectors;
public class BaseCommand implements CommandExecutor, TabExecutor { public class BaseCommand implements CommandExecutor, TabExecutor {
private final List<SubCommand> subCommands; private final List<SubCommand> subCommands;
public BaseCommand(Main main, RoundService roundService, PlayerService playerService, Round round, StatService statService) { public BaseCommand(Main main, RoundService roundService, PlayerService playerService, Round round, StatService statService, TabListService tablistService) {
PluginCommand command = main.getCommand("hungergames"); PluginCommand command = main.getCommand("hungergames");
if (command == null) { if (command == null) {
subCommands = null; subCommands = null;
@@ -37,10 +34,13 @@ public class BaseCommand implements CommandExecutor, TabExecutor {
subCommands = new ArrayList<>(List.of( subCommands = new ArrayList<>(List.of(
new Reload(main), new Reload(main),
new RoundState(roundService), new RoundState(roundService),
new Register(playerService), new Register(playerService, tablistService),
new StartRound(round, roundService), new StartRound(round, roundService, playerService),
new NextPhase(round),
new EndRound(round),
new Stuck(main), new Stuck(main),
new Stats(statService) new Stats(statService),
new ListPlayers(roundService)
)); ));
} }
@@ -0,0 +1,37 @@
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 EndRound extends SubCommand {
private final Round round;
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
round.endRound();
commandSender.sendRichMessage("<green>Ending the round.</green>");
return true;
}
@Override
public String getName() {
return "end";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
return List.of();
}
@Override
public String getHelpMessage() {
return Messages.HELP.END_ROUND;
}
}
@@ -0,0 +1,71 @@
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.data_objects.PLAYER_STATE;
import com.alttd.hunger_games.services.RoundService;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.*;
public class ListPlayers extends SubCommand {
private final RoundService roundService;
public ListPlayers(RoundService roundService) {
this.roundService = roundService;
}
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
Set<UUID> registeredUuids = roundService.getPlayers(PLAYER_STATE.REGISTERED);
Set<UUID> spectatingUuids = roundService.getPlayers(PLAYER_STATE.SPECTATING);
List<Component> registeredNames = registeredUuids.stream()
.map(Bukkit::getPlayer)
.filter(Objects::nonNull)
.map(Player::name)
.toList();
List<Component> spectatingNames = spectatingUuids.stream()
.map(Bukkit::getPlayer)
.filter(Objects::nonNull)
.map(Player::name)
.toList();
Component inGamePlayers = Component.join(JoinConfiguration.commas(true), registeredNames);
Component spectators = Component.join(JoinConfiguration.commas(true), spectatingNames);
int inGameCount = registeredNames.size();
int totalCount = inGameCount + spectatingNames.size();
commandSender.sendRichMessage(Messages.COMMANDS.LIST_PLAYERS,
Placeholder.unparsed("in_game", String.valueOf(inGameCount)),
Placeholder.unparsed("total", String.valueOf(totalCount)),
Placeholder.component("in_game_players", inGamePlayers),
Placeholder.component("spectators", spectators)
);
return true;
}
@Override
public String getName() {
return "list";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
return Collections.emptyList();
}
@Override
public String getHelpMessage() {
return Messages.HELP.LIST;
}
}
@@ -0,0 +1,37 @@
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 NextPhase extends SubCommand {
private final Round round;
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
round.forceNextPhase();
commandSender.sendRichMessage(Messages.COMMANDS.NEXT_PHASE_SUCCESS);
return true;
}
@Override
public String getName() {
return "nextphase";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
return List.of();
}
@Override
public String getHelpMessage() {
return Messages.HELP.NEXT_PHASE;
}
}
@@ -5,6 +5,8 @@ import com.alttd.hunger_games.commands.shared_parsers.OnlinePlayerParser;
import com.alttd.hunger_games.config.Messages; import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.data_objects.PLAYER_STATE; import com.alttd.hunger_games.data_objects.PLAYER_STATE;
import com.alttd.hunger_games.services.PlayerService; import com.alttd.hunger_games.services.PlayerService;
import com.alttd.hunger_games.services.TabListService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -15,16 +17,14 @@ import javax.annotation.Nullable;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@RequiredArgsConstructor
@Slf4j @Slf4j
public class Register extends SubCommand { public class Register extends SubCommand {
private final static OnlinePlayerParser ONLINE_PLAYER_PARSER = new OnlinePlayerParser(); private final static OnlinePlayerParser ONLINE_PLAYER_PARSER = new OnlinePlayerParser();
private final static int PLAYER_ARG = 1; private final static int PLAYER_ARG = 1;
private final PlayerService playerService; private final PlayerService playerService;
private final TabListService tabListService;
public Register(PlayerService playerService) {
this.playerService = playerService;
}
@Override @Override
public boolean onCommand(CommandSender commandSender, String[] args) { public boolean onCommand(CommandSender commandSender, String[] args) {
@@ -44,6 +44,7 @@ public class Register extends SubCommand {
playerToRegister.sendRichMessage(Messages.REGISTER.FAILED_TO_REGISTER_YOU); playerToRegister.sendRichMessage(Messages.REGISTER.FAILED_TO_REGISTER_YOU);
} else { } else {
notifyRegisteredPlayer(playerToRegister, nullablePlayerState); notifyRegisteredPlayer(playerToRegister, nullablePlayerState);
tabListService.updatePlayerColor(playerToRegister);
} }
if (commandSender instanceof Player commandPlayer) { if (commandSender instanceof Player commandPlayer) {
@@ -2,6 +2,7 @@ package com.alttd.hunger_games.commands.subcommands;
import com.alttd.hunger_games.commands.SubCommand; import com.alttd.hunger_games.commands.SubCommand;
import com.alttd.hunger_games.config.Messages; import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.data_objects.ROUND_STATE;
import com.alttd.hunger_games.services.RoundService; import com.alttd.hunger_games.services.RoundService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
@@ -16,9 +17,10 @@ public class RoundState extends SubCommand {
@Override @Override
public boolean onCommand(CommandSender commandSender, String[] args) { public boolean onCommand(CommandSender commandSender, String[] args) {
ROUND_STATE roundState = roundService.getRoundState();
commandSender.sendRichMessage(Messages.ROUND_STATE.ROUND_STATE, commandSender.sendRichMessage(Messages.ROUND_STATE.ROUND_STATE,
Placeholder.parsed("round_state", Placeholder.parsed("round_state",
roundService.getRoundState().getHumandReadableName())); roundState == null ? "null" : roundState.getHumanReadableName()));
return true; return true;
} }
@@ -3,10 +3,12 @@ package com.alttd.hunger_games.commands.subcommands;
import com.alttd.hunger_games.commands.SubCommand; import com.alttd.hunger_games.commands.SubCommand;
import com.alttd.hunger_games.config.Messages; import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.data_objects.ROUND_STATE; import com.alttd.hunger_games.data_objects.ROUND_STATE;
import com.alttd.hunger_games.services.PlayerService;
import com.alttd.hunger_games.services.Round; import com.alttd.hunger_games.services.Round;
import com.alttd.hunger_games.services.RoundService; import com.alttd.hunger_games.services.RoundService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.List; import java.util.List;
@@ -16,15 +18,19 @@ public class StartRound extends SubCommand {
private final Round round; private final Round round;
private final RoundService roundService; private final RoundService roundService;
private final PlayerService playerService;
@Override @Override
public boolean onCommand(CommandSender commandSender, String[] args) { public boolean onCommand(CommandSender commandSender, String[] args) {
ROUND_STATE roundState = roundService.getRoundState(); ROUND_STATE roundState = roundService.getRoundState();
if (roundState == ROUND_STATE.PLAYER_REGISTRATION) { if (roundState == ROUND_STATE.PLAYER_REGISTRATION || roundState == ROUND_STATE.ENDED) {
Bukkit.getOnlinePlayers().stream()
.filter(player -> !player.hasPermission("hungergames.auto-join-bypass"))
.forEach(playerService::registerPlayer);
round.startRound(); round.startRound();
} else { } else {
commandSender.sendRichMessage(Messages.START_ROUND.CAN_NOT_START_ROUND, commandSender.sendRichMessage(Messages.START_ROUND.CAN_NOT_START_ROUND,
Placeholder.parsed("round_state", roundState.getHumandReadableName())); Placeholder.parsed("round_state", roundState.getHumanReadableName()));
} }
return true; return true;
} }
@@ -155,18 +155,28 @@ public class Stuck extends SubCommand implements Listener {
private Optional<Location> findSafeLocation(Location start) { private Optional<Location> findSafeLocation(Location start) {
int radius = 10; int radius = 10;
Location finaleCenter = Config.DESTINATION.FINALE_CENTER;
boolean prioritizeFinale = finaleCenter != null && finaleCenter.getWorld().equals(start.getWorld());
for (int r = 0; r <= radius; r++) { for (int r = 0; r <= radius; r++) {
List<Location> candidates = new ArrayList<>();
for (int x = -r; x <= r; x++) { for (int x = -r; x <= r; x++) {
for (int z = -r; z <= r; z++) { for (int z = -r; z <= r; z++) {
if (Math.abs(x) != r && Math.abs(z) != r) { if (Math.abs(x) != r && Math.abs(z) != r) {
continue; continue;
} }
candidates.add(start.clone().add(x, 0, z));
}
}
Location baseLocation = start.clone().add(x, 0, z); if (prioritizeFinale) {
Optional<Location> safeY = findSafeY(baseLocation); candidates.sort(Comparator.comparingDouble(loc -> loc.distanceSquared(finaleCenter)));
if (safeY.isPresent()) { }
return safeY;
} for (Location baseLocation : candidates) {
Optional<Location> safeY = findSafeY(baseLocation);
if (safeY.isPresent()) {
return safeY;
} }
} }
} }
@@ -2,16 +2,12 @@ package com.alttd.hunger_games.config;
import com.alttd.hunger_games.data_objects.GameStage; import com.alttd.hunger_games.data_objects.GameStage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import java.io.File; import java.io.File;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@Slf4j @Slf4j
public class Config extends AbstractConfig { public class Config extends AbstractConfig {
@@ -44,8 +40,13 @@ public class Config extends AbstractConfig {
public static class ROUND { public static class ROUND {
private static final String prefix = "round."; private static final String prefix = "round.";
public static String HG_WORLD = "world";
public static Duration COUNTDOWN = Duration.ofSeconds(10); public static Duration COUNTDOWN = Duration.ofSeconds(10);
public static Duration SAFE_PHASE = Duration.ofSeconds(30);
public static Duration TIME_TO_FINALE = Duration.ofMinutes(5);
public static int INITIAL_BORDER_SIZE = 5000; public static int INITIAL_BORDER_SIZE = 5000;
public static int FINALE_BORDER_SIZE = 48;
public static int DEFAULT_BORDER_TRANSITION_SECONDS = 45;
public static List<GameStage> STAGES = List.of( public static List<GameStage> STAGES = List.of(
GameStage.builder().duration(Duration.ofMinutes(5)).worldBorderSize(1000).build(), 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(750).build(),
@@ -54,9 +55,16 @@ public class Config extends AbstractConfig {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static void load() { private static void load() {
HG_WORLD = config.getString(prefix, "hg-world", HG_WORLD);
int countdownSeconds = config.getInt(prefix, "countdown-seconds", Math.toIntExact(COUNTDOWN.toSeconds())); int countdownSeconds = config.getInt(prefix, "countdown-seconds", Math.toIntExact(COUNTDOWN.toSeconds()));
COUNTDOWN = Duration.ofSeconds(countdownSeconds); COUNTDOWN = Duration.ofSeconds(countdownSeconds);
int safePhaseSeconds = config.getInt(prefix, "safe-phase-seconds", Math.toIntExact(SAFE_PHASE.toSeconds()));
SAFE_PHASE = Duration.ofSeconds(safePhaseSeconds);
int killPhaseSeconds = config.getInt(prefix, "time-to-finale-seconds", Math.toIntExact(TIME_TO_FINALE.toSeconds()));
TIME_TO_FINALE = Duration.ofSeconds(killPhaseSeconds);
INITIAL_BORDER_SIZE = config.getInt(prefix, "initial-border-size", INITIAL_BORDER_SIZE); INITIAL_BORDER_SIZE = config.getInt(prefix, "initial-border-size", INITIAL_BORDER_SIZE);
FINALE_BORDER_SIZE = config.getInt(prefix, "finale-border-size", FINALE_BORDER_SIZE);
DEFAULT_BORDER_TRANSITION_SECONDS = config.getInt(prefix, "default-border-transition-seconds", DEFAULT_BORDER_TRANSITION_SECONDS);
ConfigurationSection configurationSection = config.getConfigurationSection(prefix + "stages"); ConfigurationSection configurationSection = config.getConfigurationSection(prefix + "stages");
if (configurationSection == null) { if (configurationSection == null) {
@@ -134,4 +142,17 @@ public class Config extends AbstractConfig {
STUCK_WARMUP = Duration.ofSeconds(config.getInt(prefix, "stuck-warmup-seconds", (int) STUCK_WARMUP.toSeconds())); STUCK_WARMUP = Duration.ofSeconds(config.getInt(prefix, "stuck-warmup-seconds", (int) STUCK_WARMUP.toSeconds()));
} }
} }
public static class COMMAND_WHITELIST {
private static final String prefix = "command-whitelist";
public static final List<String> PREFIXES = new ArrayList<>(List.of("msg", "ac", "gac", "tell", "r", "server", "lobby", "hub", "bayou", "creative", "hg", "lp"));
@SuppressWarnings("unused")
private static void load() {
List<String> stringList = config.getStringList(prefix, "prefix-list", PREFIXES);
PREFIXES.clear();
PREFIXES.addAll(stringList);
}
}
} }
@@ -1,12 +1,16 @@
package com.alttd.hunger_games.config; package com.alttd.hunger_games.config;
import com.alttd.hunger_games.data_objects.LootItem;
import com.alttd.hunger_games.data_objects.RARITY; import com.alttd.hunger_games.data_objects.RARITY;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Material; import org.bukkit.Material;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@Slf4j
public class LootItems extends AbstractConfig { public class LootItems extends AbstractConfig {
static LootItems config; static LootItems config;
@@ -27,18 +31,40 @@ public class LootItems extends AbstractConfig {
public static class ITEMS { public static class ITEMS {
private static final String prefix = "items."; private static final String prefix = "items.";
private static final HashMap<RARITY, List<Material>> ITEMS = new HashMap<>(); private static final HashMap<RARITY, List<LootItem>> ITEMS = new HashMap<>();
public static List<Material> get(RARITY rarity) { public static List<LootItem> get(RARITY rarity) {
return ITEMS.get(rarity); return ITEMS.get(rarity);
} }
@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.getOrDefault(rarity, List.of()).stream().map(Material::name).toList(); List<LootItem> currentItems = ITEMS.getOrDefault(rarity, List.of());
List<String> materialNameList = currentItems.stream()
.map(item -> item.material().name() + (item.maxAmount() > 1 ? ":" + item.maxAmount() : ""))
.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());
List<LootItem> lootItems = new ArrayList<>();
for (String entry : materialList) {
String[] split = entry.split(":");
Material material = Material.getMaterial(split[0].toUpperCase());
if (material == null) {
log.warn("Invalid material found in loot-items.yml: {}", split[0]);
continue;
}
int maxAmount = 1;
if (split.length > 1) {
try {
maxAmount = Integer.parseInt(split[1]);
} catch (NumberFormatException ignored) {
log.error("Invalid max amount found in loot-items.yml: {}", split[1]);
}
}
lootItems.add(new LootItem(material, maxAmount));
}
ITEMS.put(rarity, lootItems);
} }
} }
} }
@@ -29,9 +29,12 @@ public class Messages extends AbstractConfig {
public static String ROUND_STATE = "<green>Show the current round state: <gold>/hg roundstate</gold></green>"; public static String ROUND_STATE = "<green>Show the current round state: <gold>/hg roundstate</gold></green>";
public static String REGISTER = "<green>Register a player for the game: <gold>/hg register <player></gold></green>"; public static String REGISTER = "<green>Register a player for the game: <gold>/hg register <player></gold></green>";
public static String START_ROUND = "<green>Start the game: <gold>/hg start</gold></green>"; public static String START_ROUND = "<green>Start the game: <gold>/hg start</gold></green>";
public static String NEXT_PHASE = "<green>Skip to the next phase/stage: <gold>/hg nextphase</gold></green>";
public static String RELOAD = "<green>Reload config and messages: <gold>/hg reload</gold></green>"; public static String RELOAD = "<green>Reload config and messages: <gold>/hg reload</gold></green>";
public static String STUCK = "<green>Teleport to safety if stuck: <gold>/hg stuck</gold></green>"; public static String STUCK = "<green>Teleport to safety if stuck: <gold>/hg stuck</gold></green>";
public static String STATS = "<green>Show player stats: <gold>/hg stats [player]</gold></green>"; public static String STATS = "<green>Show player stats: <gold>/hg stats [player]</gold></green>";
public static String LIST = "<green>List all players in the game and spectators: <gold>/hg list</gold></green>";
public static String END_ROUND = "<green>End the game: <gold>/hg end</gold></green>";
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static void load() { private static void load() {
@@ -40,9 +43,12 @@ public class Messages extends AbstractConfig {
ROUND_STATE = config.getString(prefix, "round-state", ROUND_STATE); ROUND_STATE = config.getString(prefix, "round-state", ROUND_STATE);
REGISTER = config.getString(prefix, "register", REGISTER); REGISTER = config.getString(prefix, "register", REGISTER);
START_ROUND = config.getString(prefix, "start", START_ROUND); START_ROUND = config.getString(prefix, "start", START_ROUND);
NEXT_PHASE = config.getString(prefix, "next-phase", NEXT_PHASE);
RELOAD = config.getString(prefix, "reload", RELOAD); RELOAD = config.getString(prefix, "reload", RELOAD);
STUCK = config.getString(prefix, "stuck", STUCK); STUCK = config.getString(prefix, "stuck", STUCK);
STATS = config.getString(prefix, "stats", STATS); STATS = config.getString(prefix, "stats", STATS);
LIST = config.getString(prefix, "list", LIST);
END_ROUND = config.getString(prefix, "end-round", END_ROUND);
} }
} }
@@ -53,6 +59,7 @@ public class Messages extends AbstractConfig {
public static String PLAYER_ONLY = "<red>This command can only be executed as a player</red>"; public static String PLAYER_ONLY = "<red>This command can only be executed as a player</red>";
public static String PLAYER_NOT_FOUND = "<red>Unable to find online player <player></red>"; public static String PLAYER_NOT_FOUND = "<red>Unable to find online player <player></red>";
public static String RELOAD_SUCCESS = "<green>Config and messages have been reloaded.</green>"; public static String RELOAD_SUCCESS = "<green>Config and messages have been reloaded.</green>";
public static String COMMAND_BLOCKED = "<red>You cannot use that command here!</red>";
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static void load() { private static void load() {
@@ -60,6 +67,7 @@ public class Messages extends AbstractConfig {
PLAYER_ONLY = config.getString(prefix, "player-only", PLAYER_ONLY); PLAYER_ONLY = config.getString(prefix, "player-only", PLAYER_ONLY);
PLAYER_NOT_FOUND = config.getString(prefix, "player-only", PLAYER_NOT_FOUND); PLAYER_NOT_FOUND = config.getString(prefix, "player-only", PLAYER_NOT_FOUND);
RELOAD_SUCCESS = config.getString(prefix, "reload-success", RELOAD_SUCCESS); RELOAD_SUCCESS = config.getString(prefix, "reload-success", RELOAD_SUCCESS);
COMMAND_BLOCKED = config.getString(prefix, "command-blocked", COMMAND_BLOCKED);
} }
} }
@@ -83,12 +91,16 @@ public class Messages extends AbstractConfig {
public static String CHEST_EMPTY = "<red>This chest is empty!</red>"; public static String CHEST_EMPTY = "<red>This chest is empty!</red>";
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 PLAYER_KILLED = "<red><victim> was killed by <killer>! <gold><remaining></gold> players remaining.</red>";
public static String BOSSBAR_COUNTDOWN = "<gold>Loot phase in <time></gold>"; 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_PVP_COUNTDOWN = "<green>PVP starts in <time></green>";
public static String BOSSBAR_KILL_PHASE = "<red>Border shrink in <time></red>"; public static String BOSSBAR_SAFE_PHASE = "<green>Border shrinking in <time></green>";
public static String BOSSBAR_KILL_PHASE = "<red>Border shrinking... <time></red>";
public static String BOSSBAR_FINALE = "<gold>Remaining Players: <remaining></gold>"; public static String BOSSBAR_FINALE = "<gold>Remaining Players: <remaining></gold>";
public static String NEXT_STATE = "<gold>Starting <round>!</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);
@@ -97,11 +109,15 @@ 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);
PLAYER_KILLED = config.getString(prefix, "player-killed", PLAYER_KILLED);
BOSSBAR_COUNTDOWN = config.getString(prefix, "bossbar-countdown", BOSSBAR_COUNTDOWN); BOSSBAR_COUNTDOWN = config.getString(prefix, "bossbar-countdown", BOSSBAR_COUNTDOWN);
BOSSBAR_SAFE_PHASE = config.getString(prefix, "bossbar-safe-phase", BOSSBAR_SAFE_PHASE); BOSSBAR_PVP_COUNTDOWN = config.getString(prefix, "bossbar-pvp-countdown", BOSSBAR_PVP_COUNTDOWN);
BOSSBAR_KILL_PHASE = config.getString(prefix, "bossbar-kill-phase", BOSSBAR_KILL_PHASE); BOSSBAR_SAFE_PHASE = config.getString(prefix, "bossbar-border-shrinking-in", BOSSBAR_SAFE_PHASE);
BOSSBAR_KILL_PHASE = config.getString(prefix, "bossbar-border-shrinking", BOSSBAR_KILL_PHASE);
BOSSBAR_FINALE = config.getString(prefix, "bossbar-finale", BOSSBAR_FINALE); BOSSBAR_FINALE = config.getString(prefix, "bossbar-finale", BOSSBAR_FINALE);
NEXT_STATE = config.getString(prefix, "next-state", NEXT_STATE);
} }
} }
@@ -191,4 +207,20 @@ public class Messages extends AbstractConfig {
STATS_FORMAT = config.getString(prefix, "format", STATS_FORMAT); STATS_FORMAT = config.getString(prefix, "format", STATS_FORMAT);
} }
} }
public static class COMMANDS {
private static final String prefix = "commands.";
public static String NEXT_PHASE_SUCCESS = "<green>Skipping to the next phase/stage.</green>";
public static String LIST_PLAYERS = """
<gold>HungerGames Players (<in_game>/<total>):</gold>
<green>In Game: <white><in_game_players></white></green>
<yellow>Spectators: <white><spectators></white></yellow>""";
@SuppressWarnings("unused")
private static void load() {
NEXT_PHASE_SUCCESS = config.getString(prefix + "next-phase", "success", NEXT_PHASE_SUCCESS);
LIST_PLAYERS = config.getString(prefix, "list-players", LIST_PLAYERS);
}
}
} }
@@ -0,0 +1,6 @@
package com.alttd.hunger_games.data_objects;
import org.bukkit.Material;
public record LootItem(Material material, int maxAmount) {
}
@@ -17,7 +17,7 @@ public enum ROUND_STATE {
return Optional.of(values()[ordinal() + 1]); return Optional.of(values()[ordinal() + 1]);
} }
public String getHumandReadableName() { public String getHumanReadableName() {
return switch (this) { return switch (this) {
case PLAYER_REGISTRATION -> "Player Registration"; case PLAYER_REGISTRATION -> "Player Registration";
case COUNTDOWN -> "Countdown"; case COUNTDOWN -> "Countdown";
@@ -0,0 +1,34 @@
package com.alttd.hunger_games.event_listeners;
import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.config.Messages;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import java.util.Optional;
public class CommandListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
if (player.hasPermission("hg.bypass")) {
return;
}
// Remove leading slash
String message = event.getMessage().substring(1).toLowerCase();
String command = message.split(" ")[0];
boolean whitelisted = Config.COMMAND_WHITELIST.PREFIXES.stream()
.anyMatch(prefix -> command.startsWith(prefix.toLowerCase()));
if (!whitelisted) {
event.setCancelled(true);
player.sendRichMessage(Messages.GENERIC.COMMAND_BLOCKED);
}
}
}
@@ -0,0 +1,59 @@
package com.alttd.hunger_games.event_listeners;
import com.alttd.hunger_games.data_objects.ROUND_STATE;
import com.alttd.hunger_games.services.RoundService;
import lombok.RequiredArgsConstructor;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@RequiredArgsConstructor
public class ItemSpawnListener implements Listener {
private final RoundService roundService;
private final Set<UUID> deathDrops = new HashSet<>();
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onItemSpawn(ItemSpawnEvent event) {
if (deathDrops.remove(event.getEntity().getUniqueId())) {
event.getEntity().setTicksLived(5400); // 300 seconds total - 5400 ticks = 30 seconds left
return;
}
setDespawnDelay(event.getEntity());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerDeath(EntityDeathEvent event) {
if (!(event.getEntity() instanceof Player)) {
return;
}
ROUND_STATE roundState = roundService.getRoundState();
if (roundState == ROUND_STATE.SAFE_PHASE || roundState == ROUND_STATE.KILL_PHASE || roundState == ROUND_STATE.FINALE) {
event.getDrops().forEach(itemStack -> {
Item item = event.getEntity().getWorld().dropItemNaturally(event.getEntity().getLocation(), itemStack);
deathDrops.add(item.getUniqueId());
});
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerDropItem(PlayerDropItemEvent event) {
setDespawnDelay(event.getItemDrop());
}
private void setDespawnDelay(Item item) {
ROUND_STATE roundState = roundService.getRoundState();
if (roundState == ROUND_STATE.SAFE_PHASE || roundState == ROUND_STATE.KILL_PHASE || roundState == ROUND_STATE.FINALE) {
item.setTicksLived(5800);
}
}
}
@@ -7,6 +7,7 @@ import com.alttd.hunger_games.services.StatService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent;
@@ -39,9 +40,10 @@ public class PlayerDamageListener implements Listener {
} }
} }
@EventHandler @EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerDeath(PlayerDeathEvent event) { public void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
event.setShowDeathMessages(false);
event.deathMessage(null); event.deathMessage(null);
playerService.handlePlayerDeath(player); playerService.handlePlayerDeath(player);
statService.addDeath(player.getUniqueId()); statService.addDeath(player.getUniqueId());
@@ -54,6 +56,6 @@ public class PlayerDamageListener implements Listener {
@EventHandler @EventHandler
public void onPlayerRespawn(PlayerRespawnEvent event) { public void onPlayerRespawn(PlayerRespawnEvent event) {
playerService.handlePlayerRespawn(event.getPlayer()); playerService.handlePlayerRespawn(event);
} }
} }
@@ -1,18 +1,33 @@
package com.alttd.hunger_games.event_listeners; package com.alttd.hunger_games.event_listeners;
import com.alttd.hunger_games.services.PlayerService; import com.alttd.hunger_games.services.PlayerService;
import com.alttd.hunger_games.services.TabListService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandException;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
@Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class PlayerJoinListener implements Listener { public class PlayerJoinListener implements Listener {
private final PlayerService playerService; private final PlayerService playerService;
private final TabListService tabListService;
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
playerService.handleJoin(event.getPlayer()); Player player = event.getPlayer();
playerService.handleJoin(player);
tabListService.updatePlayerColor(player);
try {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
String.format("lp user %s permission settemp cmi.kit.hunger-games true 7d", player.getName()));
} catch (CommandException e) {
log.error("Failed to set participant permission for {}", player.getName(), e);
}
} }
} }
@@ -1,60 +1,43 @@
package com.alttd.hunger_games.game; package com.alttd.hunger_games.game;
import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.config.Messages; import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.utils.WorldBorderUtil;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.title.Title;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.WorldBorder;
import java.util.Optional; import java.time.Duration;
@UtilityClass @UtilityClass
public class GameStageHandler { public class GameStageHandler {
private static final int BORDER_TRANSITION_SECONDS = 10;
public static void handleStageChange(int worldBorderSize) { public static void handleStageChange(int worldBorderSize) {
World world = getWorld().orElseThrow(() -> new IllegalStateException("No worlds available during state change,")); WorldBorderUtil.setSize(worldBorderSize);
WorldBorder border = world.getWorldBorder();
border.setSize(worldBorderSize, BORDER_TRANSITION_SECONDS);
String message = Messages.GAME.BORDER_SHRINK String message = Messages.GAME.BORDER_SHRINK
.replace("<size>", String.valueOf(worldBorderSize)); .replace("<size>", String.valueOf(worldBorderSize));
broadcast(message); broadcast(message);
} }
public static void broadcastMessage(String message) {
broadcast(message);
}
public static void handleCountdownEnd() { public static void handleCountdownEnd() {
broadcast(Messages.GAME.STARTED); broadcast(Messages.GAME.STARTED);
} }
public static void handleWarmup() { public static void handleWarmup() {
Location center = Config.DESTINATION.START_CENTER;
if (center == null || center.getWorld() == null) {
throw new IllegalStateException("Start center is not set during warmup.");
}
WorldBorder border = center.getWorld().getWorldBorder();
border.setCenter(center.getX(), center.getZ());
border.setSize(Config.ROUND.INITIAL_BORDER_SIZE);
broadcast(Messages.GAME.WARMUP); broadcast(Messages.GAME.WARMUP);
} }
private static Optional<World> getWorld() {
Location center = Config.DESTINATION.START_CENTER;
if (center != null && center.getWorld() != null) {
return Optional.ofNullable(center.getWorld());
}
return Bukkit.getWorlds().isEmpty() ? Optional.empty() : Optional.of(Bukkit.getWorlds().getFirst());
}
private static void broadcast(String message) { private static void broadcast(String message) {
Component component = MiniMessage.miniMessage().deserialize(message); Component component = MiniMessage.miniMessage().deserialize(message);
Bukkit.broadcast(component); Bukkit.broadcast(component);
Title.Times times = Title.Times.times(Duration.ofSeconds(1), Duration.ofSeconds(5), Duration.ofSeconds(1));
Title title = Title.title(Component.empty(), component, times);
Bukkit.getOnlinePlayers().forEach(player -> player.showTitle(title));
} }
} }
@@ -42,9 +42,14 @@ public class BossBarService implements RoundListener {
public void updateCountdown(Duration timeLeft, Duration totalTime, ROUND_STATE state) { public void updateCountdown(Duration timeLeft, Duration totalTime, ROUND_STATE state) {
String message = switch (state) { String message = switch (state) {
case COUNTDOWN -> Messages.GAME.BOSSBAR_COUNTDOWN; case COUNTDOWN -> Messages.GAME.BOSSBAR_COUNTDOWN;
case SAFE_PHASE -> Messages.GAME.BOSSBAR_SAFE_PHASE; case SAFE_PHASE -> Messages.GAME.BOSSBAR_PVP_COUNTDOWN;
case KILL_PHASE -> Messages.GAME.BOSSBAR_KILL_PHASE; case KILL_PHASE -> Messages.GAME.BOSSBAR_SAFE_PHASE;
default -> throw new IllegalArgumentException("Invalid round state: " + state); default -> {
if (state.ordinal() > ROUND_STATE.KILL_PHASE.ordinal()) {
yield Messages.GAME.BOSSBAR_KILL_PHASE;
}
throw new IllegalArgumentException("Invalid round state: " + state);
}
}; };
if (message.isEmpty()) { if (message.isEmpty()) {
@@ -61,6 +66,10 @@ public class BossBarService implements RoundListener {
} }
private void updateBossBarColor(ROUND_STATE state) { private void updateBossBarColor(ROUND_STATE state) {
if (state == ROUND_STATE.ENDED || state == ROUND_STATE.PLAYER_REGISTRATION) {
hideAll();
return;
}
switch (state) { switch (state) {
case COUNTDOWN -> bossBar.color(BossBar.Color.YELLOW); case COUNTDOWN -> bossBar.color(BossBar.Color.YELLOW);
case SAFE_PHASE -> bossBar.color(BossBar.Color.GREEN); case SAFE_PHASE -> bossBar.color(BossBar.Color.GREEN);
@@ -87,27 +96,20 @@ public class BossBarService implements RoundListener {
} }
private void updateViewers() { private void updateViewers() {
Set<UUID> registeredPlayers = roundService.getPlayers(PLAYER_STATE.REGISTERED); if (roundService.getRoundState() == ROUND_STATE.ENDED || roundService.getRoundState() == ROUND_STATE.PLAYER_REGISTRATION) {
hideAll();
// Add new viewers return;
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);
}
} }
Bukkit.getOnlinePlayers().forEach(player -> {
if (viewers.add(player.getUniqueId())) {
player.showBossBar(bossBar);
}
});
viewers.removeIf(uuid -> {
Player player = Bukkit.getPlayer(uuid);
return player == null || !player.isOnline();
});
} }
private void hideAll() { private void hideAll() {
@@ -1,6 +1,7 @@
package com.alttd.hunger_games.services; package com.alttd.hunger_games.services;
import com.alttd.hunger_games.config.LootItems; import com.alttd.hunger_games.config.LootItems;
import com.alttd.hunger_games.data_objects.LootItem;
import com.alttd.hunger_games.data_objects.RARITY; import com.alttd.hunger_games.data_objects.RARITY;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@@ -38,15 +39,20 @@ public class LootService {
for (int i = 0; i < itemCount; i++) { for (int i = 0; i < itemCount; i++) {
int slot = random.nextInt(27); int slot = random.nextInt(27);
RARITY rarity = decideRarity(); RARITY rarity = decideRarity();
List<Material> possibleItems = LootItems.ITEMS.get(rarity); List<LootItem> possibleItems = LootItems.ITEMS.get(rarity);
if (possibleItems == null || possibleItems.isEmpty()) { if (possibleItems == null || possibleItems.isEmpty()) {
log.warn("No items found for rarity: {}", rarity); log.warn("No items found for rarity: {}", rarity);
continue; continue;
} }
Material material = possibleItems.get(random.nextInt(possibleItems.size())); LootItem lootItem = possibleItems.get(random.nextInt(possibleItems.size()));
inventory.setItem(slot, new ItemStack(material)); if (lootItem == null || lootItem.material() == null) {
log.warn("LootItem or material is null for rarity: {}", rarity);
continue;
}
int amount = lootItem.maxAmount() > 1 ? random.nextInt(lootItem.maxAmount()) + 1 : 1;
inventory.setItem(slot, new ItemStack(lootItem.material(), amount));
} }
return inventory; return inventory;
@@ -4,13 +4,17 @@ import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.data_objects.DESTINATION; import com.alttd.hunger_games.data_objects.DESTINATION;
import com.alttd.hunger_games.data_objects.PLAYER_STATE; import com.alttd.hunger_games.data_objects.PLAYER_STATE;
import com.alttd.hunger_games.data_objects.ROUND_STATE; import com.alttd.hunger_games.data_objects.ROUND_STATE;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerRespawnEvent;
import java.util.*; import java.util.*;
@Slf4j
public class PlayerService implements RoundListener { public class PlayerService implements RoundListener {
private static PlayerService instance = null; private static PlayerService instance = null;
@@ -36,6 +40,7 @@ public class PlayerService implements RoundListener {
@Override @Override
public void stateChange(ROUND_STATE roundState) { public void stateChange(ROUND_STATE roundState) {
this.roundState = roundState; this.roundState = roundState;
playerTeleporterService.reset();
switch (roundState) { switch (roundState) {
case PLAYER_REGISTRATION, KILL_PHASE, SAFE_PHASE -> { case PLAYER_REGISTRATION, KILL_PHASE, SAFE_PHASE -> {
//Nothing //Nothing
@@ -55,7 +60,7 @@ public class PlayerService implements RoundListener {
} }
case ENDED -> { case ENDED -> {
roundService.clear(); roundService.clear();
Bukkit.getOnlinePlayers().forEach(player -> playerTeleporterService.teleportPlayer(player, DESTINATION.START_AREA)); Bukkit.getOnlinePlayers().forEach(player -> playerTeleporterService.teleportPlayer(player, DESTINATION.SPECTATOR_AREA));
} }
} }
} }
@@ -86,6 +91,7 @@ public class PlayerService implements RoundListener {
} }
public void handleDisconnect(Player player) { public void handleDisconnect(Player player) {
player.playerListName(null);
UUID uuid = player.getUniqueId(); UUID uuid = player.getUniqueId();
roundService.setPlayerState(uuid, PLAYER_STATE.DISCONNECTED); roundService.setPlayerState(uuid, PLAYER_STATE.DISCONNECTED);
} }
@@ -108,15 +114,22 @@ public class PlayerService implements RoundListener {
roundService.setPlayerState(uuid, PLAYER_STATE.SPECTATING); roundService.setPlayerState(uuid, PLAYER_STATE.SPECTATING);
int remaining = roundService.getPlayers(PLAYER_STATE.REGISTERED).size(); int remaining = roundService.getPlayers(PLAYER_STATE.REGISTERED).size();
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Messages.GAME.PLAYER_DEATH, Player killer = player.getKiller();
Placeholder.component("player", player.name()), if (killer != null) {
Placeholder.unparsed("remaining", String.valueOf(remaining)))); Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Messages.GAME.PLAYER_KILLED,
Placeholder.component("victim", player.name()),
Placeholder.component("killer", killer.name()),
Placeholder.unparsed("remaining", String.valueOf(remaining))));
} else {
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Messages.GAME.PLAYER_DEATH,
Placeholder.component("player", player.name()),
Placeholder.unparsed("remaining", String.valueOf(remaining))));
}
} }
public void handlePlayerRespawn(Player player) { public void handlePlayerRespawn(PlayerRespawnEvent event) {
roundService.getPlayerState(player.getUniqueId()) Location spectatorArea = playerTeleporterService.getLocationFromDestination(DESTINATION.SPECTATOR_AREA);
.filter(state -> state == PLAYER_STATE.SPECTATING) event.setRespawnLocation(spectatorArea);
.ifPresent(state -> playerTeleporterService.teleportPlayer(player, DESTINATION.SPECTATOR_AREA));
} }
private Optional<PLAYER_STATE> determinePlayerState(UUID playerUuid) { private Optional<PLAYER_STATE> determinePlayerState(UUID playerUuid) {
@@ -127,13 +140,18 @@ public class PlayerService implements RoundListener {
if (optionalPlayerState.isPresent()) { if (optionalPlayerState.isPresent()) {
PLAYER_STATE playerState = optionalPlayerState.get(); PLAYER_STATE playerState = optionalPlayerState.get();
return Optional.of(switch (playerState) { return Optional.of(switch (playerState) {
case DISCONNECTED, SPECTATING -> PLAYER_STATE.SPECTATING; case DISCONNECTED, SPECTATING -> {
if (roundState.equals(ROUND_STATE.ENDED)) {
yield PLAYER_STATE.REGISTERED;
}
yield PLAYER_STATE.SPECTATING;
}
case REGISTERED -> PLAYER_STATE.REGISTERED; case REGISTERED -> PLAYER_STATE.REGISTERED;
}); });
} }
return switch (roundState) { return switch (roundState) {
case PLAYER_REGISTRATION, COUNTDOWN -> Optional.of(PLAYER_STATE.REGISTERED); case PLAYER_REGISTRATION, COUNTDOWN, ENDED -> Optional.of(PLAYER_STATE.REGISTERED);
case KILL_PHASE, FINALE, ENDED, SAFE_PHASE -> Optional.of(PLAYER_STATE.SPECTATING); case KILL_PHASE, FINALE, SAFE_PHASE -> Optional.of(PLAYER_STATE.SPECTATING);
}; };
} }
} }
@@ -4,6 +4,7 @@ import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.data_objects.DESTINATION; import com.alttd.hunger_games.data_objects.DESTINATION;
import com.alttd.hunger_games.data_objects.ROUND_STATE; import com.alttd.hunger_games.data_objects.ROUND_STATE;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -12,6 +13,7 @@ import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@Slf4j
@NoArgsConstructor @NoArgsConstructor
public class PlayerTeleporterService implements RoundListener { public class PlayerTeleporterService implements RoundListener {
@@ -29,10 +31,20 @@ public class PlayerTeleporterService implements RoundListener {
} }
public void teleportPlayer(Player player, DESTINATION destination) { public void teleportPlayer(Player player, DESTINATION destination) {
player.teleport(getLocationFromDestination(destination)); Location locationFromDestination = getLocationFromDestination(destination);
try {
player.teleportAsync(getLocationFromDestination(destination));
} catch (Exception e) {
log.error("Failed to teleport player {} to destination {} ({})", player.getName(), destination.name(), locationFromDestination, e);
}
} }
private Location getLocationFromDestination(DESTINATION destination) { public void reset() {
placementCount.clear();
usedLocations.clear();
}
public Location getLocationFromDestination(DESTINATION destination) {
return switch (destination) { return switch (destination) {
case START_AREA -> { case START_AREA -> {
Location startCenter = Config.DESTINATION.START_CENTER; Location startCenter = Config.DESTINATION.START_CENTER;
@@ -1,9 +1,11 @@
package com.alttd.hunger_games.services; package com.alttd.hunger_games.services;
import com.alttd.hunger_games.config.Config; import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.data_objects.GameStage; import com.alttd.hunger_games.data_objects.GameStage;
import com.alttd.hunger_games.data_objects.ROUND_STATE; import com.alttd.hunger_games.data_objects.ROUND_STATE;
import com.alttd.hunger_games.game.GameStageHandler; import com.alttd.hunger_games.game.GameStageHandler;
import lombok.Setter;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@@ -22,6 +24,8 @@ public class Round {
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 ScheduledFuture<?> countdownFuture = null;
private Runnable onEnd = null;
@Setter
private BossBarService bossBarService; private BossBarService bossBarService;
private Round() { private Round() {
@@ -73,7 +77,7 @@ public class Round {
} }
public void startRound() { public void startRound() {
if (!roundState.equals(ROUND_STATE.PLAYER_REGISTRATION)) { if (!roundState.equals(ROUND_STATE.PLAYER_REGISTRATION) && !roundState.equals(ROUND_STATE.ENDED)) {
throw new IllegalStateException("Round can not be started before player registration."); throw new IllegalStateException("Round can not be started before player registration.");
} }
roundState = ROUND_STATE.COUNTDOWN; roundState = ROUND_STATE.COUNTDOWN;
@@ -83,8 +87,12 @@ public class Round {
startCountdown(warmupDuration, () -> { startCountdown(warmupDuration, () -> {
GameStageHandler.handleCountdownEnd(); GameStageHandler.handleCountdownEnd();
nextStage(); nextStage(); // COUNTDOWN -> SAFE_PHASE
scheduleNextStage(0); startCountdown(Config.ROUND.SAFE_PHASE, () -> {
nextStage(); // SAFE_PHASE -> KILL_PHASE
GameStageHandler.broadcastMessage(Messages.GAME.NEXT_STATE.replace("<round>", roundState.getHumanReadableName()));
scheduleNextStage(0);
});
}); });
} }
@@ -92,9 +100,17 @@ public class Round {
if (countdownFuture != null) { if (countdownFuture != null) {
countdownFuture.cancel(true); countdownFuture.cancel(true);
} }
this.onEnd = onEnd;
final Instant endTime = Instant.now().plus(duration); final Instant endTime = Instant.now().plus(duration);
countdownFuture = scheduledExecutorService.scheduleAtFixedRate(() -> { countdownFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
if (roundState.equals(ROUND_STATE.FINALE)) {
if (countdownFuture != null) {
countdownFuture.cancel(false);
countdownFuture = null;
}
return;
}
Duration timeLeft = Duration.between(Instant.now(), endTime); Duration timeLeft = Duration.between(Instant.now(), endTime);
if (bossBarService != null) { if (bossBarService != null) {
bossBarService.updateCountdown(timeLeft, duration, roundState); bossBarService.updateCountdown(timeLeft, duration, roundState);
@@ -107,8 +123,12 @@ public class Round {
private void scheduleNextStage(int index) { private void scheduleNextStage(int index) {
List<GameStage> gameStageList = Config.ROUND.STAGES; List<GameStage> gameStageList = Config.ROUND.STAGES;
if (gameStageList.isEmpty()) {
return;
}
if (gameStageList.size() <= index) { if (gameStageList.size() <= index) {
nextStage(); startCountdown(Config.ROUND.TIME_TO_FINALE, this::nextStage);
return; return;
} }
@@ -121,7 +141,12 @@ public class Round {
}); });
} }
public void setBossBarService(BossBarService bossBarService) { public void forceNextPhase() {
this.bossBarService = bossBarService; if (countdownFuture == null || onEnd == null) {
return;
}
countdownFuture.cancel(true);
onEnd.run();
} }
} }
@@ -1,15 +1,22 @@
package com.alttd.hunger_games.services; package com.alttd.hunger_games.services;
import com.alttd.hunger_games.Main; import com.alttd.hunger_games.Main;
import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.config.Messages; import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.data_objects.DESTINATION;
import com.alttd.hunger_games.data_objects.PLAYER_STATE; import com.alttd.hunger_games.data_objects.PLAYER_STATE;
import com.alttd.hunger_games.data_objects.ROUND_STATE; import com.alttd.hunger_games.data_objects.ROUND_STATE;
import com.alttd.hunger_games.event_listeners.PlayerMovementListener; import com.alttd.hunger_games.event_listeners.PlayerMovementListener;
import com.alttd.hunger_games.utils.WorldBorderUtil;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.World;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
@@ -20,32 +27,37 @@ import java.util.stream.Collectors;
public class RoundService implements RoundListener { public class RoundService implements RoundListener {
private static RoundService instance = null; private static RoundService instance = null;
private PlayerMovementListener playerMovementListener; private final PlayerTeleporterService playerTeleporterService;
private final Main main; private final Main main;
private final LootService lootService; private final LootService lootService;
private final StatService statService; private final StatService statService;
private PlayerMovementListener playerMovementListener;
@Getter @Getter
private ROUND_STATE roundState; private ROUND_STATE roundState;
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;
@Setter
private BossBarService bossBarService; private BossBarService bossBarService;
@Setter
private TabListService tabListService;
public static RoundService createSingletonInstance(Round round, Main main, LootService lootService, StatService statService) { public static RoundService createSingletonInstance(Round round, Main main, LootService lootService, StatService statService, PlayerTeleporterService playerTeleporterService) {
if (instance != null) { if (instance != null) {
throw new IllegalStateException("RoundService is already initialized."); throw new IllegalStateException("RoundService is already initialized.");
} }
instance = new RoundService(round, main, lootService, statService); instance = new RoundService(round, main, lootService, statService, playerTeleporterService);
return instance; return instance;
} }
private RoundService(Round round, Main main, LootService lootService, StatService statService) { private RoundService(Round round, Main main, LootService lootService, StatService statService, PlayerTeleporterService playerTeleporterService) {
this.round = round; this.round = round;
this.roundState = round.register(this); this.roundState = round.register(this);
this.main = main; this.main = main;
this.lootService = lootService; this.lootService = lootService;
this.statService = statService; this.statService = statService;
this.playerTeleporterService = playerTeleporterService;
} }
public Set<UUID> getPlayers(PLAYER_STATE playerState) { public Set<UUID> getPlayers(PLAYER_STATE playerState) {
@@ -65,6 +77,13 @@ public class RoundService implements RoundListener {
} }
} }
if (tabListService != null) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
tabListService.updatePlayerColor(player);
}
}
if (!roundState.equals(ROUND_STATE.KILL_PHASE) && !roundState.equals(ROUND_STATE.FINALE)) { if (!roundState.equals(ROUND_STATE.KILL_PHASE) && !roundState.equals(ROUND_STATE.FINALE)) {
return; return;
} }
@@ -87,6 +106,13 @@ public class RoundService implements RoundListener {
statService.setWon(winnerUUID, true); statService.setWon(winnerUUID, true);
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Messages.GAME.WINNER, Placeholder.component("player", player.name()))); Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Messages.GAME.WINNER, Placeholder.component("player", player.name())));
playerTeleporterService.teleportPlayer(player, DESTINATION.SPECTATOR_AREA);
player.setHealth(20);
player.setFoodLevel(20);
player.getInventory().clear();
player.setTotalExperience(0);
player.setExp(0.0f);
player.setLevel(0);
round.endRound(); round.endRound();
} }
@@ -102,12 +128,33 @@ public class RoundService implements RoundListener {
public void stateChange(ROUND_STATE roundState) { public void stateChange(ROUND_STATE roundState) {
this.roundState = roundState; this.roundState = roundState;
stopFreeze(); stopFreeze();
if (roundState.equals(ROUND_STATE.PLAYER_REGISTRATION)) { if (roundState.equals(ROUND_STATE.PLAYER_REGISTRATION) || roundState.equals(ROUND_STATE.ENDED)) {
if (roundState.equals(ROUND_STATE.ENDED)) {
cleanupItems();
}
clear(); clear();
lootService.clear(); lootService.clear();
} }
if (roundState.equals(ROUND_STATE.COUNTDOWN)) { if (roundState.equals(ROUND_STATE.COUNTDOWN)) {
Bukkit.getOnlinePlayers().stream()
.filter(player -> PLAYER_STATE.REGISTERED.equals(getPlayerState(player.getUniqueId()).orElse(null)))
.forEach(player -> {
player.setHealth(20);
player.setFoodLevel(20);
player.setGameMode(GameMode.ADVENTURE);
player.getInventory().clear();
player.setTotalExperience(0);
player.setExp(0.0f);
player.setLevel(0);
});
startFreeze(); startFreeze();
WorldBorderUtil.resetBorder();
}
if (roundState.equals(ROUND_STATE.FINALE)) {
WorldBorderUtil.setSize(Config.ROUND.FINALE_BORDER_SIZE, 1);
}
if (!roundState.equals(ROUND_STATE.ENDED) && !roundState.equals(ROUND_STATE.PLAYER_REGISTRATION)) {
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Messages.GAME.NEXT_STATE, Placeholder.parsed("round", roundState.getHumanReadableName())));
} }
} }
@@ -126,7 +173,13 @@ public class RoundService implements RoundListener {
main.getServer().getPluginManager().registerEvents(playerMovementListener, main); main.getServer().getPluginManager().registerEvents(playerMovementListener, main);
} }
public void setBossBarService(BossBarService bossBarService) { private void cleanupItems() {
this.bossBarService = bossBarService; World world = Bukkit.getWorld(Config.ROUND.HG_WORLD);
if (world == null) {
log.error("Unable to cleanup items, world {} not found", Config.ROUND.HG_WORLD);
return;
}
world.getEntitiesByClass(Item.class).forEach(Item::remove);
} }
} }
@@ -45,8 +45,10 @@ public class StatService implements RoundListener {
@Override @Override
public void stateChange(ROUND_STATE roundState) { public void stateChange(ROUND_STATE roundState) {
if (roundState == ROUND_STATE.PLAYER_REGISTRATION) { if (roundState.equals(ROUND_STATE.ENDED)) {
saveRoundStats(); saveRoundStats();
}
if (roundState.equals(ROUND_STATE.PLAYER_REGISTRATION)) {
currentRoundStats.clear(); currentRoundStats.clear();
} }
} }
@@ -0,0 +1,46 @@
package com.alttd.hunger_games.services;
import com.alttd.hunger_games.data_objects.PLAYER_STATE;
import com.alttd.hunger_games.data_objects.ROUND_STATE;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.UUID;
public class TabListService implements RoundListener {
private final RoundService roundService;
private ROUND_STATE roundState;
public TabListService(Round round, RoundService roundService) {
this.roundService = roundService;
this.roundState = round.register(this);
}
public void updatePlayerColor(Player player) {
if (!player.isOnline()) {
return;
}
if (roundState == null || roundState == ROUND_STATE.ENDED || roundState == ROUND_STATE.PLAYER_REGISTRATION) {
player.playerListName(null);
return;
}
PLAYER_STATE state = roundService.getPlayerState(player.getUniqueId()).orElse(PLAYER_STATE.SPECTATING);
switch (state) {
case REGISTERED -> player.playerListName(player.name().color(NamedTextColor.GREEN));
case SPECTATING, DISCONNECTED -> player.playerListName(player.name().color(NamedTextColor.RED));
}
}
public void updateAllPlayers() {
Bukkit.getOnlinePlayers().forEach(this::updatePlayerColor);
}
@Override
public void stateChange(ROUND_STATE roundState) {
this.roundState = roundState;
updateAllPlayers();
}
}
@@ -0,0 +1,44 @@
package com.alttd.hunger_games.utils;
import com.alttd.hunger_games.config.Config;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.WorldBorder;
import java.util.Optional;
@UtilityClass
public class WorldBorderUtil {
public static void setSize(int size) {
setSize(size, Config.ROUND.DEFAULT_BORDER_TRANSITION_SECONDS);
}
public static void setSize(int size, int seconds) {
getWorld().ifPresent(world -> {
WorldBorder border = world.getWorldBorder();
border.setSize(size, seconds);
});
}
public static void resetBorder() {
Location center = Config.DESTINATION.START_CENTER;
if (center == null || center.getWorld() == null) {
return;
}
WorldBorder border = center.getWorld().getWorldBorder();
border.setCenter(center.getX(), center.getZ());
border.setSize(Config.ROUND.INITIAL_BORDER_SIZE);
}
private static Optional<World> getWorld() {
Location center = Config.DESTINATION.START_CENTER;
if (center != null && center.getWorld() != null) {
return Optional.ofNullable(center.getWorld());
}
return Bukkit.getWorlds().isEmpty() ? Optional.empty() : Optional.of(Bukkit.getWorlds().getFirst());
}
}
+4
View File
@@ -3,6 +3,10 @@ version: ${version}
main: com.alttd.hunger_games.Main main: com.alttd.hunger_games.Main
api-version: "1.21" api-version: "1.21"
# Load after Multiverse-Core to be able to find the teleport locations
softdepend:
- Multiverse-Core
commands: commands:
hungergames: hungergames:
description: Main HungerGames command description: Main HungerGames command