Implemented remaining phases and got the plugin to the minimum viable product
Introduced new flag-related mechanics and game phases including `CombatPhase`, `EndedPhase`, and relevant event handling for better gameplay management. Enhanced team and player functionalities while making incremental adjustments to improve overall code structure.
This commit is contained in:
parent
076f39279e
commit
6a9721d0e7
|
|
@ -1,9 +1,10 @@
|
|||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("java")
|
||||
}
|
||||
|
||||
group = "com.alttd.ctf"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.alttd:Galaxy-API:1.21-R0.1-SNAPSHOT") {
|
||||
|
|
@ -38,4 +39,41 @@ tasks.test {
|
|||
|
||||
tasks.jar {
|
||||
archiveFileName.set("CaptureTheFlag.jar")
|
||||
}
|
||||
|
||||
val versionPropsFile = file("version.properties")
|
||||
val versionProps = Properties().apply {
|
||||
if (versionPropsFile.exists()) {
|
||||
load(versionPropsFile.inputStream())
|
||||
} else {
|
||||
throw GradleException("version.properties file not found!")
|
||||
}
|
||||
}
|
||||
|
||||
val majorVersion: String = versionProps["version"] as String
|
||||
var buildNumber: Int = (versionProps["buildNumber"] as String).toInt()
|
||||
|
||||
version = "$majorVersion.$buildNumber"
|
||||
|
||||
val incrementBuildNumber = tasks.register("incrementBuildNumber") {
|
||||
doLast {
|
||||
buildNumber++
|
||||
versionProps["buildNumber"] = buildNumber.toString()
|
||||
versionProps.store(versionPropsFile.outputStream(), null)
|
||||
println("Build number incremented to $buildNumber")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("build") {
|
||||
dependsOn(incrementBuildNumber)
|
||||
}
|
||||
|
||||
tasks.withType<Jar> {
|
||||
manifest {
|
||||
attributes(
|
||||
"Implementation-Title" to project.name,
|
||||
"Implementation-Version" to version,
|
||||
"Build-Number" to buildNumber.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,19 @@ import com.alttd.ctf.commands.CommandManager;
|
|||
import com.alttd.ctf.config.Config;
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.events.OnPlayerDeath;
|
||||
import com.alttd.ctf.events.OnPlayerJoin;
|
||||
import com.alttd.ctf.events.OnSnowballHit;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.flag.FlagTryCaptureEvent;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.json_config.JacksonConfig;
|
||||
import com.alttd.ctf.json_config.JsonConfigManager;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
|
|
@ -22,15 +29,28 @@ import java.util.stream.Collectors;
|
|||
public class Main extends JavaPlugin {
|
||||
|
||||
private GameManager gameManager = null;
|
||||
private Flag flag;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
log.info("Plugin enabled!");
|
||||
Package pkg = Main.class.getPackage();
|
||||
String version = pkg.getImplementationVersion();
|
||||
log.info("Plugin enabled, version {}", version);
|
||||
|
||||
reloadConfigs();
|
||||
this.gameManager = new GameManager();
|
||||
registerTeams(); //Skipped in reloadConfig if gameManager is not created yet
|
||||
CommandManager commandManager = new CommandManager(this, gameManager);
|
||||
registerEvents();
|
||||
flag = new Flag(this, gameManager);
|
||||
CommandManager commandManager = new CommandManager(this, gameManager, flag);
|
||||
//Ensuring immediate respawn is on in all worlds
|
||||
log.info("Enabling immediate respawn for {}.", Config.FLAG.world);
|
||||
World world = Bukkit.getWorld(Config.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");
|
||||
}
|
||||
registerEvents(flag);
|
||||
}
|
||||
|
||||
public void reloadConfigs() {
|
||||
|
|
@ -42,9 +62,13 @@ public class Main extends JavaPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
private void registerEvents() {
|
||||
private void registerEvents(Flag flag) {
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
//TODO add event for player joining and clear their inv
|
||||
pluginManager.registerEvents(new OnSnowballHit(gameManager), this);
|
||||
pluginManager.registerEvents(new FlagTryCaptureEvent(flag), this);
|
||||
pluginManager.registerEvents(new OnPlayerDeath(gameManager), this);
|
||||
pluginManager.registerEvents(new OnPlayerJoin(gameManager, flag), this);
|
||||
}
|
||||
|
||||
private void registerTeams() {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.alttd.ctf.commands.subcommands.CreateTeam;
|
|||
import com.alttd.ctf.commands.subcommands.Reload;
|
||||
import com.alttd.ctf.commands.subcommands.Start;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -24,7 +25,7 @@ import java.util.stream.Collectors;
|
|||
public class CommandManager implements CommandExecutor, TabExecutor {
|
||||
private final List<SubCommand> subCommands;
|
||||
|
||||
public CommandManager(Main main, GameManager gameManager) {
|
||||
public CommandManager(Main main, GameManager gameManager, Flag flag) {
|
||||
PluginCommand command = main.getCommand("ctf");
|
||||
if (command == null) {
|
||||
subCommands = null;
|
||||
|
|
@ -36,7 +37,7 @@ public class CommandManager implements CommandExecutor, TabExecutor {
|
|||
|
||||
subCommands = Arrays.asList(
|
||||
new ChangeTeam(gameManager),
|
||||
new Start(gameManager),
|
||||
new Start(gameManager, flag),
|
||||
new CreateTeam(main, gameManager),
|
||||
new Reload(main)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public class ChangeTeam extends SubCommand {
|
|||
commandSender.sendRichMessage(String.format("<red>Please enter a valid integer, %s is not a valid integer</red>", args[2]));
|
||||
return 2;
|
||||
}
|
||||
Optional<Team> optionalTeam = gameManager.getTeams().stream().filter(team -> team.getId() == teamId).findFirst();
|
||||
Optional<Team> optionalTeam = gameManager.getTeam(teamId);
|
||||
if (optionalTeam.isEmpty()) {
|
||||
commandSender.sendRichMessage(String.format("<red>Please provide a valid team id %d is not a valid team id</red>", teamId));
|
||||
return 3;
|
||||
|
|
|
|||
|
|
@ -73,10 +73,7 @@ public class CreateTeam extends SubCommand {
|
|||
Color decodedColor = Color.decode(color);
|
||||
TeamColor teamColor = new TeamColor(decodedColor.getRed(), decodedColor.getGreen(), decodedColor.getBlue(), color);
|
||||
|
||||
int highestId = gameManager.getTeams().stream()
|
||||
.mapToInt(Team::getId)
|
||||
.max()
|
||||
.orElse(0);
|
||||
int highestId = gameManager.getMaxTeamId();
|
||||
Team team = new Team(MiniMessage.miniMessage().deserialize(String.format("<color:%s>%s</color>", color, name)),
|
||||
highestId + 1, player.getLocation(), player.getLocation(), teamColor);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.alttd.ctf.commands.subcommands;
|
|||
|
||||
import com.alttd.ctf.commands.SubCommand;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
|
|
@ -11,9 +12,11 @@ import java.util.List;
|
|||
public class Start extends SubCommand {
|
||||
|
||||
private final GameManager gameManager;
|
||||
private final Flag flag;
|
||||
|
||||
public Start(GameManager gameManager) {
|
||||
public Start(GameManager gameManager, Flag flag) {
|
||||
this.gameManager = gameManager;
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
|
|
@ -24,7 +27,7 @@ public class Start extends SubCommand {
|
|||
@Override
|
||||
public int onCommand(CommandSender commandSender, String[] args) {
|
||||
return handle(commandSender, args, combatTime -> {
|
||||
gameManager.start(combatTime);
|
||||
gameManager.start(combatTime, flag);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
|
@ -35,7 +38,7 @@ public class Start extends SubCommand {
|
|||
}
|
||||
Duration combatTime;
|
||||
try {
|
||||
combatTime = Duration.ofSeconds(Integer.parseInt(args[1]));
|
||||
combatTime = Duration.ofMinutes(Integer.parseInt(args[1]));
|
||||
} catch (NumberFormatException e) {
|
||||
commandSender.sendRichMessage("<red>Please enter a valid integer</red>");
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -41,4 +41,21 @@ public class Config extends AbstractConfig{
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
29
src/main/java/com/alttd/ctf/events/OnPlayerDeath.java
Normal file
29
src/main/java/com/alttd/ctf/events/OnPlayerDeath.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package com.alttd.ctf.events;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
|
||||
public class OnPlayerDeath implements Listener {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public OnPlayerDeath(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDeath(PlayerDeathEvent event) {
|
||||
if (gameManager.getGamePhase().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
event.deathMessage(null);
|
||||
event.setShouldDropExperience(false);
|
||||
Player player = event.getPlayer();
|
||||
player.getInventory().clear();
|
||||
player.updateInventory();
|
||||
}
|
||||
|
||||
}
|
||||
41
src/main/java/com/alttd/ctf/events/OnPlayerJoin.java
Normal file
41
src/main/java/com/alttd/ctf/events/OnPlayerJoin.java
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package com.alttd.ctf.events;
|
||||
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
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;
|
||||
|
||||
@Slf4j
|
||||
public class OnPlayerJoin implements Listener {
|
||||
|
||||
private final GameManager gameManager;
|
||||
private final Flag flag;
|
||||
|
||||
public OnPlayerJoin(GameManager gameManager, Flag flag) {//TODO remove player from team when they leave the game
|
||||
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);
|
||||
if (gameManager.getGamePhase().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
//TODO other stuff based on game state (like adding them to a team etc)
|
||||
}
|
||||
|
||||
}
|
||||
279
src/main/java/com/alttd/ctf/flag/Flag.java
Normal file
279
src/main/java/com/alttd/ctf/flag/Flag.java
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
package com.alttd.ctf.flag;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.config.Config;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.boss.BarStyle;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import org.bukkit.boss.KeyedBossBar;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class Flag implements Runnable {
|
||||
|
||||
private final HashMap<Integer, Integer> teamFlagPointCount = new HashMap<>();
|
||||
private final ItemStack flagItem = new ItemStack(Material.CYAN_BANNER);
|
||||
private final BossBar bossBar = createBossBar();
|
||||
private final HashMap<Integer, Integer> wins = new HashMap<>();
|
||||
private int lastWinningTeamId = -1;
|
||||
private Location flagLocation;
|
||||
private Team winningTeam;
|
||||
private Player flagCarrier; //TODO check for player disconnects?
|
||||
|
||||
private final Main main;
|
||||
private final GameManager gameManager;
|
||||
|
||||
private BossBar createBossBar() {
|
||||
NamespacedKey namespacedKey = NamespacedKey.fromString("ctf_flag", main);
|
||||
if (namespacedKey == null) {
|
||||
throw new IllegalStateException("No NamespaceKey could be created for the bossbar");
|
||||
}
|
||||
if (Bukkit.getBossBar(namespacedKey) != null) {
|
||||
Bukkit.removeBossBar(namespacedKey);
|
||||
}
|
||||
KeyedBossBar captureProgress = Bukkit.createBossBar(namespacedKey, "Capture progress", BarColor.GREEN, BarStyle.SEGMENTED_20);
|
||||
captureProgress.removeAll();
|
||||
captureProgress.setProgress(0);
|
||||
captureProgress.setVisible(false);
|
||||
return captureProgress;
|
||||
}
|
||||
|
||||
protected Location getFlagLocation() {
|
||||
return flagLocation;
|
||||
}
|
||||
|
||||
public void addPlayer(Player player) {
|
||||
bossBar.addPlayer(player);
|
||||
}
|
||||
|
||||
public synchronized void capture(TeamPlayer teamPlayer, Player player) {
|
||||
if (flagCarrier != null) {
|
||||
return;
|
||||
}
|
||||
//TODO knockback enemies from flag location to create space for person who captured mayb short speed boost and heal?
|
||||
//TODO add de-buffs and enable buffs for others?
|
||||
player.getInventory().setItem(EquipmentSlot.HEAD, flagItem);
|
||||
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(Material.AIR));
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green><player> from <team> captured the flag!", TagResolver.resolver(
|
||||
Placeholder.component("player", player.displayName()),
|
||||
Placeholder.component("team", teamPlayer.getTeam().getName())
|
||||
)));
|
||||
flagCarrier = player;
|
||||
}
|
||||
|
||||
public void spawnFlag() {
|
||||
//TODO disable flag capture for a minute or so, and maybe have it slowly drop down?
|
||||
World world = Bukkit.getWorld(Config.FLAG.world);
|
||||
if (world == null) {
|
||||
throw new IllegalStateException(String.format("Tried to spawn flag in world [%s] that doesn't exist", Config.FLAG.world));
|
||||
}
|
||||
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
|
||||
public void run() {
|
||||
if (flagCarrier != null) {
|
||||
checkFlagCarrier();
|
||||
return;
|
||||
}
|
||||
if (flagLocation == null) {
|
||||
log.warn("Tried to run Flag without a flag location, spawn it first");
|
||||
return;
|
||||
}
|
||||
if (!updateScoreBasedOnNearbyPlayers().join()) {
|
||||
return; //Score didn't change
|
||||
}
|
||||
if (teamFlagPointCount.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Optional<Team> optionalTeam = winnerExists();
|
||||
if (optionalTeam.isEmpty()) {
|
||||
updateDisplay();
|
||||
} else {
|
||||
winningTeam = optionalTeam.get();
|
||||
bossBar.setVisible(false);
|
||||
bossBar.setProgress(0);
|
||||
//TODO stop capture and let ppl know they can now capture the flag
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFlagCarrier() {
|
||||
if (flagCarrier.isDead()) {
|
||||
flagCarrier = null;
|
||||
spawnFlag();
|
||||
return;
|
||||
}
|
||||
double distance = winningTeam.getSpawnLocation().distance(flagCarrier.getLocation());
|
||||
if (distance > 5) {
|
||||
//TODO spawn some particles or something so a trail is made for specific classes to follow?
|
||||
return;
|
||||
}
|
||||
//TODO better message? mayb with a text thing on the screen?
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<player> captured the flag for <team>!",
|
||||
Placeholder.component("player", flagCarrier.displayName()),
|
||||
Placeholder.component("team", winningTeam.getName())));
|
||||
spawnFlag();
|
||||
wins.merge(winningTeam.getId(), 1, Integer::sum);
|
||||
winningTeam = null;
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(flagCarrier.getUniqueId());
|
||||
if (optionalTeamPlayer.isEmpty()) {
|
||||
flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null);
|
||||
} else {
|
||||
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
||||
teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer);
|
||||
flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null);
|
||||
}
|
||||
teamFlagPointCount.clear();
|
||||
flagCarrier = null;
|
||||
}
|
||||
|
||||
private Optional<Team> winnerExists() {
|
||||
Optional<Map.Entry<Integer, Integer>> max = teamFlagPointCount.entrySet().stream()
|
||||
.max(Map.Entry.comparingByValue());
|
||||
if (max.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (max.get().getValue() < 100) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return gameManager.getTeam(max.get().getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the score based on nearby players within a specified radius of the flag's location.
|
||||
* This method utilizes asynchronous and synchronous tasks to handle operations efficiently.
|
||||
*
|
||||
* @return A CompletableFuture that resolves to a Boolean indicating whether the score was successfully updated.
|
||||
*/
|
||||
private CompletableFuture<Boolean> updateScoreBasedOnNearbyPlayers() {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
Bukkit.getScheduler().runTask(main, () -> {
|
||||
Collection<Player> nearbyPlayers = flagLocation.getNearbyPlayers(10);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
|
||||
boolean result = updateScoreBasedOnNearbyPlayers(nearbyPlayers);
|
||||
future.complete(result);
|
||||
});
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* 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.
|
||||
* If a single team has the highest number of nearby players, scores are updated accordingly:
|
||||
* - Increment the score for the team with the majority presence.
|
||||
* - Decrement the scores for other teams.
|
||||
*
|
||||
* @return true if a single team has the largest number of nearby players and scores were updated,
|
||||
* false if there is a tie or no players are nearby.
|
||||
*/
|
||||
private boolean updateScoreBasedOnNearbyPlayers(Collection<Player> players) {
|
||||
List<TeamPlayer> nearbyPlayers = players.stream()
|
||||
.map(player -> gameManager.getTeamPlayer(player.getUniqueId()))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.toList();
|
||||
|
||||
if (nearbyPlayers.isEmpty()) {
|
||||
teamFlagPointCount.forEach((teamId, count) -> teamFlagPointCount.put(teamId, Math.max(0, count - 1)));
|
||||
return true; //No players in the area, decrease score
|
||||
}
|
||||
|
||||
Map<Team, Long> teamCounts = nearbyPlayers.stream()
|
||||
.collect(Collectors.groupingBy(TeamPlayer::getTeam, Collectors.counting()));
|
||||
|
||||
Optional<Map.Entry<Team, Long>> maxEntry = teamCounts.entrySet()
|
||||
.stream()
|
||||
.max(Map.Entry.comparingByValue());
|
||||
|
||||
if (maxEntry.isEmpty()) {
|
||||
return false; //No players in the area
|
||||
}
|
||||
Map.Entry<Team, Long> teamLongEntry = maxEntry.get();
|
||||
long maxCount = teamLongEntry.getValue();
|
||||
|
||||
long maxCountTeams = teamCounts.values().stream().filter(count -> count == maxCount).count();
|
||||
|
||||
if (maxCountTeams != 1) {
|
||||
return false; //There are multiple teams that have the most players in the area
|
||||
}
|
||||
Team winningTeam = teamLongEntry.getKey();
|
||||
|
||||
teamCounts.forEach((team, count) -> {
|
||||
teamFlagPointCount.merge(team.getId(), team.equals(winningTeam) ? 1 : -1, (oldValue, delta) -> {
|
||||
int updatedValue = oldValue + delta;
|
||||
log.debug("Set count to {} for team {}", updatedValue, team.getId());
|
||||
return Math.max(updatedValue, 0);
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateDisplay() {
|
||||
Integer highestKey = teamFlagPointCount.entrySet().stream()
|
||||
.max(Map.Entry.comparingByValue())
|
||||
.map(Map.Entry::getKey)
|
||||
.orElse(null);
|
||||
if (highestKey == null) {
|
||||
throw new IllegalStateException("Updating display without any teams existing in the score");
|
||||
}
|
||||
Optional<Team> team = gameManager.getTeam(highestKey);
|
||||
if (team.isEmpty()) {
|
||||
throw new IllegalStateException(String.format("Team %s in point list doesnt exist", highestKey));
|
||||
}
|
||||
if (lastWinningTeamId != highestKey) {
|
||||
bossBar.setTitle(String.format("Team %s is capturing the flag", PlainTextComponentSerializer.plainText().serialize(team.get().getName())));
|
||||
lastWinningTeamId = highestKey;
|
||||
}
|
||||
bossBar.setProgress(Math.min(100, teamFlagPointCount.get(highestKey)) / 100.0);
|
||||
bossBar.setVisible(teamFlagPointCount.get(highestKey) > 0);
|
||||
}
|
||||
|
||||
protected Optional<Team> getWinningTeam() {
|
||||
return winningTeam == null ? Optional.empty() : Optional.of(winningTeam);
|
||||
}
|
||||
|
||||
public HashMap<Team, Integer> getWins() {
|
||||
HashMap<Team, Integer> winsByTeam = new HashMap<>();
|
||||
wins.keySet().stream()
|
||||
.map(gameManager::getTeam)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.forEach(team -> winsByTeam.put(team, wins.get(team.getId())));
|
||||
return winsByTeam;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
bossBar.setVisible(false);
|
||||
teamFlagPointCount.clear();
|
||||
bossBar.setProgress(0);
|
||||
winningTeam = null;
|
||||
flagCarrier = null;
|
||||
wins.clear();
|
||||
lastWinningTeamId = -1;
|
||||
}
|
||||
}
|
||||
47
src/main/java/com/alttd/ctf/flag/FlagTryCaptureEvent.java
Normal file
47
src/main/java/com/alttd/ctf/flag/FlagTryCaptureEvent.java
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package com.alttd.ctf.flag;
|
||||
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.bukkit.Tag;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class FlagTryCaptureEvent implements Listener {
|
||||
|
||||
private final Flag flag;
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerRightClick(PlayerInteractEvent event) {
|
||||
if (!event.getAction().isRightClick()) {
|
||||
return;
|
||||
}
|
||||
Optional<Team> optionalTeam = flag.getWinningTeam();
|
||||
if (optionalTeam.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Team winningTeam = optionalTeam.get();
|
||||
Block clickedBlock = event.getClickedBlock();
|
||||
|
||||
if (clickedBlock == null || !Tag.BANNERS.isTagged(clickedBlock.getType())) {
|
||||
return;
|
||||
}
|
||||
if (!(clickedBlock.getLocation().distance(flag.getFlagLocation()) < 1)) {
|
||||
return;
|
||||
}
|
||||
Player player = event.getPlayer();
|
||||
Optional<TeamPlayer> teamPlayer = winningTeam.getPlayer(player.getUniqueId());
|
||||
if (teamPlayer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
flag.capture(teamPlayer.get(), player);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.phases.ClassSelectionPhase;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game.phases.CombatPhase;
|
||||
import com.alttd.ctf.game.phases.EndedPhase;
|
||||
import com.alttd.ctf.game.phases.GatheringPhase;
|
||||
import com.alttd.ctf.game_class.creation.FighterCreator;
|
||||
import com.alttd.ctf.game_class.implementations.Fighter;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -28,6 +27,9 @@ public class GameManager {
|
|||
public GameManager() {
|
||||
phases = new HashMap<>();
|
||||
phases.put(GamePhase.CLASS_SELECTION, new ClassSelectionPhase(this, (FighterCreator::createFighter)));
|
||||
phases.put(GamePhase.GATHERING, new GatheringPhase());
|
||||
phases.put(GamePhase.COMBAT, new CombatPhase());
|
||||
phases.put(GamePhase.ENDED, new EndedPhase());
|
||||
}
|
||||
|
||||
public Optional<GamePhase> getGamePhase() {
|
||||
|
|
@ -55,6 +57,10 @@ public class GameManager {
|
|||
return getTeams().stream().filter(filterTeam -> filterTeam.getPlayer(uuid).isPresent()).findAny();
|
||||
}
|
||||
|
||||
public Optional<Team> getTeam(int teamId) {
|
||||
return getTeams().stream().filter(filterTeam -> filterTeam.getId() == teamId).findAny();
|
||||
}
|
||||
|
||||
public Optional<TeamPlayer> getTeamPlayer(@NotNull UUID uuid) {
|
||||
return getTeams().stream()
|
||||
.map(team -> team.getPlayer(uuid))
|
||||
|
|
@ -63,13 +69,14 @@ public class GameManager {
|
|||
.orElseGet(Optional::empty);
|
||||
}
|
||||
|
||||
public void start(Duration duration) {
|
||||
public void start(Duration duration, Flag flag) {
|
||||
//TODO assign players to teams and if they are discord link give them a team role so they can be in vc with each other
|
||||
if (runningGame != null) {
|
||||
runningGame.end();
|
||||
executorService.shutdown();
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
runningGame = new RunningGame(this, duration);
|
||||
runningGame = new RunningGame(this, duration, flag);
|
||||
executorService.scheduleAtFixedRate(runningGame, 0, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
|
@ -84,4 +91,11 @@ public class GameManager {
|
|||
public void clearTeams() {
|
||||
teams.clear();
|
||||
}
|
||||
|
||||
public int getMaxTeamId() {
|
||||
return getTeams().stream()
|
||||
.mapToInt(Team::getId)
|
||||
.max()
|
||||
.orElse(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
|
||||
public interface GamePhaseExecutor {
|
||||
|
||||
void start();
|
||||
void start(Flag flag);
|
||||
|
||||
void end();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
|
|
@ -12,39 +14,47 @@ import java.time.Duration;
|
|||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
|
||||
@Slf4j
|
||||
public class RunningGame implements Runnable {
|
||||
|
||||
private final HashMap<GamePhase, Duration> phaseDurations = GameConfig.PHASES.getGAME_PHASE_DURATION();
|
||||
private final GameManager gameManager;
|
||||
private final Flag flag;
|
||||
@Getter
|
||||
private GamePhase currentPhase = GamePhase.values()[0];
|
||||
private Instant phaseStartTime = null;
|
||||
private int lastMinuteBroadcast = 0;
|
||||
|
||||
public RunningGame(GameManager gameManager, Duration gameDuration) {
|
||||
public RunningGame(GameManager gameManager, Duration gameDuration, Flag flag) {
|
||||
this.gameManager = gameManager;
|
||||
this.flag = flag;
|
||||
phaseDurations.put(GamePhase.COMBAT, gameDuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
GamePhase nextPhase = GamePhase.values().length < currentPhase.ordinal() ? null : GamePhase.values()[currentPhase.ordinal() + 1];
|
||||
if (phaseStartTime == null) {
|
||||
phaseStartTime = Instant.now();
|
||||
GamePhase nextPhase = GamePhase.values()[currentPhase.ordinal() + 1];
|
||||
nextPhaseActions(currentPhase, nextPhase);
|
||||
nextPhaseActions(null, currentPhase, nextPhase);
|
||||
}
|
||||
|
||||
if (Duration.between(phaseStartTime, Instant.now()).compareTo(phaseDurations.get(currentPhase)) >= 0) {
|
||||
GamePhase nextPhase = GamePhase.values()[currentPhase.ordinal() + 1];
|
||||
nextPhaseActions(currentPhase, nextPhase);
|
||||
currentPhase = nextPhase;
|
||||
GamePhase previousPhase = currentPhase;
|
||||
currentPhase = GamePhase.values()[currentPhase.ordinal() + 1];
|
||||
nextPhaseActions(previousPhase, currentPhase, nextPhase);
|
||||
phaseStartTime = Instant.now();
|
||||
} else if (nextPhase != null) {
|
||||
broadcastNextPhaseStartTime(currentPhase, nextPhase);
|
||||
}
|
||||
}
|
||||
|
||||
private void nextPhaseActions(@NotNull GamePhase phase, @Nullable GamePhase nextPhase) {
|
||||
//TODO class/functions for each phase that they run at the start of that phase
|
||||
// These should notify the player of what the phase is and that it started as well
|
||||
gameManager.getPhaseExecutor(phase).start();
|
||||
private void nextPhaseActions(@Nullable GamePhase previousPhase, @NotNull GamePhase phase, @Nullable GamePhase nextPhase) {
|
||||
//TODO command to go to next phase
|
||||
if (previousPhase != null) {
|
||||
gameManager.getPhaseExecutor(previousPhase).end();
|
||||
}
|
||||
gameManager.getPhaseExecutor(phase).start(flag);
|
||||
if (nextPhase != null) {
|
||||
broadcastNextPhaseStartTime(phase, nextPhase);
|
||||
}
|
||||
|
|
@ -53,19 +63,27 @@ public class RunningGame implements Runnable {
|
|||
private void broadcastNextPhaseStartTime(GamePhase currentPhase, GamePhase nextPhase) {//TODO check how this works/what it should do
|
||||
//Remaining time for this phase
|
||||
Duration duration = phaseDurations.get(currentPhase).minus(Duration.between(phaseStartTime, Instant.now()));
|
||||
if (duration.toMinutes() > 5 && duration.toMinutes() % 15 == 0) {
|
||||
log.debug(duration.toString());//TODO remove debug
|
||||
if ((duration.toMinutes() > 1 && (duration.toMinutes() % 15 == 0 || duration.toMinutes() <= 5)) && duration.toSecondsPart() < 2) {
|
||||
if (lastMinuteBroadcast == duration.toMinutes()) {
|
||||
return;
|
||||
}
|
||||
lastMinuteBroadcast = (int) duration.toMinutes();
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||
"<green>The <phase> will start in <bold><red>" + duration.toMinutes() + "</red> minutes</bold></green>",
|
||||
Placeholder.component("phase", nextPhase.getDisplayName())
|
||||
));
|
||||
} else if (duration.toMinutes() < 5) {
|
||||
//TODO start minute countdown -> second countdown
|
||||
} else if (duration.toMinutes() < 1 && duration.toSeconds() % 15 == 0) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||
"<green>The <phase> will start in <bold><red>" + duration.toSeconds() + "</red> seconds</bold></green>",
|
||||
Placeholder.component("phase", nextPhase.getDisplayName())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public void end() {
|
||||
//TODO say the phase ended early?
|
||||
currentPhase = GamePhase.ENDED;
|
||||
nextPhaseActions(currentPhase, null);
|
||||
nextPhaseActions(null, currentPhase, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
package com.alttd.ctf.game.phases;
|
||||
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import com.alttd.ctf.util.CircularIterator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
public class ClassSelectionPhase implements GamePhaseExecutor {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
|
@ -28,9 +33,21 @@ public class ClassSelectionPhase implements GamePhaseExecutor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
public void start(Flag flag) {
|
||||
teleportPlayersToStartingZone();
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>Select your class</green>"));
|
||||
CircularIterator<Team> teamCircularIterator = new CircularIterator<>(gameManager.getTeams());
|
||||
if (teamCircularIterator.hasNext()) {
|
||||
Bukkit.getOnlinePlayers().stream()
|
||||
.filter(player -> gameManager.getTeamPlayer(player.getUniqueId()).isEmpty())
|
||||
.forEach(player -> {
|
||||
Team team = teamCircularIterator.next();
|
||||
team.addPlayer(player.getUniqueId());
|
||||
player.sendRichMessage("You joined <team>!", Placeholder.component("team", team.getName()));
|
||||
});
|
||||
} else {
|
||||
log.warn("No teams to add players to");
|
||||
}
|
||||
//TODO let players select classes
|
||||
// They should always be able to do this when in their starting zone
|
||||
// They should be locked into their starting zone until the next phase starts
|
||||
|
|
@ -59,5 +76,6 @@ public class ClassSelectionPhase implements GamePhaseExecutor {
|
|||
defaultClass.apply(player);
|
||||
});
|
||||
});
|
||||
//TODO expand world border so ppl can gather things but not get near the flag yet
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
src/main/java/com/alttd/ctf/game/phases/CombatPhase.java
Normal file
34
src/main/java/com/alttd/ctf/game/phases/CombatPhase.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package com.alttd.ctf.game.phases;
|
||||
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CombatPhase implements GamePhaseExecutor {
|
||||
|
||||
private ScheduledExecutorService executorService = null;
|
||||
|
||||
@Override
|
||||
public void start(Flag flag) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>CAPTURE THE FLAG</green>"));
|
||||
flag.spawnFlag();
|
||||
if (executorService == null || !executorService.isShutdown()) {
|
||||
if (executorService != null) {
|
||||
executorService.shutdown();
|
||||
}
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
executorService.scheduleAtFixedRate(flag, 0, 1, TimeUnit.SECONDS);
|
||||
// TODO Add players to bossbar list for capture progress(or maybe only when any progress is made)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
executorService.shutdown();
|
||||
}
|
||||
}
|
||||
98
src/main/java/com/alttd/ctf/game/phases/EndedPhase.java
Normal file
98
src/main/java/com/alttd/ctf/game/phases/EndedPhase.java
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package com.alttd.ctf.game.phases;
|
||||
|
||||
import com.alttd.ctf.config.Config;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class EndedPhase implements GamePhaseExecutor {
|
||||
|
||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
@Override
|
||||
public void start(Flag flag) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>Capture the flag has ended!</green>"));
|
||||
HashMap<Team, Integer> wins = flag.getWins();
|
||||
Bukkit.broadcast(Component.join(JoinConfiguration.separator(Component.newline()), getWinnerMessages(wins)));
|
||||
new Thread(() -> {
|
||||
World world = Bukkit.getWorld(Config.FLAG.world);
|
||||
if (world == null) {
|
||||
log.error("Invalid flag world defined");
|
||||
return;
|
||||
}
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
player.getInventory().clear();
|
||||
player.updateInventory();
|
||||
player.teleportAsync(spawnLocation);
|
||||
});
|
||||
flag.reset();
|
||||
}).start();
|
||||
// TODO reset world (coreprotect) to prep for next round
|
||||
}
|
||||
|
||||
private List<Component> getWinnerMessages(HashMap<Team, Integer> wins) {
|
||||
List<Component> messages = new ArrayList<>();
|
||||
int highestScore = wins.values().stream()
|
||||
.max(Integer::compareTo)
|
||||
.orElse(0);
|
||||
|
||||
List<Team> topTeams = wins.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() == highestScore)
|
||||
.map(Map.Entry::getKey)
|
||||
.toList();
|
||||
|
||||
if (highestScore <= 0) { // No one captured the flag, no winners
|
||||
messages.add(miniMessage.deserialize("<red>No one captured the flag, it's a draw!</red>"));
|
||||
return messages;
|
||||
} else if (topTeams.size() > 1) { // Draw scenario, multiple teams have the same top score
|
||||
messages.add(miniMessage.deserialize("<yellow>It's a draw! Top teams:</yellow>"));
|
||||
topTeams.forEach(team -> {
|
||||
messages.add(miniMessage.deserialize("<team> had <score> captures.",
|
||||
Placeholder.component("team", team.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
||||
});
|
||||
addOtherTeamsScore(wins, highestScore, messages);
|
||||
return messages;
|
||||
} else { // Single winner
|
||||
Team winner = topTeams.getFirst();
|
||||
messages.add(miniMessage.deserialize("<green><team> has won with <score> captures!</green>",
|
||||
Placeholder.component("team", winner.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
||||
if (wins.size() <= 1) {
|
||||
return messages;
|
||||
}
|
||||
messages.add(miniMessage.deserialize("<yellow>Other teams:</yellow>"));
|
||||
addOtherTeamsScore(wins, highestScore, messages);
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
private void addOtherTeamsScore(HashMap<Team, Integer> wins, int winningScore, List<Component> messages) {
|
||||
messages.add(miniMessage.deserialize("<yellow>Other teams:</yellow>"));
|
||||
wins.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() < winningScore)
|
||||
.forEach(entry -> messages.add(miniMessage.deserialize("<yellow><team> had <score> captures.</yellow>",
|
||||
Placeholder.component("team", entry.getKey().getName()),
|
||||
Placeholder.parsed("score", String.valueOf(entry.getValue())))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
|
||||
}
|
||||
}
|
||||
20
src/main/java/com/alttd/ctf/game/phases/GatheringPhase.java
Normal file
20
src/main/java/com/alttd/ctf/game/phases/GatheringPhase.java
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package com.alttd.ctf.game.phases;
|
||||
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class GatheringPhase implements GamePhaseExecutor {
|
||||
@Override
|
||||
public void start(Flag flag) {
|
||||
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
|
||||
// Let them store things at base during this phase, after only one class can do this?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
//TODO Remove team area barrier
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,9 @@ public abstract class GameClass {
|
|||
private final int damage;
|
||||
|
||||
protected GameClass(List<Material> armor, List<ItemStack> tools, ItemStack displayItem, double health, int throwTickSpeed, int damage) {
|
||||
if (armor.size() != 4) {
|
||||
throw new IllegalArgumentException("Armor has to have 4 entries");
|
||||
}
|
||||
this.armor = armor;
|
||||
this.tools = tools;
|
||||
this.displayItem = displayItem;
|
||||
|
|
@ -50,6 +53,8 @@ public abstract class GameClass {
|
|||
log.error("Player does not have max health attribute");
|
||||
return;
|
||||
}
|
||||
maxHealthAttribute.setBaseValue(health);
|
||||
player.setHealth(health);
|
||||
//Always reset the player inventory since other classes might have had them get items
|
||||
player.getInventory().clear();
|
||||
|
||||
|
|
@ -58,7 +63,6 @@ public abstract class GameClass {
|
|||
setArmor(player, color.r(), color.g(), color.b());
|
||||
|
||||
player.updateInventory();
|
||||
maxHealthAttribute.setBaseValue(health);
|
||||
teamPlayer.setGameClass(this);
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +74,7 @@ public abstract class GameClass {
|
|||
leatherArmorMeta.setColor(Color.fromBGR(r, g, b));
|
||||
itemStack.setItemMeta(leatherArmorMeta);
|
||||
}
|
||||
return new ItemStack(material);
|
||||
return itemStack;
|
||||
}).toArray(ItemStack[]::new));
|
||||
}
|
||||
|
||||
|
|
@ -78,4 +82,8 @@ public abstract class GameClass {
|
|||
return displayItem;
|
||||
}
|
||||
|
||||
public void setArmor(Player player, TeamPlayer teamPlayer) {
|
||||
TeamColor color = teamPlayer.getTeam().getColor();
|
||||
setArmor(player, color.r(), color.g(), color.b());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public class FighterCreator {
|
|||
}
|
||||
|
||||
private static List<Material> getArmor() {
|
||||
return (List.of(Material.LEATHER_CHESTPLATE));
|
||||
return (List.of(Material.AIR, Material.AIR, Material.LEATHER_CHESTPLATE, Material.AIR));
|
||||
}
|
||||
|
||||
private static List<ItemStack> getTools() {
|
||||
|
|
|
|||
|
|
@ -29,9 +29,6 @@ public class JsonConfigManager<T> {
|
|||
|
||||
for (File file : files) {
|
||||
T config = objectMapper.readValue(file, clazz);
|
||||
|
||||
ValidationUtil.validate(config);
|
||||
|
||||
configs.add(config);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
package com.alttd.ctf.json_config;
|
||||
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.ValidatorFactory;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class ValidationUtil {
|
||||
|
||||
private static final ValidatorFactory VALIDATOR_FACTORY = Validation.buildDefaultValidatorFactory();
|
||||
private static final Validator VALIDATOR = VALIDATOR_FACTORY.getValidator();
|
||||
|
||||
public static <T> void validate(T object) {
|
||||
Set<ConstraintViolation<T>> violations = VALIDATOR.validate(object);
|
||||
|
||||
if (!violations.isEmpty()) {
|
||||
StringBuilder errorMessage = new StringBuilder("Validation errors:\n");
|
||||
for (ConstraintViolation<T> violation : violations) {
|
||||
errorMessage.append("- ").append(violation.getPropertyPath()).append(": ").append(violation.getMessage()).append("\n");
|
||||
}
|
||||
throw new IllegalArgumentException(errorMessage.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.alttd.ctf.team;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Location;
|
||||
|
|
@ -11,9 +13,11 @@ import org.jetbrains.annotations.NotNull;
|
|||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Team {
|
||||
|
||||
@JsonIgnore
|
||||
private final HashMap<UUID, TeamPlayer> players = new HashMap<>();
|
||||
@JsonProperty("name")
|
||||
@NotNull
|
||||
|
|
@ -21,7 +25,7 @@ public class Team {
|
|||
private Component name;
|
||||
@JsonProperty("id")
|
||||
@Getter
|
||||
private final int id;
|
||||
private int id;
|
||||
@JsonProperty("spawnLocation")
|
||||
@NotNull
|
||||
@Getter
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package com.alttd.ctf.team;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
public record TeamColor(
|
||||
@JsonProperty("red") @NotNull int r,
|
||||
@JsonProperty("green") @NotNull int g,
|
||||
@JsonProperty("blue") @NotNull int b,
|
||||
@JsonProperty("hex") @NotNull @Pattern(regexp = "^#[A-Fa-f0-9]{6}$") String hex
|
||||
@JsonProperty("red") int r,
|
||||
@JsonProperty("green") int g,
|
||||
@JsonProperty("blue") int b,
|
||||
@JsonProperty("hex")String hex
|
||||
) {
|
||||
}
|
||||
|
|
|
|||
40
src/main/java/com/alttd/ctf/util/CircularIterator.java
Normal file
40
src/main/java/com/alttd/ctf/util/CircularIterator.java
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package com.alttd.ctf.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class CircularIterator<E> implements Iterator<E> {
|
||||
|
||||
private final LinkedList<E> list;
|
||||
private int index = 0;
|
||||
|
||||
public CircularIterator(Collection<E> list) {
|
||||
this.list = new LinkedList<>(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return !list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException("No elements to iterate.");
|
||||
}
|
||||
if (index >= list.size()) {
|
||||
index = 0;
|
||||
}
|
||||
return list.get(index++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (list.isEmpty()) {
|
||||
throw new IllegalStateException("Cannot remove elements from an empty list.");
|
||||
}
|
||||
list.remove(index);
|
||||
}
|
||||
}
|
||||
3
version.properties
Normal file
3
version.properties
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#Fri Feb 07 22:07:29 CET 2025
|
||||
buildNumber=5
|
||||
version=0.1
|
||||
Loading…
Reference in New Issue
Block a user