Add initial Capture the Flag plugin implementation
Implemented the core structure for a Capture the Flag (CTF) plugin. This includes team management, game phases, player classes, command handling, and configuration support. The project is set up with Gradle for dependency management and provides placeholders for future feature expansion.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package com.alttd.ctf;
|
||||
|
||||
import com.alttd.ctf.commands.CommandManager;
|
||||
import com.alttd.ctf.config.Config;
|
||||
import com.alttd.ctf.events.OnSnowballHit;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@Slf4j
|
||||
public class Main extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
log.info("Plugin enabled!");
|
||||
reloadConfigs();
|
||||
GameManager gameManager = new GameManager();
|
||||
CommandManager commandManager = new CommandManager(this, gameManager);
|
||||
registerEvents(gameManager);
|
||||
}
|
||||
|
||||
public void reloadConfigs() {
|
||||
Config.reload(this);
|
||||
}
|
||||
|
||||
private void registerEvents(GameManager gameManager) {
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
pluginManager.registerEvents(new OnSnowballHit(gameManager), this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.alttd.ctf.commands;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.commands.subcommands.ChangeTeam;
|
||||
import com.alttd.ctf.commands.subcommands.Start;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.command.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class CommandManager implements CommandExecutor, TabExecutor {
|
||||
private final List<SubCommand> subCommands;
|
||||
|
||||
public CommandManager(Main main, GameManager gameManager) {
|
||||
PluginCommand command = main.getCommand("ctf");
|
||||
if (command == null) {
|
||||
subCommands = null;
|
||||
log.error("Unable to find transfer command.");
|
||||
return;
|
||||
}
|
||||
command.setExecutor(this);
|
||||
command.setTabCompleter(this);
|
||||
|
||||
subCommands = Arrays.asList(
|
||||
new ChangeTeam(gameManager),
|
||||
new Start(gameManager)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String cmd, @NotNull String[] args) {
|
||||
if (args.length == 0) {
|
||||
commandSender.sendRichMessage(Messages.HELP.HELP_MESSAGE_WRAPPER.replaceAll("<commands>", subCommands.stream()
|
||||
.filter(subCommand -> commandSender.hasPermission(subCommand.getPermission()))
|
||||
.map(SubCommand::getHelpMessage)
|
||||
.collect(Collectors.joining("\n"))));
|
||||
return true;
|
||||
}
|
||||
|
||||
SubCommand subCommand = getSubCommand(args[0]);
|
||||
if (subCommand == null)
|
||||
return false;
|
||||
|
||||
if (!commandSender.hasPermission(subCommand.getPermission())) {
|
||||
commandSender.sendRichMessage(Messages.GENERIC.NO_PERMISSION, Placeholder.parsed("permission", subCommand.getPermission()));
|
||||
return true;
|
||||
}
|
||||
|
||||
int failedPos = subCommand.onCommand(commandSender, args);
|
||||
if (failedPos > 0) {
|
||||
commandSender.sendRichMessage(String.format("<hover:show_text:'%s'>%s</hover>",
|
||||
getHoverText(command.getName(), args, failedPos),
|
||||
subCommand.getHelpMessage()));
|
||||
} else if (failedPos < 0) {
|
||||
commandSender.sendRichMessage(subCommand.getHelpMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getHoverText(String commandName, String[] args, int failedPos) {
|
||||
StringBuilder hoverText = new StringBuilder();
|
||||
|
||||
hoverText.append("<green>/").append(commandName);
|
||||
for (int i = 0; i < failedPos; i++) {
|
||||
hoverText.append(" ").append(args[i]);
|
||||
}
|
||||
hoverText.append("</green>");
|
||||
|
||||
if (failedPos < args.length) {
|
||||
hoverText.append(" <red>").append(args[failedPos]).append("</red>");
|
||||
}
|
||||
|
||||
for (int i = failedPos + 1; i < args.length; i++) {
|
||||
hoverText.append(" <yellow>").append(args[i]).append("</yellow>");
|
||||
}
|
||||
|
||||
return hoverText.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String cmd, @NotNull String[] args) {
|
||||
List<String> res = new ArrayList<>();
|
||||
|
||||
if (args.length <= 1) {
|
||||
res.addAll(subCommands.stream()
|
||||
.filter(subCommand -> commandSender.hasPermission(subCommand.getPermission()))
|
||||
.map(SubCommand::getName)
|
||||
.filter(name -> args.length == 0 || name.startsWith(args[0]))
|
||||
.toList()
|
||||
);
|
||||
} else {
|
||||
SubCommand subCommand = getSubCommand(args[0]);
|
||||
if (subCommand != null && commandSender.hasPermission(subCommand.getPermission()))
|
||||
res.addAll(subCommand.getTabComplete(commandSender, args).stream()
|
||||
.filter(str -> str.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
|
||||
.toList());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private SubCommand getSubCommand(String cmdName) {
|
||||
return subCommands.stream()
|
||||
.filter(subCommand -> subCommand.getName().equals(cmdName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.alttd.ctf.commands;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SubCommand {
|
||||
|
||||
public SubCommand() {}
|
||||
|
||||
//A return value other than 0 means the command failed
|
||||
// If the value is more than 0 it should be the position in the array of the argument it failed on
|
||||
// If the value is less than 0 it means something else failed (like the amount of arguments)
|
||||
public abstract int onCommand(CommandSender commandSender, String[] args);
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
public String getPermission() {
|
||||
return "ctf." + getName();
|
||||
}
|
||||
|
||||
public abstract List<String> getTabComplete(CommandSender commandSender, String[] args);
|
||||
|
||||
public abstract String getHelpMessage();
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.alttd.ctf.commands.subcommands;
|
||||
|
||||
import com.alttd.ctf.commands.SubCommand;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ChangeTeam extends SubCommand {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public ChangeTeam(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ChangeTeamConsumer {
|
||||
int apply(Player player, Team team);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(CommandSender commandSender, String[] args) {
|
||||
return handle(commandSender, args, (player, team) -> {
|
||||
changeTeam(commandSender, player, team);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
private int handle(CommandSender commandSender, String[] args, ChangeTeamConsumer consumer) {
|
||||
if (args.length != 3) {
|
||||
return -1;
|
||||
}
|
||||
Player player = Bukkit.getPlayer(args[1]);
|
||||
if (player == null) {
|
||||
commandSender.sendRichMessage("<red>Please provide a valid player</red>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int teamId;
|
||||
try {
|
||||
teamId = Integer.parseInt(args[2]);
|
||||
} catch (NumberFormatException e) {
|
||||
commandSender.sendRichMessage(String.format("<red>Please enter a valid integer, %s is not a valid integer</red>", args[2]));
|
||||
return 2;
|
||||
}
|
||||
Optional<Team> optionalTeam = gameManager.getTeams().stream().filter(team -> team.getId() == teamId).findFirst();
|
||||
if (optionalTeam.isEmpty()) {
|
||||
commandSender.sendRichMessage(String.format("<red>Please provide a valid team id %d is not a valid team id</red>", teamId));
|
||||
return 3;
|
||||
}
|
||||
return consumer.apply(player, optionalTeam.get());
|
||||
}
|
||||
|
||||
private void changeTeam(CommandSender commandSender, Player player, Team team) {
|
||||
Optional<Team> optionalOldTeam = gameManager.getTeam(player.getUniqueId());
|
||||
if (optionalOldTeam.isPresent()) {
|
||||
moveBetweenTeams(commandSender, player, team, optionalOldTeam.get());
|
||||
return;
|
||||
}
|
||||
gameManager.registerPlayer(team, player);
|
||||
commandSender.sendRichMessage("<green><player> has been placed in<team>.</green>",
|
||||
TagResolver.resolver(
|
||||
Placeholder.component("player", player.displayName()),
|
||||
Placeholder.component("team", team.getName())));
|
||||
}
|
||||
|
||||
private void moveBetweenTeams(CommandSender commandSender, Player player, Team newTeam, Team oldTeam) {
|
||||
if (oldTeam.equals(newTeam)) {
|
||||
commandSender.sendRichMessage("<green><player> was already in <team>, nothing has changed.</green>",
|
||||
TagResolver.resolver(
|
||||
Placeholder.component("player", player.displayName()),
|
||||
Placeholder.component("team", newTeam.getName())));
|
||||
return;
|
||||
}
|
||||
gameManager.registerPlayer(newTeam, player);
|
||||
commandSender.sendRichMessage("<green><player> has been moved from <old_team> to <new_team>.</green>",
|
||||
TagResolver.resolver(
|
||||
Placeholder.component("player", player.displayName()),
|
||||
Placeholder.component("old_team", oldTeam.getName())),
|
||||
Placeholder.component("new_team", newTeam.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "changeteam";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
|
||||
return switch (args.length) {
|
||||
case 2 -> Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
|
||||
case 3 -> gameManager.getTeams().stream().map(Team::getId).map(Object::toString).toList();
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpMessage() {
|
||||
return Messages.HELP.CHANGE_TEAM;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.alttd.ctf.commands.subcommands;
|
||||
|
||||
import com.alttd.ctf.commands.SubCommand;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
public class Start extends SubCommand {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public Start(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface StartConsumer {
|
||||
int apply(Duration combatTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(CommandSender commandSender, String[] args) {
|
||||
return handle(commandSender, args, combatTime -> {
|
||||
gameManager.start(combatTime);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
private int handle(CommandSender commandSender, String[] args, StartConsumer consumer) {
|
||||
if (args.length != 2) {
|
||||
return -1;
|
||||
}
|
||||
Duration combatTime;
|
||||
try {
|
||||
combatTime = Duration.ofSeconds(Integer.parseInt(args[1]));
|
||||
} catch (NumberFormatException e) {
|
||||
commandSender.sendRichMessage("<red>Please enter a valid integer</red>");
|
||||
return 1;
|
||||
}
|
||||
return consumer.apply(combatTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "start";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
return switch (args.length) {
|
||||
case 2 -> List.of("30", "45", "60");
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpMessage() {
|
||||
return Messages.HELP.START;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.alttd.ctf.config;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@SuppressWarnings({"unused", "SameParameterValue"})
|
||||
abstract class AbstractConfig {
|
||||
File file;
|
||||
YamlConfiguration yaml;
|
||||
|
||||
AbstractConfig(Main main, String filename) {
|
||||
init(new File(main.getDataFolder(), filename), filename);
|
||||
}
|
||||
|
||||
AbstractConfig(File file, String filename) {
|
||||
init(new File(file.getPath() + File.separator + filename), filename);
|
||||
}
|
||||
|
||||
private void init(File file, String filename) {
|
||||
this.file = file;
|
||||
this.yaml = new YamlConfiguration();
|
||||
try {
|
||||
yaml.load(file);
|
||||
} catch (IOException ignore) {
|
||||
} catch (InvalidConfigurationException ex) {
|
||||
log.error("Could not load {}, please correct your syntax errors", filename, ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
yaml.options().copyDefaults(true);
|
||||
}
|
||||
|
||||
void readConfig(Class<?> clazz, Object instance) {
|
||||
for (Class<?> declaredClass : clazz.getDeclaredClasses()) {
|
||||
for (Method method : declaredClass.getDeclaredMethods()) {
|
||||
if (!Modifier.isPrivate(method.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
if (method.getParameterTypes().length != 0 || method.getReturnType() != Void.TYPE) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
method.setAccessible(true);
|
||||
method.invoke(instance);
|
||||
} catch (InvocationTargetException ex) {
|
||||
throw new RuntimeException(ex.getCause());
|
||||
} catch (Exception ex) {
|
||||
log.error("Error invoking {}.", method, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
private void save() {
|
||||
try {
|
||||
yaml.save(file);
|
||||
} catch (IOException ex) {
|
||||
log.error("Could not save {}.", file.toString(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
void set(String prefix, String path, Object val) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, val);
|
||||
yaml.set(path, val);
|
||||
save();
|
||||
}
|
||||
|
||||
String getString(String prefix, String path, String def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getString(path, yaml.getString(path));
|
||||
}
|
||||
|
||||
boolean getBoolean(String prefix, String path, boolean def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getBoolean(path, yaml.getBoolean(path));
|
||||
}
|
||||
|
||||
int getInt(String prefix, String path, int def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getInt(path, yaml.getInt(path));
|
||||
}
|
||||
|
||||
double getDouble(String prefix, String path, double def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getDouble(path, yaml.getDouble(path));
|
||||
}
|
||||
|
||||
<T> List<String> getList(String prefix, String path, T def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
List<?> list = yaml.getList(path, yaml.getList(path));
|
||||
return list == null ? null : list.stream().map(Object::toString).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<String> getStringList(String prefix, String path, List<String> def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getStringList(path);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
<T> Map<String, T> getMap(String prefix, @NonNull String path, final @Nullable Map<String, T> def) {
|
||||
path = prefix + path;
|
||||
final ImmutableMap.Builder<String, T> builder = ImmutableMap.builder();
|
||||
if (def != null && yaml.getConfigurationSection(path) == null) {
|
||||
yaml.addDefault(path, def.isEmpty() ? new HashMap<>() : def);
|
||||
return def;
|
||||
}
|
||||
final ConfigurationSection section = yaml.getConfigurationSection(path);
|
||||
if (section != null) {
|
||||
for (String key : section.getKeys(false)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final T val = (T) section.get(key);
|
||||
if (val != null) {
|
||||
builder.put(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
ConfigurationSection getConfigurationSection(String path) {
|
||||
return yaml.getConfigurationSection(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.alttd.ctf.config;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
@Slf4j
|
||||
public class Config extends AbstractConfig{
|
||||
|
||||
static Config config;
|
||||
private final Main main;
|
||||
|
||||
Config(Main main) {
|
||||
super(main, "config.yml");
|
||||
this.main = main;
|
||||
}
|
||||
|
||||
public static void reload(Main main) {
|
||||
log.info("Reloading config");
|
||||
config = new Config(main);
|
||||
config.readConfig(Config.class, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class SETTINGS {
|
||||
private static final String prefix = "settings.";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
if (config.getBoolean(prefix, "debug", false)) {
|
||||
config.main.getLogger().setLevel(Level.FINE);
|
||||
} else if (config.getBoolean(prefix, "warnings", true)) {
|
||||
config.main.getLogger().setLevel(Level.WARNING);
|
||||
} else {
|
||||
config.main.getLogger().setLevel(Level.INFO);
|
||||
}
|
||||
log.debug("Debug logging is enabled");
|
||||
log.warn("Warning logging is enabled");
|
||||
log.info("Info logging is enabled");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.alttd.ctf.config;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
|
||||
@Slf4j
|
||||
public class GameConfig extends AbstractConfig{
|
||||
|
||||
static GameConfig config;
|
||||
|
||||
GameConfig(Main main) {
|
||||
super(main, "game-config.yml");
|
||||
}
|
||||
|
||||
public static void reload(Main main) {
|
||||
log.info("Reloading config");
|
||||
config = new GameConfig(main);
|
||||
config.readConfig(GameConfig.class, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class PHASES {
|
||||
private static final String prefix = "phases.";
|
||||
|
||||
private static final HashMap<GamePhase, Duration> GAME_PHASE_DURATION = new HashMap<>();
|
||||
|
||||
public static HashMap<GamePhase, Duration> getGAME_PHASE_DURATION() {
|
||||
return new HashMap<>(GAME_PHASE_DURATION);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
GAME_PHASE_DURATION.clear();
|
||||
for (GamePhase phase : GamePhase.values()) {
|
||||
if (phase.equals(GamePhase.COMBAT))
|
||||
continue;
|
||||
int phaseDurationInMinutes = config.getInt(prefix, phase.name().toLowerCase(), 5);
|
||||
GAME_PHASE_DURATION.put(phase, Duration.ofMinutes(phaseDurationInMinutes));
|
||||
log.debug("Set {} phase duration to {} minutes", phase.name(), phaseDurationInMinutes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.alttd.ctf.config;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class Messages extends AbstractConfig {
|
||||
static Messages config;
|
||||
|
||||
Messages(Main main) {
|
||||
super(main, "config.yml");
|
||||
}
|
||||
|
||||
public static void reload(Main main) {
|
||||
log.info("Reloading messages config");
|
||||
config = new Messages(main);
|
||||
config.readConfig(Messages.class, null);
|
||||
}
|
||||
|
||||
public static class HELP {
|
||||
private static final String prefix = "help.";
|
||||
|
||||
public static String HELP_MESSAGE_WRAPPER = "<gold>Main help:\n<commands></gold>";
|
||||
public static String HELP_MESSAGE = "<green>Show this menu: <gold>/ctf help</gold></green>";
|
||||
public static String CHANGE_TEAM = "<green>Change a players team: <gold>/ctf changeteam <player> <team></gold></green>";
|
||||
public static String START = "<green>Start a new game: <gold>/ctf start <time_in_minutes></gold></green>";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
HELP_MESSAGE_WRAPPER = config.getString(prefix, "help-wrapper", HELP_MESSAGE_WRAPPER);
|
||||
HELP_MESSAGE = config.getString(prefix, "help", HELP_MESSAGE);
|
||||
CHANGE_TEAM = config.getString(prefix, "change-team", CHANGE_TEAM);
|
||||
START = config.getString(prefix, "start", START);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GENERIC {
|
||||
private static final String prefix = "generic.";
|
||||
|
||||
public static String NO_PERMISSION = "<red><hover:show_text:'<red><permission></red>'>You don't have permission for this command</hover></red>";
|
||||
public static String PLAYER_ONLY = "<red>This command can only be executed as a player</red>";
|
||||
public static String PLAYER_NOT_FOUND = "<red>Unable to find online player <player></red>";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
NO_PERMISSION = config.getString(prefix, "no-permission", NO_PERMISSION);
|
||||
PLAYER_ONLY = config.getString(prefix, "player-only", PLAYER_ONLY);
|
||||
PLAYER_NOT_FOUND = config.getString(prefix, "player-only", PLAYER_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.alttd.ctf.events;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
public class OnSnowballHit implements Listener {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public OnSnowballHit(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface SnowballHitConsumer {
|
||||
void apply(Player hitPlayer, Player shooter, TeamPlayer shooterTeamPlayer);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSnowballHit(EntityDamageByEntityEvent event) {
|
||||
handle(event, (hitPlayer, shooter, shooterTeamPlayer) -> {
|
||||
GameClass shooterClass = shooterTeamPlayer.getGameClass();
|
||||
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
|
||||
|
||||
double newHealth = hitPlayer.getHealth() - shooterClass.getDamage();
|
||||
hitPlayer.setHealth(Math.max(newHealth, 0));
|
||||
log.debug("{} health was set to {} because of a snowball thrown by {}",
|
||||
hitPlayer.getName(), Math.max(newHealth, 0), shooter.getName());
|
||||
});
|
||||
}
|
||||
|
||||
private void handle(EntityDamageByEntityEvent event, SnowballHitConsumer consumer) {
|
||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||
if (optionalGamePhase.isEmpty()) {
|
||||
log.debug("No game is running but player was hit by snowball");
|
||||
return;
|
||||
}
|
||||
GamePhase gamePhase = optionalGamePhase.get();
|
||||
|
||||
if (!gamePhase.equals(GamePhase.COMBAT)) {
|
||||
log.debug("Not in combat phase but player was hit by snowball");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event.getEntity() instanceof Player hitPlayer)) {
|
||||
log.debug("An entity other than a player was hit by a snowball");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event.getDamager() instanceof org.bukkit.entity.Snowball snowball)) {
|
||||
log.debug("The player was hit by something other than a snowball");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(snowball.getShooter() instanceof Player shooter)) {
|
||||
log.debug("The shooter that hit a player with a snowball was not a player");
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(shooter.getUniqueId());
|
||||
if (teamPlayer.isEmpty()) {
|
||||
log.debug("The shooter that hit a player with a snowball was not a team player");
|
||||
return;
|
||||
}
|
||||
consumer.apply(hitPlayer, shooter, teamPlayer.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.game.phases.ClassSelectionPhase;
|
||||
import com.alttd.ctf.game_class.implementations.Fighter;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GameManager {
|
||||
|
||||
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
private final HashMap<GamePhase, GamePhaseExecutor> phases;
|
||||
private RunningGame runningGame;
|
||||
private final HashMap<Integer, Team> teams = new HashMap<>();
|
||||
|
||||
public GameManager() {
|
||||
phases = new HashMap<>();
|
||||
//TODO initialize this somewhere else (maybe load it from a json file?)
|
||||
phases.put(GamePhase.CLASS_SELECTION, new ClassSelectionPhase(this, new Fighter(List.of(Material.LEATHER_CHESTPLATE), List.of(), new ItemStack(Material.STONE), 15, 3, 5)));
|
||||
}
|
||||
|
||||
public Optional<GamePhase> getGamePhase() {
|
||||
return runningGame == null ? Optional.empty() : Optional.of(runningGame.getCurrentPhase());
|
||||
}
|
||||
|
||||
public void registerPlayer(Team team, Player player) {
|
||||
unregisterPlayer(player);
|
||||
teams.get(team.getId()).addPlayer(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void unregisterPlayer(Player player) {
|
||||
teams.values().forEach(team -> team.removePlayer(player.getUniqueId()));
|
||||
}
|
||||
|
||||
public Collection<Team> getTeams() {
|
||||
return teams.values();
|
||||
}
|
||||
|
||||
public Optional<Team> getTeam(@NotNull UUID uuid) {
|
||||
return getTeams().stream().filter(filterTeam -> filterTeam.getPlayer(uuid).isPresent()).findAny();
|
||||
}
|
||||
|
||||
public Optional<TeamPlayer> getTeamPlayer(@NotNull UUID uuid) {
|
||||
return getTeams().stream()
|
||||
.map(team -> team.getPlayer(uuid))
|
||||
.filter(Optional::isPresent)
|
||||
.findFirst()
|
||||
.orElseGet(Optional::empty);
|
||||
}
|
||||
|
||||
public void start(Duration duration) {
|
||||
if (runningGame != null) {
|
||||
runningGame.end();
|
||||
executorService.shutdown();
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
runningGame = new RunningGame(this, duration);
|
||||
executorService.scheduleAtFixedRate(runningGame, 0, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
protected GamePhaseExecutor getPhaseExecutor(GamePhase gamePhase) {
|
||||
GamePhaseExecutor gamePhaseExecutor = phases.get(gamePhase);
|
||||
if (gamePhaseExecutor == null) {
|
||||
throw new IllegalArgumentException("No phase executor found for phase " + gamePhase);
|
||||
}
|
||||
return gamePhaseExecutor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.alttd.ctf.game;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
|
||||
@Getter
|
||||
public enum GamePhase {
|
||||
CLASS_SELECTION(MiniMessage.miniMessage().deserialize("<green>Class selection phase</green>")),
|
||||
GATHERING(MiniMessage.miniMessage().deserialize("<green>Gathering phase</green>")),
|
||||
COMBAT(MiniMessage.miniMessage().deserialize("<green>Combat phase</green>")),
|
||||
ENDED(MiniMessage.miniMessage().deserialize("<green>Game end phase</green>"));
|
||||
|
||||
private final Component displayName;
|
||||
|
||||
GamePhase(Component displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.alttd.ctf.game;
|
||||
|
||||
public interface GamePhaseExecutor {
|
||||
|
||||
void start();
|
||||
|
||||
void end();
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class RunningGame implements Runnable {
|
||||
|
||||
private final HashMap<GamePhase, Duration> phaseDurations = GameConfig.PHASES.getGAME_PHASE_DURATION();
|
||||
private final GameManager gameManager;
|
||||
@Getter
|
||||
private GamePhase currentPhase = GamePhase.values()[0];
|
||||
private Instant phaseStartTime = null;
|
||||
|
||||
public RunningGame(GameManager gameManager, Duration gameDuration) {
|
||||
this.gameManager = gameManager;
|
||||
phaseDurations.put(GamePhase.COMBAT, gameDuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (phaseStartTime == null) {
|
||||
phaseStartTime = Instant.now();
|
||||
GamePhase nextPhase = GamePhase.values()[currentPhase.ordinal() + 1];
|
||||
nextPhaseActions(currentPhase, nextPhase);
|
||||
}
|
||||
|
||||
if (Duration.between(phaseStartTime, Instant.now()).compareTo(phaseDurations.get(currentPhase)) >= 0) {
|
||||
GamePhase nextPhase = GamePhase.values()[currentPhase.ordinal() + 1];
|
||||
nextPhaseActions(currentPhase, nextPhase);
|
||||
currentPhase = nextPhase;
|
||||
phaseStartTime = Instant.now();
|
||||
}
|
||||
}
|
||||
|
||||
private void nextPhaseActions(@NotNull GamePhase phase, @Nullable GamePhase nextPhase) {
|
||||
//TODO class/functions for each phase that they run at the start of that phase
|
||||
// These should notify the player of what the phase is and that it started as well
|
||||
gameManager.getPhaseExecutor(phase).start();
|
||||
if (nextPhase != null) {
|
||||
broadcastNextPhaseStartTime(phase, nextPhase);
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastNextPhaseStartTime(GamePhase currentPhase, GamePhase nextPhase) {//TODO check how this works/what it should do
|
||||
//Remaining time for this phase
|
||||
Duration duration = phaseDurations.get(currentPhase).minus(Duration.between(phaseStartTime, Instant.now()));
|
||||
if (duration.toMinutes() > 5 && duration.toMinutes() % 15 == 0) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||
"<green>The <phase> will start in <bold><red>" + duration.toMinutes() + "</red> minutes</bold></green>",
|
||||
Placeholder.component("phase", nextPhase.getDisplayName())
|
||||
));
|
||||
} else if (duration.toMinutes() < 5) {
|
||||
//TODO start minute countdown -> second countdown
|
||||
}
|
||||
}
|
||||
|
||||
public void end() {
|
||||
//TODO say the phase ended early?
|
||||
currentPhase = GamePhase.ENDED;
|
||||
nextPhaseActions(currentPhase, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.alttd.ctf.game.phases;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ClassSelectionPhase implements GamePhaseExecutor {
|
||||
|
||||
private final GameManager gameManager;
|
||||
private final GameClass defaultClass;
|
||||
|
||||
public ClassSelectionPhase(@NotNull GameManager gameManager, @NotNull GameClass defaultClass) {
|
||||
this.gameManager = gameManager;
|
||||
this.defaultClass = defaultClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
teleportPlayersToStartingZone();
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>Select your class</green>"));
|
||||
//TODO let players select classes
|
||||
// They should always be able to do this when in their starting zone
|
||||
// They should be locked into their starting zone until the next phase starts
|
||||
// That phase should handle opening the zone
|
||||
}
|
||||
|
||||
private void teleportPlayersToStartingZone() {
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
Optional<Team> team = gameManager.getTeam(player.getUniqueId());
|
||||
if (team.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Location spawnLocation = team.get().getSpawnLocation();
|
||||
player.teleportAsync(spawnLocation);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
gameManager.getTeams().forEach(team -> team.getPlayers().forEach(player -> {
|
||||
if (player.getGameClass() != null) {
|
||||
return;
|
||||
}
|
||||
defaultClass.apply(player);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.alttd.ctf.game_class;
|
||||
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.LeatherArmorMeta;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public abstract class GameClass {
|
||||
|
||||
private final List<Material> armor;
|
||||
private final List<ItemStack> tools;
|
||||
private final ItemStack displayItem;
|
||||
private final double health;
|
||||
//Delay in ticks between throws (prevents auto clickers)
|
||||
@Getter
|
||||
private final int throwTickSpeed;
|
||||
//Damage done when hitting a player with a snowball
|
||||
@Getter
|
||||
private final int damage;
|
||||
|
||||
protected GameClass(List<Material> armor, List<ItemStack> tools, ItemStack displayItem, double health, int throwTickSpeed, int damage) {
|
||||
this.armor = armor;
|
||||
this.tools = tools;
|
||||
this.displayItem = displayItem;
|
||||
this.health = health;
|
||||
this.throwTickSpeed = throwTickSpeed;
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
public void apply(TeamPlayer teamPlayer) {
|
||||
Player player = Bukkit.getPlayer(teamPlayer.getUuid());
|
||||
if (player == null || !player.isOnline()) {
|
||||
log.warn("Tried to give class to offline player {}", player == null ? teamPlayer.getUuid() : player.getName());
|
||||
return;
|
||||
}
|
||||
AttributeInstance maxHealthAttribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
||||
if (maxHealthAttribute == null) {
|
||||
log.error("Player does not have max health attribute");
|
||||
return;
|
||||
}
|
||||
//Always reset the player inventory since other classes might have had them get items
|
||||
player.getInventory().clear();
|
||||
|
||||
player.getInventory().setContents(tools.toArray(ItemStack[]::new));
|
||||
TeamColor color = teamPlayer.getTeam().getColor();
|
||||
setArmor(player, color.r(), color.g(), color.b());
|
||||
|
||||
player.updateInventory();
|
||||
maxHealthAttribute.setBaseValue(health);
|
||||
teamPlayer.setGameClass(this);
|
||||
}
|
||||
|
||||
private void setArmor(Player player, int r, int g, int b) {
|
||||
player.getInventory().setArmorContents(armor.stream().map(material -> {
|
||||
ItemStack itemStack = new ItemStack(material);
|
||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
if (itemMeta instanceof LeatherArmorMeta leatherArmorMeta) {
|
||||
leatherArmorMeta.setColor(Color.fromBGR(r, g, b));
|
||||
itemStack.setItemMeta(leatherArmorMeta);
|
||||
}
|
||||
return new ItemStack(material);
|
||||
}).toArray(ItemStack[]::new));
|
||||
}
|
||||
|
||||
ItemStack getDisplayItem() {
|
||||
return displayItem;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.alttd.ctf.game_class.implementations;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class Fighter extends GameClass {
|
||||
|
||||
public Fighter(List<Material> armor, List<ItemStack> tools, ItemStack displayItem, double health, int tickThrowSpeed, int damage) {
|
||||
super(armor, tools, displayItem, health, tickThrowSpeed, damage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.alttd.ctf.team;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
public class Team {
|
||||
|
||||
private final HashMap<UUID, TeamPlayer> players = new HashMap<>();
|
||||
@Getter
|
||||
private Component name;
|
||||
@Getter
|
||||
private final int id;
|
||||
@Getter
|
||||
private Location spawnLocation;
|
||||
@Getter
|
||||
private Location worldBorderCenter; //TODO https://github.com/yannicklamprecht/WorldBorderAPI/blob/main/how-to-use.md
|
||||
//TODO store team color to be used for kits and chat colors (in rgb?)
|
||||
@Getter
|
||||
private TeamColor color;
|
||||
|
||||
public Team(int id) {
|
||||
this.id = id;
|
||||
reloadTeamData();
|
||||
}
|
||||
|
||||
private void reloadTeamData() {
|
||||
this.color = new TeamColor(255, 0, 0, "#FF0000");
|
||||
this.name = MiniMessage.miniMessage().deserialize(String.format("<color:#%s>Test Team</color>", color.hex()));
|
||||
this.spawnLocation = Bukkit.getWorld("world").getSpawnLocation();
|
||||
//TODO load team data from config
|
||||
}
|
||||
|
||||
public void addPlayer(UUID uuid) {
|
||||
players.put(uuid, new TeamPlayer(uuid, this));
|
||||
log.debug("Added player with uuid {} to team with id {}", uuid, id);
|
||||
}
|
||||
|
||||
public Optional<TeamPlayer> getPlayer(@NotNull UUID uuid) {
|
||||
if (!players.containsKey(uuid))
|
||||
return Optional.empty();
|
||||
return Optional.of(players.get(uuid));
|
||||
}
|
||||
|
||||
public Collection<TeamPlayer> getPlayers() {
|
||||
return players.values();
|
||||
}
|
||||
|
||||
public void removePlayer(@NotNull UUID uuid) {
|
||||
TeamPlayer remove = players.remove(uuid);
|
||||
if (remove != null) {
|
||||
log.debug("Removed player with uuid {} from team with id {}", uuid, id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Team other = (Team) obj;
|
||||
return Objects.equals(id, other.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.alttd.ctf.team;
|
||||
|
||||
public record TeamColor(int r, int g, int b, String hex) {
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.alttd.ctf.team;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class TeamPlayer {
|
||||
|
||||
private final UUID uuid;
|
||||
private final Team team;
|
||||
@Setter
|
||||
private GameClass gameClass;
|
||||
|
||||
protected TeamPlayer(UUID uuid, Team team) {
|
||||
this.uuid = uuid;
|
||||
this.team = team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TeamPlayer other = (TeamPlayer) obj;
|
||||
return Objects.equals(uuid, other.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(uuid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
name: CTF
|
||||
version: '${version}'
|
||||
main: com.alttd.ctf.Main
|
||||
api-version: '1.21'
|
||||
commands:
|
||||
ctf:
|
||||
description: Base command for capture the flag
|
||||
permission: ctf.use
|
||||
usage: /ctf
|
||||
Reference in New Issue
Block a user