Compare commits
85
Commits
6f77135426
...
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 | ||
|
|
4cfc8f1572 | ||
|
|
9563e9641f | ||
|
|
aad63f174e | ||
|
|
66c24d852d | ||
|
|
7acf39b600 | ||
|
|
6f99402d16 | ||
|
|
5f2fb8fe0a | ||
|
|
dbcbb10079 | ||
|
|
7070165a94 | ||
|
|
8411db57a1 | ||
|
|
498ed774a4 | ||
|
|
e95dabccac | ||
|
|
6ae2563d16 | ||
|
|
91ee3cf8f9 | ||
|
|
4af509eb5f | ||
|
|
4f8a7a0416 | ||
|
|
55b88a950d | ||
|
|
172f9a29a2 | ||
|
|
8718ca0918 | ||
|
|
728e8b7486 | ||
|
|
74cf3589c0 | ||
|
|
d7432c9b89 | ||
|
|
7ed4c20d29 | ||
|
|
33578027d3 | ||
|
|
a52efb9dbb | ||
|
|
72dc1af903 | ||
|
|
e93580bea9 | ||
|
|
53de0fe217 | ||
|
|
b8afe209bc | ||
|
|
be7b508667 | ||
|
|
e5077b40c5 | ||
|
|
6bb4673b08 | ||
|
|
a175d1829b | ||
|
|
a4f96297c2 | ||
|
|
9eccb130e5 | ||
|
|
4aa6a0d512 |
@@ -5,7 +5,6 @@
|
|||||||
- [x] Stores all players
|
- [x] Stores all players
|
||||||
- [x] Allows starting, restarting, and ending game
|
- [x] Allows starting, restarting, and ending game
|
||||||
- [ ] Limit playing area (x, y, and z) (could be done through WorldGuard)
|
- [ ] Limit playing area (x, y, and z) (could be done through WorldGuard)
|
||||||
- [ ] Stores location for ppl to watch/wait in or handles putting them in gmsp
|
|
||||||
- [ ] Team
|
- [ ] Team
|
||||||
- [x] Stores members
|
- [x] Stores members
|
||||||
- [ ] Stores score
|
- [ ] Stores score
|
||||||
@@ -28,8 +27,8 @@
|
|||||||
- [x] Allows Team member to select class
|
- [x] Allows Team member to select class
|
||||||
- [ ] Classes
|
- [ ] Classes
|
||||||
- [x] Fighter: lower health, higher dmg/throwing speed
|
- [x] Fighter: lower health, higher dmg/throwing speed
|
||||||
- [ ] Tank: Has shield, invincibility effect, slower (short + long cooldown)?
|
- [x] Tank: Has shield, invincibility effect, slower (short + long cooldown)?
|
||||||
- [ ] Engineer: Better shovel, lower health, lower dmg, (can drop snowballs to team members?) store snow at base, can build?
|
- [x] Engineer: Better shovel, lower health, lower dmg, (can drop snowballs to team members?) store snow at base, can build?
|
||||||
- [ ] HARD Mage: Drops snowballs in area (casting cost)
|
- [ ] HARD Mage: Drops snowballs in area (casting cost)
|
||||||
- [ ] HARD Scout: low dmg, high speed, (invisible sometimes?) can see flag carrier through walls or maybe see their path in particles?
|
- [ ] HARD Scout: low dmg, high speed, (invisible sometimes?) can see flag carrier through walls or maybe see their path in particles?
|
||||||
- [ ] Game events
|
- [ ] Game events
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import java.util.Properties
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("java")
|
id("java")
|
||||||
|
id("com.gradleup.shadow") version "9.0.0-beta4"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "com.alttd.ctf"
|
group = "com.alttd.ctf"
|
||||||
@@ -31,6 +32,12 @@ dependencies {
|
|||||||
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2")
|
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2")
|
||||||
implementation("com.fasterxml.jackson.core:jackson-annotations:2.15.2")
|
implementation("com.fasterxml.jackson.core:jackson-annotations:2.15.2")
|
||||||
// End JSON config 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 {
|
tasks.test {
|
||||||
@@ -41,6 +48,21 @@ tasks.jar {
|
|||||||
archiveFileName.set("CaptureTheFlag.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 versionPropsFile = file("version.properties")
|
||||||
val versionProps = Properties().apply {
|
val versionProps = Properties().apply {
|
||||||
if (versionPropsFile.exists()) {
|
if (versionPropsFile.exists()) {
|
||||||
@@ -68,6 +90,10 @@ tasks.named("build") {
|
|||||||
dependsOn(incrementBuildNumber)
|
dependsOn(incrementBuildNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.named("shadowJar") {
|
||||||
|
dependsOn(incrementBuildNumber)
|
||||||
|
}
|
||||||
|
|
||||||
tasks.withType<Jar> {
|
tasks.withType<Jar> {
|
||||||
manifest {
|
manifest {
|
||||||
attributes(
|
attributes(
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ dependencyResolutionManagement {
|
|||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://repo.destro.xyz/snapshots") // Altitude - Galaxy
|
maven("https://repo.destro.xyz/snapshots") // Altitude - Galaxy
|
||||||
|
maven { // WorldBorderAPI
|
||||||
|
name = "eldonexus"
|
||||||
|
url = uri("https://eldonexus.de/repository/maven-releases/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,55 +4,58 @@ import com.alttd.ctf.commands.CommandManager;
|
|||||||
import com.alttd.ctf.config.Config;
|
import com.alttd.ctf.config.Config;
|
||||||
import com.alttd.ctf.config.GameConfig;
|
import com.alttd.ctf.config.GameConfig;
|
||||||
import com.alttd.ctf.config.Messages;
|
import com.alttd.ctf.config.Messages;
|
||||||
import com.alttd.ctf.events.InventoryItemInteractionEvent;
|
import com.alttd.ctf.events.*;
|
||||||
import com.alttd.ctf.events.OnPlayerDeath;
|
|
||||||
import com.alttd.ctf.events.OnPlayerJoin;
|
|
||||||
import com.alttd.ctf.events.SnowballEvent;
|
|
||||||
import com.alttd.ctf.flag.Flag;
|
import com.alttd.ctf.flag.Flag;
|
||||||
import com.alttd.ctf.flag.FlagTryCaptureEvent;
|
import com.alttd.ctf.flag.FlagTryCaptureEvent;
|
||||||
import com.alttd.ctf.game.GameManager;
|
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.gui.GUIListener;
|
||||||
import com.alttd.ctf.json_config.JacksonConfig;
|
import com.alttd.ctf.json_config.JacksonConfig;
|
||||||
import com.alttd.ctf.json_config.JsonConfigManager;
|
import com.alttd.ctf.json_config.JsonConfigManager;
|
||||||
|
import com.alttd.ctf.stats.PlayerStat;
|
||||||
import com.alttd.ctf.team.Team;
|
import com.alttd.ctf.team.Team;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.GameRule;
|
import org.bukkit.GameRule;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
|
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Main extends JavaPlugin {
|
public class Main extends JavaPlugin {
|
||||||
|
|
||||||
private GameManager gameManager = null;
|
private GameManager gameManager = null;
|
||||||
private Flag flag;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
PlayerStat.main = this;
|
||||||
|
GUIInventory.setMain(this); // sorry
|
||||||
|
ClassSelectionGUI.setMain(this); // sorry
|
||||||
Package pkg = Main.class.getPackage();
|
Package pkg = Main.class.getPackage();
|
||||||
String version = pkg.getImplementationVersion();
|
String version = pkg.getImplementationVersion();
|
||||||
log.info("Plugin enabled, version {}", version);
|
log.info("Plugin enabled, version {}", version);
|
||||||
|
|
||||||
reloadConfigs();
|
reloadConfigs();
|
||||||
this.gameManager = new GameManager();
|
WorldBorderApi worldBorderApi = worldBorder();
|
||||||
|
this.gameManager = new GameManager(this, worldBorderApi);
|
||||||
registerTeams(); //Skipped in reloadConfig if gameManager is not created yet
|
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
|
||||||
CommandManager commandManager = new CommandManager(this, gameManager, flag);
|
Flag flag = new Flag(this, gameManager);
|
||||||
|
new CommandManager(this, gameManager, flag, worldBorderApi);
|
||||||
//Ensuring immediate respawn is on in all worlds
|
//Ensuring immediate respawn is on in all worlds
|
||||||
log.info("Enabling immediate respawn for {}.", Config.FLAG.world);
|
enableImmediateRespawn();
|
||||||
World world = Bukkit.getWorld(Config.FLAG.world);
|
registerEvents(flag, worldBorderApi);
|
||||||
if (world != null) {
|
|
||||||
world.setGameRule(GameRule.DO_IMMEDIATE_RESPAWN, true);
|
|
||||||
} else {
|
|
||||||
log.error("No valid flag world defined, unable to modify game rules");
|
|
||||||
}
|
|
||||||
registerEvents(flag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadConfigs() {
|
public void reloadConfigs() {
|
||||||
@@ -61,17 +64,40 @@ public class Main extends JavaPlugin {
|
|||||||
GameConfig.reload(this);
|
GameConfig.reload(this);
|
||||||
if (gameManager != null) {
|
if (gameManager != null) {
|
||||||
registerTeams();
|
registerTeams();
|
||||||
|
loadPlayerStats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerEvents(Flag flag) {
|
private WorldBorderApi worldBorder() {
|
||||||
|
RegisteredServiceProvider<WorldBorderApi> worldBorderApiRegisteredServiceProvider = getServer().getServicesManager().getRegistration(WorldBorderApi.class);
|
||||||
|
|
||||||
|
if (worldBorderApiRegisteredServiceProvider == null) {
|
||||||
|
log.error("WorldBorder API not found, disabling plugin");
|
||||||
|
getServer().getPluginManager().disablePlugin(this);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 pluginManager = getServer().getPluginManager();
|
||||||
//TODO add event for player joining and clear their inv
|
|
||||||
pluginManager.registerEvents(new SnowballEvent(gameManager), this);
|
pluginManager.registerEvents(new SnowballEvent(gameManager), this);
|
||||||
|
pluginManager.registerEvents(new OtherGameEvents(gameManager), this);
|
||||||
pluginManager.registerEvents(new FlagTryCaptureEvent(flag), this);
|
pluginManager.registerEvents(new FlagTryCaptureEvent(flag), this);
|
||||||
pluginManager.registerEvents(new OnPlayerDeath(gameManager), this);
|
pluginManager.registerEvents(new OnPlayerDeath(gameManager, worldBorderApi, this, flag), this);
|
||||||
pluginManager.registerEvents(new InventoryItemInteractionEvent(), this);
|
pluginManager.registerEvents(new InventoryItemInteractionEvent(), this);
|
||||||
pluginManager.registerEvents(new OnPlayerJoin(gameManager, flag), this);
|
pluginManager.registerEvents(new OnPlayerOnlineStatus(gameManager, flag, worldBorderApi), this);
|
||||||
pluginManager.registerEvents(new GUIListener(), this);
|
pluginManager.registerEvents(new GUIListener(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,4 +127,39 @@ public class Main extends JavaPlugin {
|
|||||||
teams.forEach(gameManager::registerTeam);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.alttd.ctf.commands.subcommands.*;
|
|||||||
import com.alttd.ctf.config.Messages;
|
import com.alttd.ctf.config.Messages;
|
||||||
import com.alttd.ctf.flag.Flag;
|
import com.alttd.ctf.flag.Flag;
|
||||||
import com.alttd.ctf.game.GameManager;
|
import com.alttd.ctf.game.GameManager;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
@@ -22,7 +23,7 @@ import java.util.stream.Collectors;
|
|||||||
public class CommandManager implements CommandExecutor, TabExecutor {
|
public class CommandManager implements CommandExecutor, TabExecutor {
|
||||||
private final List<SubCommand> subCommands;
|
private final List<SubCommand> subCommands;
|
||||||
|
|
||||||
public CommandManager(Main main, GameManager gameManager, Flag flag) {
|
public CommandManager(Main main, GameManager gameManager, Flag flag, WorldBorderApi worldBorderApi) {
|
||||||
PluginCommand command = main.getCommand("ctf");
|
PluginCommand command = main.getCommand("ctf");
|
||||||
if (command == null) {
|
if (command == null) {
|
||||||
subCommands = null;
|
subCommands = null;
|
||||||
@@ -34,9 +35,11 @@ public class CommandManager implements CommandExecutor, TabExecutor {
|
|||||||
|
|
||||||
subCommands = Arrays.asList(
|
subCommands = Arrays.asList(
|
||||||
new ChangeTeam(gameManager),
|
new ChangeTeam(gameManager),
|
||||||
|
new SkipPhase(gameManager),
|
||||||
new Start(gameManager, flag),
|
new Start(gameManager, flag),
|
||||||
new CreateTeam(main, gameManager),
|
new CreateTeam(main, gameManager),
|
||||||
new SelectClass(gameManager),
|
new HighestStat(gameManager),
|
||||||
|
new SelectClass(gameManager, worldBorderApi),
|
||||||
new Reload(main)
|
new Reload(main)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class ChangeTeam extends SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void changeTeam(CommandSender commandSender, Player player, Team team) {
|
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()) {
|
if (optionalOldTeam.isPresent()) {
|
||||||
moveBetweenTeams(commandSender, player, team, optionalOldTeam.get());
|
moveBetweenTeams(commandSender, player, team, optionalOldTeam.get());
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
@@ -75,8 +76,8 @@ public class CreateTeam extends SubCommand {
|
|||||||
|
|
||||||
int highestId = gameManager.getMaxTeamId();
|
int highestId = gameManager.getMaxTeamId();
|
||||||
Team team = new Team(MiniMessage.miniMessage().deserialize(String.format("<color:%s>%s</color>", color, name)),
|
Team team = new Team(MiniMessage.miniMessage().deserialize(String.format("<color:%s>%s</color>", color, name)),
|
||||||
highestId + 1, player.getLocation(), player.getLocation(), teamColor);
|
highestId + 1, player.getLocation(), player.getLocation(), player.getLocation(), teamColor,
|
||||||
|
Material.RED_BANNER, "§c", -1L);
|
||||||
return consumer.apply(team);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,15 @@ package com.alttd.ctf.commands.subcommands;
|
|||||||
import com.alttd.ctf.commands.SubCommand;
|
import com.alttd.ctf.commands.SubCommand;
|
||||||
import com.alttd.ctf.config.Messages;
|
import com.alttd.ctf.config.Messages;
|
||||||
import com.alttd.ctf.game.GameManager;
|
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.GameClass;
|
||||||
import com.alttd.ctf.game_class.creation.FighterCreator;
|
import com.alttd.ctf.game_class.GameClassRetrieval;
|
||||||
import com.alttd.ctf.gui.ClassSelectionGUI;
|
import com.alttd.ctf.gui.ClassSelectionGUI;
|
||||||
import com.alttd.ctf.team.TeamPlayer;
|
import com.alttd.ctf.team.TeamPlayer;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -19,13 +20,12 @@ public class SelectClass extends SubCommand {
|
|||||||
|
|
||||||
private final GameManager gameManager;
|
private final GameManager gameManager;
|
||||||
private final HashMap<Integer, List<GameClass>> gameClasses;
|
private final HashMap<Integer, List<GameClass>> gameClasses;
|
||||||
|
private final WorldBorderApi worldBorderApi;
|
||||||
|
|
||||||
public SelectClass(GameManager gameManager) {
|
public SelectClass(GameManager gameManager, WorldBorderApi worldBorderApi) {
|
||||||
this.gameManager = gameManager;
|
this.gameManager = gameManager;
|
||||||
this.gameClasses = new HashMap<>();
|
this.worldBorderApi = worldBorderApi;
|
||||||
gameManager.getTeams().forEach(team -> {
|
gameClasses = GameClassRetrieval.getGameClassesForAllTeams(gameManager);
|
||||||
gameClasses.computeIfAbsent(team.getId(), teamId -> new ArrayList<>()).add(FighterCreator.createFighter(team.getColor()));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -34,21 +34,25 @@ public class SelectClass extends SubCommand {
|
|||||||
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_ONLY);
|
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_ONLY);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (gameManager.getGamePhase().isEmpty()) {
|
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||||
|
if (optionalGamePhase.isEmpty()) {
|
||||||
commandSender.sendRichMessage("<red>CTF has to be running to select a class.</red>");
|
commandSender.sendRichMessage("<red>CTF has to be running to select a class.</red>");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player.getUniqueId());
|
GamePhase gamePhase = optionalGamePhase.get();
|
||||||
|
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player);
|
||||||
if (optionalTeamPlayer.isEmpty()) {
|
if (optionalTeamPlayer.isEmpty()) {
|
||||||
commandSender.sendRichMessage("<red>You have to be in a CTF team to select a class.</red>");
|
commandSender.sendRichMessage("<red>You have to be in a CTF team to select a class.</red>");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
||||||
if (teamPlayer.getTeam().getSpawnLocation().distance(player.getLocation()) > 15) {
|
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>");
|
commandSender.sendRichMessage("<red>You have to be near your spawn to change classes.</red>");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
new ClassSelectionGUI(gameClasses.get(teamPlayer.getTeam().getId()), teamPlayer)
|
new ClassSelectionGUI(gameClasses.get(teamPlayer.getTeam().getId()), teamPlayer, worldBorderApi, gamePhase)
|
||||||
.open(player);
|
.open(player);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
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 lombok.AllArgsConstructor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SkipPhase extends SubCommand {
|
||||||
|
|
||||||
|
private final GameManager gameManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onCommand(CommandSender commandSender, String[] args) {
|
||||||
|
if (!gameManager.skipPhase()) {
|
||||||
|
commandSender.sendRichMessage("<red>The phase was not skipped because there is no running game or there is no next phase</red>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
commandSender.sendRichMessage("<green>The current phase was skipped!</green>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "skipphase";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpMessage() {
|
||||||
|
return Messages.HELP.SKIP_PHASE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,6 +102,12 @@ abstract class AbstractConfig {
|
|||||||
return yaml.getInt(path, yaml.getInt(path));
|
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) {
|
double getDouble(String prefix, String path, double def) {
|
||||||
path = prefix + path;
|
path = prefix + path;
|
||||||
yaml.addDefault(path, def);
|
yaml.addDefault(path, def);
|
||||||
|
|||||||
@@ -41,21 +41,36 @@ public class Config extends AbstractConfig{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class FLAG {
|
@SuppressWarnings("unused")
|
||||||
private static final String prefix = "flag.";
|
public static class DISCORD {
|
||||||
|
private static final String prefix = "discord.";
|
||||||
|
|
||||||
public static String world = "world";
|
public static long PLAYER_SERVER_ID = 0L;
|
||||||
public static double x = 0;
|
|
||||||
public static double y = 0;
|
|
||||||
public static double z = 0;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static void load() {
|
private static void load() {
|
||||||
world = config.getString(prefix, "world", world);
|
PLAYER_SERVER_ID = config.getLong(prefix, "player-server-id", PLAYER_SERVER_ID);
|
||||||
x = config.getDouble(prefix, "x", x);
|
|
||||||
y = config.getDouble(prefix, "y", y);
|
|
||||||
z = config.getDouble(prefix, "z", z);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@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,12 +3,14 @@ package com.alttd.ctf.config;
|
|||||||
import com.alttd.ctf.Main;
|
import com.alttd.ctf.Main;
|
||||||
import com.alttd.ctf.game.GamePhase;
|
import com.alttd.ctf.game.GamePhase;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GameConfig extends AbstractConfig{
|
public class GameConfig extends AbstractConfig {
|
||||||
|
|
||||||
static GameConfig config;
|
static GameConfig config;
|
||||||
|
|
||||||
@@ -45,4 +47,73 @@ public class GameConfig extends AbstractConfig{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static class WORLD_BORDER {
|
||||||
|
private static final String prefix = "world-border.";
|
||||||
|
|
||||||
|
public static double DEFAULT_SIZE = 140;
|
||||||
|
private static final HashMap<GamePhase, WorldBorderSettings> GAME_PHASE_WORLD_BORDER = new HashMap<>();
|
||||||
|
|
||||||
|
public static HashMap<GamePhase, WorldBorderSettings> getGAME_PHASE_WORLD_BORDER() {
|
||||||
|
return new HashMap<>(GAME_PHASE_WORLD_BORDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static void load() {
|
||||||
|
GAME_PHASE_WORLD_BORDER.clear();
|
||||||
|
for (GamePhase phase : GamePhase.values()) {
|
||||||
|
String stringType = config.getString(prefix, phase.name().toLowerCase() + ".type", WorldBorderType.PLAYER.name());
|
||||||
|
double size = config.getDouble(prefix, phase.name().toLowerCase() + ".size", 10);
|
||||||
|
|
||||||
|
WorldBorderType worldBorderType;
|
||||||
|
try {
|
||||||
|
worldBorderType = WorldBorderType.valueOf(stringType);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.error("Invalid world border type [{}] in game phase world border config", stringType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
GAME_PHASE_WORLD_BORDER.put(phase, new WorldBorderSettings(worldBorderType, size));
|
||||||
|
log.debug("Set {} phase world border type to {} blocks", phase.name(), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_SIZE = config.getDouble(prefix, "default-size", DEFAULT_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FLAG {
|
||||||
|
private static final String prefix = "flag.";
|
||||||
|
|
||||||
|
public static String world = "world";
|
||||||
|
public static double x = 0;
|
||||||
|
public static double y = 0;
|
||||||
|
public static double z = 0;
|
||||||
|
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() {
|
||||||
|
world = config.getString(prefix, "world", world);
|
||||||
|
x = config.getDouble(prefix, "x", x);
|
||||||
|
y = config.getDouble(prefix, "y", y);
|
||||||
|
z = config.getDouble(prefix, "z", z);
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RESPAWN {
|
||||||
|
private static final String prefix = "respawn.";
|
||||||
|
|
||||||
|
public static int TIME = 10;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static void load() {
|
||||||
|
TIME = config.getInt(prefix, "time", TIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class Messages extends AbstractConfig {
|
|||||||
static Messages config;
|
static Messages config;
|
||||||
|
|
||||||
Messages(Main main) {
|
Messages(Main main) {
|
||||||
super(main, "config.yml");
|
super(main, "messages.yml");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void reload(Main main) {
|
public static void reload(Main main) {
|
||||||
@@ -27,6 +27,8 @@ public class Messages extends AbstractConfig {
|
|||||||
public static String CREATE_TEAM = "<green>Create a team: <gold>/ctf createteam <team_name> <hex_color></gold></green>";
|
public static String CREATE_TEAM = "<green>Create a team: <gold>/ctf createteam <team_name> <hex_color></gold></green>";
|
||||||
public static String START = "<green>Start a new game: <gold>/ctf start <time_in_minutes></gold></green>";
|
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 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")
|
@SuppressWarnings("unused")
|
||||||
private static void load() {
|
private static void load() {
|
||||||
@@ -37,6 +39,8 @@ public class Messages extends AbstractConfig {
|
|||||||
CREATE_TEAM = config.getString(prefix, "create-team", CREATE_TEAM);
|
CREATE_TEAM = config.getString(prefix, "create-team", CREATE_TEAM);
|
||||||
START = config.getString(prefix, "start", START);
|
START = config.getString(prefix, "start", START);
|
||||||
SELECT_CLASS = config.getString(prefix, "select-class", SELECT_CLASS);
|
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,4 @@
|
|||||||
|
package com.alttd.ctf.config;
|
||||||
|
|
||||||
|
public record WorldBorderSettings(WorldBorderType type, double size) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.alttd.ctf.config;
|
||||||
|
|
||||||
|
public enum WorldBorderType {
|
||||||
|
FLAG,
|
||||||
|
PLAYER
|
||||||
|
}
|
||||||
@@ -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.Material;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
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.PlayerAttemptPickupItemEvent;
|
||||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||||
|
|
||||||
@@ -26,4 +29,20 @@ public class InventoryItemInteractionEvent implements Listener {
|
|||||||
event.setCancelled(true);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,39 @@
|
|||||||
package com.alttd.ctf.events;
|
package com.alttd.ctf.events;
|
||||||
|
|
||||||
|
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.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.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class OnPlayerDeath implements Listener {
|
public class OnPlayerDeath implements Listener {
|
||||||
|
|
||||||
private final GameManager gameManager;
|
private final GameManager gameManager;
|
||||||
|
private final WorldBorderApi worldBorderApi;
|
||||||
|
private final Main main;
|
||||||
|
private final Flag flag;
|
||||||
|
|
||||||
public OnPlayerDeath(GameManager gameManager) {
|
public OnPlayerDeath(GameManager gameManager, WorldBorderApi worldBorderApi, Main main, Flag flag) {
|
||||||
this.gameManager = gameManager;
|
this.gameManager = gameManager;
|
||||||
|
this.worldBorderApi = worldBorderApi;
|
||||||
|
this.main = main;
|
||||||
|
this.flag = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
@@ -19,11 +41,43 @@ public class OnPlayerDeath implements Listener {
|
|||||||
if (gameManager.getGamePhase().isEmpty()) {
|
if (gameManager.getGamePhase().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.deathMessage(null);
|
event.deathMessage(Component.empty());
|
||||||
event.setShouldDropExperience(false);
|
event.setShouldDropExperience(false);
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
player.getInventory().clear();
|
player.getInventory().clear();
|
||||||
player.updateInventory();
|
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
|
||||||
|
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
Optional<GamePhase> gamePhase = gameManager.getGamePhase();
|
||||||
|
if (gamePhase.isEmpty()) {
|
||||||
|
log.warn("Player {} died while the game wasn't running", player.getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player);
|
||||||
|
if (optionalTeamPlayer.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
||||||
|
event.setRespawnLocation(player.getWorld().getSpawnLocation());
|
||||||
|
player.sendRichMessage("<red>You died</red><nl><green>You will respawn in <seconds> seconds.</green>",
|
||||||
|
Placeholder.component("nl", Component.newline()),
|
||||||
|
Placeholder.parsed("seconds", String.valueOf(GameConfig.RESPAWN.TIME)));
|
||||||
|
Bukkit.getScheduler().runTaskLater(main, () -> teamPlayer.getGameClass().apply(teamPlayer, worldBorderApi, gamePhase.get(), true), GameConfig.RESPAWN.TIME * 20L);//10 x 20 ticks aka 10 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.alttd.ctf.events;
|
|
||||||
|
|
||||||
import com.alttd.ctf.flag.Flag;
|
|
||||||
import com.alttd.ctf.game.GameManager;
|
|
||||||
import com.alttd.ctf.game.GamePhase;
|
|
||||||
import com.alttd.ctf.team.Team;
|
|
||||||
import com.alttd.ctf.team.TeamPlayer;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.bukkit.attribute.Attribute;
|
|
||||||
import org.bukkit.attribute.AttributeInstance;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class OnPlayerJoin implements Listener {
|
|
||||||
|
|
||||||
private final GameManager gameManager;
|
|
||||||
private final Flag flag;
|
|
||||||
|
|
||||||
public OnPlayerJoin(GameManager gameManager, Flag flag) {
|
|
||||||
this.gameManager = gameManager;
|
|
||||||
this.flag = flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
|
||||||
Player player = event.getPlayer();
|
|
||||||
AttributeInstance maxHealthAttribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
|
||||||
if (maxHealthAttribute == null) {
|
|
||||||
log.error("Player does not have max health attribute");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
maxHealthAttribute.setBaseValue(20);
|
|
||||||
player.setHealth(20);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
teamPlayer = min.get().addPlayer(player.getUniqueId());
|
|
||||||
} else {
|
|
||||||
teamPlayer = optionalTeamPlayer.get();
|
|
||||||
}
|
|
||||||
player.teleportAsync(teamPlayer.getTeam().getSpawnLocation());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
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;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class OnPlayerOnlineStatus implements Listener {
|
||||||
|
|
||||||
|
private final GameManager gameManager;
|
||||||
|
private final Flag flag;
|
||||||
|
private final WorldBorderApi worldBorderApi;
|
||||||
|
|
||||||
|
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");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
maxHealthAttribute.setBaseValue(20);
|
||||||
|
player.setHealth(20);
|
||||||
|
|
||||||
|
flag.addPlayer(player);
|
||||||
|
player.getInventory().clear();
|
||||||
|
player.updateInventory();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
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,17 +3,28 @@ package com.alttd.ctf.events;
|
|||||||
import com.alttd.ctf.game.GameManager;
|
import com.alttd.ctf.game.GameManager;
|
||||||
import com.alttd.ctf.game.GamePhase;
|
import com.alttd.ctf.game.GamePhase;
|
||||||
import com.alttd.ctf.game_class.GameClass;
|
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 com.alttd.ctf.team.TeamPlayer;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.entity.Snowball;
|
import org.bukkit.entity.Snowball;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
import org.bukkit.event.entity.ProjectileLaunchEvent;
|
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.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SnowballEvent implements Listener {
|
public class SnowballEvent implements Listener {
|
||||||
@@ -26,35 +37,127 @@ public class SnowballEvent implements Listener {
|
|||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
private interface SnowballHitConsumer {
|
private interface SnowballHitConsumer {
|
||||||
void apply(Player hitPlayer, Player shooter, TeamPlayer shooterTeamPlayer);
|
void apply(Player hitPlayer, Player shooter, TeamPlayer shooterTeamPlayer, Snowball snowball);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
private interface SnowballThrownConsumer {
|
private interface SnowballThrownConsumer {
|
||||||
void apply(Player shooter, TeamPlayer shooterTeamPlayer);
|
void apply(Player shooter, TeamPlayer shooterTeamPlayer, Snowball snowball);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onSnowballHit(EntityDamageByEntityEvent event) {
|
public void onSnowballHit(EntityDamageByEntityEvent event) {
|
||||||
handleSnowballHit(event, (hitPlayer, shooter, shooterTeamPlayer) -> {
|
handleSnowballHit(event, (hitPlayer, shooter, shooterTeamPlayer, snowball) -> {
|
||||||
|
if (blockedAttack(hitPlayer, shooter, snowball)) {
|
||||||
|
playBlockSounds(hitPlayer, shooter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
GameClass shooterClass = shooterTeamPlayer.getGameClass();
|
GameClass shooterClass = shooterTeamPlayer.getGameClass();
|
||||||
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
|
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
|
||||||
|
|
||||||
double newHealth = hitPlayer.getHealth() - shooterClass.getDamage();
|
double newHealth = Math.max(hitPlayer.getHealth() - shooterClass.getDamage(), 0);
|
||||||
hitPlayer.setHealth(Math.max(newHealth, 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 {}",
|
log.debug("{} health was set to {} because of a snowball thrown by {}",
|
||||||
hitPlayer.getName(), Math.max(newHealth, 0), shooter.getName());
|
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
|
@EventHandler
|
||||||
public void onSnowballHitBySnowball(ProjectileLaunchEvent event) {
|
public void onSnowballThrown(ProjectileLaunchEvent event) {
|
||||||
handleSnowballThrown(event, (shooter, shooterTeamPlayer) -> {
|
handleSnowballThrown(event, (shooter, shooterTeamPlayer, snowball) -> {
|
||||||
GameClass shooterClass = shooterTeamPlayer.getGameClass();
|
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());
|
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
|
||||||
|
shooterTeamPlayer.increaseStat(Stat.SNOWBALLS_THROWN);
|
||||||
|
if (shooterClass instanceof Mage mage) {
|
||||||
|
mage.duplicateSnowBalls(shooter, 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();
|
||||||
|
Vector impactDirection = snowballLocation.toVector().subtract(playerLocation.toVector()).normalize();
|
||||||
|
|
||||||
|
double angle = playerFacing.angle(impactDirection);
|
||||||
|
|
||||||
|
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) {
|
private void handleSnowballThrown(ProjectileLaunchEvent event, SnowballThrownConsumer consumer) {
|
||||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||||
if (optionalGamePhase.isEmpty()) {
|
if (optionalGamePhase.isEmpty()) {
|
||||||
@@ -69,24 +172,25 @@ public class SnowballEvent implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(shooter.getUniqueId());
|
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(shooter);
|
||||||
if (teamPlayer.isEmpty()) {
|
if (teamPlayer.isEmpty()) {
|
||||||
log.debug("The shooter that threw a snowball was not a team player");
|
log.debug("The shooter that threw a snowball was not a team player");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
consumer.apply(shooter, teamPlayer.get());
|
consumer.apply(shooter, teamPlayer.get(), snowball);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSnowballHit(EntityDamageByEntityEvent event, SnowballHitConsumer consumer) {
|
private void handleSnowballHit(EntityDamageByEntityEvent event, SnowballHitConsumer consumer) {
|
||||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||||
if (optionalGamePhase.isEmpty()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
GamePhase gamePhase = optionalGamePhase.get();
|
GamePhase gamePhase = optionalGamePhase.get();
|
||||||
|
|
||||||
if (!gamePhase.equals(GamePhase.COMBAT)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +200,8 @@ public class SnowballEvent implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(event.getDamager() instanceof org.bukkit.entity.Snowball snowball)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,11 +210,22 @@ public class SnowballEvent implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(shooter.getUniqueId());
|
Optional<TeamPlayer> teamPlayerShooter = gameManager.getTeamPlayer(shooter);
|
||||||
if (teamPlayer.isEmpty()) {
|
if (teamPlayerShooter.isEmpty()) {
|
||||||
log.debug("The shooter that hit a player with a snowball was not a team player");
|
log.debug("The shooter that hit a player with a snowball was not a team player");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
consumer.apply(hitPlayer, shooter, teamPlayer.get());
|
|
||||||
|
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().equals(teamPlayerShooter.get().getTeam().getId())) {
|
||||||
|
log.debug("The shooter hit a member of their own team");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
consumer.apply(hitPlayer, shooter, teamPlayerShooter.get(), snowball);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
package com.alttd.ctf.flag;
|
package com.alttd.ctf.flag;
|
||||||
|
|
||||||
import com.alttd.ctf.Main;
|
import com.alttd.ctf.Main;
|
||||||
import com.alttd.ctf.config.Config;
|
import com.alttd.ctf.config.GameConfig;
|
||||||
import com.alttd.ctf.game.GameManager;
|
import com.alttd.ctf.game.GameManager;
|
||||||
|
import com.alttd.ctf.stats.Stat;
|
||||||
import com.alttd.ctf.team.Team;
|
import com.alttd.ctf.team.Team;
|
||||||
|
import com.alttd.ctf.team.TeamColor;
|
||||||
import com.alttd.ctf.team.TeamPlayer;
|
import com.alttd.ctf.team.TeamPlayer;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import net.kyori.adventure.title.Title;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.boss.BarColor;
|
import org.bukkit.boss.BarColor;
|
||||||
import org.bukkit.boss.BarStyle;
|
import org.bukkit.boss.BarStyle;
|
||||||
@@ -19,30 +22,42 @@ import org.bukkit.boss.KeyedBossBar;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.EquipmentSlot;
|
import org.bukkit.inventory.EquipmentSlot;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.potion.PotionEffect;
|
||||||
import org.bukkit.scheduler.BukkitScheduler;
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class Flag implements Runnable {
|
public class Flag implements Runnable {
|
||||||
|
|
||||||
|
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||||
|
|
||||||
private final HashMap<Integer, Integer> teamFlagPointCount = new HashMap<>();
|
private final HashMap<Integer, Integer> teamFlagPointCount = new HashMap<>();
|
||||||
private final ItemStack flagItem = new ItemStack(Material.CYAN_BANNER);
|
|
||||||
private final BossBar bossBar = createBossBar();
|
private final BossBar bossBar = createBossBar();
|
||||||
private final HashMap<Integer, Integer> wins = new HashMap<>();
|
private final HashMap<Integer, Integer> wins = new HashMap<>();
|
||||||
private int lastWinningTeamId = -1;
|
private int lastWinningTeamId = -1;
|
||||||
private Location flagLocation;
|
@Getter
|
||||||
|
private final Location flagLocation;
|
||||||
private Team winningTeam;
|
private Team winningTeam;
|
||||||
private Player flagCarrier; //TODO check for player disconnects?
|
private Player flagCarrier;
|
||||||
|
|
||||||
private final Main main;
|
private final Main main;
|
||||||
private final GameManager gameManager;
|
private final GameManager gameManager;
|
||||||
|
|
||||||
|
public Flag(Main main, GameManager gameManager) {
|
||||||
|
this.main = main;
|
||||||
|
this.gameManager = gameManager;
|
||||||
|
World world = Bukkit.getWorld(GameConfig.FLAG.world);
|
||||||
|
if (world == null) {
|
||||||
|
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() {
|
private BossBar createBossBar() {
|
||||||
NamespacedKey namespacedKey = NamespacedKey.fromString("ctf_flag", main);
|
NamespacedKey namespacedKey = NamespacedKey.fromString("ctf_flag", main);
|
||||||
if (namespacedKey == null) {
|
if (namespacedKey == null) {
|
||||||
@@ -58,10 +73,6 @@ public class Flag implements Runnable {
|
|||||||
return captureProgress;
|
return captureProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Location getFlagLocation() {
|
|
||||||
return flagLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addPlayer(Player player) {
|
public void addPlayer(Player player) {
|
||||||
bossBar.addPlayer(player);
|
bossBar.addPlayer(player);
|
||||||
}
|
}
|
||||||
@@ -70,26 +81,57 @@ public class Flag implements Runnable {
|
|||||||
if (flagCarrier != null) {
|
if (flagCarrier != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//TODO knockback enemies from flag location to create space for person who captured mayb short speed boost and heal?
|
Team team = teamPlayer.getTeam();
|
||||||
//TODO add de-buffs and enable buffs for others?
|
flagLocation.getNearbyPlayers(5).forEach(nearbyPlayer -> {
|
||||||
player.getInventory().setItem(EquipmentSlot.HEAD, flagItem);
|
if (nearbyPlayer.getUniqueId().equals(player.getUniqueId())) {
|
||||||
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(Material.AIR));
|
return;
|
||||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green><player> from <team> captured the flag!", TagResolver.resolver(
|
}
|
||||||
Placeholder.component("player", player.displayName()),
|
Optional<TeamPlayer> nearByTeamPlayer = gameManager.getTeamPlayer(nearbyPlayer);
|
||||||
Placeholder.component("team", teamPlayer.getTeam().getName())
|
if (nearByTeamPlayer.isEmpty()) {
|
||||||
)));
|
return;
|
||||||
flagCarrier = player;
|
} else if (nearByTeamPlayer.get().getTeam().equals(team)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void spawnFlag() {
|
Vector direction = nearbyPlayer.getLocation().toVector().subtract(flagLocation.toVector()).normalize();
|
||||||
//TODO disable flag capture for a minute or so, and maybe have it slowly drop down?
|
direction.setY(0.2);
|
||||||
World world = Bukkit.getWorld(Config.FLAG.world);
|
nearbyPlayer.setVelocity(direction.multiply(5));
|
||||||
if (world == null) {
|
});
|
||||||
throw new IllegalStateException(String.format("Tried to spawn flag in world [%s] that doesn't exist", Config.FLAG.world));
|
player.getInventory().setItem(EquipmentSlot.HEAD, new ItemStack(teamPlayer.getTeam().getFlagMaterial()));
|
||||||
|
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(Material.AIR));
|
||||||
|
flagCarrier = player;
|
||||||
|
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(Material material) {
|
||||||
|
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnFlagParticleRing() {
|
||||||
|
Location center = flagLocation.clone();
|
||||||
|
World world = center.getWorld();
|
||||||
|
double radius = 0.7;
|
||||||
|
double gap = 0.2;
|
||||||
|
double circumference = 2 * Math.PI * radius;
|
||||||
|
int particleCount = (int) (circumference / gap);
|
||||||
|
|
||||||
|
Particle particle = Particle.DUST;
|
||||||
|
TeamColor color = winningTeam.getColor();
|
||||||
|
// Generate particle positions
|
||||||
|
for (double heightOffset = 0; heightOffset < 2; heightOffset += 0.5) {
|
||||||
|
center.setY(center.getY() + 0.5);
|
||||||
|
for (int i = 0; i < particleCount; i++) {
|
||||||
|
double angle = 2 * Math.PI * i / particleCount;
|
||||||
|
double x = center.getX() + radius * Math.cos(angle);
|
||||||
|
double z = center.getZ() + radius * Math.sin(angle);
|
||||||
|
double y = center.getY();
|
||||||
|
|
||||||
|
world.spawnParticle(particle, x, y, z, 1, 0, 0, 0, new Particle.DustOptions(Color.fromRGB(color.r(), color.g(), color.b()), 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.flagLocation = new Location(world, Config.FLAG.x, Config.FLAG.y, Config.FLAG.z);
|
|
||||||
//Place block on main thread
|
|
||||||
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(flagItem.getType()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -98,10 +140,15 @@ public class Flag implements Runnable {
|
|||||||
checkFlagCarrier();
|
checkFlagCarrier();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (winningTeam != null) {
|
||||||
|
spawnFlagParticleRing();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (flagLocation == null) {
|
if (flagLocation == null) {
|
||||||
log.warn("Tried to run Flag without a flag location, spawn it first");
|
log.warn("Tried to run Flag without a flag location, spawn it first");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
spawnParticlesOnSquareBorder(flagLocation, GameConfig.FLAG.CAPTURE_RADIUS);
|
||||||
if (!updateScoreBasedOnNearbyPlayers().join()) {
|
if (!updateScoreBasedOnNearbyPlayers().join()) {
|
||||||
return; //Score didn't change
|
return; //Score didn't change
|
||||||
}
|
}
|
||||||
@@ -113,40 +160,102 @@ public class Flag implements Runnable {
|
|||||||
updateDisplay();
|
updateDisplay();
|
||||||
} else {
|
} else {
|
||||||
winningTeam = optionalTeam.get();
|
winningTeam = optionalTeam.get();
|
||||||
bossBar.setVisible(false);
|
spawnFlag(winningTeam.getFlagMaterial());
|
||||||
bossBar.setProgress(0);
|
updateDisplay();
|
||||||
//TODO stop capture and let ppl know they can now capture the flag
|
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, () -> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private final LinkedList<Location> particleTrail = new LinkedList<>();
|
||||||
|
|
||||||
|
private void spawnTrail() {
|
||||||
|
TeamColor color = winningTeam.getColor();
|
||||||
|
particleTrail.forEach(location -> location.getWorld().spawnParticle(Particle.DUST, location, 3, 0, 0, 0,
|
||||||
|
new Particle.DustOptions(Color.fromRGB(color.r(), color.g(), color.b()), 1)));
|
||||||
|
if (particleTrail.size() > 15) {
|
||||||
|
particleTrail.removeFirst();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkFlagCarrier() {
|
private void checkFlagCarrier() {
|
||||||
if (flagCarrier.isDead()) {
|
if (flagCarrier.isDead() || !flagCarrier.isOnline()) {
|
||||||
flagCarrier = null;
|
resetFlagCarrier();
|
||||||
spawnFlag();
|
spawnFlag(GameConfig.FLAG.MATERIAL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double distance = winningTeam.getSpawnLocation().distance(flagCarrier.getLocation());
|
|
||||||
if (distance > 5) {
|
double distance = winningTeam.getFlagTurnInLocation().distance(flagCarrier.getLocation());
|
||||||
//TODO spawn some particles or something so a trail is made for specific classes to follow?
|
if (distance > GameConfig.FLAG.TURN_IN_RADIUS) {
|
||||||
|
Location location = flagCarrier.getLocation();
|
||||||
|
location.setY(location.getY() + 1);
|
||||||
|
particleTrail.add(location);
|
||||||
|
spawnTrail();
|
||||||
|
spawnParticlesOnSquareBorder(winningTeam.getFlagTurnInLocation(), GameConfig.FLAG.TURN_IN_RADIUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//TODO better message? mayb with a text thing on the screen?
|
|
||||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<player> captured the flag for <team>!",
|
notifyAboutTurnIn();
|
||||||
|
spawnFlag(GameConfig.FLAG.MATERIAL);
|
||||||
|
|
||||||
|
wins.merge(winningTeam.getId(), 1, Integer::sum);
|
||||||
|
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 notifyAboutTurnIn() {
|
||||||
|
Bukkit.broadcast(miniMessage.deserialize("<player> captured the flag for <team>!",
|
||||||
Placeholder.component("player", flagCarrier.displayName()),
|
Placeholder.component("player", flagCarrier.displayName()),
|
||||||
Placeholder.component("team", winningTeam.getName())));
|
Placeholder.component("team", winningTeam.getName())));
|
||||||
spawnFlag();
|
Title title = Title.title(Component.empty(),
|
||||||
wins.merge(winningTeam.getId(), 1, Integer::sum);
|
miniMessage.deserialize("<green><player> captured the flag for <team> team</green>",
|
||||||
winningTeam = null;
|
Placeholder.component("player", flagCarrier.displayName()),
|
||||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(flagCarrier.getUniqueId());
|
Placeholder.component("team", winningTeam.getName())));
|
||||||
if (optionalTeamPlayer.isEmpty()) {
|
Bukkit.getOnlinePlayers().forEach(player -> player.showTitle(title));
|
||||||
flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null);
|
|
||||||
} else {
|
|
||||||
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
|
||||||
teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer);
|
|
||||||
flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null);
|
|
||||||
}
|
|
||||||
teamFlagPointCount.clear();
|
|
||||||
flagCarrier = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Team> winnerExists() {
|
private Optional<Team> winnerExists() {
|
||||||
@@ -155,7 +264,7 @@ public class Flag implements Runnable {
|
|||||||
if (max.isEmpty()) {
|
if (max.isEmpty()) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
if (max.get().getValue() < 100) {
|
if (max.get().getValue() < GameConfig.FLAG.CAPTURE_SCORE) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return gameManager.getTeam(max.get().getKey());
|
return gameManager.getTeam(max.get().getKey());
|
||||||
@@ -170,7 +279,7 @@ public class Flag implements Runnable {
|
|||||||
private CompletableFuture<Boolean> updateScoreBasedOnNearbyPlayers() {
|
private CompletableFuture<Boolean> updateScoreBasedOnNearbyPlayers() {
|
||||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||||
Bukkit.getScheduler().runTask(main, () -> {
|
Bukkit.getScheduler().runTask(main, () -> {
|
||||||
Collection<Player> nearbyPlayers = flagLocation.getNearbyPlayers(10);
|
Collection<Player> nearbyPlayers = flagLocation.getNearbyPlayers(GameConfig.FLAG.CAPTURE_RADIUS);
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
|
||||||
boolean result = updateScoreBasedOnNearbyPlayers(nearbyPlayers);
|
boolean result = updateScoreBasedOnNearbyPlayers(nearbyPlayers);
|
||||||
future.complete(result);
|
future.complete(result);
|
||||||
@@ -181,7 +290,7 @@ public class Flag implements Runnable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the score of teams based on the nearby players within a specified range.
|
* Updates the score of teams based on the nearby players within a specified range.
|
||||||
* This method identifies nearby players around the current location within a 10-block radius,
|
* This method identifies nearby players around the current location within a CAPTURE_RADIUS-block radius,
|
||||||
* determines their respective teams, and calculates the team with the maximum number of players
|
* determines their respective teams, and calculates the team with the maximum number of players
|
||||||
* in proximity. If there is a tie for the maximum count, the method exits without updating scores.
|
* in proximity. If there is a tie for the maximum count, the method exits without updating scores.
|
||||||
* If a single team has the highest number of nearby players, scores are updated accordingly:
|
* If a single team has the highest number of nearby players, scores are updated accordingly:
|
||||||
@@ -193,7 +302,7 @@ public class Flag implements Runnable {
|
|||||||
*/
|
*/
|
||||||
private boolean updateScoreBasedOnNearbyPlayers(Collection<Player> players) {
|
private boolean updateScoreBasedOnNearbyPlayers(Collection<Player> players) {
|
||||||
List<TeamPlayer> nearbyPlayers = players.stream()
|
List<TeamPlayer> nearbyPlayers = players.stream()
|
||||||
.map(player -> gameManager.getTeamPlayer(player.getUniqueId()))
|
.map(gameManager::getTeamPlayer)
|
||||||
.filter(Optional::isPresent)
|
.filter(Optional::isPresent)
|
||||||
.map(Optional::get)
|
.map(Optional::get)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -223,13 +332,15 @@ public class Flag implements Runnable {
|
|||||||
}
|
}
|
||||||
Team winningTeam = teamLongEntry.getKey();
|
Team winningTeam = teamLongEntry.getKey();
|
||||||
|
|
||||||
teamCounts.forEach((team, count) -> {
|
teamFlagPointCount.putIfAbsent(winningTeam.getId(), 0);
|
||||||
teamFlagPointCount.merge(team.getId(), team.equals(winningTeam) ? 1 : -1, (oldValue, delta) -> {
|
teamFlagPointCount.entrySet().forEach(entry -> {
|
||||||
int updatedValue = oldValue + delta;
|
if (entry.getKey().equals(winningTeam.getId())) {
|
||||||
log.debug("Set count to {} for team {}", updatedValue, team.getId());
|
entry.setValue(entry.getValue() + 1);
|
||||||
return Math.max(updatedValue, 0);
|
} else {
|
||||||
});
|
entry.setValue(Math.max(0, entry.getValue() - 1));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
nearbyPlayers.forEach(teamPlayer -> teamPlayer.increaseStat(Stat.TIME_SPEND_CAPTURING_FLAG));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +360,7 @@ public class Flag implements Runnable {
|
|||||||
bossBar.setTitle(String.format("Team %s is capturing the flag", PlainTextComponentSerializer.plainText().serialize(team.get().getName())));
|
bossBar.setTitle(String.format("Team %s is capturing the flag", PlainTextComponentSerializer.plainText().serialize(team.get().getName())));
|
||||||
lastWinningTeamId = highestKey;
|
lastWinningTeamId = highestKey;
|
||||||
}
|
}
|
||||||
bossBar.setProgress(Math.min(100, teamFlagPointCount.get(highestKey)) / 100.0);
|
bossBar.setProgress(Math.min(GameConfig.FLAG.CAPTURE_SCORE, teamFlagPointCount.get(highestKey)) / (double) GameConfig.FLAG.CAPTURE_SCORE);
|
||||||
bossBar.setVisible(teamFlagPointCount.get(highestKey) > 0);
|
bossBar.setVisible(teamFlagPointCount.get(highestKey) > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,13 +378,43 @@ public class Flag implements Runnable {
|
|||||||
return winsByTeam;
|
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);
|
bossBar.setVisible(false);
|
||||||
teamFlagPointCount.clear();
|
teamFlagPointCount.clear();
|
||||||
bossBar.setProgress(0);
|
bossBar.setProgress(0);
|
||||||
winningTeam = null;
|
|
||||||
flagCarrier = null;
|
|
||||||
wins.clear();
|
|
||||||
lastWinningTeamId = -1;
|
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;
|
||||||
|
}
|
||||||
|
if (!flagCarrier.getUniqueId().equals(player.getUniqueId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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()))),
|
||||||
|
() -> log.warn("A flag carrier died who was not part of a team"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import org.bukkit.entity.Player;
|
|||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
import org.bukkit.inventory.EquipmentSlot;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ public class FlagTryCaptureEvent implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
Optional<TeamPlayer> teamPlayer = winningTeam.getPlayer(player.getUniqueId());
|
Optional<TeamPlayer> teamPlayer = winningTeam.getPlayer(player);
|
||||||
if (teamPlayer.isEmpty()) {
|
if (teamPlayer.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package com.alttd.ctf.game;
|
package com.alttd.ctf.game;
|
||||||
|
|
||||||
|
import com.alttd.ctf.Main;
|
||||||
import com.alttd.ctf.flag.Flag;
|
import com.alttd.ctf.flag.Flag;
|
||||||
import com.alttd.ctf.game.phases.ClassSelectionPhase;
|
import com.alttd.ctf.game.phases.ClassSelectionPhase;
|
||||||
import com.alttd.ctf.game.phases.CombatPhase;
|
import com.alttd.ctf.game.phases.CombatPhase;
|
||||||
import com.alttd.ctf.game.phases.EndedPhase;
|
import com.alttd.ctf.game.phases.EndedPhase;
|
||||||
import com.alttd.ctf.game.phases.GatheringPhase;
|
import com.alttd.ctf.game.phases.GatheringPhase;
|
||||||
import com.alttd.ctf.game_class.creation.FighterCreator;
|
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.Team;
|
||||||
import com.alttd.ctf.team.TeamPlayer;
|
import com.alttd.ctf.team.TeamPlayer;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@@ -23,29 +26,34 @@ public class GameManager {
|
|||||||
private final HashMap<GamePhase, GamePhaseExecutor> phases;
|
private final HashMap<GamePhase, GamePhaseExecutor> phases;
|
||||||
private RunningGame runningGame;
|
private RunningGame runningGame;
|
||||||
private final HashMap<Integer, Team> teams = new HashMap<>();
|
private final HashMap<Integer, Team> teams = new HashMap<>();
|
||||||
|
private final HashMap<UUID, PlayerStat> playerStats = new HashMap<>();
|
||||||
|
|
||||||
public GameManager() {
|
public GameManager(Main main, WorldBorderApi worldBorderApi) {
|
||||||
phases = new HashMap<>();
|
phases = new HashMap<>();
|
||||||
phases.put(GamePhase.CLASS_SELECTION, new ClassSelectionPhase(this, (FighterCreator::createFighter)));
|
phases.put(GamePhase.CLASS_SELECTION, new ClassSelectionPhase(this, FighterCreator::createFighter, worldBorderApi));
|
||||||
phases.put(GamePhase.GATHERING, new GatheringPhase());
|
phases.put(GamePhase.GATHERING, new GatheringPhase(this, worldBorderApi));
|
||||||
phases.put(GamePhase.COMBAT, new CombatPhase());
|
phases.put(GamePhase.COMBAT, new CombatPhase());
|
||||||
phases.put(GamePhase.ENDED, new EndedPhase());
|
phases.put(GamePhase.ENDED, new EndedPhase(main, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<GamePhase> getGamePhase() {
|
public Optional<GamePhase> getGamePhase() {
|
||||||
return runningGame == null ? Optional.empty() : Optional.of(runningGame.getCurrentPhase());
|
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);
|
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) {
|
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) {
|
public void registerTeam(Team team) {
|
||||||
|
team.registerGameManger(this);
|
||||||
teams.put(team.getId(), team);
|
teams.put(team.getId(), team);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,17 +61,17 @@ public class GameManager {
|
|||||||
return teams.values();
|
return teams.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Team> getTeam(@NotNull UUID uuid) {
|
public Optional<Team> getTeam(@NotNull Player player) {
|
||||||
return getTeams().stream().filter(filterTeam -> filterTeam.getPlayer(uuid).isPresent()).findAny();
|
return getTeams().stream().filter(filterTeam -> filterTeam.getPlayer(player).isPresent()).findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Team> getTeam(int teamId) {
|
public Optional<Team> getTeam(int teamId) {
|
||||||
return getTeams().stream().filter(filterTeam -> filterTeam.getId() == teamId).findAny();
|
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()
|
return getTeams().stream()
|
||||||
.map(team -> team.getPlayer(uuid))
|
.map(team -> team.getPlayer(player))
|
||||||
.filter(Optional::isPresent)
|
.filter(Optional::isPresent)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseGet(Optional::empty);
|
.orElseGet(Optional::empty);
|
||||||
@@ -76,7 +84,7 @@ public class GameManager {
|
|||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
}
|
}
|
||||||
runningGame = new RunningGame(this, duration, flag);
|
runningGame = new RunningGame(this, duration, flag, executorService);
|
||||||
executorService.scheduleAtFixedRate(runningGame, 0, 1, TimeUnit.SECONDS);
|
executorService.scheduleAtFixedRate(runningGame, 0, 1, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,4 +106,27 @@ public class GameManager {
|
|||||||
.max()
|
.max()
|
||||||
.orElse(0);
|
.orElse(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean skipPhase() {
|
||||||
|
if (runningGame == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ public interface GamePhaseExecutor {
|
|||||||
|
|
||||||
void start(Flag flag);
|
void start(Flag flag);
|
||||||
|
|
||||||
void end();
|
void end(GamePhase nextGamePhase);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.alttd.ctf.game;
|
|||||||
|
|
||||||
import com.alttd.ctf.config.GameConfig;
|
import com.alttd.ctf.config.GameConfig;
|
||||||
import com.alttd.ctf.flag.Flag;
|
import com.alttd.ctf.flag.Flag;
|
||||||
|
import com.alttd.ctf.team.TeamScoreboard;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
@@ -13,6 +14,8 @@ import javax.annotation.Nullable;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class RunningGame implements Runnable {
|
public class RunningGame implements Runnable {
|
||||||
@@ -20,60 +23,78 @@ public class RunningGame implements Runnable {
|
|||||||
private final HashMap<GamePhase, Duration> phaseDurations = GameConfig.PHASES.getGAME_PHASE_DURATION();
|
private final HashMap<GamePhase, Duration> phaseDurations = GameConfig.PHASES.getGAME_PHASE_DURATION();
|
||||||
private final GameManager gameManager;
|
private final GameManager gameManager;
|
||||||
private final Flag flag;
|
private final Flag flag;
|
||||||
|
private final ScheduledExecutorService executorService;
|
||||||
@Getter
|
@Getter
|
||||||
private GamePhase currentPhase = GamePhase.values()[0];
|
private GamePhase currentPhase = GamePhase.values()[0];
|
||||||
private Instant phaseStartTime = null;
|
private Instant phaseStartTime = null;
|
||||||
private int lastMinuteBroadcast = 0;
|
private int lastMinuteBroadcast = 0;
|
||||||
|
|
||||||
public RunningGame(GameManager gameManager, Duration gameDuration, Flag flag) {
|
public RunningGame(GameManager gameManager, Duration gameDuration, Flag flag, ScheduledExecutorService executorService) {
|
||||||
this.gameManager = gameManager;
|
this.gameManager = gameManager;
|
||||||
this.flag = flag;
|
this.flag = flag;
|
||||||
|
this.executorService = executorService;
|
||||||
phaseDurations.put(GamePhase.COMBAT, gameDuration);
|
phaseDurations.put(GamePhase.COMBAT, gameDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
GamePhase nextPhase = GamePhase.values().length < currentPhase.ordinal() ? null : GamePhase.values()[currentPhase.ordinal() + 1];
|
try {
|
||||||
|
GamePhase nextPhase = (currentPhase.ordinal() + 1 < GamePhase.values().length) ? GamePhase.values()[currentPhase.ordinal() + 1] : null;
|
||||||
if (phaseStartTime == null) {
|
if (phaseStartTime == null) {
|
||||||
phaseStartTime = Instant.now();
|
nextPhaseActions(null, currentPhase);
|
||||||
nextPhaseActions(null, currentPhase, nextPhase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Duration.between(phaseStartTime, Instant.now()).compareTo(phaseDurations.get(currentPhase)) >= 0) {
|
if (nextPhase != null && Duration.between(phaseStartTime, Instant.now()).compareTo(phaseDurations.get(currentPhase)) >= 0) {
|
||||||
GamePhase previousPhase = currentPhase;
|
nextPhaseActions(currentPhase, nextPhase);
|
||||||
currentPhase = GamePhase.values()[currentPhase.ordinal() + 1];
|
|
||||||
nextPhaseActions(previousPhase, currentPhase, nextPhase);
|
|
||||||
phaseStartTime = Instant.now();
|
phaseStartTime = Instant.now();
|
||||||
} else if (nextPhase != null) {
|
} else if (nextPhase != null) {
|
||||||
broadcastNextPhaseStartTime(currentPhase, nextPhase);
|
broadcastNextPhaseStartTime(currentPhase, nextPhase);
|
||||||
|
} else {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
TeamScoreboard.refreshScoreboard(gameManager);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Unexpected error in running game", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void nextPhaseActions(@Nullable GamePhase previousPhase, @NotNull GamePhase phase, @Nullable GamePhase nextPhase) {
|
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) {
|
if (previousPhase != null) {
|
||||||
gameManager.getPhaseExecutor(previousPhase).end();
|
gameManager.getPhaseExecutor(previousPhase).end(phase);
|
||||||
}
|
}
|
||||||
gameManager.getPhaseExecutor(phase).start(flag);
|
gameManager.getPhaseExecutor(phase).start(flag);
|
||||||
if (nextPhase != null) {
|
if (phase.equals(GamePhase.ENDED)) {
|
||||||
broadcastNextPhaseStartTime(phase, nextPhase);
|
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
|
//Remaining time for this phase
|
||||||
Duration duration = phaseDurations.get(currentPhase).minus(Duration.between(phaseStartTime, Instant.now()));
|
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 ((duration.toMinutes() > 1 && (duration.toMinutes() % 15 == 0 || duration.toMinutes() <= 5)) && duration.toSecondsPart() < 2) {
|
||||||
if (lastMinuteBroadcast == duration.toMinutes()) {
|
if (lastMinuteBroadcast == duration.toMinutes()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastMinuteBroadcast = (int) duration.toMinutes();
|
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(
|
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||||
"<green>The <phase> will start in <bold><red>" + duration.toMinutes() + "</red> minutes</bold></green>",
|
"<green>The <phase> will start in <bold><red>" + duration.toMinutes() + "</red> minutes</bold></green>",
|
||||||
Placeholder.component("phase", nextPhase.getDisplayName())
|
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(
|
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||||
"<green>The <phase> will start in <bold><red>" + duration.toSeconds() + "</red> seconds</bold></green>",
|
"<green>The <phase> will start in <bold><red>" + duration.toSeconds() + "</red> seconds</bold></green>",
|
||||||
Placeholder.component("phase", nextPhase.getDisplayName())
|
Placeholder.component("phase", nextPhase.getDisplayName())
|
||||||
@@ -81,9 +102,23 @@ public class RunningGame implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean skipCurrentPhase() {
|
||||||
|
GamePhase nextPhase = (currentPhase.ordinal() + 1 < GamePhase.values().length) ? GamePhase.values()[currentPhase.ordinal() + 1] : null;
|
||||||
|
if (nextPhase == null) {
|
||||||
|
log.warn("Tried to skip phase {} but there is no next phase", currentPhase);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nextPhaseActions(currentPhase, nextPhase);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void end() {
|
public void end() {
|
||||||
//TODO say the phase ended early?
|
//TODO say the phase ended early?
|
||||||
currentPhase = GamePhase.ENDED;
|
nextPhaseActions(currentPhase, GamePhase.ENDED);
|
||||||
nextPhaseActions(null, currentPhase, null);
|
}
|
||||||
|
|
||||||
|
public Duration getRemainingTime() {
|
||||||
|
Duration duration = phaseDurations.get(currentPhase);
|
||||||
|
return duration.minus(Duration.between(phaseStartTime, Instant.now()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
package com.alttd.ctf.game.phases;
|
package com.alttd.ctf.game.phases;
|
||||||
|
|
||||||
|
import com.alttd.ctf.config.GameConfig;
|
||||||
import com.alttd.ctf.flag.Flag;
|
import com.alttd.ctf.flag.Flag;
|
||||||
import com.alttd.ctf.game.GameManager;
|
import com.alttd.ctf.game.GameManager;
|
||||||
|
import com.alttd.ctf.game.GamePhase;
|
||||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||||
import com.alttd.ctf.game_class.GameClass;
|
import com.alttd.ctf.game_class.GameClass;
|
||||||
import com.alttd.ctf.team.Team;
|
import com.alttd.ctf.team.Team;
|
||||||
import com.alttd.ctf.team.TeamColor;
|
import com.alttd.ctf.team.TeamColor;
|
||||||
|
import com.alttd.ctf.team.TeamPlayer;
|
||||||
import com.alttd.ctf.util.CircularIterator;
|
import com.alttd.ctf.util.CircularIterator;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.IWorldBorder;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.Position;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -21,61 +30,78 @@ public class ClassSelectionPhase implements GamePhaseExecutor {
|
|||||||
|
|
||||||
private final GameManager gameManager;
|
private final GameManager gameManager;
|
||||||
private final DefaultClassCreator defaultClassCreator;
|
private final DefaultClassCreator defaultClassCreator;
|
||||||
|
private final WorldBorderApi worldBorderApi;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface DefaultClassCreator {
|
public interface DefaultClassCreator {
|
||||||
@NotNull GameClass apply(TeamColor teamColor);
|
@NotNull GameClass apply(TeamColor teamColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClassSelectionPhase(@NotNull GameManager gameManager, DefaultClassCreator defaultClassCreator) {
|
public ClassSelectionPhase(@NotNull GameManager gameManager, DefaultClassCreator defaultClassCreator,
|
||||||
|
@NotNull WorldBorderApi worldBorderApi) {
|
||||||
this.gameManager = gameManager;
|
this.gameManager = gameManager;
|
||||||
this.defaultClassCreator = defaultClassCreator;
|
this.defaultClassCreator = defaultClassCreator;
|
||||||
|
this.worldBorderApi = worldBorderApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Flag flag) {
|
public void start(Flag flag) {
|
||||||
teleportPlayersToStartingZone();
|
Location flagLocation = flag.getFlagLocation();
|
||||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>Select your class</green>"));
|
IWorldBorder worldBorder = worldBorderApi.getWorldBorder(flagLocation.getWorld());
|
||||||
|
worldBorder.setCenter(Position.of(flagLocation));
|
||||||
|
worldBorder.setSize(GameConfig.WORLD_BORDER.DEFAULT_SIZE);
|
||||||
|
|
||||||
|
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>Select your class with <gold>/ctf selectclass</gold></green>"));
|
||||||
CircularIterator<Team> teamCircularIterator = new CircularIterator<>(gameManager.getTeams());
|
CircularIterator<Team> teamCircularIterator = new CircularIterator<>(gameManager.getTeams());
|
||||||
if (teamCircularIterator.hasNext()) {
|
if (teamCircularIterator.hasNext()) {
|
||||||
Bukkit.getOnlinePlayers().stream()
|
ArrayList<? extends Player> players = new ArrayList<>(Bukkit.getOnlinePlayers());
|
||||||
.filter(player -> gameManager.getTeamPlayer(player.getUniqueId()).isEmpty())
|
Collections.shuffle(players);
|
||||||
|
players.stream()
|
||||||
|
.filter(player -> !player.hasPermission("ctf.bypass"))
|
||||||
|
.filter(player -> gameManager.getTeamPlayer(player).isEmpty())
|
||||||
.forEach(player -> {
|
.forEach(player -> {
|
||||||
Team team = teamCircularIterator.next();
|
Team team = teamCircularIterator.next();
|
||||||
team.addPlayer(player.getUniqueId());
|
gameManager.registerPlayer(team, player);
|
||||||
player.sendRichMessage("You joined <team>!", Placeholder.component("team", team.getName()));
|
player.sendRichMessage("You joined <team>!", Placeholder.component("team", team.getName()));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.warn("No teams to add players to");
|
log.warn("No teams to add players to");
|
||||||
}
|
}
|
||||||
//TODO let players select classes
|
teleportPlayersToStartingZone();
|
||||||
// They should always be able to do this when in their starting zone
|
|
||||||
// They should be locked into their starting zone until the next phase starts
|
|
||||||
// That phase should handle opening the zone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void teleportPlayersToStartingZone() {
|
private void teleportPlayersToStartingZone() {
|
||||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||||
Optional<Team> team = gameManager.getTeam(player.getUniqueId());
|
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(player);
|
||||||
if (team.isEmpty()) {
|
if (teamPlayer.isEmpty()) {
|
||||||
|
log.warn("{} is not a team player when teleporting to starting zone", player.getName());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Location spawnLocation = team.get().getSpawnLocation();
|
Optional<GamePhase> gamePhase = gameManager.getGamePhase();
|
||||||
player.teleportAsync(spawnLocation);
|
if (gamePhase.isEmpty()) {
|
||||||
|
log.warn("Game phase is empty when teleporting to starting zone");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
teamPlayer.get().respawn(player, worldBorderApi, gamePhase.get());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void end() {
|
public void end(GamePhase nextPhase) {
|
||||||
gameManager.getTeams().forEach(team -> {
|
gameManager.getTeams().forEach(team -> {
|
||||||
GameClass defaultClass = defaultClassCreator.apply(team.getColor());
|
GameClass defaultClass = defaultClassCreator.apply(team.getColor());
|
||||||
team.getPlayers().forEach(player -> {
|
team.getPlayers().forEach(teamPlayer -> {
|
||||||
if (player.getGameClass() != null) {
|
Player player = Bukkit.getPlayer(teamPlayer.getUuid());
|
||||||
|
if (player == null || !player.isOnline()) {
|
||||||
|
log.debug("Tried to reset world border for offline player");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
defaultClass.apply(player);
|
teamPlayer.resetWorldBorder(player, worldBorderApi, nextPhase, team.getWorldBorderCenter());
|
||||||
|
if (teamPlayer.getGameClass() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
defaultClass.apply(teamPlayer, worldBorderApi, nextPhase, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
//TODO expand world border so ppl can gather things but not get near the flag yet
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.alttd.ctf.game.phases;
|
package com.alttd.ctf.game.phases;
|
||||||
|
|
||||||
|
import com.alttd.ctf.config.GameConfig;
|
||||||
import com.alttd.ctf.flag.Flag;
|
import com.alttd.ctf.flag.Flag;
|
||||||
|
import com.alttd.ctf.game.GamePhase;
|
||||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@@ -14,21 +16,24 @@ public class CombatPhase implements GamePhaseExecutor {
|
|||||||
private ScheduledExecutorService executorService = null;
|
private ScheduledExecutorService executorService = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Flag flag) {
|
public synchronized void start(Flag flag) {
|
||||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>CAPTURE THE FLAG</green>"));
|
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>CAPTURE THE FLAG</green>"));
|
||||||
flag.spawnFlag();
|
flag.spawnFlag(GameConfig.FLAG.MATERIAL);
|
||||||
if (executorService == null || !executorService.isShutdown()) {
|
if (executorService == null) {
|
||||||
if (executorService != null) {
|
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
} else if (executorService.isTerminated() || executorService.isShutdown()) {
|
||||||
|
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
} else {
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
}
|
|
||||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
}
|
}
|
||||||
executorService.scheduleAtFixedRate(flag, 0, 1, TimeUnit.SECONDS);
|
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
|
@Override
|
||||||
public void end() {
|
public void end(GamePhase ignored) {
|
||||||
|
if (executorService != null) {
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.alttd.ctf.game.phases;
|
package com.alttd.ctf.game.phases;
|
||||||
|
|
||||||
import com.alttd.ctf.config.Config;
|
import com.alttd.ctf.Main;
|
||||||
|
import com.alttd.ctf.config.GameConfig;
|
||||||
import com.alttd.ctf.flag.Flag;
|
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.GamePhaseExecutor;
|
||||||
import com.alttd.ctf.team.Team;
|
import com.alttd.ctf.team.Team;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -11,9 +14,11 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
|||||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.attribute.Attribute;
|
import org.bukkit.attribute.Attribute;
|
||||||
import org.bukkit.attribute.AttributeInstance;
|
import org.bukkit.attribute.AttributeInstance;
|
||||||
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -24,6 +29,13 @@ import java.util.Map;
|
|||||||
public class EndedPhase implements GamePhaseExecutor {
|
public class EndedPhase implements GamePhaseExecutor {
|
||||||
|
|
||||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
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
|
@Override
|
||||||
public void start(Flag flag) {
|
public void start(Flag flag) {
|
||||||
@@ -31,7 +43,7 @@ public class EndedPhase implements GamePhaseExecutor {
|
|||||||
HashMap<Team, Integer> wins = flag.getWins();
|
HashMap<Team, Integer> wins = flag.getWins();
|
||||||
Bukkit.broadcast(Component.join(JoinConfiguration.separator(Component.newline()), getWinnerMessages(wins)));
|
Bukkit.broadcast(Component.join(JoinConfiguration.separator(Component.newline()), getWinnerMessages(wins)));
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
World world = Bukkit.getWorld(Config.FLAG.world);
|
World world = Bukkit.getWorld(GameConfig.FLAG.world);
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
log.error("Invalid flag world defined");
|
log.error("Invalid flag world defined");
|
||||||
return;
|
return;
|
||||||
@@ -49,9 +61,9 @@ public class EndedPhase implements GamePhaseExecutor {
|
|||||||
maxHealthAttribute.setBaseValue(20);
|
maxHealthAttribute.setBaseValue(20);
|
||||||
player.setHealth(20);
|
player.setHealth(20);
|
||||||
});
|
});
|
||||||
flag.reset();
|
flag.resetAll();
|
||||||
|
gameManager.getTeams().forEach(team -> Bukkit.getOnlinePlayers().forEach(team::removePlayer));
|
||||||
}).start();
|
}).start();
|
||||||
// TODO reset world (coreprotect) to prep for next round
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Component> getWinnerMessages(HashMap<Team, Integer> wins) {
|
private List<Component> getWinnerMessages(HashMap<Team, Integer> wins) {
|
||||||
@@ -70,11 +82,10 @@ public class EndedPhase implements GamePhaseExecutor {
|
|||||||
return messages;
|
return messages;
|
||||||
} else if (topTeams.size() > 1) { // Draw scenario, multiple teams have the same top score
|
} 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>"));
|
messages.add(miniMessage.deserialize("<yellow>It's a draw! Top teams:</yellow>"));
|
||||||
topTeams.forEach(team -> {
|
topTeams.forEach(team -> messages.add(
|
||||||
messages.add(miniMessage.deserialize("<team> had <score> captures.",
|
miniMessage.deserialize("<team> had <score> captures.",
|
||||||
Placeholder.component("team", team.getName()),
|
Placeholder.component("team", team.getName()),
|
||||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
Placeholder.parsed("score", String.valueOf(highestScore)))));
|
||||||
});
|
|
||||||
addOtherTeamsScore(wins, highestScore, messages);
|
addOtherTeamsScore(wins, highestScore, messages);
|
||||||
return messages;
|
return messages;
|
||||||
} else { // Single winner
|
} else { // Single winner
|
||||||
@@ -82,6 +93,18 @@ public class EndedPhase implements GamePhaseExecutor {
|
|||||||
messages.add(miniMessage.deserialize("<green><team> has won with <score> captures!</green>",
|
messages.add(miniMessage.deserialize("<green><team> has won with <score> captures!</green>",
|
||||||
Placeholder.component("team", winner.getName()),
|
Placeholder.component("team", winner.getName()),
|
||||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
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) {
|
if (wins.size() <= 1) {
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
@@ -101,7 +124,7 @@ public class EndedPhase implements GamePhaseExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void end() {
|
public void end(GamePhase ignored) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,48 @@
|
|||||||
package com.alttd.ctf.game.phases;
|
package com.alttd.ctf.game.phases;
|
||||||
|
|
||||||
import com.alttd.ctf.flag.Flag;
|
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.GamePhaseExecutor;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class GatheringPhase implements GamePhaseExecutor {
|
public class GatheringPhase implements GamePhaseExecutor {
|
||||||
|
|
||||||
|
private final GameManager gameManager;
|
||||||
|
private final WorldBorderApi worldBorderApi;
|
||||||
|
private Flag flag;
|
||||||
|
|
||||||
|
public GatheringPhase(GameManager gameManager, WorldBorderApi worldBorderApi) {
|
||||||
|
this.gameManager = gameManager;
|
||||||
|
this.worldBorderApi = worldBorderApi;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Flag flag) {
|
public void start(Flag flag) {
|
||||||
|
this.flag = flag;
|
||||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>Gather materials and prepare for combat!</green>"));
|
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>Gather materials and prepare for combat!</green>"));
|
||||||
//TODO give everyone haste or something so they can mine faster till this game phase ends
|
//TODO give everyone haste or something so they can mine faster till this game phase ends
|
||||||
// Let them store things at base during this phase, after only one class can do this?
|
// Let them store things at base during this phase, after only one class can do this?
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void end() {
|
public void end(GamePhase nextPhase) {
|
||||||
//TODO Remove team area barrier
|
if (flag == null) {
|
||||||
|
log.error("Unable to update world border due to missing Flag");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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,10 +1,13 @@
|
|||||||
package com.alttd.ctf.game_class;
|
package com.alttd.ctf.game_class;
|
||||||
|
|
||||||
|
import com.alttd.ctf.game.GamePhase;
|
||||||
import com.alttd.ctf.team.TeamColor;
|
import com.alttd.ctf.team.TeamColor;
|
||||||
import com.alttd.ctf.team.TeamPlayer;
|
import com.alttd.ctf.team.TeamPlayer;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.kyori.adventure.text.Component;
|
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.Placeholder;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Color;
|
import org.bukkit.Color;
|
||||||
@@ -17,10 +20,12 @@ import org.bukkit.inventory.meta.ItemMeta;
|
|||||||
import org.bukkit.inventory.meta.LeatherArmorMeta;
|
import org.bukkit.inventory.meta.LeatherArmorMeta;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class GameClass {
|
public abstract class GameClass {
|
||||||
|
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||||
|
|
||||||
private final List<Material> armor;
|
private final List<Material> armor;
|
||||||
private final List<ItemStack> tools;
|
private final List<ItemStack> tools;
|
||||||
@@ -36,7 +41,8 @@ public abstract class GameClass {
|
|||||||
@Getter
|
@Getter
|
||||||
private final int damage;
|
private final int damage;
|
||||||
|
|
||||||
protected GameClass(@NotNull List<Material> armor, @NotNull List<ItemStack> tools, @NotNull ItemStack displayItem, double health, int throwTickSpeed, int damage) {
|
protected GameClass(@NotNull List<Material> armor, @NotNull List<ItemStack> tools, @NotNull ItemStack displayItem,
|
||||||
|
double health, int throwTickSpeed, int damage) {
|
||||||
if (armor.size() != 4) {
|
if (armor.size() != 4) {
|
||||||
throw new IllegalArgumentException("Armor has to have 4 entries");
|
throw new IllegalArgumentException("Armor has to have 4 entries");
|
||||||
}
|
}
|
||||||
@@ -51,9 +57,21 @@ public abstract class GameClass {
|
|||||||
throw new IllegalArgumentException("Display item has no item meta");
|
throw new IllegalArgumentException("Display item has no item meta");
|
||||||
}
|
}
|
||||||
this.className = itemMeta.displayName();
|
this.className = itemMeta.displayName();
|
||||||
|
List<Component> lore = itemMeta.lore();
|
||||||
|
if (lore == null) {
|
||||||
|
throw new IllegalArgumentException("Display item has no lore");
|
||||||
|
}
|
||||||
|
ArrayList<Component> loreList = new ArrayList<>(lore);
|
||||||
|
loreList.add(miniMessage.deserialize("<gold>Health: <health>, Damage: <damage> Throw speed: <throw_speed></gold>",
|
||||||
|
Placeholder.parsed("health", String.valueOf(health)),
|
||||||
|
Placeholder.parsed("damage", String.valueOf(damage)),
|
||||||
|
Placeholder.parsed("throw_speed", String.valueOf(throwTickSpeed))
|
||||||
|
));
|
||||||
|
itemMeta.lore(loreList);
|
||||||
|
displayItem.setItemMeta(itemMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void apply(TeamPlayer teamPlayer) {
|
public void apply(TeamPlayer teamPlayer, WorldBorderApi worldBorderApi, GamePhase gamePhase, boolean teleport) {
|
||||||
Player player = Bukkit.getPlayer(teamPlayer.getUuid());
|
Player player = Bukkit.getPlayer(teamPlayer.getUuid());
|
||||||
if (player == null || !player.isOnline()) {
|
if (player == null || !player.isOnline()) {
|
||||||
log.warn("Tried to give class to offline player {}", player == null ? teamPlayer.getUuid() : player.getName());
|
log.warn("Tried to give class to offline player {}", player == null ? teamPlayer.getUuid() : player.getName());
|
||||||
@@ -68,6 +86,7 @@ public abstract class GameClass {
|
|||||||
player.setHealth(health);
|
player.setHealth(health);
|
||||||
//Always reset the player inventory since other classes might have had them get items
|
//Always reset the player inventory since other classes might have had them get items
|
||||||
player.getInventory().clear();
|
player.getInventory().clear();
|
||||||
|
player.clearActivePotionEffects();
|
||||||
|
|
||||||
player.getInventory().setContents(tools.toArray(ItemStack[]::new));
|
player.getInventory().setContents(tools.toArray(ItemStack[]::new));
|
||||||
TeamColor color = teamPlayer.getTeam().getColor();
|
TeamColor color = teamPlayer.getTeam().getColor();
|
||||||
@@ -76,7 +95,7 @@ public abstract class GameClass {
|
|||||||
player.updateInventory();
|
player.updateInventory();
|
||||||
teamPlayer.setGameClass(this);
|
teamPlayer.setGameClass(this);
|
||||||
player.sendRichMessage("You selected the <class_name> class", Placeholder.component("class_name", className));
|
player.sendRichMessage("You selected the <class_name> class", Placeholder.component("class_name", className));
|
||||||
player.teleportAsync(teamPlayer.getTeam().getSpawnLocation());
|
teamPlayer.respawn(player, worldBorderApi, gamePhase, teleport);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setArmor(Player player, int r, int g, int b) {
|
private void setArmor(Player player, int r, int g, int b) {
|
||||||
@@ -84,7 +103,7 @@ public abstract class GameClass {
|
|||||||
ItemStack itemStack = new ItemStack(material);
|
ItemStack itemStack = new ItemStack(material);
|
||||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||||
if (itemMeta instanceof LeatherArmorMeta leatherArmorMeta) {
|
if (itemMeta instanceof LeatherArmorMeta leatherArmorMeta) {
|
||||||
leatherArmorMeta.setColor(Color.fromBGR(r, g, b));
|
leatherArmorMeta.setColor(Color.fromRGB(r, g, b));
|
||||||
itemStack.setItemMeta(leatherArmorMeta);
|
itemStack.setItemMeta(leatherArmorMeta);
|
||||||
}
|
}
|
||||||
return itemStack;
|
return itemStack;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.alttd.ctf.game_class;
|
||||||
|
|
||||||
|
import com.alttd.ctf.game.GameManager;
|
||||||
|
import com.alttd.ctf.game_class.creation.*;
|
||||||
|
import com.alttd.ctf.team.Team;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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)));
|
||||||
|
return gameClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<GameClass> getGameClassesForTeam(Team team) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
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.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.bukkit.inventory.meta.PotionMeta;
|
||||||
|
import org.bukkit.potion.PotionType;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class EngineerCreator {
|
||||||
|
|
||||||
|
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||||
|
|
||||||
|
@Contract("_ -> new")
|
||||||
|
public static @NotNull GameClass createEngineer(@NotNull TeamColor teamColor) {
|
||||||
|
return new Engineer(getArmor(), getTools(teamColor), getDisplayItem(teamColor),
|
||||||
|
15, 5, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract(value = " -> new", pure = true)
|
||||||
|
private static @NotNull @Unmodifiable List<Material> getArmor() {
|
||||||
|
return (List.of(Material.AIR, Material.AIR, Material.LEATHER_CHESTPLATE, Material.AIR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract("_ -> new")
|
||||||
|
private static @NotNull @Unmodifiable List<ItemStack> getTools(@NotNull TeamColor teamColor) {
|
||||||
|
return (List.of(getShovel(teamColor), getPotion(teamColor)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull ItemStack getPotion(@NotNull TeamColor teamColor) {
|
||||||
|
ItemStack healthPot = new ItemStack(Material.POTION);
|
||||||
|
if (!(healthPot.getItemMeta() instanceof PotionMeta potionMeta)) {
|
||||||
|
throw new IllegalStateException("No potion meta from a potion");
|
||||||
|
}
|
||||||
|
potionMeta.setBasePotionType(PotionType.STRONG_SWIFTNESS);
|
||||||
|
potionMeta.itemName(miniMessage.deserialize(
|
||||||
|
String.format("<color:%s>Emergency escape kit</color>", teamColor.hex())));
|
||||||
|
healthPot.setItemMeta(potionMeta);
|
||||||
|
healthPot.setAmount(3);
|
||||||
|
return healthPot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull ItemStack getShovel(@NotNull TeamColor teamColor) {
|
||||||
|
ItemStack shovel = new ItemStack(Material.IRON_SHOVEL);
|
||||||
|
ItemMeta meta = shovel.getItemMeta();
|
||||||
|
meta.itemName(miniMessage.deserialize(String.format("<color:%s>Snow excavator</color>", teamColor.hex())));
|
||||||
|
meta.addEnchant(Enchantment.EFFICIENCY, 4, false);
|
||||||
|
meta.setUnbreakable(true);
|
||||||
|
shovel.setItemMeta(meta);
|
||||||
|
return shovel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull ItemStack getDisplayItem(@NotNull TeamColor teamColor) {
|
||||||
|
ItemStack itemStack = new ItemStack(Material.IRON_SHOVEL);
|
||||||
|
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||||
|
itemMeta.displayName(miniMessage.deserialize(String.format("<color:%s>Engineer</color>", teamColor.hex())));
|
||||||
|
itemMeta.lore(List.of(
|
||||||
|
miniMessage.deserialize("<gold>The Engineer can mine snow fast</gold>"),
|
||||||
|
miniMessage.deserialize("<gold>They can use speed potions to escape danger</gold>")
|
||||||
|
));
|
||||||
|
itemStack.setItemMeta(itemMeta);
|
||||||
|
return itemStack;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,30 +3,65 @@ package com.alttd.ctf.game_class.creation;
|
|||||||
import com.alttd.ctf.game_class.GameClass;
|
import com.alttd.ctf.game_class.GameClass;
|
||||||
import com.alttd.ctf.game_class.implementations.Fighter;
|
import com.alttd.ctf.game_class.implementations.Fighter;
|
||||||
import com.alttd.ctf.team.TeamColor;
|
import com.alttd.ctf.team.TeamColor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.enchantments.Enchantment;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.meta.ItemMeta;
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.inventory.meta.PotionMeta;
|
||||||
|
import org.bukkit.potion.PotionType;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class FighterCreator {
|
public class FighterCreator {
|
||||||
|
|
||||||
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||||
|
|
||||||
public static GameClass createFighter(TeamColor teamColor) {
|
@Contract("_ -> new")
|
||||||
return new Fighter(getArmor(), getTools(), getDisplayItem(teamColor), 15, 3, 5);
|
public static @NotNull GameClass createFighter(@NotNull TeamColor teamColor) {
|
||||||
|
return new Fighter(getArmor(), getTools(teamColor), getDisplayItem(teamColor),
|
||||||
|
15, 3, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Material> getArmor() {
|
@Contract(value = " -> new", pure = true)
|
||||||
|
private static @NotNull @Unmodifiable List<Material> getArmor() {
|
||||||
return (List.of(Material.AIR, Material.AIR, Material.LEATHER_CHESTPLATE, Material.AIR));
|
return (List.of(Material.AIR, Material.AIR, Material.LEATHER_CHESTPLATE, Material.AIR));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ItemStack> getTools() {
|
@Contract("_ -> new")
|
||||||
return (List.of());
|
private static @NotNull @Unmodifiable List<ItemStack> getTools(@NotNull TeamColor teamColor) {
|
||||||
|
return (List.of(getShovel(teamColor), getPotion(teamColor)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ItemStack getDisplayItem(TeamColor teamColor) {
|
private static @NotNull ItemStack getPotion(@NotNull TeamColor teamColor) {
|
||||||
|
ItemStack healthPot = new ItemStack(Material.SPLASH_POTION);
|
||||||
|
if (!(healthPot.getItemMeta() instanceof PotionMeta potionMeta)) {
|
||||||
|
throw new IllegalStateException("No potion meta from a splash potion");
|
||||||
|
}
|
||||||
|
potionMeta.setBasePotionType(PotionType.STRONG_HEALING);
|
||||||
|
potionMeta.itemName(miniMessage.deserialize(
|
||||||
|
String.format("<color:%s>Emergency AOE med kit</color>", teamColor.hex())));
|
||||||
|
healthPot.setItemMeta(potionMeta);
|
||||||
|
healthPot.setAmount(3);
|
||||||
|
return healthPot;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.addEnchant(Enchantment.EFFICIENCY, 1, false);
|
||||||
|
meta.setUnbreakable(true);
|
||||||
|
shovel.setItemMeta(meta);
|
||||||
|
return shovel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull ItemStack getDisplayItem(@NotNull TeamColor teamColor) {
|
||||||
ItemStack itemStack = new ItemStack(Material.IRON_SWORD);
|
ItemStack itemStack = new ItemStack(Material.IRON_SWORD);
|
||||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||||
itemMeta.displayName(miniMessage.deserialize(String.format("<color:%s>Fighter</color>", teamColor.hex())));
|
itemMeta.displayName(miniMessage.deserialize(String.format("<color:%s>Fighter</color>", teamColor.hex())));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.alttd.ctf.game_class.creation;
|
||||||
|
|
||||||
|
import com.alttd.ctf.game_class.GameClass;
|
||||||
|
import com.alttd.ctf.game_class.implementations.Tank;
|
||||||
|
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.ItemFlag;
|
||||||
|
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 TankCreator {
|
||||||
|
|
||||||
|
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||||
|
|
||||||
|
@Contract("_ -> new")
|
||||||
|
public static @NotNull GameClass createTank(@NotNull TeamColor teamColor) {
|
||||||
|
return new Tank(getArmor(), getTools(teamColor), getDisplayItem(teamColor),
|
||||||
|
30, 7, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract(value = " -> new", pure = true)
|
||||||
|
private static @Unmodifiable List<Material> getArmor() {
|
||||||
|
return (List.of(Material.CHAINMAIL_BOOTS, Material.CHAINMAIL_LEGGINGS, Material.LEATHER_CHESTPLATE, Material.CHAINMAIL_HELMET));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract("_ -> new")
|
||||||
|
private static @NotNull @Unmodifiable List<ItemStack> getTools(TeamColor teamColor) {
|
||||||
|
return (List.of(getShovel(teamColor), getShield(teamColor)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull ItemStack getShield(@NotNull TeamColor teamColor) {
|
||||||
|
ItemStack shield = new ItemStack(Material.SHIELD);
|
||||||
|
ItemMeta itemMeta = shield.getItemMeta();
|
||||||
|
itemMeta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
|
||||||
|
itemMeta.itemName(MiniMessage.miniMessage().deserialize(
|
||||||
|
String.format("<color:%s>Shield</color>", teamColor.hex())));
|
||||||
|
itemMeta.setUnbreakable(true);
|
||||||
|
shield.setItemMeta(itemMeta);
|
||||||
|
return shield;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
shovel.setItemMeta(meta);
|
||||||
|
return shovel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull ItemStack getDisplayItem(@NotNull TeamColor teamColor) {
|
||||||
|
ItemStack itemStack = new ItemStack(Material.SHIELD);
|
||||||
|
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||||
|
itemMeta.displayName(miniMessage.deserialize(String.format("<color:%s>Tank</color>", teamColor.hex())));
|
||||||
|
itemMeta.lore(List.of(
|
||||||
|
miniMessage.deserialize("<gold>The Tank is slow but has a lot of health</gold>"),
|
||||||
|
miniMessage.deserialize("<gold>They can protect themselves and their team with their shield.</gold>")
|
||||||
|
));
|
||||||
|
itemStack.setItemMeta(itemMeta);
|
||||||
|
return itemStack;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 Engineer extends GameClass {
|
||||||
|
public Engineer(@NotNull List<Material> armor, @NotNull List<ItemStack> tools, @NotNull ItemStack displayItem,
|
||||||
|
double health, int throwTickSpeed, int damage) {
|
||||||
|
super(armor, tools, displayItem, health, throwTickSpeed, damage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ import java.util.List;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class Fighter extends GameClass {
|
public class Fighter extends GameClass {
|
||||||
|
|
||||||
public Fighter(List<Material> armor, List<ItemStack> tools, ItemStack displayItem, double health, int tickThrowSpeed, int damage) {
|
public Fighter(List<Material> armor, List<ItemStack> tools, ItemStack displayItem,
|
||||||
|
double health, int tickThrowSpeed, int damage) {
|
||||||
super(armor, tools, displayItem, health, tickThrowSpeed, damage);
|
super(armor, tools, displayItem, health, tickThrowSpeed, damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,16 @@
|
|||||||
|
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 Tank extends GameClass {
|
||||||
|
|
||||||
|
public Tank(@NotNull List<Material> armor, @NotNull List<ItemStack> tools, @NotNull ItemStack displayItem,
|
||||||
|
double health, int throwTickSpeed, int damage) {
|
||||||
|
super(armor, tools, displayItem, health, throwTickSpeed, damage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.alttd.ctf.gui;
|
package com.alttd.ctf.gui;
|
||||||
|
|
||||||
|
import com.alttd.ctf.Main;
|
||||||
|
import com.alttd.ctf.game.GamePhase;
|
||||||
import com.alttd.ctf.game_class.GameClass;
|
import com.alttd.ctf.game_class.GameClass;
|
||||||
import com.alttd.ctf.team.TeamPlayer;
|
import com.alttd.ctf.team.TeamPlayer;
|
||||||
|
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||||
|
import lombok.Setter;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||||
import org.bukkit.event.inventory.InventoryType;
|
import org.bukkit.event.inventory.InventoryType;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -11,17 +16,21 @@ import java.util.List;
|
|||||||
|
|
||||||
public class ClassSelectionGUI extends GUIInventory {
|
public class ClassSelectionGUI extends GUIInventory {
|
||||||
|
|
||||||
public ClassSelectionGUI(@NotNull List<GameClass> gameClasses, @NotNull TeamPlayer teamPlayer) {
|
@Setter
|
||||||
|
private static Main main;
|
||||||
|
|
||||||
|
public ClassSelectionGUI(@NotNull List<GameClass> gameClasses, @NotNull TeamPlayer teamPlayer,
|
||||||
|
@NotNull WorldBorderApi worldBorderApi, @NotNull GamePhase gamePhase) {
|
||||||
super(InventoryType.CHEST, teamPlayer.getTeam().getName().append(MiniMessage.miniMessage().deserialize(" - class selection")));
|
super(InventoryType.CHEST, teamPlayer.getTeam().getName().append(MiniMessage.miniMessage().deserialize(" - class selection")));
|
||||||
createClassSelection(gameClasses, teamPlayer);
|
createClassSelection(gameClasses, teamPlayer, worldBorderApi, gamePhase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createClassSelection(@NotNull List<GameClass> gameClasses, @NotNull TeamPlayer teamPlayer) {
|
private void createClassSelection(@NotNull List<GameClass> gameClasses, @NotNull TeamPlayer teamPlayer, @NotNull WorldBorderApi worldBorderApi, @NotNull GamePhase gamePhase) {
|
||||||
int pos = (9 + (9 - gameClasses.size()) / 2);
|
int pos = (9 + (9 - gameClasses.size()) / 2);
|
||||||
for (GameClass gameClass : gameClasses) {
|
for (GameClass gameClass : gameClasses) {
|
||||||
setItem(pos++, gameClass.getDisplayItem(), player -> {
|
setItem(pos++, gameClass.getDisplayItem(), player -> {
|
||||||
gameClass.apply(teamPlayer);
|
gameClass.apply(teamPlayer, worldBorderApi, gamePhase, true);
|
||||||
player.closeInventory(InventoryCloseEvent.Reason.PLUGIN);
|
Bukkit.getScheduler().runTask(main, () -> player.closeInventory(InventoryCloseEvent.Reason.PLUGIN));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package com.alttd.ctf.gui;
|
|||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.Inventory;
|
import org.bukkit.inventory.Inventory;
|
||||||
import org.bukkit.inventory.Merchant;
|
|
||||||
import org.bukkit.inventory.MerchantInventory;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
package com.alttd.ctf.gui;
|
package com.alttd.ctf.gui;
|
||||||
|
|
||||||
|
import com.alttd.ctf.Main;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.inventory.InventoryType;
|
import org.bukkit.event.inventory.InventoryType;
|
||||||
import org.bukkit.inventory.Inventory;
|
import org.bukkit.inventory.Inventory;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.Merchant;
|
|
||||||
import org.bukkit.inventory.MerchantInventory;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public abstract class GUIInventory implements GUI {
|
public abstract class GUIInventory implements GUI {
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private static Main main;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
protected final Inventory inventory;
|
protected final Inventory inventory;
|
||||||
protected final HashMap<Integer, GUIAction> guiActions;
|
protected final HashMap<Integer, GUIAction> guiActions;
|
||||||
@@ -35,7 +38,7 @@ public abstract class GUIInventory implements GUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void open(Player player) {
|
public void open(Player player) {
|
||||||
player.openInventory(inventory);
|
Bukkit.getScheduler().runTask(main, () -> player.openInventory(inventory));
|
||||||
GUIByUUID.put(player.getUniqueId(), this);
|
GUIByUUID.put(player.getUniqueId(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
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.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class Team {
|
public class Team {
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private final TeamScoreboard scoreboard = new TeamScoreboard(this);
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private final HashMap<UUID, TeamPlayer> players = new HashMap<>();
|
private final HashMap<UUID, TeamPlayer> players = new HashMap<>();
|
||||||
@JsonProperty("name")
|
@JsonProperty("name")
|
||||||
@@ -25,7 +31,8 @@ public class Team {
|
|||||||
private Component name;
|
private Component name;
|
||||||
@JsonProperty("id")
|
@JsonProperty("id")
|
||||||
@Getter
|
@Getter
|
||||||
private int id;
|
@NotNull
|
||||||
|
private Integer id;
|
||||||
@JsonProperty("spawnLocation")
|
@JsonProperty("spawnLocation")
|
||||||
@NotNull
|
@NotNull
|
||||||
@Getter
|
@Getter
|
||||||
@@ -33,20 +40,51 @@ public class Team {
|
|||||||
@JsonProperty("worldBorderCenter")
|
@JsonProperty("worldBorderCenter")
|
||||||
@NotNull
|
@NotNull
|
||||||
@Getter
|
@Getter
|
||||||
private Location worldBorderCenter; //TODO https://github.com/yannicklamprecht/WorldBorderAPI/blob/main/how-to-use.md
|
private Location worldBorderCenter; // https://github.com/yannicklamprecht/WorldBorderAPI/blob/main/how-to-use.md
|
||||||
|
@JsonProperty("flagTurnInLocation")
|
||||||
|
@NotNull
|
||||||
|
@Getter
|
||||||
|
private Location flagTurnInLocation;
|
||||||
@JsonProperty("teamColor")
|
@JsonProperty("teamColor")
|
||||||
@NotNull
|
@NotNull
|
||||||
@Getter
|
@Getter
|
||||||
private TeamColor color;
|
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) {
|
public void registerGameManger(GameManager gameManager) {
|
||||||
TeamPlayer teamPlayer = new TeamPlayer(uuid, this);
|
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);
|
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;
|
return teamPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<TeamPlayer> getPlayer(@NotNull UUID uuid) {
|
public Optional<TeamPlayer> getPlayer(@NotNull Player player) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
if (!players.containsKey(uuid))
|
if (!players.containsKey(uuid))
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
return Optional.of(players.get(uuid));
|
return Optional.of(players.get(uuid));
|
||||||
@@ -56,11 +94,30 @@ public class Team {
|
|||||||
return players.values();
|
return players.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePlayer(@NotNull UUID uuid) {
|
public void removePlayer(@NotNull Player player) {
|
||||||
TeamPlayer remove = players.remove(uuid);
|
removeFromScoreboard(player);
|
||||||
if (remove != null) {
|
TeamPlayer remove = players.remove(player.getUniqueId());
|
||||||
log.debug("Removed player with uuid {} from team with id {}", uuid, id);
|
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
|
@Override
|
||||||
|
|||||||
@@ -1,23 +1,78 @@
|
|||||||
package com.alttd.ctf.team;
|
package com.alttd.ctf.team;
|
||||||
|
|
||||||
|
import com.alttd.ctf.config.GameConfig;
|
||||||
|
import com.alttd.ctf.config.WorldBorderSettings;
|
||||||
|
import com.alttd.ctf.config.WorldBorderType;
|
||||||
|
import com.alttd.ctf.game.GamePhase;
|
||||||
import com.alttd.ctf.game_class.GameClass;
|
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.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.*;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Getter
|
@Getter
|
||||||
public class TeamPlayer {
|
public class TeamPlayer {
|
||||||
|
|
||||||
|
private final PlayerStat playerStat;
|
||||||
private final UUID uuid;
|
private final UUID uuid;
|
||||||
private final Team team;
|
private final Team team;
|
||||||
@Setter
|
@Setter
|
||||||
private GameClass gameClass;
|
private GameClass gameClass;
|
||||||
|
private boolean isDead = false;
|
||||||
|
|
||||||
protected TeamPlayer(UUID uuid, Team team) {
|
protected TeamPlayer(Player player, Team team, PlayerStat playerStat) {
|
||||||
this.uuid = uuid;
|
this.uuid = player.getUniqueId();
|
||||||
this.team = team;
|
this.team = team;
|
||||||
|
this.playerStat = playerStat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void respawn(@NotNull Player player, @NotNull WorldBorderApi worldBorderApi, @NotNull GamePhase gamePhase) {
|
||||||
|
respawn(player, worldBorderApi, gamePhase, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void respawn(Player player, WorldBorderApi worldBorderApi, GamePhase gamePhase, boolean teleport) {
|
||||||
|
Location spawnLocation = team.getSpawnLocation();
|
||||||
|
Location worldBorderCenter = team.getWorldBorderCenter();
|
||||||
|
|
||||||
|
List<GameClass> gameClasses = GameClassRetrieval.getGameClassesForTeam(team);
|
||||||
|
new ClassSelectionGUI(gameClasses, this, worldBorderApi, GamePhase.CLASS_SELECTION).open(player);
|
||||||
|
|
||||||
|
if (!teleport) {
|
||||||
|
resetWorldBorder(player, worldBorderApi, gamePhase, worldBorderCenter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
if (worldBorderSettings.type().equals(WorldBorderType.FLAG)) {
|
||||||
|
worldBorderApi.resetWorldBorderToGlobal(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
worldBorderApi.setBorder(player, worldBorderSettings.size(), worldBorderCenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -36,4 +91,12 @@ public class TeamPlayer {
|
|||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(uuid);
|
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 @@
|
|||||||
#Fri Feb 07 23:34:22 CET 2025
|
#Sun Mar 02 00:42:30 CET 2025
|
||||||
buildNumber=9
|
buildNumber=112
|
||||||
version=0.1
|
version=0.1
|
||||||
|
|||||||
Reference in New Issue
Block a user