Compare commits

...

13 Commits

Author SHA1 Message Date
Teriuihi 9563e9641f Add skip phase command to bypass game phases
Introduced a new `/ctf skipphase` command allowing admins to skip the current game phase. Updated relevant classes to handle phase-skipping logic and ensure proper game state transitions. Added corresponding message configuration and subcommand registration.
2025-02-11 21:45:14 +01:00
Teriuihi aad63f174e Refactor phase transitions and add particle effects for flags
Simplified phase transition logic by removing unnecessary parameters and added proper executor shutdown at the end of the game. Enhanced visuals by introducing particle effects around flags when a team wins. Also implemented a method to skip the current game phase.
2025-02-11 21:41:33 +01:00
Teriuihi 66c24d852d Refactor and enhance flag capture notification system.
Extracted the flag capture notification logic into a dedicated method for better readability and reusability. Added team-specific title messages to notify players of the flag capture, improving clarity and game immersion.
2025-02-11 21:27:34 +01:00
Teriuihi 7acf39b600 Fix armor color setting method from BGR to RGB.
The method for setting armor colors was incorrect and used `Color.fromBGR` instead of `Color.fromRGB`. This ensures proper color rendering and aligns with expected behavior.
2025-02-11 21:27:26 +01:00
Teriuihi 6f99402d16 Add TURN_IN_RADIUS config and spawn particles at turn in point as well
Introduced a new TURN_IN_RADIUS configuration parameter to manage flag turn-in distance. Refactored particle spawning logic to use configurable radii and adjusted related methods accordingly for better flexibility and maintainability.
2025-02-11 21:08:20 +01:00
Teriuihi 5f2fb8fe0a Send death message with respawn timer on player death
Players now receive a formatted message indicating their death and the respawn time remaining. This enhances feedback and improves the overall player experience.
2025-02-09 22:12:50 +01:00
Teriuihi dbcbb10079 Refactor player status handling and improve flag logic
Rename OnPlayerJoin to OnPlayerOnlineStatus, adding logic for player disconnects. Enhance OnPlayerDeath and Flag classes to handle flag reset and notifications when a flag carrier dies or disconnects.
2025-02-09 22:10:48 +01:00
Teriuihi 7070165a94 Rename WINNING_SCORE to CAPTURE_SCORE in GameConfig.
Updated variable and configuration references to reflect the renaming of WINNING_SCORE to CAPTURE_SCORE for clarity and consistency. Adjusted related usage in Flag class to align with the new naming convention.
2025-02-09 22:01:12 +01:00
Teriuihi 8411db57a1 Update flag capture radius to use configurable value
Replaced the hardcoded 10-block radius with a configurable value from GameConfig.FLAG.CAPTURE_RADIUS. This ensures the capture radius can be easily adjusted without code changes and improves flexibility.
2025-02-09 22:00:51 +01:00
Teriuihi 498ed774a4 Increase particle trail density and remove obsolete comment.
Updated the particle trail to spawn three particles instead of one, enhancing visual clarity. Also removed an outdated TODO comment that is no longer relevant to the functionality.
2025-02-09 21:59:12 +01:00
Teriuihi e95dabccac Set breakable items to unbreakable in class creators
Made items unbreakable for Engineer, Tank, and Fighter classes to improve durability and gameplay consistency. This change ensures tools and equipment don't wear out during use, aligning with game design goals.
2025-02-09 21:57:41 +01:00
Teriuihi 6ae2563d16 Set death message to empty Component on player death.
Replaced `null` with `Component.empty()` for the death message to comply with updated API standards and improve code clarity.
2025-02-09 21:57:27 +01:00
Teriuihi 91ee3cf8f9 Add blocking mechanics for snowball hits
Introduced a check to disable snowball damage if the target is blocking and the impact angle is within 80 degrees. Extended `SnowballHitConsumer` to include the `Snowball` object for more accurate handling of hits. This improves gameplay by allowing players to block snowball attacks with proper positioning.
2025-02-09 21:57:16 +01:00
16 changed files with 214 additions and 42 deletions

View File

@ -6,7 +6,7 @@ import com.alttd.ctf.config.GameConfig;
import com.alttd.ctf.config.Messages;
import com.alttd.ctf.events.InventoryItemInteractionEvent;
import com.alttd.ctf.events.OnPlayerDeath;
import com.alttd.ctf.events.OnPlayerJoin;
import com.alttd.ctf.events.OnPlayerOnlineStatus;
import com.alttd.ctf.events.SnowballEvent;
import com.alttd.ctf.flag.Flag;
import com.alttd.ctf.flag.FlagTryCaptureEvent;
@ -87,9 +87,9 @@ public class Main extends JavaPlugin {
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new SnowballEvent(gameManager), this);
pluginManager.registerEvents(new FlagTryCaptureEvent(flag), this);
pluginManager.registerEvents(new OnPlayerDeath(gameManager, worldBorderApi, this), this);
pluginManager.registerEvents(new OnPlayerDeath(gameManager, worldBorderApi, this, flag), this);
pluginManager.registerEvents(new InventoryItemInteractionEvent(), this);
pluginManager.registerEvents(new OnPlayerJoin(gameManager, flag), this);
pluginManager.registerEvents(new OnPlayerOnlineStatus(gameManager, flag), this);
pluginManager.registerEvents(new GUIListener(), this);
}

View File

@ -35,6 +35,7 @@ public class CommandManager implements CommandExecutor, TabExecutor {
subCommands = Arrays.asList(
new ChangeTeam(gameManager),
new SkipPhase(gameManager),
new Start(gameManager, flag),
new CreateTeam(main, gameManager),
new SelectClass(gameManager, worldBorderApi),

View File

@ -0,0 +1,46 @@
package com.alttd.ctf.commands.subcommands;
import com.alttd.ctf.commands.SubCommand;
import com.alttd.ctf.config.Messages;
import com.alttd.ctf.game.GameManager;
import com.alttd.ctf.team.Team;
import lombok.AllArgsConstructor;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Optional;
@AllArgsConstructor
public class SkipPhase extends SubCommand {
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;
}
}

View File

@ -86,7 +86,8 @@ public class GameConfig extends AbstractConfig {
public static double y = 0;
public static double z = 0;
public static double CAPTURE_RADIUS = 5;
public static int WINNING_SCORE = 50;
public static int CAPTURE_SCORE = 50;
public static double TURN_IN_RADIUS = 3;
@SuppressWarnings("unused")
private static void load() {
@ -95,7 +96,8 @@ public class GameConfig extends AbstractConfig {
y = config.getDouble(prefix, "y", y);
z = config.getDouble(prefix, "z", z);
CAPTURE_RADIUS = config.getDouble(prefix, "capture-radius", CAPTURE_RADIUS);
WINNING_SCORE = config.getInt(prefix, "winning-score", WINNING_SCORE);
CAPTURE_SCORE = config.getInt(prefix, "capture-score", CAPTURE_SCORE);
TURN_IN_RADIUS = config.getDouble(prefix, "turn-in-radius", TURN_IN_RADIUS);
}
}

View File

@ -27,6 +27,7 @@ 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 START = "<green>Start a new game: <gold>/ctf start <time_in_minutes></gold></green>";
public static String SELECT_CLASS = "<green>Open class selection: <gold>/ctf selectclass</gold></green>";
public static String SKIP_PHASE = "<green>Skip the current phase: <gold>/ctf skipphase</gold></green>";
@SuppressWarnings("unused")
private static void load() {
@ -37,6 +38,7 @@ public class Messages extends AbstractConfig {
CREATE_TEAM = config.getString(prefix, "create-team", CREATE_TEAM);
START = config.getString(prefix, "start", START);
SELECT_CLASS = config.getString(prefix, "select-class", SELECT_CLASS);
SKIP_PHASE = config.getString(prefix, "skip-phase", SKIP_PHASE);
}
}

View File

@ -1,13 +1,15 @@
package com.alttd.ctf.events;
import com.alttd.ctf.Main;
import com.alttd.ctf.config.Config;
import com.alttd.ctf.config.GameConfig;
import com.alttd.ctf.flag.Flag;
import com.alttd.ctf.game.GameManager;
import com.alttd.ctf.game.GamePhase;
import com.alttd.ctf.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.entity.Player;
import org.bukkit.event.EventHandler;
@ -23,11 +25,13 @@ public class OnPlayerDeath implements Listener {
private final GameManager gameManager;
private final WorldBorderApi worldBorderApi;
private final Main main;
private final Flag flag;
public OnPlayerDeath(GameManager gameManager, WorldBorderApi worldBorderApi, Main main) {
public OnPlayerDeath(GameManager gameManager, WorldBorderApi worldBorderApi, Main main, Flag flag) {
this.gameManager = gameManager;
this.worldBorderApi = worldBorderApi;
this.main = main;
this.flag = flag;
}
@EventHandler
@ -35,11 +39,12 @@ public class OnPlayerDeath implements Listener {
if (gameManager.getGamePhase().isEmpty()) {
return;
}
event.deathMessage(null);
event.deathMessage(Component.empty());
event.setShouldDropExperience(false);
Player player = event.getPlayer();
player.getInventory().clear();
player.updateInventory();
flag.handleCarrierDeathOrDisconnect(player);
}
@EventHandler
@ -56,6 +61,9 @@ public class OnPlayerDeath implements Listener {
}
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
}

View File

@ -12,17 +12,19 @@ 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 OnPlayerJoin implements Listener {
public class OnPlayerOnlineStatus implements Listener {
private final GameManager gameManager;
private final Flag flag;
public OnPlayerJoin(GameManager gameManager, Flag flag) {
public OnPlayerOnlineStatus(GameManager gameManager, Flag flag) {
this.gameManager = gameManager;
this.flag = flag;
}
@ -64,4 +66,9 @@ public class OnPlayerJoin implements Listener {
player.teleportAsync(teamPlayer.getTeam().getSpawnLocation());
}
@EventHandler
public void onPlayerJoin(@NotNull PlayerQuitEvent event) {
flag.handleCarrierDeathOrDisconnect(event.getPlayer());
}
}

View File

@ -5,6 +5,7 @@ import com.alttd.ctf.game.GamePhase;
import com.alttd.ctf.game_class.GameClass;
import com.alttd.ctf.team.TeamPlayer;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Snowball;
import org.bukkit.event.EventHandler;
@ -12,6 +13,8 @@ import org.bukkit.event.Listener;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
@ -26,7 +29,7 @@ public class SnowballEvent implements Listener {
@FunctionalInterface
private interface SnowballHitConsumer {
void apply(Player hitPlayer, Player shooter, TeamPlayer shooterTeamPlayer);
void apply(Player hitPlayer, Player shooter, TeamPlayer shooterTeamPlayer, Snowball snowball);
}
@FunctionalInterface
@ -36,7 +39,10 @@ public class SnowballEvent implements Listener {
@EventHandler
public void onSnowballHit(EntityDamageByEntityEvent event) {
handleSnowballHit(event, (hitPlayer, shooter, shooterTeamPlayer) -> {
handleSnowballHit(event, (hitPlayer, shooter, shooterTeamPlayer, snowball) -> {
if (blockedAttack(hitPlayer, snowball)) { //Disable damage when it is blocked
return;
}
GameClass shooterClass = shooterTeamPlayer.getGameClass();
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
@ -55,6 +61,20 @@ public class SnowballEvent implements Listener {
});
}
private boolean blockedAttack(@NotNull Player hitPlayer, @NotNull Snowball snowball) {
if (!hitPlayer.isBlocking()) {
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 handleSnowballThrown(ProjectileLaunchEvent event, SnowballThrownConsumer consumer) {
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
if (optionalGamePhase.isEmpty()) {
@ -121,6 +141,6 @@ public class SnowballEvent implements Listener {
log.debug("The shooter hit a member of their own team");
return;
}
consumer.apply(hitPlayer, shooter, teamPlayerHit.get());
consumer.apply(hitPlayer, shooter, teamPlayerHit.get(), snowball);
}
}

View File

@ -12,6 +12,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.title.Title;
import org.bukkit.*;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
@ -28,6 +29,8 @@ import java.util.stream.Collectors;
@Slf4j
public class Flag implements Runnable {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
private final HashMap<Integer, Integer> teamFlagPointCount = new HashMap<>();
private final ItemStack flagItem = new ItemStack(Material.BLACK_BANNER);
private final BossBar bossBar = createBossBar();
@ -84,23 +87,54 @@ public class Flag implements Runnable {
)));
flagCarrier = player;
teamFlagPointCount.clear();
winningTeam = null;
lastWinningTeamId = -1;
bossBar.setProgress(0);
}
public void spawnFlag() {
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(flagItem.getType()));
}
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));
}
}
}
@Override
public void run() {
if (flagCarrier != null) {
checkFlagCarrier();
return;
}
if (winningTeam != null) {
spawnFlagParticleRing();
return;
}
if (flagLocation == null) {
log.warn("Tried to run Flag without a flag location, spawn it first");
return;
}
spawnParticlesOnSquareBorder(flagLocation.getWorld(), flagLocation);
spawnParticlesOnSquareBorder(flagLocation, GameConfig.FLAG.CAPTURE_RADIUS);
if (!updateScoreBasedOnNearbyPlayers().join()) {
return; //Score didn't change
}
@ -118,9 +152,9 @@ public class Flag implements Runnable {
}
}
private void spawnParticlesOnSquareBorder(World world, Location center) {
double size = 10;
private void spawnParticlesOnSquareBorder(Location center, double size) {
double step = 0.2;
World world = center.getWorld();
Location finalCenter = center.clone().add(0, 0.5, 0);
Bukkit.getScheduler().runTask(main, () -> {
// Top and Bottom (Z varies, X constant)
@ -141,7 +175,7 @@ public class Flag implements Runnable {
private void spawnTrail() {
TeamColor color = winningTeam.getColor();
particleTrail.forEach(location -> location.getWorld().spawnParticle(Particle.DUST, location, 1, 0, 0, 0,
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();
@ -156,18 +190,15 @@ public class Flag implements Runnable {
return;
}
double distance = winningTeam.getFlagTurnInLocation().distance(flagCarrier.getLocation());
if (distance > 2) {
if (distance > GameConfig.FLAG.TURN_IN_RADIUS) {
Location location = flagCarrier.getLocation();
location.setY(location.getY() + 1);
particleTrail.add(location);
spawnTrail();
//TODO spawn some particles or something so a trail is made for specific classes to follow?
spawnParticlesOnSquareBorder(winningTeam.getWorldBorderCenter(), GameConfig.FLAG.TURN_IN_RADIUS);
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())));
notifyAboutCapture();
spawnFlag();
wins.merge(winningTeam.getId(), 1, Integer::sum);
winningTeam = null;
@ -183,13 +214,30 @@ public class Flag implements Runnable {
particleTrail.clear();
}
private void notifyAboutCapture() {
Bukkit.broadcast(miniMessage.deserialize("<player> captured the flag for <team>!",
Placeholder.component("player", flagCarrier.displayName()),
Placeholder.component("team", winningTeam.getName())));
Title capturingTeamTitle = Title.title(miniMessage.deserialize("<green><team> captured the flag!</green>",
Placeholder.component("team", winningTeam.getName())),
miniMessage.deserialize("<green>protect <player> while they bring it to your base.</green>",
Placeholder.component("player", flagCarrier.displayName())));
Title huntingTeamTitle = Title.title(miniMessage.deserialize("<red><team> captured the flag!</red>",
Placeholder.component("team", winningTeam.getName())),
miniMessage.deserialize("<red>kill <player> before they bring it to their base.</red>",
Placeholder.component("player", flagCarrier.displayName())));
Bukkit.getOnlinePlayers().forEach(player ->
gameManager.getTeam(player.getUniqueId()).ifPresent(team ->
player.showTitle(team.getId() == winningTeam.getId() ? capturingTeamTitle : huntingTeamTitle)));
}
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() < GameConfig.FLAG.WINNING_SCORE) {
if (max.get().getValue() < GameConfig.FLAG.CAPTURE_SCORE) {
return Optional.empty();
}
return gameManager.getTeam(max.get().getKey());
@ -215,7 +263,7 @@ public class Flag implements Runnable {
/**
* 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
* 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:
@ -283,7 +331,7 @@ public class Flag implements Runnable {
bossBar.setTitle(String.format("Team %s is capturing the flag", PlainTextComponentSerializer.plainText().serialize(team.get().getName())));
lastWinningTeamId = highestKey;
}
bossBar.setProgress(Math.min(GameConfig.FLAG.WINNING_SCORE, teamFlagPointCount.get(highestKey)) / (double) GameConfig.FLAG.WINNING_SCORE);
bossBar.setProgress(Math.min(GameConfig.FLAG.CAPTURE_SCORE, teamFlagPointCount.get(highestKey)) / (double) GameConfig.FLAG.CAPTURE_SCORE);
bossBar.setVisible(teamFlagPointCount.get(highestKey) > 0);
}
@ -311,4 +359,21 @@ public class Flag implements Runnable {
wins.clear();
lastWinningTeamId = -1;
}
public void handleCarrierDeathOrDisconnect(Player player) {
if (flagCarrier == null) {
return;
}
if (!flagCarrier.getUniqueId().equals(player.getUniqueId())) {
return;
}
flagCarrier = null;
particleTrail.clear();
spawnFlag();
gameManager.getTeam(player.getUniqueId())
.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"));
}
}

View File

@ -77,7 +77,7 @@ public class GameManager {
executorService.shutdown();
executorService = Executors.newSingleThreadScheduledExecutor();
}
runningGame = new RunningGame(this, duration, flag);
runningGame = new RunningGame(this, duration, flag, executorService);
executorService.scheduleAtFixedRate(runningGame, 0, 1, TimeUnit.SECONDS);
}
@ -99,4 +99,11 @@ public class GameManager {
.max()
.orElse(0);
}
public boolean skipPhase() {
if (runningGame == null) {
return false;
}
return runningGame.skipCurrentPhase();
}
}

View File

@ -13,6 +13,7 @@ import javax.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.concurrent.ScheduledExecutorService;
@Slf4j
public class RunningGame implements Runnable {
@ -20,14 +21,16 @@ public class RunningGame implements Runnable {
private final HashMap<GamePhase, Duration> phaseDurations = GameConfig.PHASES.getGAME_PHASE_DURATION();
private final GameManager gameManager;
private final Flag flag;
private final ScheduledExecutorService executorService;
@Getter
private GamePhase currentPhase = GamePhase.values()[0];
private Instant phaseStartTime = null;
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.flag = flag;
this.executorService = executorService;
phaseDurations.put(GamePhase.COMBAT, gameDuration);
}
@ -37,16 +40,16 @@ public class RunningGame implements Runnable {
GamePhase nextPhase = (currentPhase.ordinal() + 1 < GamePhase.values().length) ? GamePhase.values()[currentPhase.ordinal() + 1] : null;
if (phaseStartTime == null) {
phaseStartTime = Instant.now();
nextPhaseActions(null, currentPhase, nextPhase);
nextPhaseActions(null, currentPhase);
}
if (Duration.between(phaseStartTime, Instant.now()).compareTo(phaseDurations.get(currentPhase)) >= 0) {
GamePhase previousPhase = currentPhase;
currentPhase = GamePhase.values()[currentPhase.ordinal() + 1]; //TODO fix this running out of bounds
nextPhaseActions(previousPhase, currentPhase, nextPhase);
if (nextPhase != null && Duration.between(phaseStartTime, Instant.now()).compareTo(phaseDurations.get(currentPhase)) >= 0) {
nextPhaseActions(currentPhase, nextPhase);
phaseStartTime = Instant.now();
} else if (nextPhase != null) {
broadcastNextPhaseStartTime(currentPhase, nextPhase);
} else {
executorService.shutdown();
}
} catch (Exception e) {
log.error("Unexpected error in running game", e);
@ -54,15 +57,13 @@ public class RunningGame implements Runnable {
}
}
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;
if (previousPhase != null) {
gameManager.getPhaseExecutor(previousPhase).end(phase);
}
gameManager.getPhaseExecutor(phase).start(flag);
if (nextPhase != null) {
broadcastNextPhaseStartTime(phase, nextPhase);
}
}
private void broadcastNextPhaseStartTime(GamePhase currentPhase, GamePhase nextPhase) {//TODO check how this works/what it should do
@ -86,9 +87,18 @@ 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() {
//TODO say the phase ended early?
currentPhase = GamePhase.ENDED;
nextPhaseActions(null, currentPhase, null);
nextPhaseActions(currentPhase, GamePhase.ENDED);
}
}

View File

@ -103,7 +103,7 @@ public abstract class GameClass {
ItemStack itemStack = new ItemStack(material);
ItemMeta itemMeta = itemStack.getItemMeta();
if (itemMeta instanceof LeatherArmorMeta leatherArmorMeta) {
leatherArmorMeta.setColor(Color.fromBGR(r, g, b));
leatherArmorMeta.setColor(Color.fromRGB(r, g, b));
itemStack.setItemMeta(leatherArmorMeta);
}
return itemStack;

View File

@ -57,6 +57,7 @@ public class EngineerCreator {
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;
}

View File

@ -56,6 +56,7 @@ public class FighterCreator {
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;
}

View File

@ -43,6 +43,7 @@ public class TankCreator {
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;
}
@ -51,6 +52,7 @@ public class TankCreator {
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;
}

View File

@ -1,3 +1,3 @@
#Sat Feb 08 23:31:55 CET 2025
buildNumber=33
#Tue Feb 11 21:41:29 CET 2025
buildNumber=37
version=0.1