Add player registration system with /hg register command and refactor services for improved state management.

This commit is contained in:
akastijn 2026-05-24 00:58:27 +02:00
parent 2fd6d4ba30
commit ab6112b9da
6 changed files with 195 additions and 28 deletions

View File

@ -3,6 +3,7 @@ package com.alttd.hunger_games;
import com.alttd.hunger_games.commands.BaseCommand;
import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.services.PlayerService;
import com.alttd.hunger_games.services.Round;
import com.alttd.hunger_games.services.RoundService;
import lombok.extern.slf4j.Slf4j;
@ -12,9 +13,8 @@ import org.bukkit.plugin.java.JavaPlugin;
@Slf4j
public final class Main extends JavaPlugin {
private BaseCommand command;
private Round round;
private RoundService roundService;
private PlayerService playerService;
@Override
public void onEnable() {
@ -27,8 +27,9 @@ public final class Main extends JavaPlugin {
}
private void registerServices() {
round = Round.createSingletonInstance();
Round round = Round.createSingletonInstance();
roundService = new RoundService(round);
playerService = new PlayerService(roundService);
}
@Override
@ -37,7 +38,7 @@ public final class Main extends JavaPlugin {
}
private void registerCommands() {
command = new BaseCommand(this, roundService);
BaseCommand command = new BaseCommand(this, roundService, playerService);
}
private void registerEvents() {

View File

@ -1,8 +1,10 @@
package com.alttd.hunger_games.commands;
import com.alttd.hunger_games.Main;
import com.alttd.hunger_games.commands.subcommands.Register;
import com.alttd.hunger_games.commands.subcommands.RoundState;
import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.services.PlayerService;
import com.alttd.hunger_games.services.RoundService;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@ -19,7 +21,7 @@ import java.util.stream.Collectors;
public class BaseCommand implements CommandExecutor, TabExecutor {
private final List<SubCommand> subCommands;
public BaseCommand(Main main, RoundService roundService) {
public BaseCommand(Main main, RoundService roundService, PlayerService playerService) {
PluginCommand command = main.getCommand("hungergames");
if (command == null) {
subCommands = null;
@ -31,7 +33,8 @@ public class BaseCommand implements CommandExecutor, TabExecutor {
command.setAliases(List.of("hg"));
subCommands = new ArrayList<>(List.of(
new RoundState(roundService)
new RoundState(roundService),
new Register(playerService)
));
}

View File

@ -0,0 +1,110 @@
package com.alttd.hunger_games.commands.subcommands;
import com.alttd.hunger_games.commands.SubCommand;
import com.alttd.hunger_games.commands.shared_parsers.OnlinePlayerParser;
import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.data_objects.PLAYER_STATE;
import com.alttd.hunger_games.services.PlayerService;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;
@Slf4j
public class Register extends SubCommand {
private final static OnlinePlayerParser ONLINE_PLAYER_PARSER = new OnlinePlayerParser();
private final static int PLAYER_ARG = 1;
private final PlayerService playerService;
public Register(PlayerService playerService) {
this.playerService = playerService;
}
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
if (args.length != 2) {
return false;
}
Optional<Player> optionalPlayer = ONLINE_PLAYER_PARSER.parse(commandSender, args[PLAYER_ARG]);
if (optionalPlayer.isEmpty()) {
return true;
}
Player playerToRegister = optionalPlayer.get();
Optional<PLAYER_STATE> optionalPlayerState = playerService.registerPlayer(playerToRegister);
PLAYER_STATE nullablePlayerState = optionalPlayerState.orElse(null);
if (optionalPlayerState.isEmpty()) {
playerToRegister.sendRichMessage(Messages.REGISTER.FAILED_TO_REGISTER_YOU);
} else {
notifyRegisteredPlayer(playerToRegister, nullablePlayerState);
}
if (commandSender instanceof Player commandPlayer) {
notifyPlayerCommandSender(commandPlayer, nullablePlayerState, playerToRegister);
}
logResult(nullablePlayerState, playerToRegister);
return true;
}
private void notifyRegisteredPlayer(Player playerToRegister, PLAYER_STATE nullablePlayerState) {
switch (nullablePlayerState) {
case REGISTERED -> playerToRegister.sendRichMessage(Messages.REGISTER.SUCCESSFUL_REGISTER);
case SPECTATING -> playerToRegister.sendRichMessage(Messages.REGISTER.GAME_RUNNING);
}
}
private void logResult(PLAYER_STATE nullablePlayerState, Player playerToRegister) {
if (nullablePlayerState == null) {
log.info("Failed to register player {}", playerToRegister.getName());
return;
}
switch (nullablePlayerState) {
case REGISTERED -> log.info("Registered player {} for the next round", playerToRegister.getName());
case IN_GAME -> log.info("Player {} is already in a round", playerToRegister.getName());
case DISCONNECTED -> log.info("Player {} is disconnected (should not be possible)", playerToRegister.getName());
case SPECTATING -> log.info("Player {} is now spectating the game", playerToRegister.getName());
}
}
private static void notifyPlayerCommandSender(Player commandPlayer, @Nullable PLAYER_STATE playerState, Player playerToRegister) {
if (playerState == null) {
commandPlayer.sendRichMessage(Messages.REGISTER.FAILED_TO_REGISTER_PLAYER,
Placeholder.component("player", playerToRegister.name()));
return;
}
switch (playerState) {
case REGISTERED -> commandPlayer.sendRichMessage(Messages.REGISTER.PLAYER_REGISTERED,
Placeholder.component("player", playerToRegister.name()));
case IN_GAME -> commandPlayer.sendRichMessage(Messages.REGISTER.PLAYER_ALREADY_IN_ROUND,
Placeholder.component("player", playerToRegister.name()));
case DISCONNECTED -> {
//should not be possible
}
case SPECTATING -> commandPlayer.sendRichMessage(Messages.REGISTER.PLAYER_SPECTATING,
Placeholder.component("player", playerToRegister.name()));
}
}
@Override
public String getName() {
return "register";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
return List.of();
}
@Override
public String getHelpMessage() {
return Messages.HELP.REGISTER;
}
}

View File

@ -69,11 +69,21 @@ public class Messages extends AbstractConfig {
public static String FAILED_TO_REGISTER_YOU = "<red>You were unable to join HungerGames because no HungerGames round was found</red>";
public static String FAILED_TO_REGISTER_PLAYER = "<red>Unable to register player <player></red>";
public static String PLAYER_REGISTERED = "<green>Registered <player> for the game</green>";
public static String PLAYER_ALREADY_IN_ROUND = "<yellow><player> is already in a round</yellow>";
public static String PLAYER_SPECTATING = "<yellow><player> is now spectating the game</yellow>";
public static String SUCCESSFUL_REGISTER = "<green>Successfully registered you for the upcoming Hunger Games round</green>";
public static String GAME_RUNNING = "<yellow>The game is currently running, you will need to spectate the current round</yellow>";
@SuppressWarnings("unused")
private static void load() {
FAILED_TO_REGISTER_YOU = config.getString(prefix, "failed", FAILED_TO_REGISTER_YOU);
FAILED_TO_REGISTER_PLAYER = config.getString(prefix, "failed-player", FAILED_TO_REGISTER_PLAYER);
PLAYER_REGISTERED = config.getString(prefix, "player-registered", PLAYER_REGISTERED);
PLAYER_ALREADY_IN_ROUND = config.getString(prefix, "player-already-in-game", PLAYER_ALREADY_IN_ROUND);
PLAYER_SPECTATING = config.getString(prefix, "player-spectating", PLAYER_SPECTATING);
SUCCESSFUL_REGISTER = config.getString(prefix, "successful", SUCCESSFUL_REGISTER);
GAME_RUNNING = config.getString(prefix, "game-running", GAME_RUNNING);
}
}
}

View File

@ -3,9 +3,10 @@ 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 lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Optional;
import java.util.*;
@RequiredArgsConstructor
public class PlayerService implements RoundListener {
@ -16,29 +17,70 @@ public class PlayerService implements RoundListener {
@Override
public void stateChange(ROUND_STATE roundState) {
this.roundState = roundState;
//TODO: Implement player state change logic
switch (roundState) {
case PLAYER_REGISTRATION, KILL_PHASE -> {
//Nothing
}
case COUNTDOWN -> {
setNonRegisteredPlayersToSpectator();
//TODO: teleport players to spawn location
}
case FINALE -> {
//TODO: teleport players to finale location
}
case ENDED -> {
roundService.clear();
//TODO: teleport everyone to spawn (or spectator?) location
}
}
}
private void setNonRegisteredPlayersToSpectator() {
Set<UUID> playerUUIDs = roundService.getPlayers(PLAYER_STATE.REGISTERED);
List<Player> playerList = new ArrayList<>(Bukkit.getOnlinePlayers());
List<Player> unregisteredPlayers = playerList.stream().filter(player -> !playerUUIDs.contains(player.getUniqueId())).toList();
unregisteredPlayers.forEach(player -> roundService.setPlayerState(player.getUniqueId(), PLAYER_STATE.SPECTATING));
}
@Override
public void roundReset() {
//TODO: Implement player reset logic
//TODO: teleport everyone to spawn (or spectator?) location (from where they can join the game)
}
public Optional<PLAYER_STATE> registerPlayer(Player player) {
if (roundState == null) {
return Optional.empty();
}
switch (roundState) {
case PLAYER_REGISTRATION -> {
Optional<PLAYER_STATE> optionalPlayerState = determinePlayerState(player.getUniqueId());
if (optionalPlayerState.isEmpty()) {
return Optional.empty();
}
case COUNTDOWN -> {
PLAYER_STATE playerState = optionalPlayerState.get();
if (playerState == PLAYER_STATE.SPECTATING) {
//TODO: teleport player to spectator location
}
case KILL_PHASE -> {
}
case FINALE -> {
}
case ENDED -> {
roundService.setPlayerState(player.getUniqueId(), playerState);
return optionalPlayerState;
}
private Optional<PLAYER_STATE> determinePlayerState(UUID playerUuid) {
if (roundState == null) {
return Optional.empty();
}
Optional<PLAYER_STATE> optionalPlayerState = roundService.getPlayerState(playerUuid);
if (optionalPlayerState.isPresent()) {
PLAYER_STATE playerState = optionalPlayerState.get();
return Optional.of(switch (playerState) {
case DISCONNECTED, SPECTATING -> PLAYER_STATE.SPECTATING;
case IN_GAME -> PLAYER_STATE.IN_GAME;
case REGISTERED -> PLAYER_STATE.REGISTERED;
});
}
return switch (roundState) {
case PLAYER_REGISTRATION, COUNTDOWN -> Optional.of(PLAYER_STATE.REGISTERED);
case KILL_PHASE, FINALE, ENDED -> Optional.of(PLAYER_STATE.SPECTATING);
};
}
}

View File

@ -5,10 +5,7 @@ import com.alttd.hunger_games.data_objects.ROUND_STATE;
import lombok.Getter;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
public class RoundService implements RoundListener {
@ -32,8 +29,12 @@ public class RoundService implements RoundListener {
players.put(uuid, playerState);
}
public PLAYER_STATE getPlayerState(UUID uuid) {
return players.get(uuid);
protected void clear() {
players.clear();
}
public Optional<PLAYER_STATE> getPlayerState(UUID uuid) {
return Optional.ofNullable(players.get(uuid));
}
@Override
@ -43,7 +44,7 @@ public class RoundService implements RoundListener {
@Override
public void roundReset() {
players.clear();
clear();
roundState = null;
}
}