Improve CTF gameplay mechanics and particle effects

Updated class selection to restrict changes during non-selection phases and adjusted spawn radius. Added flag particle trails and square border effects, improving visual feedback. Enhanced flag handling, clearing trails when the carrier is lost or offline.
This commit is contained in:
Teriuihi 2025-02-08 00:39:05 +01:00
parent 9eccb130e5
commit a4f96297c2
3 changed files with 50 additions and 7 deletions

View File

@ -3,6 +3,7 @@ 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.creation.FighterCreator;
import com.alttd.ctf.game_class.creation.TankCreator; import com.alttd.ctf.game_class.creation.TankCreator;
@ -36,17 +37,19 @@ 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;
} }
GamePhase gamePhase = optionalGamePhase.get();
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player.getUniqueId()); Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player.getUniqueId());
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 (!gamePhase.equals(GamePhase.CLASS_SELECTION) && teamPlayer.getTeam().getSpawnLocation().distance(player.getLocation()) > 5) {
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;
} }

View File

@ -4,6 +4,7 @@ import com.alttd.ctf.Main;
import com.alttd.ctf.config.Config; import com.alttd.ctf.config.Config;
import com.alttd.ctf.game.GameManager; import com.alttd.ctf.game.GameManager;
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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -24,6 +25,7 @@ import org.bukkit.scheduler.BukkitScheduler;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -38,7 +40,7 @@ public class Flag implements Runnable {
private int lastWinningTeamId = -1; private int lastWinningTeamId = -1;
private Location flagLocation; private 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;
@ -79,6 +81,7 @@ public class Flag implements Runnable {
Placeholder.component("team", teamPlayer.getTeam().getName()) Placeholder.component("team", teamPlayer.getTeam().getName())
))); )));
flagCarrier = player; flagCarrier = player;
teamFlagPointCount.clear();
} }
public void spawnFlag() { public void spawnFlag() {
@ -102,6 +105,7 @@ public class Flag implements Runnable {
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.getWorld(), flagLocation);
if (!updateScoreBasedOnNearbyPlayers().join()) { if (!updateScoreBasedOnNearbyPlayers().join()) {
return; //Score didn't change return; //Score didn't change
} }
@ -119,14 +123,49 @@ public class Flag implements Runnable {
} }
} }
private void spawnParticlesOnSquareBorder(World world, Location center) {
double size = 10;
double step = 0.2;
center.add(0, 0.5, 0);
Bukkit.getScheduler().runTask(main, () -> {
// Top and Bottom (Z varies, X constant)
for (double z = -size; z <= size; z += step) {
world.spawnParticle(Particle.FLAME, center.getX() + size, center.getY(), center.getZ() + z, 1, 0, 0, 0, 0);
world.spawnParticle(Particle.FLAME, center.getX() - size, center.getY(), center.getZ() + z, 1, 0, 0, 0, 0);
}
// Left and Right (X varies, Z constant)
for (double x = -size; x <= size; x += step) {
world.spawnParticle(Particle.FLAME, center.getX() + x, center.getY(), center.getZ() + size, 1, 0, 0, 0, 0);
world.spawnParticle(Particle.FLAME, center.getX() + x, center.getY(), center.getZ() - size, 1, 0, 0, 0, 0);
}
});
}
LinkedList<Location> particleTrail = new LinkedList<>();
private void spawnTrail() {
TeamColor color = winningTeam.getColor();
particleTrail.forEach(location -> location.getWorld().spawnParticle(Particle.DUST, location, 1, 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; flagCarrier = null;
particleTrail.clear();
spawnFlag(); spawnFlag();
return; return;
} }
double distance = winningTeam.getSpawnLocation().distance(flagCarrier.getLocation()); double distance = winningTeam.getSpawnLocation().distance(flagCarrier.getLocation());
if (distance > 5) { if (distance > 5) {
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? //TODO spawn some particles or something so a trail is made for specific classes to follow?
return; return;
} }
@ -145,8 +184,8 @@ public class Flag implements Runnable {
teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer); teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer);
flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null); flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null);
} }
teamFlagPointCount.clear();
flagCarrier = null; flagCarrier = null;
particleTrail.clear();
} }
private Optional<Team> winnerExists() { private Optional<Team> winnerExists() {
@ -273,6 +312,7 @@ public class Flag implements Runnable {
bossBar.setProgress(0); bossBar.setProgress(0);
winningTeam = null; winningTeam = null;
flagCarrier = null; flagCarrier = null;
particleTrail.clear();
wins.clear(); wins.clear();
lastWinningTeamId = -1; lastWinningTeamId = -1;
} }

View File

@ -1,3 +1,3 @@
#Fri Feb 07 23:34:22 CET 2025 #Sat Feb 08 00:30:51 CET 2025
buildNumber=9 buildNumber=11
version=0.1 version=0.1