Compare commits
49
Commits
4cfc8f1572
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4953cec215 | ||
|
|
92f9e732a1 | ||
|
|
95b54d1a03 | ||
|
|
05a63b9a52 | ||
|
|
137e5a5125 | ||
|
|
3c5842a92c | ||
|
|
f5bb1564ae | ||
|
|
fa8d320da9 | ||
|
|
816c429346 | ||
|
|
afbd1a9f09 | ||
|
|
5430af2149 | ||
|
|
ce7297afb8 | ||
|
|
a10607092b | ||
|
|
fe39cdf5a1 | ||
|
|
6008378258 | ||
|
|
18b211f681 | ||
|
|
b5c59cf173 | ||
|
|
436b462ffe | ||
|
|
d11cf25fc8 | ||
|
|
e8c015790f | ||
|
|
ba8fbe83e0 | ||
|
|
d56e8eaea5 | ||
|
|
3550d75634 | ||
|
|
14158f73b8 | ||
|
|
c738f02d17 | ||
|
|
e62d0df9df | ||
|
|
eeed5b4c54 | ||
|
|
8386e773ce | ||
|
|
e5656f23ce | ||
|
|
409a1aa596 | ||
|
|
57f2898451 | ||
|
|
2242fea737 | ||
|
|
e04892156c | ||
|
|
c09c55ed7a | ||
|
|
2c80b2d474 | ||
|
|
4f0a4eff6f | ||
|
|
c02bda6ea7 | ||
|
|
400032a94c | ||
|
|
6893e07619 | ||
|
|
f27038f91d | ||
|
|
07b700bc32 | ||
|
|
7ae91bcf06 | ||
|
|
56455b4c50 | ||
|
|
3b4beefb37 | ||
|
|
9ae63ba1e4 | ||
|
|
c3b11995c7 | ||
|
|
8b09e54106 | ||
|
|
237cae37ca | ||
|
|
a3c22cf074 |
@@ -2,6 +2,7 @@ import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("java")
|
||||
id("com.gradleup.shadow") version "9.0.0-beta4"
|
||||
}
|
||||
|
||||
group = "com.alttd.ctf"
|
||||
@@ -33,6 +34,10 @@ dependencies {
|
||||
// End JSON config dependencies
|
||||
// WorldBorderAPI
|
||||
compileOnly("com.github.yannicklamprecht:worldborderapi:1.210.0:dev")
|
||||
|
||||
//Database
|
||||
implementation("org.mybatis:mybatis:3.5.13")
|
||||
implementation("mysql:mysql-connector-java:8.0.33")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
@@ -43,6 +48,21 @@ tasks.jar {
|
||||
archiveFileName.set("CaptureTheFlag.jar")
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveFileName.set("CaptureTheFlag.jar")
|
||||
listOf(
|
||||
"org.apache.ibatis"
|
||||
).forEach { relocate(it, "${rootProject.group}.lib.$it") }
|
||||
}
|
||||
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
val versionPropsFile = file("version.properties")
|
||||
val versionProps = Properties().apply {
|
||||
if (versionPropsFile.exists()) {
|
||||
@@ -70,6 +90,10 @@ tasks.named("build") {
|
||||
dependsOn(incrementBuildNumber)
|
||||
}
|
||||
|
||||
tasks.named("shadowJar") {
|
||||
dependsOn(incrementBuildNumber)
|
||||
}
|
||||
|
||||
tasks.withType<Jar> {
|
||||
manifest {
|
||||
attributes(
|
||||
|
||||
@@ -4,18 +4,17 @@ import com.alttd.ctf.commands.CommandManager;
|
||||
import com.alttd.ctf.config.Config;
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.events.InventoryItemInteractionEvent;
|
||||
import com.alttd.ctf.events.OnPlayerDeath;
|
||||
import com.alttd.ctf.events.OnPlayerOnlineStatus;
|
||||
import com.alttd.ctf.events.SnowballEvent;
|
||||
import com.alttd.ctf.events.*;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.flag.FlagTryCaptureEvent;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.phases.EndedPhase;
|
||||
import com.alttd.ctf.gui.ClassSelectionGUI;
|
||||
import com.alttd.ctf.gui.GUIInventory;
|
||||
import com.alttd.ctf.gui.GUIListener;
|
||||
import com.alttd.ctf.json_config.JacksonConfig;
|
||||
import com.alttd.ctf.json_config.JsonConfigManager;
|
||||
import com.alttd.ctf.stats.PlayerStat;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -29,16 +28,18 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class Main extends JavaPlugin {
|
||||
|
||||
private GameManager gameManager = null;
|
||||
private Flag flag;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
PlayerStat.main = this;
|
||||
GUIInventory.setMain(this); // sorry
|
||||
ClassSelectionGUI.setMain(this); // sorry
|
||||
Package pkg = Main.class.getPackage();
|
||||
@@ -47,18 +48,13 @@ public class Main extends JavaPlugin {
|
||||
|
||||
reloadConfigs();
|
||||
WorldBorderApi worldBorderApi = worldBorder();
|
||||
this.gameManager = new GameManager(worldBorderApi);
|
||||
this.gameManager = new GameManager(this, worldBorderApi);
|
||||
registerTeams(); //Skipped in reloadConfig if gameManager is not created yet
|
||||
flag = new Flag(this, gameManager);
|
||||
loadPlayerStats(); //Skipped in reloadConfig if gameManager is not created yet
|
||||
Flag flag = new Flag(this, gameManager);
|
||||
new CommandManager(this, gameManager, flag, worldBorderApi);
|
||||
//Ensuring immediate respawn is on in all worlds
|
||||
log.info("Enabling immediate respawn for {}.", GameConfig.FLAG.world);
|
||||
World world = Bukkit.getWorld(GameConfig.FLAG.world);
|
||||
if (world != null) {
|
||||
world.setGameRule(GameRule.DO_IMMEDIATE_RESPAWN, true);
|
||||
} else {
|
||||
log.error("No valid flag world defined, unable to modify game rules");
|
||||
}
|
||||
enableImmediateRespawn();
|
||||
registerEvents(flag, worldBorderApi);
|
||||
}
|
||||
|
||||
@@ -68,6 +64,7 @@ public class Main extends JavaPlugin {
|
||||
GameConfig.reload(this);
|
||||
if (gameManager != null) {
|
||||
registerTeams();
|
||||
loadPlayerStats();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,13 +80,24 @@ public class Main extends JavaPlugin {
|
||||
return worldBorderApiRegisteredServiceProvider.getProvider();
|
||||
}
|
||||
|
||||
private void enableImmediateRespawn() {
|
||||
log.info("Enabling immediate respawn for {}.", GameConfig.FLAG.world);
|
||||
World world = Bukkit.getWorld(GameConfig.FLAG.world);
|
||||
if (world != null) {
|
||||
world.setGameRule(GameRule.DO_IMMEDIATE_RESPAWN, true);
|
||||
} else {
|
||||
log.error("No valid flag world defined, unable to modify game rules");
|
||||
}
|
||||
}
|
||||
|
||||
private void registerEvents(Flag flag, WorldBorderApi worldBorderApi) {
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
pluginManager.registerEvents(new SnowballEvent(gameManager), this);
|
||||
pluginManager.registerEvents(new OtherGameEvents(gameManager), this);
|
||||
pluginManager.registerEvents(new FlagTryCaptureEvent(flag), this);
|
||||
pluginManager.registerEvents(new OnPlayerDeath(gameManager, worldBorderApi, this, flag), this);
|
||||
pluginManager.registerEvents(new InventoryItemInteractionEvent(), this);
|
||||
pluginManager.registerEvents(new OnPlayerOnlineStatus(gameManager, flag), this);
|
||||
pluginManager.registerEvents(new OnPlayerOnlineStatus(gameManager, flag, worldBorderApi), this);
|
||||
pluginManager.registerEvents(new GUIListener(), this);
|
||||
}
|
||||
|
||||
@@ -119,4 +127,39 @@ public class Main extends JavaPlugin {
|
||||
teams.forEach(gameManager::registerTeam);
|
||||
}
|
||||
|
||||
private void loadPlayerStats() {
|
||||
JsonConfigManager<PlayerStat> config = new JsonConfigManager<>(JacksonConfig.configureMapper());
|
||||
List<PlayerStat> playerStats;
|
||||
|
||||
try {
|
||||
File playerStatsDirectory = new File(getDataFolder(), "player_stats");
|
||||
if (!playerStatsDirectory.exists() && !playerStatsDirectory.mkdirs()) {
|
||||
log.error("Unable to make playerStats directory at {} shutting down plugin", playerStatsDirectory.getAbsolutePath());
|
||||
}
|
||||
playerStats = config.loadConfigs(PlayerStat.class, playerStatsDirectory);
|
||||
} catch (IOException e) {
|
||||
log.error("Unable to load teams, shutting down plugin", e);
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
gameManager.setPlayerStats(playerStats);
|
||||
final JsonConfigManager<PlayerStat> jsonConfigManager = new JsonConfigManager<>(JacksonConfig.configureMapper());
|
||||
Runnable runnable = () -> {
|
||||
log.info("Saving player stats");
|
||||
File playerStatsDirectory = new File(getDataFolder(), "player_stats");
|
||||
gameManager.getPlayerStats().stream().filter(PlayerStat::isTouched).forEach(playerStat -> {
|
||||
try {
|
||||
jsonConfigManager.saveConfig(playerStat, playerStatsDirectory, playerStat.getUuid().toString());
|
||||
playerStat.setUnTouched();
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to save player stats for [{}].", playerStat.getUuid(), e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
||||
scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 1, java.util.concurrent.TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,7 @@ public class CommandManager implements CommandExecutor, TabExecutor {
|
||||
new SkipPhase(gameManager),
|
||||
new Start(gameManager, flag),
|
||||
new CreateTeam(main, gameManager),
|
||||
new HighestStat(gameManager),
|
||||
new SelectClass(gameManager, worldBorderApi),
|
||||
new Reload(main)
|
||||
);
|
||||
|
||||
@@ -58,7 +58,7 @@ public class ChangeTeam extends SubCommand {
|
||||
}
|
||||
|
||||
private void changeTeam(CommandSender commandSender, Player player, Team team) {
|
||||
Optional<Team> optionalOldTeam = gameManager.getTeam(player.getUniqueId());
|
||||
Optional<Team> optionalOldTeam = gameManager.getTeam(player);
|
||||
if (optionalOldTeam.isPresent()) {
|
||||
moveBetweenTeams(commandSender, player, team, optionalOldTeam.get());
|
||||
return;
|
||||
|
||||
@@ -12,6 +12,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -75,8 +76,8 @@ public class CreateTeam extends SubCommand {
|
||||
|
||||
int highestId = gameManager.getMaxTeamId();
|
||||
Team team = new Team(MiniMessage.miniMessage().deserialize(String.format("<color:%s>%s</color>", color, name)),
|
||||
highestId + 1, player.getLocation(), player.getLocation(), player.getLocation(), teamColor);
|
||||
|
||||
highestId + 1, player.getLocation(), player.getLocation(), player.getLocation(), teamColor,
|
||||
Material.RED_BANNER, "§c", -1L);
|
||||
return consumer.apply(team);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
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.stats.PlayerStat;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
public class HighestStat extends SubCommand {
|
||||
|
||||
private final GameManager gameManager;
|
||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
public HighestStat(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
private record HighestStatValue(Stat stat, UUID uuid, double value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(CommandSender commandSender, String[] args) {
|
||||
ArrayList<HighestStatValue> highestStatValues = new ArrayList<>();
|
||||
Collection<PlayerStat> playerStats = gameManager.getPlayerStats();
|
||||
for (Stat stat : Stat.values()) {
|
||||
Optional<PlayerStat> max = playerStats.stream().max(Comparator.comparingDouble(playerStat -> playerStat.getStat(stat)));
|
||||
if (max.isEmpty()) {
|
||||
log.warn("No max stat found for {}", stat.toString());
|
||||
continue;
|
||||
}
|
||||
PlayerStat playerStat = max.get();
|
||||
highestStatValues.add(new HighestStatValue(stat, playerStat.getUuid(), playerStat.getStat(stat)));
|
||||
}
|
||||
Component message = highestStatValues.stream().map(highestStatValue -> {
|
||||
TextComponent messageBuilder = Component.empty();
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(highestStatValue.uuid);
|
||||
Player player = offlinePlayer.getPlayer();
|
||||
if (!offlinePlayer.isOnline() || player == null) {
|
||||
messageBuilder = messageBuilder.append(miniMessage.deserialize(offlinePlayer.getName() == null ? highestStatValue.uuid.toString() : offlinePlayer.getName()));
|
||||
} else {
|
||||
messageBuilder = messageBuilder.append(player.name());
|
||||
}
|
||||
messageBuilder = messageBuilder.append(Component.text(": "));
|
||||
messageBuilder = messageBuilder.append(Component.text(highestStatValue.stat.toString()));
|
||||
messageBuilder = messageBuilder.append(Component.text(": "));
|
||||
messageBuilder = messageBuilder.append(Component.text(highestStatValue.value()));
|
||||
return messageBuilder;
|
||||
}).reduce(Component.empty(), (a, b) -> a.append(Component.newline()).append(b));
|
||||
commandSender.sendMessage(message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "higheststat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpMessage() {
|
||||
return Messages.HELP.HIGHEST_STAT;
|
||||
}
|
||||
}
|
||||
@@ -40,13 +40,15 @@ public class SelectClass extends SubCommand {
|
||||
return 0;
|
||||
}
|
||||
GamePhase gamePhase = optionalGamePhase.get();
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player.getUniqueId());
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player);
|
||||
if (optionalTeamPlayer.isEmpty()) {
|
||||
commandSender.sendRichMessage("<red>You have to be in a CTF team to select a class.</red>");
|
||||
return 0;
|
||||
}
|
||||
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
||||
if (!gamePhase.equals(GamePhase.CLASS_SELECTION) && teamPlayer.getTeam().getSpawnLocation().distance(player.getLocation()) > 5) {
|
||||
if (!teamPlayer.isDead()
|
||||
&& !gamePhase.equals(GamePhase.CLASS_SELECTION)
|
||||
&& teamPlayer.getTeam().getSpawnLocation().distance(player.getLocation()) > 10) {
|
||||
commandSender.sendRichMessage("<red>You have to be near your spawn to change classes.</red>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3,16 +3,10 @@ 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 lombok.AllArgsConstructor;
|
||||
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;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class SkipPhase extends SubCommand {
|
||||
|
||||
@@ -102,6 +102,12 @@ abstract class AbstractConfig {
|
||||
return yaml.getInt(path, yaml.getInt(path));
|
||||
}
|
||||
|
||||
public long getLong(String prefix, String path, long def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getLong(path, yaml.getLong(path));
|
||||
}
|
||||
|
||||
double getDouble(String prefix, String path, double def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
|
||||
@@ -41,4 +41,36 @@ public class Config extends AbstractConfig{
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class DISCORD {
|
||||
private static final String prefix = "discord.";
|
||||
|
||||
public static long PLAYER_SERVER_ID = 0L;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
PLAYER_SERVER_ID = config.getLong(prefix, "player-server-id", PLAYER_SERVER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class DISCORD_DATABASE {
|
||||
private static final String prefix = "discord-database.";
|
||||
|
||||
public static String HOST = "localhost";
|
||||
public static int PORT = 3306;
|
||||
public static String DATABASE = "discordLink";
|
||||
public static String USERNAME = "username";
|
||||
public static String PASSWORD = "password";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
HOST = config.getString(prefix, "host", HOST);
|
||||
DATABASE = config.getString(prefix, "database", DATABASE);
|
||||
PORT = config.getInt(prefix, "port", PORT);
|
||||
USERNAME = config.getString(prefix, "username", USERNAME);
|
||||
PASSWORD = config.getString(prefix, "password", PASSWORD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.alttd.ctf.config;
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
@@ -88,6 +90,7 @@ public class GameConfig extends AbstractConfig {
|
||||
public static double CAPTURE_RADIUS = 5;
|
||||
public static int CAPTURE_SCORE = 50;
|
||||
public static double TURN_IN_RADIUS = 3;
|
||||
public static @NotNull Material MATERIAL = Material.BLACK_BANNER;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
@@ -98,6 +101,7 @@ public class GameConfig extends AbstractConfig {
|
||||
CAPTURE_RADIUS = config.getDouble(prefix, "capture-radius", CAPTURE_RADIUS);
|
||||
CAPTURE_SCORE = config.getInt(prefix, "capture-score", CAPTURE_SCORE);
|
||||
TURN_IN_RADIUS = config.getDouble(prefix, "turn-in-radius", TURN_IN_RADIUS);
|
||||
MATERIAL = Material.valueOf(config.getString(prefix, "material", MATERIAL.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ public class Messages extends AbstractConfig {
|
||||
public static String START = "<green>Start a new game: <gold>/ctf start <time_in_minutes></gold></green>";
|
||||
public static String SELECT_CLASS = "<green>Open class selection: <gold>/ctf selectclass</gold></green>";
|
||||
public static String SKIP_PHASE = "<green>Skip the current phase: <gold>/ctf skipphase</gold></green>";
|
||||
public static String HIGHEST_STAT = "<green>Display the highest stat and the player: <gold>/ctf higheststat</gold></green>";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
@@ -39,6 +40,7 @@ public class Messages extends AbstractConfig {
|
||||
START = config.getString(prefix, "start", START);
|
||||
SELECT_CLASS = config.getString(prefix, "select-class", SELECT_CLASS);
|
||||
SKIP_PHASE = config.getString(prefix, "skip-phase", SKIP_PHASE);
|
||||
HIGHEST_STAT = config.getString(prefix, "highest-stat", HIGHEST_STAT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.alttd.ctf.database;
|
||||
|
||||
import com.alttd.ctf.config.Config;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.datasource.pooled.PooledDataSource;
|
||||
import org.apache.ibatis.mapping.Environment;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
|
||||
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Slf4j
|
||||
public class Connections {
|
||||
|
||||
private static SqlSessionFactory discordSqlSessionFactory = null;
|
||||
|
||||
public static void runDiscordQuery(Consumer<SqlSession> consumer) {
|
||||
new Thread(() -> {
|
||||
if (discordSqlSessionFactory == null) {
|
||||
discordSqlSessionFactory = createDiscordSqlSessionFactory();
|
||||
}
|
||||
|
||||
try (SqlSession session = discordSqlSessionFactory.openSession()) {
|
||||
consumer.accept(session);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to run discord query", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private static SqlSessionFactory createDiscordSqlSessionFactory() {
|
||||
PooledDataSource dataSource = new PooledDataSource();
|
||||
dataSource.setDriver("com.mysql.cj.jdbc.Driver");
|
||||
dataSource.setUrl(String.format("jdbc:mysql://%s:%d/%s", Config.DISCORD_DATABASE.HOST,
|
||||
Config.DISCORD_DATABASE.PORT, Config.DISCORD_DATABASE.DATABASE));
|
||||
dataSource.setUsername(Config.DISCORD_DATABASE.USERNAME);
|
||||
dataSource.setPassword(Config.DISCORD_DATABASE.PASSWORD);
|
||||
|
||||
Environment environment = new Environment("production", new JdbcTransactionFactory(), dataSource);
|
||||
Configuration configuration = new Configuration(environment);
|
||||
configuration.addMapper(DiscordUserMapper.class);
|
||||
|
||||
return new SqlSessionFactoryBuilder().build(configuration);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alttd.ctf.database;
|
||||
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
public interface DiscordUserMapper {
|
||||
@Select("SELECT discord_id FROM linked_accounts WHERE player_uuid = #{uuid}")
|
||||
Long getDiscordId(String uuid);
|
||||
|
||||
}
|
||||
@@ -3,6 +3,9 @@ package com.alttd.ctf.events;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.PlayerAttemptPickupItemEvent;
|
||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||
|
||||
@@ -26,4 +29,20 @@ public class InventoryItemInteractionEvent implements Listener {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (event.getSlotType() != InventoryType.SlotType.ARMOR) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryDrag(InventoryDragEvent event) {
|
||||
if (event.getRawSlots().stream().noneMatch(slot -> slot >= 5 && slot <= 8)) { // Slot numbers 5-8 for armor
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.damage.DamageEffect;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -44,7 +46,18 @@ public class OnPlayerDeath implements Listener {
|
||||
Player player = event.getPlayer();
|
||||
player.getInventory().clear();
|
||||
player.updateInventory();
|
||||
gameManager.getTeamPlayer(player)
|
||||
.ifPresent(TeamPlayer::setDead);
|
||||
flag.handleCarrierDeathOrDisconnect(player);
|
||||
|
||||
try {
|
||||
if (event.getDamageSource().getDamageType().getDamageEffect().equals(DamageEffect.FREEZING)) {
|
||||
gameManager.getTeamPlayer(player)
|
||||
.ifPresent(teamPlayer -> teamPlayer.increaseStat(Stat.DEATHS_IN_POWDERED_SNOW));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to check for death cause due to exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -55,7 +68,7 @@ public class OnPlayerDeath implements Listener {
|
||||
log.warn("Player {} died while the game wasn't running", player.getName());
|
||||
return;
|
||||
}
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player.getUniqueId());
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player);
|
||||
if (optionalTeamPlayer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package com.alttd.ctf.events;
|
||||
|
||||
import com.alttd.ctf.database.Connections;
|
||||
import com.alttd.ctf.database.DiscordUserMapper;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game_class.creation.FighterCreator;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.exceptions.PersistenceException;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -23,15 +28,53 @@ public class OnPlayerOnlineStatus implements Listener {
|
||||
|
||||
private final GameManager gameManager;
|
||||
private final Flag flag;
|
||||
private final WorldBorderApi worldBorderApi;
|
||||
|
||||
public OnPlayerOnlineStatus(GameManager gameManager, Flag flag) {
|
||||
public OnPlayerOnlineStatus(GameManager gameManager, Flag flag, WorldBorderApi worldBorderApi) {
|
||||
this.gameManager = gameManager;
|
||||
this.flag = flag;
|
||||
this.worldBorderApi = worldBorderApi;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
resetPlayer(player);
|
||||
if (!player.hasPermission("ctf.bypass")) {
|
||||
handleRunningGame(player);
|
||||
}
|
||||
handleDiscordLink(player);
|
||||
gameManager.getTeamPlayer(player).ifPresent(teamPlayer ->
|
||||
player.teleportAsync(teamPlayer.getTeam().getSpawnLocation()));
|
||||
}
|
||||
|
||||
private void handleRunningGame(Player player) {
|
||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||
if (optionalGamePhase.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
GamePhase gamePhase = optionalGamePhase.get();
|
||||
if (gamePhase.equals(GamePhase.ENDED)) {
|
||||
return;
|
||||
}
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player);
|
||||
TeamPlayer teamPlayer;
|
||||
if (optionalTeamPlayer.isEmpty()) {
|
||||
Optional<Team> min = gameManager.getTeams().stream().min(Comparator.comparingInt(team -> team.getPlayers().size()));
|
||||
if (min.isEmpty()) {
|
||||
log.error("No team found when attempting to add freshly joined player to a team");
|
||||
return;
|
||||
}
|
||||
teamPlayer = gameManager.registerPlayer(min.get(), player);
|
||||
} else {
|
||||
teamPlayer = optionalTeamPlayer.get();
|
||||
teamPlayer.getTeam().addToScoreboard(player);
|
||||
}
|
||||
FighterCreator.createFighter(teamPlayer.getTeam().getColor())
|
||||
.apply(teamPlayer, worldBorderApi, gamePhase, true);
|
||||
}
|
||||
|
||||
private void resetPlayer(Player player) {
|
||||
AttributeInstance maxHealthAttribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
||||
if (maxHealthAttribute == null) {
|
||||
log.error("Player does not have max health attribute");
|
||||
@@ -43,32 +86,29 @@ public class OnPlayerOnlineStatus implements Listener {
|
||||
flag.addPlayer(player);
|
||||
player.getInventory().clear();
|
||||
player.updateInventory();
|
||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||
if (optionalGamePhase.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
GamePhase gamePhase = optionalGamePhase.get();
|
||||
if (gamePhase.equals(GamePhase.ENDED)) {
|
||||
return;
|
||||
}
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player.getUniqueId());
|
||||
TeamPlayer teamPlayer;
|
||||
if (optionalTeamPlayer.isEmpty()) {
|
||||
Optional<Team> min = gameManager.getTeams().stream().min(Comparator.comparingInt(team -> team.getPlayers().size()));
|
||||
if (min.isEmpty()) {
|
||||
log.error("No team found when attempting to add freshly joined player to a team");
|
||||
return;
|
||||
}
|
||||
|
||||
private void handleDiscordLink(Player player) {
|
||||
Connections.runDiscordQuery(sqlSession -> {
|
||||
try {
|
||||
DiscordUserMapper mapper = sqlSession.getMapper(DiscordUserMapper.class);
|
||||
Long discordId = mapper.getDiscordId(player.getUniqueId().toString());
|
||||
if (discordId == null) {
|
||||
log.info("Player {} is not linked", player.getName());
|
||||
return;
|
||||
}
|
||||
log.info("Set discord id to {} for {}", discordId, player.getName());
|
||||
player.setDiscordId(discordId);
|
||||
} catch (PersistenceException e) {
|
||||
log.error("Failed to set discord id for {}", player.getName(), e);
|
||||
}
|
||||
teamPlayer = min.get().addPlayer(player.getUniqueId());
|
||||
} else {
|
||||
teamPlayer = optionalTeamPlayer.get();
|
||||
}
|
||||
player.teleportAsync(teamPlayer.getTeam().getSpawnLocation());
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(@NotNull PlayerQuitEvent event) {
|
||||
flag.handleCarrierDeathOrDisconnect(event.getPlayer());
|
||||
public void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
flag.handleCarrierDeathOrDisconnect(player);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.alttd.ctf.events;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Tag;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.ThrownPotion;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.PotionSplashEvent;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
public class OtherGameEvents implements Listener {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public OtherGameEvents(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
Optional<GamePhase> gamePhase = gameManager.getGamePhase();
|
||||
if (gamePhase.isEmpty() || gamePhase.get() == GamePhase.ENDED) {
|
||||
return;
|
||||
}
|
||||
if (!Tag.SNOW.isTagged(event.getBlock().getType())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
gameManager.getTeamPlayer(event.getPlayer())
|
||||
.ifPresent(teamPlayer -> teamPlayer.increaseStat(Stat.SNOW_MINED));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
Optional<GamePhase> gamePhase = gameManager.getGamePhase();
|
||||
if (gamePhase.isEmpty() || gamePhase.get() == GamePhase.ENDED) {
|
||||
return;
|
||||
}
|
||||
if (!Tag.SNOW.isTagged(event.getBlock().getType())) {
|
||||
event.setCancelled(true);
|
||||
log.warn("Player {} placed a block that wasn't snow: {}",
|
||||
event.getPlayer().getName(), event.getBlock().getType());
|
||||
return;
|
||||
}
|
||||
gameManager.getTeamPlayer(event.getPlayer())
|
||||
.ifPresent(teamPlayer -> teamPlayer.increaseStat(Stat.BLOCKS_PLACED));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPotionSplash(PotionSplashEvent event) {
|
||||
ThrownPotion thrownPotion = event.getPotion();
|
||||
if (!(thrownPotion.getShooter() instanceof Player player))
|
||||
return;
|
||||
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player);
|
||||
if (optionalTeamPlayer.isEmpty())
|
||||
return;
|
||||
|
||||
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
||||
|
||||
event.getAffectedEntities().stream()
|
||||
.filter(livingEntity -> livingEntity instanceof Player)
|
||||
.map(livingEntity -> (Player) livingEntity)
|
||||
.forEach(target -> {
|
||||
if (shouldHeal(teamPlayer, target)) {
|
||||
return;
|
||||
}
|
||||
event.setIntensity(target, 0);
|
||||
});
|
||||
|
||||
double totalHealing = thrownPotion.getEffects().stream()
|
||||
.filter(effect -> effect.getType() == PotionEffectType.INSTANT_HEALTH)
|
||||
.flatMapToDouble(effect -> event.getAffectedEntities().stream()
|
||||
.filter(livingEntity -> livingEntity instanceof Player)
|
||||
.map(livingEntity -> (Player) livingEntity)
|
||||
.mapToDouble(target -> calculateActualHealing(target, effect, event.getIntensity(target))))
|
||||
.sum();
|
||||
|
||||
teamPlayer.increaseStat(Stat.DAMAGE_HEALED, totalHealing);
|
||||
}
|
||||
|
||||
private boolean shouldHeal(TeamPlayer healer, Player target) {
|
||||
Optional<TeamPlayer> optionalTeamTarget = gameManager.getTeamPlayer(target);
|
||||
return optionalTeamTarget.isPresent() && healer.getTeam() == optionalTeamTarget.get().getTeam();
|
||||
}
|
||||
|
||||
private double calculateActualHealing(Player target, PotionEffect effect, double intensity) {
|
||||
AttributeInstance playerMaxHealth = target.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
||||
if (playerMaxHealth == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double missingHealth = playerMaxHealth.getValue() - target.getHealth();
|
||||
//Only counts healing on teammates since intensity was set to 0 for non teammates
|
||||
double potentialHealing = (effect.getAmplifier() + 1) * intensity;
|
||||
return Math.min(potentialHealing, missingHealth);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,10 +3,14 @@ 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.game_class.implementations.Mage;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Snowball;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -16,7 +20,11 @@ import org.bukkit.event.entity.ProjectileLaunchEvent;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
public class SnowballEvent implements Listener {
|
||||
@@ -34,37 +42,81 @@ public class SnowballEvent implements Listener {
|
||||
|
||||
@FunctionalInterface
|
||||
private interface SnowballThrownConsumer {
|
||||
void apply(Player shooter, TeamPlayer shooterTeamPlayer);
|
||||
void apply(Player shooter, TeamPlayer shooterTeamPlayer, Snowball snowball);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSnowballHit(EntityDamageByEntityEvent event) {
|
||||
handleSnowballHit(event, (hitPlayer, shooter, shooterTeamPlayer, snowball) -> {
|
||||
if (blockedAttack(hitPlayer, snowball)) { //Disable damage when it is blocked
|
||||
if (blockedAttack(hitPlayer, shooter, snowball)) {
|
||||
playBlockSounds(hitPlayer, shooter);
|
||||
return;
|
||||
}
|
||||
GameClass shooterClass = shooterTeamPlayer.getGameClass();
|
||||
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
|
||||
|
||||
double newHealth = hitPlayer.getHealth() - shooterClass.getDamage();
|
||||
hitPlayer.setHealth(Math.max(newHealth, 0));
|
||||
double newHealth = Math.max(hitPlayer.getHealth() - shooterClass.getDamage(), 0);
|
||||
hitPlayer.setHealth(newHealth);
|
||||
|
||||
shooterTeamPlayer.increaseStat(Stat.DAMAGE_DONE, shooterClass.getDamage());
|
||||
if (newHealth <= 0) {
|
||||
shooterTeamPlayer.increaseStat(Stat.KILLS);
|
||||
}
|
||||
log.debug("{} health was set to {} because of a snowball thrown by {}",
|
||||
hitPlayer.getName(), Math.max(newHealth, 0), shooter.getName());
|
||||
applyDamageEffects(hitPlayer, shooter);
|
||||
});
|
||||
}
|
||||
|
||||
private void playBlockSounds(Player hitPlayer, Player shooter) {
|
||||
hitPlayer.playSound(hitPlayer.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1.0f, 1.5f);
|
||||
shooter.playSound(shooter.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1.0f, 1.5f);
|
||||
}
|
||||
|
||||
private void applyDamageEffects(Player hitPlayer, Player shooter) {
|
||||
hitPlayer.getWorld().playSound(hitPlayer.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 1.0f);
|
||||
|
||||
hitPlayer.getWorld().spawnParticle(
|
||||
Particle.DAMAGE_INDICATOR,
|
||||
hitPlayer.getLocation().add(0, 1, 0), 10, 0.5, 0.5, 0.5, 0.1
|
||||
);
|
||||
|
||||
shooter.playSound(shooter.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 1.5f);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSnowballThrown(ProjectileLaunchEvent event) {
|
||||
handleSnowballThrown(event, (shooter, shooterTeamPlayer) -> {
|
||||
handleSnowballThrown(event, (shooter, shooterTeamPlayer, snowball) -> {
|
||||
GameClass shooterClass = shooterTeamPlayer.getGameClass();
|
||||
if (shooterClass == null) {
|
||||
shooter.sendRichMessage("<red>You appear to not have selected a class, please do so by going to spawn and using /ctf selectclass</red>");
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
|
||||
shooterTeamPlayer.increaseStat(Stat.SNOWBALLS_THROWN);
|
||||
if (shooterClass instanceof Mage mage) {
|
||||
mage.duplicateSnowBalls(shooter, snowball);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean blockedAttack(@NotNull Player hitPlayer, @NotNull Snowball snowball) {
|
||||
private final HashMap<UUID, LinkedList<Instant>> lastTankHits = new HashMap<>();
|
||||
|
||||
private boolean blockedAttack(@NotNull Player hitPlayer, @NotNull Player shooter, @NotNull Snowball snowball) {
|
||||
if (!hitPlayer.isBlocking()) {
|
||||
return false;
|
||||
}
|
||||
if (reachedMaxHits(hitPlayer)) {
|
||||
hitPlayer.setCooldown(Material.SHIELD, 40);
|
||||
if (hitPlayer.getInventory().getItemInMainHand().getType().equals(Material.SHIELD)) {
|
||||
hitPlayer.swingMainHand();
|
||||
} else if (hitPlayer.getInventory().getItemInOffHand().getType().equals(Material.SHIELD)) {
|
||||
hitPlayer.swingOffHand();
|
||||
}
|
||||
applyBlockBrokenEffects(hitPlayer, shooter);
|
||||
return false;
|
||||
}
|
||||
Location playerLocation = hitPlayer.getLocation();
|
||||
Vector playerFacing = playerLocation.getDirection().normalize();
|
||||
Location snowballLocation = snowball.getLocation();
|
||||
@@ -75,6 +127,37 @@ public class SnowballEvent implements Listener {
|
||||
return !(Math.toDegrees(angle) > 80); //Blocked if the angle was <= 80
|
||||
}
|
||||
|
||||
private void applyBlockBrokenEffects(@NotNull Player hitPlayer, @NotNull Player shooter) {
|
||||
hitPlayer.getWorld().playSound(hitPlayer.getLocation(), Sound.ITEM_SHIELD_BREAK, 1.0f, 1.0f);
|
||||
|
||||
Location shieldLocation = hitPlayer.getLocation()
|
||||
.add(hitPlayer.getLocation().getDirection().normalize().multiply(0.5))
|
||||
.add(0, 0.5, 0);
|
||||
hitPlayer.getWorld().spawnParticle(
|
||||
Particle.CRIT,
|
||||
shieldLocation, 10, 0.5, 0.5, 0.5, 0.1
|
||||
);
|
||||
|
||||
shooter.playSound(shooter.getLocation(), Sound.ITEM_SHIELD_BREAK, 1.0f, 1.5f);
|
||||
}
|
||||
|
||||
private boolean reachedMaxHits(@NotNull Player hitPlayer) {
|
||||
boolean reachedMaxHits = false;
|
||||
Instant now = Instant.now();
|
||||
LinkedList<Instant> lastHits = lastTankHits.getOrDefault(hitPlayer.getUniqueId(), new LinkedList<>());
|
||||
Instant cutoffTime = now.minusMillis(300);
|
||||
while (!lastHits.isEmpty() && lastHits.peek().isBefore(cutoffTime)) {
|
||||
lastHits.poll();
|
||||
}
|
||||
lastHits.addLast(now);
|
||||
if (lastHits.size() >= 3) {
|
||||
lastHits.clear();
|
||||
reachedMaxHits = true;
|
||||
}
|
||||
lastTankHits.put(hitPlayer.getUniqueId(), lastHits);
|
||||
return reachedMaxHits;
|
||||
}
|
||||
|
||||
private void handleSnowballThrown(ProjectileLaunchEvent event, SnowballThrownConsumer consumer) {
|
||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||
if (optionalGamePhase.isEmpty()) {
|
||||
@@ -89,24 +172,25 @@ public class SnowballEvent implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(shooter.getUniqueId());
|
||||
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(shooter);
|
||||
if (teamPlayer.isEmpty()) {
|
||||
log.debug("The shooter that threw a snowball was not a team player");
|
||||
return;
|
||||
}
|
||||
consumer.apply(shooter, teamPlayer.get());
|
||||
consumer.apply(shooter, teamPlayer.get(), snowball);
|
||||
}
|
||||
|
||||
private void handleSnowballHit(EntityDamageByEntityEvent event, SnowballHitConsumer consumer) {
|
||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||
if (optionalGamePhase.isEmpty()) {
|
||||
log.debug("No game is running but player was hit by snowball");
|
||||
log.debug("No game is running but player was hit");
|
||||
return;
|
||||
}
|
||||
GamePhase gamePhase = optionalGamePhase.get();
|
||||
|
||||
if (!gamePhase.equals(GamePhase.COMBAT)) {
|
||||
log.debug("Not in combat phase but player was hit by snowball");
|
||||
log.debug("Not in combat phase but player was hit, cancelling event");
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,7 +200,8 @@ public class SnowballEvent implements Listener {
|
||||
}
|
||||
|
||||
if (!(event.getDamager() instanceof org.bukkit.entity.Snowball snowball)) {
|
||||
log.debug("The player was hit by something other than a snowball");
|
||||
log.debug("The player was hit by something other than a snowball, canceling event");
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,22 +210,22 @@ public class SnowballEvent implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<TeamPlayer> teamPlayerShooter = gameManager.getTeamPlayer(shooter.getUniqueId());
|
||||
Optional<TeamPlayer> teamPlayerShooter = gameManager.getTeamPlayer(shooter);
|
||||
if (teamPlayerShooter.isEmpty()) {
|
||||
log.debug("The shooter that hit a player with a snowball was not a team player");
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<TeamPlayer> teamPlayerHit = gameManager.getTeamPlayer(hitPlayer.getUniqueId());
|
||||
Optional<TeamPlayer> teamPlayerHit = gameManager.getTeamPlayer(hitPlayer);
|
||||
if (teamPlayerHit.isEmpty()) {
|
||||
log.debug("The shooter that hit a player with a snowball was not a team player");
|
||||
return;
|
||||
}
|
||||
|
||||
if (teamPlayerHit.get().getTeam().getId() == teamPlayerShooter.get().getTeam().getId()) {
|
||||
if (teamPlayerHit.get().getTeam().getId().equals(teamPlayerShooter.get().getTeam().getId())) {
|
||||
log.debug("The shooter hit a member of their own team");
|
||||
return;
|
||||
}
|
||||
consumer.apply(hitPlayer, shooter, teamPlayerHit.get(), snowball);
|
||||
consumer.apply(hitPlayer, shooter, teamPlayerShooter.get(), snowball);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@ package com.alttd.ctf.flag;
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import org.bukkit.*;
|
||||
@@ -21,6 +22,9 @@ import org.bukkit.boss.KeyedBossBar;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -32,7 +36,6 @@ public class Flag implements Runnable {
|
||||
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
private final HashMap<Integer, Integer> teamFlagPointCount = new HashMap<>();
|
||||
private final ItemStack flagItem = new ItemStack(Material.BLACK_BANNER);
|
||||
private final BossBar bossBar = createBossBar();
|
||||
private final HashMap<Integer, Integer> wins = new HashMap<>();
|
||||
private int lastWinningTeamId = -1;
|
||||
@@ -52,6 +55,7 @@ public class Flag implements Runnable {
|
||||
throw new IllegalStateException(String.format("Tried to spawn flag in world [%s] that doesn't exist", GameConfig.FLAG.world));
|
||||
}
|
||||
this.flagLocation = new Location(world, GameConfig.FLAG.x, GameConfig.FLAG.y, GameConfig.FLAG.z);
|
||||
gameManager.getTeams().forEach(team -> team.setScore(0));
|
||||
}
|
||||
|
||||
private BossBar createBossBar() {
|
||||
@@ -77,23 +81,33 @@ public class Flag implements Runnable {
|
||||
if (flagCarrier != null) {
|
||||
return;
|
||||
}
|
||||
//TODO knockback enemies from flag location to create space for person who captured mayb short speed boost and heal?
|
||||
//TODO add de-buffs and enable buffs for others?
|
||||
player.getInventory().setItem(EquipmentSlot.HEAD, flagItem);
|
||||
Team team = teamPlayer.getTeam();
|
||||
flagLocation.getNearbyPlayers(5).forEach(nearbyPlayer -> {
|
||||
if (nearbyPlayer.getUniqueId().equals(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
Optional<TeamPlayer> nearByTeamPlayer = gameManager.getTeamPlayer(nearbyPlayer);
|
||||
if (nearByTeamPlayer.isEmpty()) {
|
||||
return;
|
||||
} else if (nearByTeamPlayer.get().getTeam().equals(team)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector direction = nearbyPlayer.getLocation().toVector().subtract(flagLocation.toVector()).normalize();
|
||||
direction.setY(0.2);
|
||||
nearbyPlayer.setVelocity(direction.multiply(5));
|
||||
});
|
||||
player.getInventory().setItem(EquipmentSlot.HEAD, new ItemStack(teamPlayer.getTeam().getFlagMaterial()));
|
||||
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(Material.AIR));
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green><player> from <team> captured the flag!", TagResolver.resolver(
|
||||
Placeholder.component("player", player.displayName()),
|
||||
Placeholder.component("team", teamPlayer.getTeam().getName())
|
||||
)));
|
||||
flagCarrier = player;
|
||||
teamFlagPointCount.clear();
|
||||
winningTeam = null;
|
||||
lastWinningTeamId = -1;
|
||||
bossBar.setProgress(0);
|
||||
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, PotionEffect.INFINITE_DURATION, 0, false, false));
|
||||
player.addPotionEffect(new PotionEffect(PotionEffectType.GLOWING, PotionEffect.INFINITE_DURATION, 0, false, false));
|
||||
notifyAboutCapture();
|
||||
resetFlag();
|
||||
}
|
||||
|
||||
public void spawnFlag() {
|
||||
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(flagItem.getType()));
|
||||
public void spawnFlag(Material material) {
|
||||
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(material));
|
||||
}
|
||||
|
||||
private void spawnFlagParticleRing() {
|
||||
@@ -146,24 +160,42 @@ public class Flag implements Runnable {
|
||||
updateDisplay();
|
||||
} else {
|
||||
winningTeam = optionalTeam.get();
|
||||
bossBar.setVisible(false);
|
||||
bossBar.setProgress(0);
|
||||
//TODO stop capture and let ppl know they can now capture the flag
|
||||
spawnFlag(winningTeam.getFlagMaterial());
|
||||
updateDisplay();
|
||||
Title title = Title.title(MiniMessage.miniMessage().deserialize("Team <team> can capture the flag",
|
||||
Placeholder.component("team", winningTeam.getName())), Component.empty());
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.showTitle(title));
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyAboutCapture() {
|
||||
Bukkit.broadcast(miniMessage.deserialize("<player> is carrying the flag for <team>!",
|
||||
Placeholder.component("player", flagCarrier.displayName()),
|
||||
Placeholder.component("team", winningTeam.getName())));
|
||||
Title capturingTeamTitle = Title.title(miniMessage.deserialize("<green><team> obtained the flag!</green>",
|
||||
Placeholder.component("team", winningTeam.getName())),
|
||||
miniMessage.deserialize("<green>protect <player> while they bring it to your base.</green>",
|
||||
Placeholder.component("player", flagCarrier.displayName())));
|
||||
Title huntingTeamTitle = Title.title(miniMessage.deserialize("<red><team> obtained the flag!</red>",
|
||||
Placeholder.component("team", winningTeam.getName())),
|
||||
miniMessage.deserialize("<red>kill <player> before they bring it to their base.</red>",
|
||||
Placeholder.component("player", flagCarrier.displayName())));
|
||||
Bukkit.getOnlinePlayers().forEach(player ->
|
||||
gameManager.getTeam(player).ifPresent(team ->
|
||||
player.showTitle(team.getId().intValue() == winningTeam.getId().intValue()
|
||||
? capturingTeamTitle : huntingTeamTitle)));
|
||||
}
|
||||
|
||||
private void spawnParticlesOnSquareBorder(Location center, double size) {
|
||||
double step = 0.2;
|
||||
World world = center.getWorld();
|
||||
Location finalCenter = center.clone().add(0, 0.5, 0);
|
||||
Bukkit.getScheduler().runTask(main, () -> {
|
||||
// Top and Bottom (Z varies, X constant)
|
||||
for (double z = -size; z <= size; z += step) {
|
||||
world.spawnParticle(Particle.FLAME, finalCenter.getX() + size, finalCenter.getY(), finalCenter.getZ() + z, 1, 0, 0, 0, 0);
|
||||
world.spawnParticle(Particle.FLAME, finalCenter.getX() - size, finalCenter.getY(), finalCenter.getZ() + z, 1, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Left and Right (X varies, Z constant)
|
||||
for (double x = -size; x <= size; x += step) {
|
||||
world.spawnParticle(Particle.FLAME, finalCenter.getX() + x, finalCenter.getY(), finalCenter.getZ() + size, 1, 0, 0, 0, 0);
|
||||
world.spawnParticle(Particle.FLAME, finalCenter.getX() + x, finalCenter.getY(), finalCenter.getZ() - size, 1, 0, 0, 0, 0);
|
||||
@@ -171,7 +203,7 @@ public class Flag implements Runnable {
|
||||
});
|
||||
}
|
||||
|
||||
LinkedList<Location> particleTrail = new LinkedList<>();
|
||||
private final LinkedList<Location> particleTrail = new LinkedList<>();
|
||||
|
||||
private void spawnTrail() {
|
||||
TeamColor color = winningTeam.getColor();
|
||||
@@ -184,11 +216,11 @@ public class Flag implements Runnable {
|
||||
|
||||
private void checkFlagCarrier() {
|
||||
if (flagCarrier.isDead() || !flagCarrier.isOnline()) {
|
||||
flagCarrier = null;
|
||||
particleTrail.clear();
|
||||
spawnFlag();
|
||||
resetFlagCarrier();
|
||||
spawnFlag(GameConfig.FLAG.MATERIAL);
|
||||
return;
|
||||
}
|
||||
|
||||
double distance = winningTeam.getFlagTurnInLocation().distance(flagCarrier.getLocation());
|
||||
if (distance > GameConfig.FLAG.TURN_IN_RADIUS) {
|
||||
Location location = flagCarrier.getLocation();
|
||||
@@ -198,37 +230,32 @@ public class Flag implements Runnable {
|
||||
spawnParticlesOnSquareBorder(winningTeam.getFlagTurnInLocation(), GameConfig.FLAG.TURN_IN_RADIUS);
|
||||
return;
|
||||
}
|
||||
notifyAboutCapture();
|
||||
spawnFlag();
|
||||
|
||||
notifyAboutTurnIn();
|
||||
spawnFlag(GameConfig.FLAG.MATERIAL);
|
||||
|
||||
wins.merge(winningTeam.getId(), 1, Integer::sum);
|
||||
winningTeam = null;
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(flagCarrier.getUniqueId());
|
||||
if (optionalTeamPlayer.isEmpty()) {
|
||||
flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null);
|
||||
} else {
|
||||
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
||||
teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer);
|
||||
flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null);
|
||||
}
|
||||
flagCarrier = null;
|
||||
particleTrail.clear();
|
||||
wins.forEach((id, score) -> gameManager.getTeam(id).ifPresent(team -> team.setScore(score)));
|
||||
|
||||
flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null);
|
||||
gameManager.getTeamPlayer(flagCarrier)
|
||||
.ifPresent(teamPlayer -> {
|
||||
teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer);
|
||||
teamPlayer.increaseStat(Stat.FLAGS_CAPTURED);
|
||||
});
|
||||
|
||||
resetFlagCarrier();
|
||||
}
|
||||
|
||||
private void notifyAboutCapture() {
|
||||
private void notifyAboutTurnIn() {
|
||||
Bukkit.broadcast(miniMessage.deserialize("<player> captured the flag for <team>!",
|
||||
Placeholder.component("player", flagCarrier.displayName()),
|
||||
Placeholder.component("team", winningTeam.getName())));
|
||||
Title capturingTeamTitle = Title.title(miniMessage.deserialize("<green><team> captured the flag!</green>",
|
||||
Placeholder.component("team", winningTeam.getName())),
|
||||
miniMessage.deserialize("<green>protect <player> while they bring it to your base.</green>",
|
||||
Placeholder.component("player", flagCarrier.displayName())));
|
||||
Title huntingTeamTitle = Title.title(miniMessage.deserialize("<red><team> captured the flag!</red>",
|
||||
Placeholder.component("team", winningTeam.getName())),
|
||||
miniMessage.deserialize("<red>kill <player> before they bring it to their base.</red>",
|
||||
Placeholder.component("player", flagCarrier.displayName())));
|
||||
Bukkit.getOnlinePlayers().forEach(player ->
|
||||
gameManager.getTeam(player.getUniqueId()).ifPresent(team ->
|
||||
player.showTitle(team.getId() == winningTeam.getId() ? capturingTeamTitle : huntingTeamTitle)));
|
||||
Title title = Title.title(Component.empty(),
|
||||
miniMessage.deserialize("<green><player> captured the flag for <team> team</green>",
|
||||
Placeholder.component("player", flagCarrier.displayName()),
|
||||
Placeholder.component("team", winningTeam.getName())));
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.showTitle(title));
|
||||
}
|
||||
|
||||
private Optional<Team> winnerExists() {
|
||||
@@ -275,7 +302,7 @@ public class Flag implements Runnable {
|
||||
*/
|
||||
private boolean updateScoreBasedOnNearbyPlayers(Collection<Player> players) {
|
||||
List<TeamPlayer> nearbyPlayers = players.stream()
|
||||
.map(player -> gameManager.getTeamPlayer(player.getUniqueId()))
|
||||
.map(gameManager::getTeamPlayer)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.toList();
|
||||
@@ -305,13 +332,15 @@ public class Flag implements Runnable {
|
||||
}
|
||||
Team winningTeam = teamLongEntry.getKey();
|
||||
|
||||
teamCounts.forEach((team, count) -> {
|
||||
teamFlagPointCount.merge(team.getId(), team.equals(winningTeam) ? 1 : -1, (oldValue, delta) -> {
|
||||
int updatedValue = oldValue + delta;
|
||||
log.debug("Set count to {} for team {}", updatedValue, team.getId());
|
||||
return Math.max(updatedValue, 0);
|
||||
});
|
||||
teamFlagPointCount.putIfAbsent(winningTeam.getId(), 0);
|
||||
teamFlagPointCount.entrySet().forEach(entry -> {
|
||||
if (entry.getKey().equals(winningTeam.getId())) {
|
||||
entry.setValue(entry.getValue() + 1);
|
||||
} else {
|
||||
entry.setValue(Math.max(0, entry.getValue() - 1));
|
||||
}
|
||||
});
|
||||
nearbyPlayers.forEach(teamPlayer -> teamPlayer.increaseStat(Stat.TIME_SPEND_CAPTURING_FLAG));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -349,17 +378,30 @@ public class Flag implements Runnable {
|
||||
return winsByTeam;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
public void resetFlagCarrier() {
|
||||
final Player player = flagCarrier;
|
||||
if (player != null) {
|
||||
Bukkit.getScheduler().runTask(main, player::clearActivePotionEffects);
|
||||
}
|
||||
flagCarrier = null;
|
||||
winningTeam = null;
|
||||
particleTrail.clear();
|
||||
}
|
||||
|
||||
public void resetFlag() {
|
||||
bossBar.setVisible(false);
|
||||
teamFlagPointCount.clear();
|
||||
bossBar.setProgress(0);
|
||||
winningTeam = null;
|
||||
flagCarrier = null;
|
||||
particleTrail.clear();
|
||||
wins.clear();
|
||||
lastWinningTeamId = -1;
|
||||
}
|
||||
|
||||
public void resetAll() {
|
||||
resetFlagCarrier();
|
||||
resetFlag();
|
||||
wins.clear();
|
||||
gameManager.getTeams().forEach(team -> team.setScore(0));
|
||||
}
|
||||
|
||||
public void handleCarrierDeathOrDisconnect(Player player) {
|
||||
if (flagCarrier == null) {
|
||||
return;
|
||||
@@ -367,10 +409,9 @@ public class Flag implements Runnable {
|
||||
if (!flagCarrier.getUniqueId().equals(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
flagCarrier = null;
|
||||
particleTrail.clear();
|
||||
spawnFlag();
|
||||
gameManager.getTeam(player.getUniqueId())
|
||||
resetFlagCarrier();
|
||||
spawnFlag(GameConfig.FLAG.MATERIAL);
|
||||
gameManager.getTeam(player)
|
||||
.ifPresentOrElse(team -> Bukkit.broadcast(MiniMessage.miniMessage()
|
||||
.deserialize("<red><team>'s flag carrier died! The flag has respawned",
|
||||
Placeholder.component("team", team.getName()))),
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -37,7 +36,7 @@ public class FlagTryCaptureEvent implements Listener {
|
||||
return;
|
||||
}
|
||||
Player player = event.getPlayer();
|
||||
Optional<TeamPlayer> teamPlayer = winningTeam.getPlayer(player.getUniqueId());
|
||||
Optional<TeamPlayer> teamPlayer = winningTeam.getPlayer(player);
|
||||
if (teamPlayer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.phases.ClassSelectionPhase;
|
||||
import com.alttd.ctf.game.phases.CombatPhase;
|
||||
import com.alttd.ctf.game.phases.EndedPhase;
|
||||
import com.alttd.ctf.game.phases.GatheringPhase;
|
||||
import com.alttd.ctf.game_class.creation.FighterCreator;
|
||||
import com.alttd.ctf.stats.PlayerStat;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
@@ -24,29 +26,34 @@ public class GameManager {
|
||||
private final HashMap<GamePhase, GamePhaseExecutor> phases;
|
||||
private RunningGame runningGame;
|
||||
private final HashMap<Integer, Team> teams = new HashMap<>();
|
||||
private final HashMap<UUID, PlayerStat> playerStats = new HashMap<>();
|
||||
|
||||
public GameManager(WorldBorderApi worldBorderApi) {
|
||||
public GameManager(Main main, WorldBorderApi worldBorderApi) {
|
||||
phases = new HashMap<>();
|
||||
phases.put(GamePhase.CLASS_SELECTION, new ClassSelectionPhase(this, FighterCreator::createFighter, worldBorderApi));
|
||||
phases.put(GamePhase.GATHERING, new GatheringPhase(this, worldBorderApi));
|
||||
phases.put(GamePhase.COMBAT, new CombatPhase());
|
||||
phases.put(GamePhase.ENDED, new EndedPhase());
|
||||
phases.put(GamePhase.ENDED, new EndedPhase(main, this));
|
||||
}
|
||||
|
||||
public Optional<GamePhase> getGamePhase() {
|
||||
return runningGame == null ? Optional.empty() : Optional.of(runningGame.getCurrentPhase());
|
||||
}
|
||||
|
||||
public void registerPlayer(Team team, Player player) {
|
||||
public TeamPlayer registerPlayer(Team team, Player player) {
|
||||
unregisterPlayer(player);
|
||||
teams.get(team.getId()).addPlayer(player.getUniqueId());
|
||||
UUID uuid = player.getUniqueId();
|
||||
PlayerStat playerStat = playerStats
|
||||
.computeIfAbsent(uuid, (ignored) -> new PlayerStat(uuid, player.getName()));
|
||||
return teams.get(team.getId()).addPlayer(player, playerStat);
|
||||
}
|
||||
|
||||
public void unregisterPlayer(Player player) {
|
||||
teams.values().forEach(team -> team.removePlayer(player.getUniqueId()));
|
||||
teams.values().forEach(team -> team.removePlayer(player));
|
||||
}
|
||||
|
||||
public void registerTeam(Team team) {
|
||||
team.registerGameManger(this);
|
||||
teams.put(team.getId(), team);
|
||||
}
|
||||
|
||||
@@ -54,17 +61,17 @@ public class GameManager {
|
||||
return teams.values();
|
||||
}
|
||||
|
||||
public Optional<Team> getTeam(@NotNull UUID uuid) {
|
||||
return getTeams().stream().filter(filterTeam -> filterTeam.getPlayer(uuid).isPresent()).findAny();
|
||||
public Optional<Team> getTeam(@NotNull Player player) {
|
||||
return getTeams().stream().filter(filterTeam -> filterTeam.getPlayer(player).isPresent()).findAny();
|
||||
}
|
||||
|
||||
public Optional<Team> getTeam(int teamId) {
|
||||
return getTeams().stream().filter(filterTeam -> filterTeam.getId() == teamId).findAny();
|
||||
}
|
||||
|
||||
public Optional<TeamPlayer> getTeamPlayer(@NotNull UUID uuid) {
|
||||
public Optional<TeamPlayer> getTeamPlayer(@NotNull Player player) {
|
||||
return getTeams().stream()
|
||||
.map(team -> team.getPlayer(uuid))
|
||||
.map(team -> team.getPlayer(player))
|
||||
.filter(Optional::isPresent)
|
||||
.findFirst()
|
||||
.orElseGet(Optional::empty);
|
||||
@@ -106,4 +113,20 @@ public class GameManager {
|
||||
}
|
||||
return runningGame.skipCurrentPhase();
|
||||
}
|
||||
|
||||
public void setPlayerStats(List<PlayerStat> playerStats) {
|
||||
this.playerStats.clear();
|
||||
playerStats.forEach(playerStat -> this.playerStats.put(playerStat.getUuid(), playerStat));
|
||||
}
|
||||
|
||||
public Collection<PlayerStat> getPlayerStats() {
|
||||
return this.playerStats.values();
|
||||
}
|
||||
|
||||
public Optional<Duration> getRemainingTime() {
|
||||
if (runningGame == null || runningGame.getCurrentPhase().equals(GamePhase.ENDED)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(runningGame.getRemainingTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.team.TeamScoreboard;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
@@ -13,6 +14,7 @@ import javax.annotation.Nullable;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
@Slf4j
|
||||
@@ -39,7 +41,6 @@ public class RunningGame implements Runnable {
|
||||
try {
|
||||
GamePhase nextPhase = (currentPhase.ordinal() + 1 < GamePhase.values().length) ? GamePhase.values()[currentPhase.ordinal() + 1] : null;
|
||||
if (phaseStartTime == null) {
|
||||
phaseStartTime = Instant.now();
|
||||
nextPhaseActions(null, currentPhase);
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ public class RunningGame implements Runnable {
|
||||
} else {
|
||||
executorService.shutdown();
|
||||
}
|
||||
TeamScoreboard.refreshScoreboard(gameManager);
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error in running game", e);
|
||||
throw new RuntimeException(e);
|
||||
@@ -58,28 +60,41 @@ public class RunningGame implements Runnable {
|
||||
}
|
||||
|
||||
private void nextPhaseActions(@Nullable GamePhase previousPhase, @NotNull GamePhase phase) {
|
||||
//TODO command to go to next phase
|
||||
this.currentPhase = phase;
|
||||
this.phaseStartTime = Instant.now();
|
||||
if (previousPhase != null) {
|
||||
gameManager.getPhaseExecutor(previousPhase).end(phase);
|
||||
}
|
||||
gameManager.getPhaseExecutor(phase).start(flag);
|
||||
if (phase.equals(GamePhase.ENDED)) {
|
||||
executorService.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastNextPhaseStartTime(GamePhase currentPhase, GamePhase nextPhase) {//TODO check how this works/what it should do
|
||||
private static final List<Integer> NOTIFY_SECONDS = List.of(45, 30, 15, 10, 5, 3, 2, 1);
|
||||
private void broadcastNextPhaseStartTime(GamePhase currentPhase, GamePhase nextPhase) {
|
||||
//Remaining time for this phase
|
||||
Duration duration = phaseDurations.get(currentPhase).minus(Duration.between(phaseStartTime, Instant.now()));
|
||||
log.debug(duration.toString());//TODO remove debug
|
||||
if ((duration.toMinutes() > 1 && (duration.toMinutes() % 15 == 0 || duration.toMinutes() <= 5)) && duration.toSecondsPart() < 2) {
|
||||
if (lastMinuteBroadcast == duration.toMinutes()) {
|
||||
return;
|
||||
}
|
||||
lastMinuteBroadcast = (int) duration.toMinutes();
|
||||
if (nextPhase.equals(GamePhase.ENDED)) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||
"<green>The game will end in <bold><red>" + duration.toMinutes() + "</red> minutes</bold></green>"));
|
||||
return;
|
||||
}
|
||||
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() < 1 && duration.toSeconds() % 15 == 0) {
|
||||
} else if (duration.toMinutes() < 1 && NOTIFY_SECONDS.contains(duration.toSecondsPart())) {
|
||||
if (nextPhase.equals(GamePhase.ENDED)) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||
"<green>The game will end in <bold><red>" + duration.toSeconds() + "</red> seconds</bold></green>"));
|
||||
return;
|
||||
}
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||
"<green>The <phase> will start in <bold><red>" + duration.toSeconds() + "</red> seconds</bold></green>",
|
||||
Placeholder.component("phase", nextPhase.getDisplayName())
|
||||
@@ -101,4 +116,9 @@ public class RunningGame implements Runnable {
|
||||
//TODO say the phase ended early?
|
||||
nextPhaseActions(currentPhase, GamePhase.ENDED);
|
||||
}
|
||||
|
||||
public Duration getRemainingTime() {
|
||||
Duration duration = phaseDurations.get(currentPhase);
|
||||
return duration.minus(Duration.between(phaseStartTime, Instant.now()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@@ -52,12 +54,14 @@ public class ClassSelectionPhase implements GamePhaseExecutor {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>Select your class with <gold>/ctf selectclass</gold></green>"));
|
||||
CircularIterator<Team> teamCircularIterator = new CircularIterator<>(gameManager.getTeams());
|
||||
if (teamCircularIterator.hasNext()) {
|
||||
Bukkit.getOnlinePlayers().stream()
|
||||
ArrayList<? extends Player> players = new ArrayList<>(Bukkit.getOnlinePlayers());
|
||||
Collections.shuffle(players);
|
||||
players.stream()
|
||||
.filter(player -> !player.hasPermission("ctf.bypass"))
|
||||
.filter(player -> gameManager.getTeamPlayer(player.getUniqueId()).isEmpty())
|
||||
.filter(player -> gameManager.getTeamPlayer(player).isEmpty())
|
||||
.forEach(player -> {
|
||||
Team team = teamCircularIterator.next();
|
||||
team.addPlayer(player.getUniqueId());
|
||||
gameManager.registerPlayer(team, player);
|
||||
player.sendRichMessage("You joined <team>!", Placeholder.component("team", team.getName()));
|
||||
});
|
||||
} else {
|
||||
@@ -68,7 +72,7 @@ public class ClassSelectionPhase implements GamePhaseExecutor {
|
||||
|
||||
private void teleportPlayersToStartingZone() {
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(player.getUniqueId());
|
||||
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(player);
|
||||
if (teamPlayer.isEmpty()) {
|
||||
log.warn("{} is not a team player when teleporting to starting zone", player.getName());
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.alttd.ctf.game.phases;
|
||||
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
@@ -15,21 +16,24 @@ public class CombatPhase implements GamePhaseExecutor {
|
||||
private ScheduledExecutorService executorService = null;
|
||||
|
||||
@Override
|
||||
public void start(Flag flag) {
|
||||
public synchronized void start(Flag flag) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>CAPTURE THE FLAG</green>"));
|
||||
flag.spawnFlag();
|
||||
if (executorService == null || !executorService.isShutdown()) {
|
||||
if (executorService != null) {
|
||||
executorService.shutdown();
|
||||
}
|
||||
flag.spawnFlag(GameConfig.FLAG.MATERIAL);
|
||||
if (executorService == null) {
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
} else if (executorService.isTerminated() || executorService.isShutdown()) {
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
} else {
|
||||
executorService.shutdown();
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
executorService.scheduleAtFixedRate(flag, 0, 1, TimeUnit.SECONDS);
|
||||
// TODO Add players to bossbar list for capture progress(or maybe only when any progress is made)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(GamePhase ignored) {
|
||||
executorService.shutdown();
|
||||
if (executorService != null) {
|
||||
executorService.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.alttd.ctf.game.phases;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import com.alttd.ctf.team.Team;
|
||||
@@ -12,9 +14,11 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -25,6 +29,13 @@ import java.util.Map;
|
||||
public class EndedPhase implements GamePhaseExecutor {
|
||||
|
||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
private final Main main;
|
||||
private final GameManager gameManager;
|
||||
|
||||
public EndedPhase(Main main, GameManager gameManager) {
|
||||
this.main = main;
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Flag flag) {
|
||||
@@ -50,9 +61,9 @@ public class EndedPhase implements GamePhaseExecutor {
|
||||
maxHealthAttribute.setBaseValue(20);
|
||||
player.setHealth(20);
|
||||
});
|
||||
flag.reset();
|
||||
flag.resetAll();
|
||||
gameManager.getTeams().forEach(team -> Bukkit.getOnlinePlayers().forEach(team::removePlayer));
|
||||
}).start();
|
||||
// TODO reset world (coreprotect) to prep for next round
|
||||
}
|
||||
|
||||
private List<Component> getWinnerMessages(HashMap<Team, Integer> wins) {
|
||||
@@ -71,18 +82,29 @@ public class EndedPhase implements GamePhaseExecutor {
|
||||
return messages;
|
||||
} else if (topTeams.size() > 1) { // Draw scenario, multiple teams have the same top score
|
||||
messages.add(miniMessage.deserialize("<yellow>It's a draw! Top teams:</yellow>"));
|
||||
topTeams.forEach(team -> {
|
||||
messages.add(miniMessage.deserialize("<team> had <score> captures.",
|
||||
Placeholder.component("team", team.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
||||
});
|
||||
topTeams.forEach(team -> messages.add(
|
||||
miniMessage.deserialize("<team> had <score> captures.",
|
||||
Placeholder.component("team", team.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore)))));
|
||||
addOtherTeamsScore(wins, highestScore, messages);
|
||||
return messages;
|
||||
} else { // Single winner
|
||||
Team winner = topTeams.getFirst();
|
||||
messages.add(miniMessage.deserialize("<green><team> has won with <score> captures!</green>",
|
||||
Placeholder.component("team", winner.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
||||
Placeholder.component("team", winner.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
||||
ConsoleCommandSender consoleSender = Bukkit.getConsoleSender();
|
||||
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
|
||||
winner.getPlayers().forEach(teamPlayer -> {
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(teamPlayer.getUuid());
|
||||
String name = offlinePlayer.getName();
|
||||
if (name == null) {
|
||||
return;
|
||||
}
|
||||
Bukkit.dispatchCommand(consoleSender, String.format("lp user %s permission settemp ctf.victory.%s true 14d", name, winner.getId()));
|
||||
});
|
||||
});
|
||||
|
||||
if (wins.size() <= 1) {
|
||||
return messages;
|
||||
}
|
||||
@@ -97,8 +119,8 @@ public class EndedPhase implements GamePhaseExecutor {
|
||||
wins.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() < winningScore)
|
||||
.forEach(entry -> messages.add(miniMessage.deserialize("<yellow><team> had <score> captures.</yellow>",
|
||||
Placeholder.component("team", entry.getKey().getName()),
|
||||
Placeholder.parsed("score", String.valueOf(entry.getValue())))));
|
||||
Placeholder.component("team", entry.getKey().getName()),
|
||||
Placeholder.parsed("score", String.valueOf(entry.getValue())))));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,11 +4,11 @@ import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@Slf4j
|
||||
public class GatheringPhase implements GamePhaseExecutor {
|
||||
@@ -36,10 +36,13 @@ public class GatheringPhase implements GamePhaseExecutor {
|
||||
log.error("Unable to update world border due to missing Flag");
|
||||
return;
|
||||
}
|
||||
gameManager.getTeams().forEach(team -> {
|
||||
team.getPlayers().forEach(player -> {
|
||||
player.resetWorldBorder(Bukkit.getPlayer(player.getUuid()), worldBorderApi, nextPhase, flag.getFlagLocation());
|
||||
});
|
||||
});
|
||||
gameManager.getTeams()
|
||||
.forEach(team -> team.getPlayers().forEach(teamPlayer -> {
|
||||
Player player = Bukkit.getPlayer(teamPlayer.getUuid());
|
||||
if (player == null || !player.isOnline()) {
|
||||
return;
|
||||
}
|
||||
teamPlayer.resetWorldBorder(player, worldBorderApi, nextPhase, flag.getFlagLocation());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.alttd.ctf.game_class;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game_class.creation.EngineerCreator;
|
||||
import com.alttd.ctf.game_class.creation.FighterCreator;
|
||||
import com.alttd.ctf.game_class.creation.TankCreator;
|
||||
import com.alttd.ctf.game_class.creation.*;
|
||||
import com.alttd.ctf.team.Team;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -14,9 +12,7 @@ public class GameClassRetrieval {
|
||||
|
||||
public static HashMap<Integer, List<GameClass>> getGameClassesForAllTeams(GameManager gameManager) {
|
||||
final HashMap<Integer, List<GameClass>> gameClasses = new HashMap<>();
|
||||
gameManager.getTeams().forEach(team -> {
|
||||
gameClasses.put(team.getId(), getGameClassesForTeam(team));
|
||||
});
|
||||
gameManager.getTeams().forEach(team -> gameClasses.put(team.getId(), getGameClassesForTeam(team)));
|
||||
return gameClasses;
|
||||
}
|
||||
|
||||
@@ -24,7 +20,9 @@ public class GameClassRetrieval {
|
||||
final List<GameClass> gameClasses = new ArrayList<>();
|
||||
gameClasses.add(FighterCreator.createFighter(team.getColor()));
|
||||
gameClasses.add(TankCreator.createTank(team.getColor()));
|
||||
gameClasses.add(TrapperCreator.createTrapper(team.getColor()));
|
||||
gameClasses.add(EngineerCreator.createEngineer(team.getColor()));
|
||||
gameClasses.add(MageCreator.createMage(team.getColor()));
|
||||
return gameClasses;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.alttd.ctf.game_class.creation;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.implementations.Engineer;
|
||||
import com.alttd.ctf.game_class.implementations.Fighter;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.alttd.ctf.game_class.creation;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.implementations.Mage;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class MageCreator {
|
||||
|
||||
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull GameClass createMage(@NotNull TeamColor teamColor) {
|
||||
return new Mage(getArmor(), getTools(teamColor), getDisplayItem(teamColor),
|
||||
20, 100, 1);
|
||||
}
|
||||
|
||||
@Contract(value = " -> new", pure = true)
|
||||
private static @NotNull @Unmodifiable List<Material> getArmor() {
|
||||
return (List.of(Material.CHAINMAIL_BOOTS, Material.AIR, Material.AIR, Material.LEATHER_HELMET));
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
private static @NotNull @Unmodifiable List<ItemStack> getTools(@NotNull TeamColor teamColor) {
|
||||
return (List.of(getShovel(teamColor)));
|
||||
}
|
||||
|
||||
private static @NotNull ItemStack getShovel(@NotNull TeamColor teamColor) {
|
||||
ItemStack shovel = new ItemStack(Material.WOODEN_SHOVEL);
|
||||
ItemMeta meta = shovel.getItemMeta();
|
||||
meta.itemName(miniMessage.deserialize(String.format("<color:%s>Snow shovel</color>", teamColor.hex())));
|
||||
meta.setUnbreakable(true);
|
||||
meta.addEnchant(Enchantment.EFFICIENCY, 1, false);
|
||||
shovel.setItemMeta(meta);
|
||||
return shovel;
|
||||
}
|
||||
|
||||
private static @NotNull ItemStack getDisplayItem(@NotNull TeamColor teamColor) {
|
||||
ItemStack itemStack = new ItemStack(Material.SNOWBALL);
|
||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
itemMeta.displayName(miniMessage.deserialize(String.format("<color:%s>Mage</color>", teamColor.hex())));
|
||||
itemMeta.lore(List.of(
|
||||
miniMessage.deserialize("<gold>The Mage can throw many snowballs at once</gold>"),
|
||||
miniMessage.deserialize("<gold>But it has a long cooldown.</gold>")
|
||||
));
|
||||
itemStack.setItemMeta(itemMeta);
|
||||
return itemStack;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.alttd.ctf.game_class.creation;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.implementations.Fighter;
|
||||
import com.alttd.ctf.game_class.implementations.Tank;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -22,7 +21,7 @@ public class TankCreator {
|
||||
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull GameClass createTank(@NotNull TeamColor teamColor) {//TODO add ability to become temp invulnerable (with some particle effects mayb?)
|
||||
public static @NotNull GameClass createTank(@NotNull TeamColor teamColor) {
|
||||
return new Tank(getArmor(), getTools(teamColor), getDisplayItem(teamColor),
|
||||
30, 7, 4);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.alttd.ctf.game_class.creation;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.implementations.Trapper;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class TrapperCreator {
|
||||
|
||||
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull GameClass createTrapper(@NotNull TeamColor teamColor) {
|
||||
return new Trapper(getArmor(), getTools(teamColor), getDisplayItem(teamColor),
|
||||
20, 5, 4);
|
||||
}
|
||||
|
||||
@Contract(value = " -> new", pure = true)
|
||||
private static @Unmodifiable List<Material> getArmor() {
|
||||
return (List.of(Material.LEATHER_BOOTS, Material.LEATHER_LEGGINGS, Material.LEATHER_CHESTPLATE, Material.AIR));
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
private static @NotNull @Unmodifiable List<ItemStack> getTools(TeamColor teamColor) {
|
||||
List<ItemStack> itemStacks = new ArrayList<>(List.of(getShovel(teamColor)));
|
||||
itemStacks.addAll(getPowderedSnow(teamColor));
|
||||
return itemStacks;
|
||||
}
|
||||
|
||||
private static @NotNull List<ItemStack> getPowderedSnow(@NotNull TeamColor teamColor) {
|
||||
ItemStack powderedSnow = new ItemStack(Material.POWDER_SNOW_BUCKET);
|
||||
ItemMeta itemMeta = powderedSnow.getItemMeta();
|
||||
itemMeta.itemName(MiniMessage.miniMessage().deserialize(
|
||||
String.format("<color:%s>Snow Trap</color>", teamColor.hex())));
|
||||
powderedSnow.setItemMeta(itemMeta);
|
||||
List<ItemStack> snowBuckets = new ArrayList<>();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
snowBuckets.add(powderedSnow.clone());
|
||||
}
|
||||
return snowBuckets;
|
||||
}
|
||||
|
||||
private static @NotNull ItemStack getShovel(@NotNull TeamColor teamColor) {
|
||||
ItemStack shovel = new ItemStack(Material.WOODEN_SHOVEL);
|
||||
ItemMeta meta = shovel.getItemMeta();
|
||||
meta.setUnbreakable(true);
|
||||
meta.itemName(miniMessage.deserialize(String.format("<color:%s>Snow shovel</color>", teamColor.hex())));
|
||||
shovel.setItemMeta(meta);
|
||||
return shovel;
|
||||
}
|
||||
|
||||
private static @NotNull ItemStack getDisplayItem(@NotNull TeamColor teamColor) {
|
||||
ItemStack itemStack = new ItemStack(Material.POWDER_SNOW_BUCKET);
|
||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
itemMeta.displayName(miniMessage.deserialize(String.format("<color:%s>Trapper</color>", teamColor.hex())));
|
||||
itemMeta.lore(List.of(
|
||||
miniMessage.deserialize("<gold>The Trapper is a normal class, but it has a trick up it's sleeve</gold>"),
|
||||
miniMessage.deserialize("<gold>They can place their powdered snow to trap the enemy team.</gold>")
|
||||
));
|
||||
itemStack.setItemMeta(itemMeta);
|
||||
return itemStack;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.alttd.ctf.game_class.implementations;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.alttd.ctf.game_class.implementations;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Snowball;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class Mage extends GameClass {
|
||||
public Mage(@NotNull List<Material> armor, @NotNull List<ItemStack> tools, @NotNull ItemStack displayItem,
|
||||
double health, int throwTickSpeed, int damage) {
|
||||
super(armor, tools, displayItem, health, throwTickSpeed, damage);
|
||||
}
|
||||
|
||||
public void duplicateSnowBalls(Player shooter, Snowball snowball) {
|
||||
Location location = snowball.getLocation();
|
||||
PlayerInventory inventory = shooter.getInventory();
|
||||
ItemStack itemStack = inventory.getItemInMainHand();
|
||||
if (itemStack.getType() != Material.SNOWBALL) {
|
||||
ItemStack itemInOffHand = inventory.getItemInOffHand();
|
||||
if (itemInOffHand.getType() != Material.SNOWBALL) {
|
||||
log.warn("Unable to find snowballs in main/off hand");
|
||||
return;
|
||||
}
|
||||
itemStack = itemInOffHand;
|
||||
}
|
||||
if (itemStack.getAmount() <= 1) {
|
||||
return;
|
||||
}
|
||||
duplicate(shooter, location, snowball.getVelocity(), itemStack.getAmount() - 1);
|
||||
itemStack.setAmount(0);
|
||||
}
|
||||
|
||||
private void duplicate(@NotNull Player shooter, @NotNull Location location, @NotNull Vector velocity, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
double offsetX = (Math.random() - 0.5) * 2;
|
||||
double offsetY = (Math.random() - 0.5) * 2;
|
||||
double offsetZ = (Math.random() - 0.5) * 2;
|
||||
|
||||
Location newLocation = location.clone().add(offsetX, offsetY, offsetZ);
|
||||
Snowball snowball = location.getWorld().spawn(newLocation, Snowball.class);
|
||||
|
||||
snowball.setShooter(shooter);
|
||||
|
||||
double velocityChangeX = (Math.random() - 0.5) * 0.5;
|
||||
double velocityChangeY = (Math.random() - 0.5) * 0.5;
|
||||
double velocityChangeZ = (Math.random() - 0.5) * 0.5;
|
||||
|
||||
Vector newVelocity = velocity.clone().add(new Vector(velocityChangeX, velocityChangeY, velocityChangeZ));
|
||||
|
||||
snowball.setVelocity(newVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.alttd.ctf.game_class.implementations;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Trapper extends GameClass {
|
||||
public Trapper(@NotNull List<Material> armor, @NotNull List<ItemStack> tools, @NotNull ItemStack displayItem,
|
||||
double health, int throwTickSpeed, int damage) {
|
||||
super(armor, tools, displayItem, health, throwTickSpeed, damage);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package com.alttd.ctf.gui;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.Merchant;
|
||||
import org.bukkit.inventory.MerchantInventory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.alttd.ctf.stats;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@NoArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public final class PlayerStat {
|
||||
@JsonIgnore
|
||||
public static Main main;
|
||||
|
||||
@JsonIgnore
|
||||
private boolean touched;
|
||||
@JsonIgnore
|
||||
private static final CommandSender commandSender = Bukkit.getConsoleSender();
|
||||
@JsonIgnore
|
||||
private static boolean someoneDiedInPowderedSnow = false;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private UUID uuid;
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private String inGameName;
|
||||
|
||||
private boolean completedGame = false;
|
||||
private int flagsCaptured = 0;
|
||||
private int kills = 0;
|
||||
private double damageDone = 0;
|
||||
private double damageHealed = 0;
|
||||
private int snowMined = 0;
|
||||
private int blocksPlaced = 0;
|
||||
private int snowballsThrown = 0;
|
||||
private long timeSpendCapturingFlag = 0;
|
||||
private int deathsInPowderedSnow = 0;
|
||||
|
||||
public void increaseStat(Stat stat) throws IllegalArgumentException {
|
||||
switch (stat) {
|
||||
case COMPLETED_GAME -> {
|
||||
if (!completedGame) {
|
||||
Bukkit.getScheduler().runTask(main, () -> {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
|
||||
Bukkit.dispatchCommand(commandSender, String.format("lp user %s permission settemp ctf.game.completed true 14d", inGameName));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
completedGame = true;
|
||||
}
|
||||
case FLAGS_CAPTURED -> flagsCaptured++;
|
||||
case KILLS -> kills++;
|
||||
case SNOW_MINED -> snowMined++;
|
||||
case BLOCKS_PLACED -> blocksPlaced++;
|
||||
case SNOWBALLS_THROWN -> snowballsThrown++;
|
||||
case TIME_SPEND_CAPTURING_FLAG -> timeSpendCapturingFlag++;
|
||||
case DEATHS_IN_POWDERED_SNOW -> {
|
||||
if (someoneDiedInPowderedSnow) {
|
||||
deathsInPowderedSnow++;
|
||||
return;
|
||||
}
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
|
||||
if (!offlinePlayer.isOnline()) {
|
||||
return;
|
||||
}
|
||||
Player player = offlinePlayer.getPlayer();
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<red><player> will receive a consolation prize for being the first to die in powdered snow.</red>",
|
||||
Placeholder.component("player",player.displayName())));
|
||||
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
|
||||
Bukkit.dispatchCommand(commandSender, String.format("lp user %s permission settemp ctf.game.first_powdered_snow_death true 14d", inGameName));
|
||||
});
|
||||
|
||||
someoneDiedInPowderedSnow = true;
|
||||
}
|
||||
case DAMAGE_DONE, DAMAGE_HEALED -> throw new IllegalArgumentException(String.format("%s requires a number", stat.name()));
|
||||
}
|
||||
touched = true;
|
||||
}
|
||||
|
||||
public void increaseStat(Stat stat, double value) throws IllegalArgumentException {
|
||||
switch (stat) {
|
||||
case DAMAGE_DONE -> damageDone += value;
|
||||
case DAMAGE_HEALED -> damageHealed += value;
|
||||
default -> throw new IllegalArgumentException(String.format("%s cannot be passed with a number", stat.name()));
|
||||
}
|
||||
touched = true;
|
||||
}
|
||||
|
||||
public void setUnTouched() {
|
||||
touched = false;
|
||||
}
|
||||
|
||||
public double getStat(Stat stat) {
|
||||
return switch (stat) {
|
||||
case COMPLETED_GAME -> completedGame ? 1 : 0;
|
||||
case FLAGS_CAPTURED -> flagsCaptured;
|
||||
case KILLS -> kills;
|
||||
case DAMAGE_DONE -> damageDone;
|
||||
case DAMAGE_HEALED -> damageHealed;
|
||||
case SNOW_MINED -> snowMined;
|
||||
case BLOCKS_PLACED -> blocksPlaced;
|
||||
case SNOWBALLS_THROWN -> snowballsThrown;
|
||||
case TIME_SPEND_CAPTURING_FLAG -> timeSpendCapturingFlag;
|
||||
case DEATHS_IN_POWDERED_SNOW -> deathsInPowderedSnow;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.alttd.ctf.stats;
|
||||
|
||||
public enum Stat {
|
||||
COMPLETED_GAME,
|
||||
FLAGS_CAPTURED,
|
||||
KILLS,
|
||||
DAMAGE_DONE,
|
||||
DAMAGE_HEALED,
|
||||
SNOW_MINED,
|
||||
BLOCKS_PLACED,
|
||||
SNOWBALLS_THROWN,
|
||||
TIME_SPEND_CAPTURING_FLAG,
|
||||
DEATHS_IN_POWDERED_SNOW
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.alttd.ctf.team;
|
||||
|
||||
import com.alttd.ctf.config.Config;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.galaxy.discord.Bot;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Slf4j
|
||||
public class DiscordTeam {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public DiscordTeam(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Validate {
|
||||
void apply(Role role, Member member);
|
||||
}
|
||||
|
||||
private void validateModifyRole(Team team, Player player, Validate consumer) {
|
||||
if (!player.isDiscordLinked()) {
|
||||
log.info("{} is not discord linked", player.getName());
|
||||
return;
|
||||
}
|
||||
if (team.getDiscordRole() == -1) {
|
||||
log.warn("No valid discord role set for team {}", team.getId());
|
||||
return;
|
||||
}
|
||||
Bot bot = Bukkit.getBot();
|
||||
if (bot == null) {
|
||||
log.error("Unable to get bot");
|
||||
return;
|
||||
}
|
||||
JDA jda = bot.getJDA();
|
||||
if (jda == null) {
|
||||
log.error("Unable to get JDA");
|
||||
return;
|
||||
}
|
||||
Guild guild = jda.getGuildById(Config.DISCORD.PLAYER_SERVER_ID);
|
||||
if (guild == null) {
|
||||
log.warn("Unable to get guild {}", Config.DISCORD.PLAYER_SERVER_ID);
|
||||
return;
|
||||
}
|
||||
Role role = guild.getRoleById(team.getDiscordRole());
|
||||
if (role == null) {
|
||||
log.warn("Unable to get role {} for team {}", team.getDiscordRole(), team.getId());
|
||||
return;
|
||||
}
|
||||
Member nullableMember = guild.getMemberById(player.getDiscordID());
|
||||
if (nullableMember == null) {
|
||||
guild.retrieveMemberById(player.getDiscordID()).queue(member -> consumer.apply(role, member));
|
||||
} else {
|
||||
consumer.apply(role, nullableMember);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeRole(Team team, @NotNull Player player) {
|
||||
validateModifyRole(team, player, (role, member) -> {
|
||||
if (member.isOwner()) {
|
||||
log.info("Unable to remove team role from server owner");
|
||||
return;
|
||||
}
|
||||
member.getGuild().removeRoleFromMember(member, role).queue();
|
||||
});
|
||||
}
|
||||
|
||||
public void addRole(Team team, Player player) {
|
||||
validateModifyRole(team, player, (role, member) -> {
|
||||
if (member.isOwner()) {
|
||||
log.info("Unable to add team role to server owner");
|
||||
return;
|
||||
}
|
||||
gameManager.getTeams().forEach(otherTeam ->
|
||||
member.getRoles().stream()
|
||||
.filter(otherRole -> otherRole.getIdLong() == otherTeam.getDiscordRole())
|
||||
.forEach(otherRole -> member.getGuild().removeRoleFromMember(member, otherRole).queue()));
|
||||
member.getGuild().addRoleToMember(member, role).queue();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,28 @@
|
||||
package com.alttd.ctf.team;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.stats.PlayerStat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
public class Team {
|
||||
|
||||
@JsonIgnore
|
||||
private final TeamScoreboard scoreboard = new TeamScoreboard(this);
|
||||
@JsonIgnore
|
||||
private final HashMap<UUID, TeamPlayer> players = new HashMap<>();
|
||||
@JsonProperty("name")
|
||||
@@ -25,7 +31,8 @@ public class Team {
|
||||
private Component name;
|
||||
@JsonProperty("id")
|
||||
@Getter
|
||||
private int id;
|
||||
@NotNull
|
||||
private Integer id;
|
||||
@JsonProperty("spawnLocation")
|
||||
@NotNull
|
||||
@Getter
|
||||
@@ -42,15 +49,42 @@ public class Team {
|
||||
@NotNull
|
||||
@Getter
|
||||
private TeamColor color;
|
||||
@JsonProperty("flagMaterial")
|
||||
@NotNull
|
||||
@Getter
|
||||
private Material flagMaterial;
|
||||
@JsonProperty("legacyTeamColor")
|
||||
@NotNull
|
||||
@Getter
|
||||
private String legacyTeamColor;
|
||||
@JsonProperty("discordRole")
|
||||
@Getter
|
||||
@NotNull
|
||||
private Long discordRole;
|
||||
@JsonIgnore
|
||||
private DiscordTeam discordTeam;
|
||||
|
||||
public TeamPlayer addPlayer(UUID uuid) {
|
||||
TeamPlayer teamPlayer = new TeamPlayer(uuid, this);
|
||||
public void registerGameManger(GameManager gameManager) {
|
||||
discordTeam = new DiscordTeam(gameManager);
|
||||
}
|
||||
|
||||
public TeamPlayer addPlayer(Player player, PlayerStat playerStat) {
|
||||
removeFromScoreboard(player);
|
||||
UUID uuid = player.getUniqueId();
|
||||
TeamPlayer teamPlayer = new TeamPlayer(player, this, playerStat);
|
||||
players.put(uuid, teamPlayer);
|
||||
log.debug("Added player with uuid {} to team with id {}", uuid, id);
|
||||
addToScoreboard(player);
|
||||
if (discordTeam != null) {
|
||||
discordTeam.addRole(this, player);
|
||||
} else {
|
||||
log.warn("No discord team set for {} to add role", id);
|
||||
}
|
||||
log.debug("Added player {} to team with id {}", player.getName(), id);
|
||||
return teamPlayer;
|
||||
}
|
||||
|
||||
public Optional<TeamPlayer> getPlayer(@NotNull UUID uuid) {
|
||||
public Optional<TeamPlayer> getPlayer(@NotNull Player player) {
|
||||
UUID uuid = player.getUniqueId();
|
||||
if (!players.containsKey(uuid))
|
||||
return Optional.empty();
|
||||
return Optional.of(players.get(uuid));
|
||||
@@ -60,11 +94,30 @@ public class Team {
|
||||
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);
|
||||
public void removePlayer(@NotNull Player player) {
|
||||
removeFromScoreboard(player);
|
||||
TeamPlayer remove = players.remove(player.getUniqueId());
|
||||
if (remove == null) {
|
||||
return;
|
||||
}
|
||||
if (discordTeam != null) {
|
||||
discordTeam.removeRole(this, player);
|
||||
} else {
|
||||
log.warn("No discord team set for {} to remove role", id);
|
||||
}
|
||||
log.debug("Removed player {} from team with id {}", player.getName(), id);
|
||||
}
|
||||
|
||||
public void addToScoreboard(Player player) {
|
||||
scoreboard.addToScoreboard(player);
|
||||
}
|
||||
|
||||
private void removeFromScoreboard(Player player) {
|
||||
scoreboard.removeFromScoreboard(player);
|
||||
}
|
||||
|
||||
public void setScore(int newScore) {
|
||||
scoreboard.setScore(newScore);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.GameClassRetrieval;
|
||||
import com.alttd.ctf.gui.ClassSelectionGUI;
|
||||
import com.alttd.ctf.stats.PlayerStat;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -21,14 +23,17 @@ import java.util.*;
|
||||
@Getter
|
||||
public class TeamPlayer {
|
||||
|
||||
private final PlayerStat playerStat;
|
||||
private final UUID uuid;
|
||||
private final Team team;
|
||||
@Setter
|
||||
private GameClass gameClass;
|
||||
private boolean isDead = false;
|
||||
|
||||
protected TeamPlayer(UUID uuid, Team team) {
|
||||
this.uuid = uuid;
|
||||
protected TeamPlayer(Player player, Team team, PlayerStat playerStat) {
|
||||
this.uuid = player.getUniqueId();
|
||||
this.team = team;
|
||||
this.playerStat = playerStat;
|
||||
}
|
||||
|
||||
public void respawn(@NotNull Player player, @NotNull WorldBorderApi worldBorderApi, @NotNull GamePhase gamePhase) {
|
||||
@@ -48,9 +53,17 @@ public class TeamPlayer {
|
||||
}
|
||||
player.teleportAsync(spawnLocation).thenAcceptAsync(unused ->
|
||||
resetWorldBorder(player, worldBorderApi, gamePhase, worldBorderCenter));
|
||||
isDead = false;
|
||||
}
|
||||
|
||||
public void setDead() {
|
||||
isDead = true;
|
||||
}
|
||||
|
||||
public void resetWorldBorder(Player player, WorldBorderApi worldBorderApi, GamePhase gamePhase, Location worldBorderCenter) {
|
||||
if (player == null || !player.isOnline()) {
|
||||
return;
|
||||
}
|
||||
WorldBorderSettings worldBorderSettings = GameConfig.WORLD_BORDER.getGAME_PHASE_WORLD_BORDER().get(gamePhase);
|
||||
if (worldBorderSettings == null) {
|
||||
throw new IllegalStateException("All phases need to have world border settings");
|
||||
@@ -78,4 +91,12 @@ public class TeamPlayer {
|
||||
public int hashCode() {
|
||||
return Objects.hash(uuid);
|
||||
}
|
||||
|
||||
public void increaseStat(Stat stat) {
|
||||
playerStat.increaseStat(stat);
|
||||
}
|
||||
|
||||
public void increaseStat(Stat stat, double amount) {
|
||||
playerStat.increaseStat(stat, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.alttd.ctf.team;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import net.kyori.adventure.text.format.*;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scoreboard.*;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class TeamScoreboard {
|
||||
|
||||
private final static Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
private final Team team;
|
||||
|
||||
public TeamScoreboard(Team team) {
|
||||
this.team = team;
|
||||
}
|
||||
|
||||
public void addToScoreboard(Player player) {
|
||||
org.bukkit.scoreboard.Team scoreboardTeam = scoreboard.getTeam("ctf_" + team.getId());
|
||||
if (scoreboardTeam == null) {
|
||||
scoreboardTeam = scoreboard.registerNewTeam("ctf_" + team.getId());
|
||||
scoreboardTeam.displayName(team.getName());
|
||||
TeamColor color = team.getColor();
|
||||
NamedTextColor namedTextColor = NamedTextColor.nearestTo(TextColor.color(color.r(), color.g(), color.b()));
|
||||
scoreboardTeam.color(namedTextColor);
|
||||
}
|
||||
scoreboardTeam.addPlayer(player);
|
||||
player.setScoreboard(scoreboard);
|
||||
}
|
||||
|
||||
protected void removeFromScoreboard(Player player) {
|
||||
scoreboard.getTeams().stream()
|
||||
.filter(team -> team.getName().startsWith("ctf_"))
|
||||
.filter(team -> team.hasPlayer(player))
|
||||
.forEach(team -> team.removePlayer(player));
|
||||
}
|
||||
|
||||
protected void setScore(int newScore) {
|
||||
Objective objective = getOrCreateObjective();
|
||||
Score score = objective.getScore(team.getLegacyTeamColor() +
|
||||
PlainTextComponentSerializer.plainText().serialize(team.getName()));
|
||||
score.setScore(newScore);
|
||||
}
|
||||
|
||||
private static Objective getOrCreateObjective() {
|
||||
Objective objective = scoreboard.getObjective("teamScores");
|
||||
if (objective == null) {
|
||||
objective = scoreboard.registerNewObjective("teamScores", Criteria.DUMMY,
|
||||
MiniMessage.miniMessage().deserialize("<gold>CTF score</gold>"));
|
||||
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
|
||||
}
|
||||
return objective;
|
||||
}
|
||||
|
||||
private record PhaseScore(GamePhase gamePhase, Score score) {}
|
||||
private static PhaseScore phaseScore = null;
|
||||
private static void updateTime(GamePhase gamePhase, Duration duration) {
|
||||
if (phaseScore == null) {
|
||||
phaseScore = new PhaseScore(gamePhase, getOrCreateObjective()
|
||||
.getScore(ChatColor.GREEN + PlainTextComponentSerializer.plainText().serialize(gamePhase.getDisplayName())));
|
||||
} else if (phaseScore.gamePhase() != gamePhase) {
|
||||
phaseScore.score.resetScore();
|
||||
phaseScore = new PhaseScore(gamePhase, getOrCreateObjective()
|
||||
.getScore(ChatColor.GREEN + PlainTextComponentSerializer.plainText().serialize(gamePhase.getDisplayName())));
|
||||
}
|
||||
phaseScore.score.setScore(duration.toMinutesPart() == 0 ? duration.toSecondsPart() : duration.toMinutesPart());
|
||||
}
|
||||
|
||||
public static void refreshScoreboard(GameManager gameManager) {
|
||||
gameManager.getGamePhase().ifPresent(gamePhase ->
|
||||
gameManager.getRemainingTime().ifPresent(duration ->
|
||||
updateTime(gamePhase, duration)));
|
||||
// TODO if game is active show time + game phase
|
||||
// if someone has flag show that
|
||||
// show how many of each team in circle?
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
#Tue Feb 11 21:46:26 CET 2025
|
||||
buildNumber=39
|
||||
#Sun Mar 02 00:42:30 CET 2025
|
||||
buildNumber=112
|
||||
version=0.1
|
||||
|
||||
Reference in New Issue
Block a user