feat: initial CR-Core library (team + player + command framework)
Pure Maven library for CR Minecraft game plugins, targeting Paper 1.16.5. Common abstractions (fr.luc.crcore.common): Identifiable, Named, ScoreHolder, AbstractEntity, Repository<T>. Team domain (fr.luc.crcore.team): Team entity with name/tag/color/leader/ visibility (PUBLIC|PRIVATE)/members/scores/spawn point, TeamMember, TeamRole/TeamColor/TeamVisibility enums, TeamRanking record, TeamService with overridable hooks (factories, validations, lifecycle events), in-memory repository, dedicated exception hierarchy. Player domain (fr.luc.crcore.player): PlayerProfile with named scores per player, PlayerProfileService with auto-creation, individual rankings, exception hierarchy. Both Team and PlayerProfile implement ScoreHolder. Command framework (fr.luc.crcore.command): Command interface, AbstractCommand base, BaseCommand (CommandExecutor + TabCompleter), SubCommand, CommandContext, CommandResult, ArgumentType<T> + ArgumentTypes catalogue (STRING, INTEGER, DOUBLE, BOOLEAN, ONLINE_PLAYER, enumOf, choice). Docs (docs/) is the single source of truth: README, setup, features, decisions log, and 6 PlantUML diagrams (team class/sequence/activity/join, player class, command class). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class AbstractCommand implements Command {
|
||||
|
||||
private final String name;
|
||||
private final List<String> aliases = new ArrayList<>();
|
||||
private final List<ArgumentDef> arguments = new ArrayList<>();
|
||||
private String permission;
|
||||
private boolean playerOnly;
|
||||
private String description = "";
|
||||
private String usage;
|
||||
|
||||
protected AbstractCommand(String name, String... aliases) {
|
||||
this.name = Objects.requireNonNull(name, "name").toLowerCase();
|
||||
for (String alias : aliases) {
|
||||
this.aliases.add(alias.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> getAliases() {
|
||||
return Collections.unmodifiableList(aliases);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isPlayerOnly() {
|
||||
return playerOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public final String getUsage() {
|
||||
return usage != null ? usage : buildDefaultUsage();
|
||||
}
|
||||
|
||||
protected final void addAlias(String... aliases) {
|
||||
for (String alias : aliases) {
|
||||
this.aliases.add(alias.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
protected final void permission(String permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
protected final void playerOnly() {
|
||||
this.playerOnly = true;
|
||||
}
|
||||
|
||||
protected final void description(String description) {
|
||||
this.description = Objects.requireNonNullElse(description, "");
|
||||
}
|
||||
|
||||
protected final void usage(String usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
protected final void argument(String name, ArgumentType<?> type) {
|
||||
arguments.add(new ArgumentDef(name, type, true));
|
||||
}
|
||||
|
||||
protected final void optionalArgument(String name, ArgumentType<?> type) {
|
||||
arguments.add(new ArgumentDef(name, type, false));
|
||||
}
|
||||
|
||||
public final int getRequiredArgumentCount() {
|
||||
return (int) arguments.stream().filter(ArgumentDef::isRequired).count();
|
||||
}
|
||||
|
||||
public final int getTotalArgumentCount() {
|
||||
return arguments.size();
|
||||
}
|
||||
|
||||
final List<ArgumentDef> getArgumentDefs() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
protected String buildDefaultUsage() {
|
||||
StringBuilder sb = new StringBuilder("/").append(name);
|
||||
for (ArgumentDef def : arguments) {
|
||||
sb.append(' ');
|
||||
sb.append(def.isRequired() ? '<' : '[');
|
||||
sb.append(def.getName());
|
||||
sb.append(def.isRequired() ? '>' : ']');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(CommandSender sender, int argIndex, String partial) {
|
||||
if (argIndex >= 0 && argIndex < arguments.size()) {
|
||||
return arguments.get(argIndex).getType().suggestions(sender, partial);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
protected CommandContext buildContext(CommandSender sender, String label, String[] subArgs) {
|
||||
Map<String, Object> parsed = new LinkedHashMap<>();
|
||||
int max = Math.min(subArgs.length, arguments.size());
|
||||
for (int i = 0; i < max; i++) {
|
||||
ArgumentDef def = arguments.get(i);
|
||||
try {
|
||||
Object value = def.getType().parse(subArgs[i]);
|
||||
parsed.put(def.getName(), value);
|
||||
} catch (CommandException ex) {
|
||||
throw new CommandException("Argument '" + def.getName() + "': " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
return new CommandContext(sender, label, subArgs, parsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
final class ArgumentDef {
|
||||
|
||||
private final String name;
|
||||
private final ArgumentType<?> type;
|
||||
private final boolean required;
|
||||
|
||||
ArgumentDef(String name, ArgumentType<?> type, boolean required) {
|
||||
this.name = Objects.requireNonNull(name, "name");
|
||||
this.type = Objects.requireNonNull(type, "type");
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
ArgumentType<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public interface ArgumentType<T> {
|
||||
|
||||
T parse(String input) throws CommandException;
|
||||
|
||||
default List<String> suggestions(CommandSender sender, String partial) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class ArgumentTypes {
|
||||
|
||||
private ArgumentTypes() {
|
||||
}
|
||||
|
||||
public static final ArgumentType<String> STRING = new ArgumentType<>() {
|
||||
@Override
|
||||
public String parse(String input) {
|
||||
return input;
|
||||
}
|
||||
};
|
||||
|
||||
public static final ArgumentType<Integer> INTEGER = new ArgumentType<>() {
|
||||
@Override
|
||||
public Integer parse(String input) {
|
||||
try {
|
||||
return Integer.parseInt(input);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new CommandException("Invalid integer: " + input);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static final ArgumentType<Double> DOUBLE = new ArgumentType<>() {
|
||||
@Override
|
||||
public Double parse(String input) {
|
||||
try {
|
||||
return Double.parseDouble(input);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new CommandException("Invalid number: " + input);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static final ArgumentType<Boolean> BOOLEAN = new ArgumentType<>() {
|
||||
@Override
|
||||
public Boolean parse(String input) {
|
||||
return switch (input.toLowerCase()) {
|
||||
case "true", "yes", "y", "1", "on" -> true;
|
||||
case "false", "no", "n", "0", "off" -> false;
|
||||
default -> throw new CommandException("Invalid boolean: " + input);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggestions(CommandSender sender, String partial) {
|
||||
return filter(List.of("true", "false"), partial);
|
||||
}
|
||||
};
|
||||
|
||||
public static final ArgumentType<Player> ONLINE_PLAYER = new ArgumentType<>() {
|
||||
@Override
|
||||
public Player parse(String input) {
|
||||
Player player = Bukkit.getPlayerExact(input);
|
||||
if (player == null) {
|
||||
throw new CommandException("Player not found: " + input);
|
||||
}
|
||||
return player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggestions(CommandSender sender, String partial) {
|
||||
return filter(Bukkit.getOnlinePlayers().stream()
|
||||
.map(Player::getName)
|
||||
.collect(Collectors.toList()), partial);
|
||||
}
|
||||
};
|
||||
|
||||
public static <E extends Enum<E>> ArgumentType<E> enumOf(Class<E> type) {
|
||||
Objects.requireNonNull(type, "type");
|
||||
return new ArgumentType<>() {
|
||||
@Override
|
||||
public E parse(String input) {
|
||||
try {
|
||||
return Enum.valueOf(type, input.toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
throw new CommandException(
|
||||
"Invalid value for " + type.getSimpleName() + ": " + input);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggestions(CommandSender sender, String partial) {
|
||||
return filter(Arrays.stream(type.getEnumConstants())
|
||||
.map(Enum::name)
|
||||
.collect(Collectors.toList()), partial);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ArgumentType<String> choice(String... choices) {
|
||||
Objects.requireNonNull(choices, "choices");
|
||||
List<String> list = List.of(choices);
|
||||
return new ArgumentType<>() {
|
||||
@Override
|
||||
public String parse(String input) {
|
||||
if (!list.contains(input)) {
|
||||
throw new CommandException("Invalid choice: " + input + " (expected: " + list + ")");
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggestions(CommandSender sender, String partial) {
|
||||
return filter(list, partial);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static List<String> filter(List<String> source, String partial) {
|
||||
String lower = partial.toLowerCase();
|
||||
return source.stream()
|
||||
.filter(value -> value.toLowerCase().startsWith(lower))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class BaseCommand extends AbstractCommand
|
||||
implements CommandExecutor, TabCompleter {
|
||||
|
||||
private final Map<String, SubCommand> subCommandsByName = new LinkedHashMap<>();
|
||||
private final Map<String, SubCommand> subCommandsByAlias = new HashMap<>();
|
||||
|
||||
protected BaseCommand(String name, String... aliases) {
|
||||
super(name, aliases);
|
||||
}
|
||||
|
||||
protected final void addSubCommand(SubCommand sub) {
|
||||
Objects.requireNonNull(sub, "sub");
|
||||
if (subCommandsByName.containsKey(sub.getName())) {
|
||||
throw new IllegalStateException("Sub-command already registered: " + sub.getName());
|
||||
}
|
||||
subCommandsByName.put(sub.getName(), sub);
|
||||
for (String alias : sub.getAliases()) {
|
||||
subCommandsByAlias.put(alias, sub);
|
||||
}
|
||||
}
|
||||
|
||||
public final Collection<SubCommand> getSubCommands() {
|
||||
return Collections.unmodifiableCollection(subCommandsByName.values());
|
||||
}
|
||||
|
||||
public final Optional<SubCommand> findSubCommand(String label) {
|
||||
if (label == null) return Optional.empty();
|
||||
String lc = label.toLowerCase();
|
||||
SubCommand sub = subCommandsByName.get(lc);
|
||||
if (sub != null) return Optional.of(sub);
|
||||
return Optional.ofNullable(subCommandsByAlias.get(lc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when no sub-command matches the first argument. Override for custom
|
||||
* fallback behavior. Default: lists available sub-commands.
|
||||
*/
|
||||
protected CommandResult execute(CommandContext context) {
|
||||
if (subCommandsByName.isEmpty()) {
|
||||
return CommandResult.invalidUsage("No action specified.");
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(ChatColor.YELLOW + "Available sub-commands:");
|
||||
for (SubCommand sub : subCommandsByName.values()) {
|
||||
sb.append('\n').append(ChatColor.GRAY).append(" - ")
|
||||
.append(ChatColor.WHITE).append(sub.getName());
|
||||
if (!sub.getDescription().isEmpty()) {
|
||||
sb.append(ChatColor.GRAY).append(" — ").append(sub.getDescription());
|
||||
}
|
||||
}
|
||||
context.reply(sb.toString());
|
||||
return CommandResult.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onCommand(CommandSender sender, org.bukkit.command.Command command,
|
||||
String label, String[] args) {
|
||||
if (!checkAccess(sender, this)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length == 0 || findSubCommand(args[0]).isEmpty()) {
|
||||
CommandContext ctx = new CommandContext(sender, label, args, Collections.emptyMap());
|
||||
try {
|
||||
handleResult(sender, execute(ctx));
|
||||
} catch (CommandException ex) {
|
||||
sender.sendMessage(ChatColor.RED + ex.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SubCommand sub = findSubCommand(args[0]).orElseThrow();
|
||||
if (!checkAccess(sender, sub)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
if (subArgs.length < sub.getRequiredArgumentCount()) {
|
||||
sender.sendMessage(ChatColor.RED + "Usage: " + buildUsageFor(label, sub));
|
||||
return true;
|
||||
}
|
||||
|
||||
CommandContext ctx;
|
||||
try {
|
||||
ctx = sub.buildContext(sender, label, subArgs);
|
||||
} catch (CommandException ex) {
|
||||
sender.sendMessage(ChatColor.RED + ex.getMessage());
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
handleResult(sender, sub.execute(ctx));
|
||||
} catch (CommandException ex) {
|
||||
sender.sendMessage(ChatColor.RED + ex.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> onTabComplete(CommandSender sender, org.bukkit.command.Command command,
|
||||
String alias, String[] args) {
|
||||
if (args.length == 0) return Collections.emptyList();
|
||||
|
||||
if (args.length == 1) {
|
||||
String partial = args[0].toLowerCase();
|
||||
List<String> names = new ArrayList<>();
|
||||
for (SubCommand sub : subCommandsByName.values()) {
|
||||
if (sub.getPermission() != null && !sender.hasPermission(sub.getPermission())) {
|
||||
continue;
|
||||
}
|
||||
names.add(sub.getName());
|
||||
names.addAll(sub.getAliases());
|
||||
}
|
||||
return names.stream()
|
||||
.filter(n -> n.startsWith(partial))
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
Optional<SubCommand> subOpt = findSubCommand(args[0]);
|
||||
if (subOpt.isEmpty()) return Collections.emptyList();
|
||||
SubCommand sub = subOpt.get();
|
||||
if (sub.getPermission() != null && !sender.hasPermission(sub.getPermission())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
int argIndex = args.length - 2;
|
||||
String partial = args[args.length - 1];
|
||||
return sub.tabComplete(sender, argIndex, partial);
|
||||
}
|
||||
|
||||
protected boolean checkAccess(CommandSender sender, Command target) {
|
||||
if (target.getPermission() != null && !sender.hasPermission(target.getPermission())) {
|
||||
sender.sendMessage(ChatColor.RED + "You don't have permission.");
|
||||
return false;
|
||||
}
|
||||
if (target.isPlayerOnly() && !(sender instanceof Player)) {
|
||||
sender.sendMessage(ChatColor.RED + "Only players can use this command.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected String buildUsageFor(String label, SubCommand sub) {
|
||||
StringBuilder sb = new StringBuilder("/").append(label).append(' ').append(sub.getName());
|
||||
for (ArgumentDef def : sub.getArgumentDefs()) {
|
||||
sb.append(' ');
|
||||
sb.append(def.isRequired() ? '<' : '[');
|
||||
sb.append(def.getName());
|
||||
sb.append(def.isRequired() ? '>' : ']');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected void handleResult(CommandSender sender, CommandResult result) {
|
||||
switch (result.getType()) {
|
||||
case SUCCESS -> {
|
||||
if (result.getMessage() != null) {
|
||||
sender.sendMessage(ChatColor.GREEN + result.getMessage());
|
||||
}
|
||||
}
|
||||
case FAILURE -> sender.sendMessage(ChatColor.RED +
|
||||
(result.getMessage() != null ? result.getMessage() : "Command failed."));
|
||||
case INVALID_USAGE -> sender.sendMessage(ChatColor.RED +
|
||||
(result.getMessage() != null ? result.getMessage() : "Invalid usage."));
|
||||
case NO_PERMISSION -> sender.sendMessage(ChatColor.RED + "You don't have permission.");
|
||||
case PLAYER_ONLY -> sender.sendMessage(ChatColor.RED + "Only players can use this command.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface Command {
|
||||
|
||||
String getName();
|
||||
|
||||
List<String> getAliases();
|
||||
|
||||
String getPermission();
|
||||
|
||||
boolean isPlayerOnly();
|
||||
|
||||
String getDescription();
|
||||
|
||||
CommandResult execute(CommandContext context);
|
||||
|
||||
List<String> tabComplete(CommandSender sender, int argIndex, String partial);
|
||||
|
||||
default boolean matches(String label) {
|
||||
if (label == null) return false;
|
||||
String lc = label.toLowerCase();
|
||||
if (getName().equalsIgnoreCase(lc)) return true;
|
||||
for (String alias : getAliases()) {
|
||||
if (alias.equalsIgnoreCase(lc)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CommandContext {
|
||||
|
||||
private final CommandSender sender;
|
||||
private final String label;
|
||||
private final String[] rawArgs;
|
||||
private final Map<String, Object> parsedArgs;
|
||||
|
||||
public CommandContext(CommandSender sender, String label, String[] rawArgs,
|
||||
Map<String, Object> parsedArgs) {
|
||||
this.sender = Objects.requireNonNull(sender, "sender");
|
||||
this.label = Objects.requireNonNull(label, "label");
|
||||
this.rawArgs = Objects.requireNonNull(rawArgs, "rawArgs");
|
||||
this.parsedArgs = Collections.unmodifiableMap(
|
||||
Objects.requireNonNull(parsedArgs, "parsedArgs"));
|
||||
}
|
||||
|
||||
public CommandSender getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String[] getRawArgs() {
|
||||
return rawArgs;
|
||||
}
|
||||
|
||||
public boolean isPlayer() {
|
||||
return sender instanceof Player;
|
||||
}
|
||||
|
||||
public Optional<Player> getPlayer() {
|
||||
return sender instanceof Player ? Optional.of((Player) sender) : Optional.empty();
|
||||
}
|
||||
|
||||
public Player requirePlayer() {
|
||||
if (!(sender instanceof Player player)) {
|
||||
throw new CommandException("This command can only be used by a player.");
|
||||
}
|
||||
return player;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String name) {
|
||||
Objects.requireNonNull(name, "name");
|
||||
Object value = parsedArgs.get(name);
|
||||
if (value == null) {
|
||||
throw new CommandException("Missing argument: " + name);
|
||||
}
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getOptional(String name) {
|
||||
return Optional.ofNullable((T) parsedArgs.get(name));
|
||||
}
|
||||
|
||||
public boolean has(String name) {
|
||||
return parsedArgs.containsKey(name);
|
||||
}
|
||||
|
||||
public void reply(String message) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
public class CommandException extends RuntimeException {
|
||||
|
||||
public CommandException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CommandException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
public final class CommandResult {
|
||||
|
||||
public enum Type {
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
INVALID_USAGE,
|
||||
NO_PERMISSION,
|
||||
PLAYER_ONLY
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final String message;
|
||||
|
||||
private CommandResult(Type type, String message) {
|
||||
this.type = type;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return type == Type.SUCCESS;
|
||||
}
|
||||
|
||||
public static CommandResult success() {
|
||||
return new CommandResult(Type.SUCCESS, null);
|
||||
}
|
||||
|
||||
public static CommandResult success(String message) {
|
||||
return new CommandResult(Type.SUCCESS, message);
|
||||
}
|
||||
|
||||
public static CommandResult failure(String message) {
|
||||
return new CommandResult(Type.FAILURE, message);
|
||||
}
|
||||
|
||||
public static CommandResult invalidUsage() {
|
||||
return new CommandResult(Type.INVALID_USAGE, null);
|
||||
}
|
||||
|
||||
public static CommandResult invalidUsage(String message) {
|
||||
return new CommandResult(Type.INVALID_USAGE, message);
|
||||
}
|
||||
|
||||
public static CommandResult noPermission() {
|
||||
return new CommandResult(Type.NO_PERMISSION, null);
|
||||
}
|
||||
|
||||
public static CommandResult playerOnly() {
|
||||
return new CommandResult(Type.PLAYER_ONLY, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fr.luc.crcore.command;
|
||||
|
||||
public abstract class SubCommand extends AbstractCommand {
|
||||
|
||||
protected SubCommand(String name, String... aliases) {
|
||||
super(name, aliases);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package fr.luc.crcore.common;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class AbstractEntity implements Identifiable {
|
||||
|
||||
private final UUID id;
|
||||
|
||||
protected AbstractEntity(UUID id) {
|
||||
this.id = Objects.requireNonNull(id, "id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
if (other == null || getClass() != other.getClass()) return false;
|
||||
return id.equals(((AbstractEntity) other).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fr.luc.crcore.common;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface Identifiable {
|
||||
|
||||
UUID getId();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package fr.luc.crcore.common;
|
||||
|
||||
public interface Named {
|
||||
|
||||
String getName();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package fr.luc.crcore.common;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface Repository<T extends Identifiable> {
|
||||
|
||||
T save(T entity);
|
||||
|
||||
Optional<T> findById(UUID id);
|
||||
|
||||
Collection<T> findAll();
|
||||
|
||||
boolean delete(UUID id);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package fr.luc.crcore.common;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface ScoreHolder {
|
||||
|
||||
int getScore(String scoreName);
|
||||
|
||||
boolean hasScore(String scoreName);
|
||||
|
||||
Map<String, Integer> getScores();
|
||||
|
||||
int getTotalScore();
|
||||
|
||||
int addScore(String scoreName, int delta);
|
||||
|
||||
int setScore(String scoreName, int value);
|
||||
|
||||
boolean resetScore(String scoreName);
|
||||
|
||||
void resetAllScores();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package fr.luc.crcore.player;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class InMemoryPlayerProfileRepository implements PlayerProfileRepository {
|
||||
|
||||
private final Map<UUID, PlayerProfile> profiles = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public PlayerProfile save(PlayerProfile profile) {
|
||||
Objects.requireNonNull(profile, "profile");
|
||||
profiles.put(profile.getId(), profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PlayerProfile> findById(UUID id) {
|
||||
Objects.requireNonNull(id, "id");
|
||||
return Optional.ofNullable(profiles.get(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PlayerProfile> findAll() {
|
||||
return Collections.unmodifiableCollection(profiles.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(UUID id) {
|
||||
Objects.requireNonNull(id, "id");
|
||||
return profiles.remove(id) != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package fr.luc.crcore.player;
|
||||
|
||||
public class PlayerException extends RuntimeException {
|
||||
|
||||
public PlayerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PlayerException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package fr.luc.crcore.player;
|
||||
|
||||
import fr.luc.crcore.common.AbstractEntity;
|
||||
import fr.luc.crcore.common.ScoreHolder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerProfile extends AbstractEntity implements ScoreHolder {
|
||||
|
||||
private final Map<String, Integer> scores;
|
||||
|
||||
public PlayerProfile(UUID playerId) {
|
||||
super(playerId);
|
||||
this.scores = new HashMap<>();
|
||||
}
|
||||
|
||||
public UUID getPlayerId() {
|
||||
return getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScore(String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
return scores.getOrDefault(scoreName, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScore(String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
return scores.containsKey(scoreName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Integer> getScores() {
|
||||
return Collections.unmodifiableMap(scores);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalScore() {
|
||||
return scores.values().stream().mapToInt(Integer::intValue).sum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addScore(String scoreName, int delta) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
int newValue = getScore(scoreName) + delta;
|
||||
scores.put(scoreName, newValue);
|
||||
return newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setScore(String scoreName, int value) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
scores.put(scoreName, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetScore(String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
return scores.remove(scoreName) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAllScores() {
|
||||
scores.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fr.luc.crcore.player;
|
||||
|
||||
public class PlayerProfileNotFoundException extends PlayerException {
|
||||
|
||||
public PlayerProfileNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package fr.luc.crcore.player;
|
||||
|
||||
import fr.luc.crcore.common.Repository;
|
||||
|
||||
public interface PlayerProfileRepository extends Repository<PlayerProfile> {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package fr.luc.crcore.player;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public interface PlayerProfileService {
|
||||
|
||||
PlayerProfile getOrCreateProfile(UUID playerId);
|
||||
|
||||
Optional<PlayerProfile> getProfile(UUID playerId);
|
||||
|
||||
boolean deleteProfile(UUID playerId);
|
||||
|
||||
Collection<PlayerProfile> getAllProfiles();
|
||||
|
||||
// ---- Scores ----
|
||||
|
||||
int addScore(UUID playerId, String scoreName, int delta);
|
||||
|
||||
int setScore(UUID playerId, String scoreName, int value);
|
||||
|
||||
int getScore(UUID playerId, String scoreName);
|
||||
|
||||
boolean resetScore(UUID playerId, String scoreName);
|
||||
|
||||
void resetAllScores(UUID playerId);
|
||||
|
||||
// ---- Rankings ----
|
||||
|
||||
List<PlayerRanking> getRankingByScore(String scoreName);
|
||||
|
||||
List<PlayerRanking> getGlobalRanking();
|
||||
|
||||
default List<PlayerRanking> getTopRankingByScore(String scoreName, int limit) {
|
||||
return getRankingByScore(scoreName).stream().limit(limit).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
default List<PlayerRanking> getTopGlobalRanking(int limit) {
|
||||
return getGlobalRanking().stream().limit(limit).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package fr.luc.crcore.player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
public class PlayerProfileServiceImpl implements PlayerProfileService {
|
||||
|
||||
private final PlayerProfileRepository repository;
|
||||
|
||||
public PlayerProfileServiceImpl(PlayerProfileRepository repository) {
|
||||
this.repository = Objects.requireNonNull(repository, "repository");
|
||||
}
|
||||
|
||||
protected PlayerProfileRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
// ---- Lifecycle ----
|
||||
|
||||
@Override
|
||||
public PlayerProfile getOrCreateProfile(UUID playerId) {
|
||||
Objects.requireNonNull(playerId, "playerId");
|
||||
Optional<PlayerProfile> existing = repository.findById(playerId);
|
||||
if (existing.isPresent()) return existing.get();
|
||||
PlayerProfile profile = newProfile(playerId);
|
||||
PlayerProfile saved = repository.save(profile);
|
||||
onProfileCreated(saved);
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PlayerProfile> getProfile(UUID playerId) {
|
||||
return repository.findById(playerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteProfile(UUID playerId) {
|
||||
Objects.requireNonNull(playerId, "playerId");
|
||||
Optional<PlayerProfile> profileOpt = repository.findById(playerId);
|
||||
if (profileOpt.isEmpty()) return false;
|
||||
boolean deleted = repository.delete(playerId);
|
||||
if (deleted) onProfileDeleted(profileOpt.get());
|
||||
return deleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PlayerProfile> getAllProfiles() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
// ---- Scores ----
|
||||
|
||||
@Override
|
||||
public int addScore(UUID playerId, String scoreName, int delta) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
PlayerProfile profile = getOrCreateProfile(playerId);
|
||||
int oldValue = profile.getScore(scoreName);
|
||||
int newValue = profile.addScore(scoreName, delta);
|
||||
repository.save(profile);
|
||||
if (oldValue != newValue) {
|
||||
onScoreChanged(profile, scoreName, oldValue, newValue);
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setScore(UUID playerId, String scoreName, int value) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
PlayerProfile profile = getOrCreateProfile(playerId);
|
||||
int oldValue = profile.getScore(scoreName);
|
||||
profile.setScore(scoreName, value);
|
||||
repository.save(profile);
|
||||
if (oldValue != value) {
|
||||
onScoreChanged(profile, scoreName, oldValue, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScore(UUID playerId, String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
return repository.findById(playerId)
|
||||
.map(profile -> profile.getScore(scoreName))
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetScore(UUID playerId, String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
Optional<PlayerProfile> profileOpt = repository.findById(playerId);
|
||||
if (profileOpt.isEmpty()) return false;
|
||||
PlayerProfile profile = profileOpt.get();
|
||||
int oldValue = profile.getScore(scoreName);
|
||||
boolean removed = profile.resetScore(scoreName);
|
||||
if (removed) {
|
||||
repository.save(profile);
|
||||
onScoreChanged(profile, scoreName, oldValue, 0);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAllScores(UUID playerId) {
|
||||
Optional<PlayerProfile> profileOpt = repository.findById(playerId);
|
||||
if (profileOpt.isEmpty()) return;
|
||||
PlayerProfile profile = profileOpt.get();
|
||||
Map<String, Integer> snapshot = new LinkedHashMap<>(profile.getScores());
|
||||
if (snapshot.isEmpty()) return;
|
||||
profile.resetAllScores();
|
||||
repository.save(profile);
|
||||
snapshot.forEach((scoreName, oldValue) ->
|
||||
onScoreChanged(profile, scoreName, oldValue, 0));
|
||||
}
|
||||
|
||||
// ---- Rankings ----
|
||||
|
||||
@Override
|
||||
public List<PlayerRanking> getRankingByScore(String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
return rank(profile -> profile.getScore(scoreName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlayerRanking> getGlobalRanking() {
|
||||
return rank(PlayerProfile::getTotalScore);
|
||||
}
|
||||
|
||||
protected List<PlayerRanking> rank(ToIntFunction<PlayerProfile> scoreFn) {
|
||||
Collection<PlayerProfile> all = repository.findAll();
|
||||
List<PlayerProfile> sorted = new ArrayList<>(all);
|
||||
sorted.sort(Comparator
|
||||
.comparingInt(scoreFn).reversed()
|
||||
.thenComparing(PlayerProfile::getId));
|
||||
List<PlayerRanking> result = new ArrayList<>(sorted.size());
|
||||
int currentRank = 1;
|
||||
for (PlayerProfile profile : sorted) {
|
||||
result.add(newRanking(currentRank++, profile, scoreFn.applyAsInt(profile)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- Override hooks ----
|
||||
|
||||
protected PlayerProfile newProfile(UUID playerId) {
|
||||
return new PlayerProfile(playerId);
|
||||
}
|
||||
|
||||
protected PlayerRanking newRanking(int rank, PlayerProfile profile, int score) {
|
||||
return new PlayerRanking(rank, profile, score);
|
||||
}
|
||||
|
||||
protected void onProfileCreated(PlayerProfile profile) {
|
||||
}
|
||||
|
||||
protected void onProfileDeleted(PlayerProfile profile) {
|
||||
}
|
||||
|
||||
protected void onScoreChanged(PlayerProfile profile, String scoreName,
|
||||
int oldValue, int newValue) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package fr.luc.crcore.player;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record PlayerRanking(int rank, PlayerProfile profile, int score) {
|
||||
|
||||
public PlayerRanking {
|
||||
Objects.requireNonNull(profile, "profile");
|
||||
if (rank < 1) {
|
||||
throw new IllegalArgumentException("rank must be >= 1, got " + rank);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class InMemoryTeamRepository implements TeamRepository {
|
||||
|
||||
private final Map<UUID, Team> teams = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public Team save(Team team) {
|
||||
Objects.requireNonNull(team, "team");
|
||||
teams.put(team.getId(), team);
|
||||
return team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Team> findById(UUID id) {
|
||||
Objects.requireNonNull(id, "id");
|
||||
return Optional.ofNullable(teams.get(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Team> findAll() {
|
||||
return Collections.unmodifiableCollection(teams.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(UUID id) {
|
||||
Objects.requireNonNull(id, "id");
|
||||
return teams.remove(id) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Team> findByName(String name) {
|
||||
Objects.requireNonNull(name, "name");
|
||||
return teams.values().stream()
|
||||
.filter(team -> team.getName().equalsIgnoreCase(name))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Team> findByTag(String tag) {
|
||||
Objects.requireNonNull(tag, "tag");
|
||||
return teams.values().stream()
|
||||
.filter(team -> team.getTag().equalsIgnoreCase(tag))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Team> findByMember(UUID playerId) {
|
||||
Objects.requireNonNull(playerId, "playerId");
|
||||
return teams.values().stream()
|
||||
.filter(team -> team.hasMember(playerId))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
import fr.luc.crcore.common.AbstractEntity;
|
||||
import fr.luc.crcore.common.Named;
|
||||
import fr.luc.crcore.common.ScoreHolder;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Team extends AbstractEntity implements Named, ScoreHolder {
|
||||
|
||||
private final String name;
|
||||
private final String tag;
|
||||
private final TeamColor color;
|
||||
private final Set<TeamMember> members;
|
||||
private final Map<String, Integer> scores;
|
||||
private UUID leaderId;
|
||||
private TeamVisibility visibility;
|
||||
private Location spawnPoint;
|
||||
|
||||
public Team(UUID id, String name, String tag, TeamColor color, UUID leaderId) {
|
||||
this(id, name, tag, color, leaderId, TeamVisibility.PRIVATE);
|
||||
}
|
||||
|
||||
public Team(UUID id, String name, String tag, TeamColor color, UUID leaderId,
|
||||
TeamVisibility visibility) {
|
||||
super(id);
|
||||
this.name = Objects.requireNonNull(name, "name");
|
||||
this.tag = Objects.requireNonNull(tag, "tag");
|
||||
this.color = Objects.requireNonNull(color, "color");
|
||||
this.leaderId = Objects.requireNonNull(leaderId, "leaderId");
|
||||
this.visibility = Objects.requireNonNull(visibility, "visibility");
|
||||
this.members = new HashSet<>();
|
||||
this.scores = new HashMap<>();
|
||||
this.members.add(newMember(leaderId, TeamRole.LEADER));
|
||||
}
|
||||
|
||||
/** Override to instantiate a custom TeamMember subclass. */
|
||||
protected TeamMember newMember(UUID playerId, TeamRole role) {
|
||||
return new TeamMember(playerId, role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public TeamColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public UUID getLeaderId() {
|
||||
return leaderId;
|
||||
}
|
||||
|
||||
public TeamVisibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public void setVisibility(TeamVisibility visibility) {
|
||||
this.visibility = Objects.requireNonNull(visibility, "visibility");
|
||||
}
|
||||
|
||||
public boolean isPublic() {
|
||||
return visibility.isPublic();
|
||||
}
|
||||
|
||||
public TeamMember getLeader() {
|
||||
return getMember(leaderId).orElseThrow(
|
||||
() -> new IllegalStateException("Team has no leader: " + getId()));
|
||||
}
|
||||
|
||||
public Set<TeamMember> getMembers() {
|
||||
return Collections.unmodifiableSet(members);
|
||||
}
|
||||
|
||||
public Optional<TeamMember> getMember(UUID playerId) {
|
||||
Objects.requireNonNull(playerId, "playerId");
|
||||
return members.stream()
|
||||
.filter(member -> member.getPlayerId().equals(playerId))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public boolean hasMember(UUID playerId) {
|
||||
return getMember(playerId).isPresent();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return members.size();
|
||||
}
|
||||
|
||||
public TeamMember addMember(UUID playerId) {
|
||||
Objects.requireNonNull(playerId, "playerId");
|
||||
Optional<TeamMember> existing = getMember(playerId);
|
||||
if (existing.isPresent()) {
|
||||
return existing.get();
|
||||
}
|
||||
TeamMember member = newMember(playerId, TeamRole.MEMBER);
|
||||
members.add(member);
|
||||
return member;
|
||||
}
|
||||
|
||||
public boolean removeMember(UUID playerId) {
|
||||
Objects.requireNonNull(playerId, "playerId");
|
||||
if (playerId.equals(leaderId)) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot remove the leader; transfer leadership first.");
|
||||
}
|
||||
return members.removeIf(member -> member.getPlayerId().equals(playerId));
|
||||
}
|
||||
|
||||
public void transferLeadership(UUID newLeaderId) {
|
||||
Objects.requireNonNull(newLeaderId, "newLeaderId");
|
||||
if (newLeaderId.equals(leaderId)) {
|
||||
return;
|
||||
}
|
||||
TeamMember newLeader = getMember(newLeaderId).orElseThrow(
|
||||
() -> new IllegalArgumentException(
|
||||
"New leader must already be a member of the team."));
|
||||
TeamMember oldLeader = getLeader();
|
||||
members.remove(oldLeader);
|
||||
members.remove(newLeader);
|
||||
members.add(oldLeader.withRole(TeamRole.MEMBER));
|
||||
members.add(newLeader.withRole(TeamRole.LEADER));
|
||||
this.leaderId = newLeaderId;
|
||||
}
|
||||
|
||||
// ---- Scores ----
|
||||
|
||||
public int getScore(String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
return scores.getOrDefault(scoreName, 0);
|
||||
}
|
||||
|
||||
public boolean hasScore(String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
return scores.containsKey(scoreName);
|
||||
}
|
||||
|
||||
public Map<String, Integer> getScores() {
|
||||
return Collections.unmodifiableMap(scores);
|
||||
}
|
||||
|
||||
public int getTotalScore() {
|
||||
return scores.values().stream().mapToInt(Integer::intValue).sum();
|
||||
}
|
||||
|
||||
public int addScore(String scoreName, int delta) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
int newValue = getScore(scoreName) + delta;
|
||||
scores.put(scoreName, newValue);
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public int setScore(String scoreName, int value) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
scores.put(scoreName, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean resetScore(String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
return scores.remove(scoreName) != null;
|
||||
}
|
||||
|
||||
public void resetAllScores() {
|
||||
scores.clear();
|
||||
}
|
||||
|
||||
// ---- Spawn point ----
|
||||
|
||||
public Optional<Location> getSpawnPoint() {
|
||||
return spawnPoint == null ? Optional.empty() : Optional.of(spawnPoint.clone());
|
||||
}
|
||||
|
||||
public boolean hasSpawnPoint() {
|
||||
return spawnPoint != null;
|
||||
}
|
||||
|
||||
public void setSpawnPoint(Location location) {
|
||||
this.spawnPoint = location == null ? null : location.clone();
|
||||
}
|
||||
|
||||
public void clearSpawnPoint() {
|
||||
this.spawnPoint = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
public class TeamAccessException extends TeamException {
|
||||
|
||||
public TeamAccessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
public class TeamAlreadyExistsException extends TeamException {
|
||||
|
||||
public TeamAlreadyExistsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.DyeColor;
|
||||
|
||||
public enum TeamColor {
|
||||
|
||||
RED(ChatColor.RED, DyeColor.RED, "Red"),
|
||||
BLUE(ChatColor.BLUE, DyeColor.BLUE, "Blue"),
|
||||
GREEN(ChatColor.GREEN, DyeColor.LIME, "Green"),
|
||||
YELLOW(ChatColor.YELLOW, DyeColor.YELLOW, "Yellow"),
|
||||
AQUA(ChatColor.AQUA, DyeColor.LIGHT_BLUE, "Aqua"),
|
||||
LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, DyeColor.MAGENTA, "Pink"),
|
||||
GOLD(ChatColor.GOLD, DyeColor.ORANGE, "Gold"),
|
||||
WHITE(ChatColor.WHITE, DyeColor.WHITE, "White"),
|
||||
BLACK(ChatColor.BLACK, DyeColor.BLACK, "Black"),
|
||||
DARK_BLUE(ChatColor.DARK_BLUE, DyeColor.BLUE, "Dark Blue"),
|
||||
DARK_GREEN(ChatColor.DARK_GREEN, DyeColor.GREEN, "Dark Green"),
|
||||
DARK_AQUA(ChatColor.DARK_AQUA, DyeColor.CYAN, "Dark Aqua"),
|
||||
DARK_RED(ChatColor.DARK_RED, DyeColor.RED, "Dark Red"),
|
||||
DARK_PURPLE(ChatColor.DARK_PURPLE, DyeColor.PURPLE, "Purple"),
|
||||
DARK_GRAY(ChatColor.DARK_GRAY, DyeColor.GRAY, "Dark Gray"),
|
||||
GRAY(ChatColor.GRAY, DyeColor.LIGHT_GRAY, "Gray");
|
||||
|
||||
private final ChatColor chatColor;
|
||||
private final DyeColor dyeColor;
|
||||
private final String displayName;
|
||||
|
||||
TeamColor(ChatColor chatColor, DyeColor dyeColor, String displayName) {
|
||||
this.chatColor = chatColor;
|
||||
this.dyeColor = dyeColor;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public ChatColor getChatColor() {
|
||||
return chatColor;
|
||||
}
|
||||
|
||||
public DyeColor getDyeColor() {
|
||||
return dyeColor;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
public class TeamException extends RuntimeException {
|
||||
|
||||
public TeamException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TeamException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
import fr.luc.crcore.common.AbstractEntity;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class TeamMember extends AbstractEntity {
|
||||
|
||||
private final TeamRole role;
|
||||
private final Instant joinedAt;
|
||||
|
||||
public TeamMember(UUID playerId, TeamRole role) {
|
||||
this(playerId, role, Instant.now());
|
||||
}
|
||||
|
||||
public TeamMember(UUID playerId, TeamRole role, Instant joinedAt) {
|
||||
super(playerId);
|
||||
this.role = Objects.requireNonNull(role, "role");
|
||||
this.joinedAt = Objects.requireNonNull(joinedAt, "joinedAt");
|
||||
}
|
||||
|
||||
public UUID getPlayerId() {
|
||||
return getId();
|
||||
}
|
||||
|
||||
public TeamRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public Instant getJoinedAt() {
|
||||
return joinedAt;
|
||||
}
|
||||
|
||||
public boolean isLeader() {
|
||||
return role.isLeader();
|
||||
}
|
||||
|
||||
public TeamMember withRole(TeamRole newRole) {
|
||||
Objects.requireNonNull(newRole, "newRole");
|
||||
if (newRole == this.role) {
|
||||
return this;
|
||||
}
|
||||
return new TeamMember(getPlayerId(), newRole, joinedAt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
public class TeamNotFoundException extends TeamException {
|
||||
|
||||
public TeamNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record TeamRanking(int rank, Team team, int score) {
|
||||
|
||||
public TeamRanking {
|
||||
Objects.requireNonNull(team, "team");
|
||||
if (rank < 1) {
|
||||
throw new IllegalArgumentException("rank must be >= 1, got " + rank);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
import fr.luc.crcore.common.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface TeamRepository extends Repository<Team> {
|
||||
|
||||
Optional<Team> findByName(String name);
|
||||
|
||||
Optional<Team> findByTag(String tag);
|
||||
|
||||
Optional<Team> findByMember(UUID playerId);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
public enum TeamRole {
|
||||
|
||||
LEADER,
|
||||
MEMBER;
|
||||
|
||||
public boolean isLeader() {
|
||||
return this == LEADER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public interface TeamService {
|
||||
|
||||
// ---- Lifecycle ----
|
||||
|
||||
Team createTeam(String name, String tag, TeamColor color, UUID leaderId);
|
||||
|
||||
Team createTeam(String name, String tag, TeamColor color, UUID leaderId,
|
||||
TeamVisibility visibility);
|
||||
|
||||
boolean dissolveTeam(UUID teamId);
|
||||
|
||||
// ---- Membership ----
|
||||
|
||||
boolean addMember(UUID teamId, UUID playerId);
|
||||
|
||||
boolean removeMember(UUID teamId, UUID playerId);
|
||||
|
||||
boolean joinTeam(UUID teamId, UUID playerId);
|
||||
|
||||
boolean transferLeadership(UUID teamId, UUID newLeaderId);
|
||||
|
||||
void setVisibility(UUID teamId, TeamVisibility visibility);
|
||||
|
||||
// ---- Scores ----
|
||||
|
||||
int addScore(UUID teamId, String scoreName, int delta);
|
||||
|
||||
int setScore(UUID teamId, String scoreName, int value);
|
||||
|
||||
int getScore(UUID teamId, String scoreName);
|
||||
|
||||
boolean resetScore(UUID teamId, String scoreName);
|
||||
|
||||
void resetAllScores(UUID teamId);
|
||||
|
||||
// ---- Rankings ----
|
||||
|
||||
List<TeamRanking> getRankingByScore(String scoreName);
|
||||
|
||||
List<TeamRanking> getGlobalRanking();
|
||||
|
||||
default List<TeamRanking> getTopRankingByScore(String scoreName, int limit) {
|
||||
return getRankingByScore(scoreName).stream().limit(limit).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
default List<TeamRanking> getTopGlobalRanking(int limit) {
|
||||
return getGlobalRanking().stream().limit(limit).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ---- Spawn point ----
|
||||
|
||||
void setSpawnPoint(UUID teamId, Location location);
|
||||
|
||||
void clearSpawnPoint(UUID teamId);
|
||||
|
||||
Optional<Location> getSpawnPoint(UUID teamId);
|
||||
|
||||
// ---- Queries ----
|
||||
|
||||
Optional<Team> getTeam(UUID teamId);
|
||||
|
||||
Optional<Team> getTeamByName(String name);
|
||||
|
||||
Optional<Team> getTeamByTag(String tag);
|
||||
|
||||
Optional<Team> getTeamOfPlayer(UUID playerId);
|
||||
|
||||
Collection<Team> getAllTeams();
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
public class TeamServiceImpl implements TeamService {
|
||||
|
||||
private final TeamRepository repository;
|
||||
|
||||
public TeamServiceImpl(TeamRepository repository) {
|
||||
this.repository = Objects.requireNonNull(repository, "repository");
|
||||
}
|
||||
|
||||
protected TeamRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
// ---- Lifecycle ----
|
||||
|
||||
@Override
|
||||
public Team createTeam(String name, String tag, TeamColor color, UUID leaderId) {
|
||||
return createTeam(name, tag, color, leaderId, TeamVisibility.PRIVATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Team createTeam(String name, String tag, TeamColor color, UUID leaderId,
|
||||
TeamVisibility visibility) {
|
||||
validateName(name);
|
||||
validateTag(tag);
|
||||
validateLeader(leaderId);
|
||||
Objects.requireNonNull(visibility, "visibility");
|
||||
|
||||
Team team = newTeam(UUID.randomUUID(), name, tag, color, leaderId, visibility);
|
||||
onBeforeSave(team);
|
||||
Team saved = repository.save(team);
|
||||
onAfterCreate(saved);
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dissolveTeam(UUID teamId) {
|
||||
Objects.requireNonNull(teamId, "teamId");
|
||||
Optional<Team> teamOpt = repository.findById(teamId);
|
||||
if (teamOpt.isEmpty()) return false;
|
||||
Team team = teamOpt.get();
|
||||
onBeforeDissolve(team);
|
||||
boolean deleted = repository.delete(teamId);
|
||||
if (deleted) onAfterDissolve(team);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
// ---- Membership ----
|
||||
|
||||
@Override
|
||||
public boolean addMember(UUID teamId, UUID playerId) {
|
||||
Objects.requireNonNull(playerId, "playerId");
|
||||
Team team = requireTeam(teamId);
|
||||
if (repository.findByMember(playerId).isPresent()) {
|
||||
return false;
|
||||
}
|
||||
TeamMember member = team.addMember(playerId);
|
||||
repository.save(team);
|
||||
onMemberAdded(team, member);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeMember(UUID teamId, UUID playerId) {
|
||||
Objects.requireNonNull(playerId, "playerId");
|
||||
Team team = requireTeam(teamId);
|
||||
boolean removed = team.removeMember(playerId);
|
||||
if (removed) {
|
||||
repository.save(team);
|
||||
onMemberRemoved(team, playerId);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean joinTeam(UUID teamId, UUID playerId) {
|
||||
Objects.requireNonNull(playerId, "playerId");
|
||||
Team team = requireTeam(teamId);
|
||||
validateJoinable(team, playerId);
|
||||
TeamMember member = team.addMember(playerId);
|
||||
repository.save(team);
|
||||
onMemberAdded(team, member);
|
||||
onPlayerJoined(team, member);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transferLeadership(UUID teamId, UUID newLeaderId) {
|
||||
Objects.requireNonNull(newLeaderId, "newLeaderId");
|
||||
Team team = requireTeam(teamId);
|
||||
UUID oldLeaderId = team.getLeaderId();
|
||||
team.transferLeadership(newLeaderId);
|
||||
repository.save(team);
|
||||
onLeadershipTransferred(team, oldLeaderId, newLeaderId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(UUID teamId, TeamVisibility visibility) {
|
||||
Objects.requireNonNull(visibility, "visibility");
|
||||
Team team = requireTeam(teamId);
|
||||
TeamVisibility old = team.getVisibility();
|
||||
if (old == visibility) return;
|
||||
team.setVisibility(visibility);
|
||||
repository.save(team);
|
||||
onVisibilityChanged(team, old, visibility);
|
||||
}
|
||||
|
||||
// ---- Scores ----
|
||||
|
||||
@Override
|
||||
public int addScore(UUID teamId, String scoreName, int delta) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
Team team = requireTeam(teamId);
|
||||
int oldValue = team.getScore(scoreName);
|
||||
int newValue = team.addScore(scoreName, delta);
|
||||
repository.save(team);
|
||||
if (oldValue != newValue) {
|
||||
onScoreChanged(team, scoreName, oldValue, newValue);
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setScore(UUID teamId, String scoreName, int value) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
Team team = requireTeam(teamId);
|
||||
int oldValue = team.getScore(scoreName);
|
||||
team.setScore(scoreName, value);
|
||||
repository.save(team);
|
||||
if (oldValue != value) {
|
||||
onScoreChanged(team, scoreName, oldValue, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScore(UUID teamId, String scoreName) {
|
||||
return requireTeam(teamId).getScore(scoreName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetScore(UUID teamId, String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
Team team = requireTeam(teamId);
|
||||
int oldValue = team.getScore(scoreName);
|
||||
boolean removed = team.resetScore(scoreName);
|
||||
if (removed) {
|
||||
repository.save(team);
|
||||
onScoreChanged(team, scoreName, oldValue, 0);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAllScores(UUID teamId) {
|
||||
Team team = requireTeam(teamId);
|
||||
Map<String, Integer> snapshot = new LinkedHashMap<>(team.getScores());
|
||||
if (snapshot.isEmpty()) return;
|
||||
team.resetAllScores();
|
||||
repository.save(team);
|
||||
snapshot.forEach((scoreName, oldValue) ->
|
||||
onScoreChanged(team, scoreName, oldValue, 0));
|
||||
}
|
||||
|
||||
// ---- Rankings ----
|
||||
|
||||
@Override
|
||||
public List<TeamRanking> getRankingByScore(String scoreName) {
|
||||
Objects.requireNonNull(scoreName, "scoreName");
|
||||
return rank(team -> team.getScore(scoreName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TeamRanking> getGlobalRanking() {
|
||||
return rank(Team::getTotalScore);
|
||||
}
|
||||
|
||||
protected List<TeamRanking> rank(ToIntFunction<Team> scoreFn) {
|
||||
Collection<Team> all = repository.findAll();
|
||||
List<Team> sorted = new ArrayList<>(all);
|
||||
sorted.sort(Comparator
|
||||
.comparingInt(scoreFn).reversed()
|
||||
.thenComparing(Team::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
List<TeamRanking> result = new ArrayList<>(sorted.size());
|
||||
int currentRank = 1;
|
||||
for (Team team : sorted) {
|
||||
result.add(newRanking(currentRank++, team, scoreFn.applyAsInt(team)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected TeamRanking newRanking(int rank, Team team, int score) {
|
||||
return new TeamRanking(rank, team, score);
|
||||
}
|
||||
|
||||
// ---- Spawn point ----
|
||||
|
||||
@Override
|
||||
public void setSpawnPoint(UUID teamId, Location location) {
|
||||
Team team = requireTeam(teamId);
|
||||
Location old = team.getSpawnPoint().orElse(null);
|
||||
team.setSpawnPoint(location);
|
||||
repository.save(team);
|
||||
onSpawnPointChanged(team, old, location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSpawnPoint(UUID teamId) {
|
||||
setSpawnPoint(teamId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Location> getSpawnPoint(UUID teamId) {
|
||||
return requireTeam(teamId).getSpawnPoint();
|
||||
}
|
||||
|
||||
// ---- Queries ----
|
||||
|
||||
@Override
|
||||
public Optional<Team> getTeam(UUID teamId) {
|
||||
return repository.findById(teamId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Team> getTeamByName(String name) {
|
||||
return repository.findByName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Team> getTeamByTag(String tag) {
|
||||
return repository.findByTag(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Team> getTeamOfPlayer(UUID playerId) {
|
||||
return repository.findByMember(playerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Team> getAllTeams() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
// ---- Override hooks ----
|
||||
|
||||
protected Team newTeam(UUID id, String name, String tag, TeamColor color, UUID leaderId,
|
||||
TeamVisibility visibility) {
|
||||
return new Team(id, name, tag, color, leaderId, visibility);
|
||||
}
|
||||
|
||||
protected void validateName(String name) {
|
||||
Objects.requireNonNull(name, "name");
|
||||
repository.findByName(name).ifPresent(existing -> {
|
||||
throw new TeamAlreadyExistsException("Team name already in use: " + name);
|
||||
});
|
||||
}
|
||||
|
||||
protected void validateTag(String tag) {
|
||||
Objects.requireNonNull(tag, "tag");
|
||||
repository.findByTag(tag).ifPresent(existing -> {
|
||||
throw new TeamAlreadyExistsException("Team tag already in use: " + tag);
|
||||
});
|
||||
}
|
||||
|
||||
protected void validateLeader(UUID leaderId) {
|
||||
Objects.requireNonNull(leaderId, "leaderId");
|
||||
repository.findByMember(leaderId).ifPresent(existing -> {
|
||||
throw new TeamAlreadyExistsException(
|
||||
"Player already belongs to team: " + existing.getName());
|
||||
});
|
||||
}
|
||||
|
||||
protected void validateJoinable(Team team, UUID playerId) {
|
||||
if (!team.isPublic()) {
|
||||
throw new TeamAccessException(
|
||||
"Team " + team.getName() + " is private; ask the leader for an invite.");
|
||||
}
|
||||
repository.findByMember(playerId).ifPresent(existing -> {
|
||||
throw new TeamAccessException(
|
||||
"You already belong to team: " + existing.getName());
|
||||
});
|
||||
}
|
||||
|
||||
protected void onBeforeSave(Team team) {
|
||||
}
|
||||
|
||||
protected void onAfterCreate(Team team) {
|
||||
}
|
||||
|
||||
protected void onBeforeDissolve(Team team) {
|
||||
}
|
||||
|
||||
protected void onAfterDissolve(Team team) {
|
||||
}
|
||||
|
||||
protected void onMemberAdded(Team team, TeamMember member) {
|
||||
}
|
||||
|
||||
protected void onMemberRemoved(Team team, UUID playerId) {
|
||||
}
|
||||
|
||||
protected void onPlayerJoined(Team team, TeamMember member) {
|
||||
}
|
||||
|
||||
protected void onLeadershipTransferred(Team team, UUID oldLeaderId, UUID newLeaderId) {
|
||||
}
|
||||
|
||||
protected void onVisibilityChanged(Team team, TeamVisibility oldValue, TeamVisibility newValue) {
|
||||
}
|
||||
|
||||
protected void onScoreChanged(Team team, String scoreName, int oldValue, int newValue) {
|
||||
}
|
||||
|
||||
protected void onSpawnPointChanged(Team team, Location oldLocation, Location newLocation) {
|
||||
}
|
||||
|
||||
protected Team requireTeam(UUID teamId) {
|
||||
Objects.requireNonNull(teamId, "teamId");
|
||||
return repository.findById(teamId).orElseThrow(
|
||||
() -> new TeamNotFoundException("No team with id: " + teamId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package fr.luc.crcore.team;
|
||||
|
||||
public enum TeamVisibility {
|
||||
|
||||
PUBLIC,
|
||||
PRIVATE;
|
||||
|
||||
public boolean isPublic() {
|
||||
return this == PUBLIC;
|
||||
}
|
||||
|
||||
public boolean isPrivate() {
|
||||
return this == PRIVATE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user