show participant status in tab using colors

announce killer if present
show everyone the bossbar
drop items on death
give players participation price permission
This commit is contained in:
akastijn 2026-07-11 22:13:52 +02:00
parent fc7b5c76a6
commit 416c44a8f3
12 changed files with 147 additions and 38 deletions

View File

@ -20,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() {
@ -42,6 +43,8 @@ public final class Main extends JavaPlugin {
bossBarService = BossBarService.createSingletonInstance(round, roundService); bossBarService = BossBarService.createSingletonInstance(round, roundService);
round.setBossBarService(bossBarService); round.setBossBarService(bossBarService);
roundService.setBossBarService(bossBarService); roundService.setBossBarService(bossBarService);
tabListService = new TabListService(round, roundService);
} }
@Override @Override
@ -50,13 +53,13 @@ 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 ItemSpawnListener(roundService), this);

View File

@ -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,7 +34,7 @@ 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, playerService), new StartRound(round, roundService, playerService),
new NextPhase(round), new NextPhase(round),
new EndRound(round), new EndRound(round),

View File

@ -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) {

View File

@ -40,6 +40,7 @@ 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 SAFE_PHASE = Duration.ofSeconds(30);
public static Duration TIME_TO_FINALE = Duration.ofMinutes(5); public static Duration TIME_TO_FINALE = Duration.ofMinutes(5);
@ -54,6 +55,7 @@ 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())); int safePhaseSeconds = config.getInt(prefix, "safe-phase-seconds", Math.toIntExact(SAFE_PHASE.toSeconds()));

View File

@ -89,6 +89,7 @@ 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_PVP_COUNTDOWN = "<green>PVP starts in <time></green>"; public static String BOSSBAR_PVP_COUNTDOWN = "<green>PVP starts in <time></green>";
@ -106,6 +107,7 @@ 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_PVP_COUNTDOWN = config.getString(prefix, "bossbar-pvp-countdown", BOSSBAR_PVP_COUNTDOWN); BOSSBAR_PVP_COUNTDOWN = config.getString(prefix, "bossbar-pvp-countdown", BOSSBAR_PVP_COUNTDOWN);

View File

@ -4,22 +4,47 @@ 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 org.bukkit.entity.Item; import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerDropItemEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ItemSpawnListener implements Listener { public class ItemSpawnListener implements Listener {
private final RoundService roundService; private final RoundService roundService;
private final Set<UUID> deathDrops = new HashSet<>();
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onItemSpawn(ItemSpawnEvent event) { 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()); 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) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerDropItem(PlayerDropItemEvent event) { public void onPlayerDropItem(PlayerDropItemEvent event) {
setDespawnDelay(event.getItemDrop()); setDespawnDelay(event.getItemDrop());

View File

@ -45,8 +45,6 @@ public class PlayerDamageListener implements Listener {
Player player = event.getPlayer(); Player player = event.getPlayer();
event.setShowDeathMessages(false); event.setShowDeathMessages(false);
event.deathMessage(null); event.deathMessage(null);
event.getDrops().clear();
player.getInventory().clear();
playerService.handlePlayerDeath(player); playerService.handlePlayerDeath(player);
statService.addDeath(player.getUniqueId()); statService.addDeath(player.getUniqueId());

View File

@ -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);
}
} }
} }

View File

@ -66,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);
@ -92,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();
return;
}
Bukkit.getOnlinePlayers().forEach(player -> {
if (viewers.add(player.getUniqueId())) {
player.showBossBar(bossBar);
}
});
// Add new viewers viewers.removeIf(uuid -> {
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); Player player = Bukkit.getPlayer(uuid);
if (player != null) { return player == null || !player.isOnline();
player.hideBossBar(bossBar); });
}
}
} }
private void hideAll() { private void hideAll() {

View File

@ -91,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);
} }
@ -113,10 +114,18 @@ 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();
Player killer = player.getKiller();
if (killer != null) {
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, Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Messages.GAME.PLAYER_DEATH,
Placeholder.component("player", player.name()), Placeholder.component("player", player.name()),
Placeholder.unparsed("remaining", String.valueOf(remaining)))); Placeholder.unparsed("remaining", String.valueOf(remaining))));
} }
}
public void handlePlayerRespawn(PlayerRespawnEvent event) { public void handlePlayerRespawn(PlayerRespawnEvent event) {
Location spectatorArea = playerTeleporterService.getLocationFromDestination(DESTINATION.SPECTATOR_AREA); Location spectatorArea = playerTeleporterService.getLocationFromDestination(DESTINATION.SPECTATOR_AREA);

View File

@ -15,6 +15,8 @@ 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.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;
@ -118,6 +120,9 @@ public class RoundService implements RoundListener {
this.roundState = roundState; this.roundState = roundState;
stopFreeze(); stopFreeze();
if (roundState.equals(ROUND_STATE.PLAYER_REGISTRATION) || roundState.equals(ROUND_STATE.ENDED)) { 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();
} }
@ -159,4 +164,13 @@ public class RoundService implements RoundListener {
main.getServer().getPluginManager().registerEvents(playerMovementListener, main); main.getServer().getPluginManager().registerEvents(playerMovementListener, main);
} }
private void cleanupItems() {
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);
}
} }

View File

@ -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();
}
}