Compare commits

...
7 Commits
Author SHA1 Message Date
auto 4953cec215 Make game logic adjustments and improve async command handling
Added permission checks for players bypassing game actions and refined block event handling based on game phase. Introduced asynchronous handling for permission commands to improve server performance. Updated constructors and dependencies to streamline game phase management.
2025-03-06 19:47:28 +01:00
auto 92f9e732a1 Remove redundant code and add victory reward commands
Removed unused methods and logging, such as `kickFromVoiceIfNeeded` and unnecessary permission checks. Added a feature to grant temporary permissions to winning players using LuckPerms. Updated the build number to reflect the changes.
2025-03-01 22:40:37 +01:00
auto 95b54d1a03 Prevent same-team players from being knocked back by flags.
Added a check to ensure players on the same team as the flag carrier are not affected by knockback when near the flag. Updated the build number to reflect the new changes.
2025-03-01 20:17:41 +01:00
auto 05a63b9a52 Prevent snowball throw if player hasn't selected a class
Added
2025-03-01 20:04:48 +01:00
auto 137e5a5125 Remove unused log, add null check, and update player teleport.
Removed a redundant logging statement from `OtherGameEvents`. Added a null check for shooter class in `SnowballEvent` to avoid potential issues. Enhanced player handling in `OnPlayerOnlineStatus` by teleporting players to their team’s spawn location if applicable.
2025-03-01 20:03:40 +01:00
auto 3c5842a92c Implement shield mechanics and enhance game end reset logic
Added shield blocking, cooldown, and breaking mechanics with sound and particle effects. Improved game reset functionality to properly remove players from teams at the end. Updated capture material and refactored imports for cleaner structure.
2025-03-01 19:29:46 +01:00
auto f5bb1564ae Increase spawn proximity threshold for class change checks.
Previously, players needed to be within 5 blocks of their spawn to switch classes. This threshold has been increased to 10 blocks, providing a better balance between gameplay flexibility and restrictions.
2025-03-01 18:29:09 +01:00
12 changed files with 147 additions and 37 deletions
+5 -5
View File
@@ -4,13 +4,11 @@ import com.alttd.ctf.commands.CommandManager;
import com.alttd.ctf.config.Config;
import com.alttd.ctf.config.GameConfig;
import com.alttd.ctf.config.Messages;
import com.alttd.ctf.events.InventoryItemInteractionEvent;
import com.alttd.ctf.events.OnPlayerDeath;
import com.alttd.ctf.events.OnPlayerOnlineStatus;
import com.alttd.ctf.events.SnowballEvent;
import com.alttd.ctf.events.*;
import com.alttd.ctf.flag.Flag;
import com.alttd.ctf.flag.FlagTryCaptureEvent;
import com.alttd.ctf.game.GameManager;
import com.alttd.ctf.game.phases.EndedPhase;
import com.alttd.ctf.gui.ClassSelectionGUI;
import com.alttd.ctf.gui.GUIInventory;
import com.alttd.ctf.gui.GUIListener;
@@ -41,6 +39,7 @@ public class Main extends JavaPlugin {
@Override
public void onEnable() {
PlayerStat.main = this;
GUIInventory.setMain(this); // sorry
ClassSelectionGUI.setMain(this); // sorry
Package pkg = Main.class.getPackage();
@@ -49,7 +48,7 @@ public class Main extends JavaPlugin {
reloadConfigs();
WorldBorderApi worldBorderApi = worldBorder();
this.gameManager = new GameManager(worldBorderApi);
this.gameManager = new GameManager(this, worldBorderApi);
registerTeams(); //Skipped in reloadConfig if gameManager is not created yet
loadPlayerStats(); //Skipped in reloadConfig if gameManager is not created yet
Flag flag = new Flag(this, gameManager);
@@ -94,6 +93,7 @@ public class Main extends JavaPlugin {
private void registerEvents(Flag flag, WorldBorderApi worldBorderApi) {
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new SnowballEvent(gameManager), this);
pluginManager.registerEvents(new OtherGameEvents(gameManager), this);
pluginManager.registerEvents(new FlagTryCaptureEvent(flag), this);
pluginManager.registerEvents(new OnPlayerDeath(gameManager, worldBorderApi, this, flag), this);
pluginManager.registerEvents(new InventoryItemInteractionEvent(), this);
@@ -48,7 +48,7 @@ public class SelectClass extends SubCommand {
TeamPlayer teamPlayer = optionalTeamPlayer.get();
if (!teamPlayer.isDead()
&& !gamePhase.equals(GamePhase.CLASS_SELECTION)
&& teamPlayer.getTeam().getSpawnLocation().distance(player.getLocation()) > 5) {
&& teamPlayer.getTeam().getSpawnLocation().distance(player.getLocation()) > 10) {
commandSender.sendRichMessage("<red>You have to be near your spawn to change classes.</red>");
return 0;
}
@@ -90,7 +90,7 @@ public class GameConfig extends AbstractConfig {
public static double CAPTURE_RADIUS = 5;
public static int CAPTURE_SCORE = 50;
public static double TURN_IN_RADIUS = 3;
public static @NotNull Material MATERIAL = Material.RED_BANNER;
public static @NotNull Material MATERIAL = Material.BLACK_BANNER;
@SuppressWarnings("unused")
private static void load() {
@@ -40,8 +40,12 @@ public class OnPlayerOnlineStatus implements Listener {
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
resetPlayer(player);
handleRunningGame(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) {
@@ -1,6 +1,7 @@
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;
@@ -12,6 +13,7 @@ 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;
@@ -29,7 +31,8 @@ public class OtherGameEvents implements Listener {
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
if (gameManager.getGamePhase().isEmpty()) {
Optional<GamePhase> gamePhase = gameManager.getGamePhase();
if (gamePhase.isEmpty() || gamePhase.get() == GamePhase.ENDED) {
return;
}
if (!Tag.SNOW.isTagged(event.getBlock().getType())) {
@@ -41,8 +44,9 @@ public class OtherGameEvents implements Listener {
}
@EventHandler
public void onBlockPlace(BlockBreakEvent event) {
if (gameManager.getGamePhase().isEmpty()) {
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())) {
@@ -9,6 +9,8 @@ import com.alttd.ctf.team.TeamPlayer;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Snowball;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@@ -18,7 +20,11 @@ import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.time.Instant;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Optional;
import java.util.UUID;
@Slf4j
public class SnowballEvent implements Listener {
@@ -42,7 +48,8 @@ public class SnowballEvent implements Listener {
@EventHandler
public void onSnowballHit(EntityDamageByEntityEvent event) {
handleSnowballHit(event, (hitPlayer, shooter, shooterTeamPlayer, snowball) -> {
if (blockedAttack(hitPlayer, snowball)) { //Disable damage when it is blocked
if (blockedAttack(hitPlayer, shooter, snowball)) {
playBlockSounds(hitPlayer, shooter);
return;
}
GameClass shooterClass = shooterTeamPlayer.getGameClass();
@@ -57,13 +64,35 @@ public class SnowballEvent implements Listener {
}
log.debug("{} health was set to {} because of a snowball thrown by {}",
hitPlayer.getName(), Math.max(newHealth, 0), shooter.getName());
applyDamageEffects(hitPlayer, shooter);
});
}
private void playBlockSounds(Player hitPlayer, Player shooter) {
hitPlayer.playSound(hitPlayer.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1.0f, 1.5f);
shooter.playSound(shooter.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1.0f, 1.5f);
}
private void applyDamageEffects(Player hitPlayer, Player shooter) {
hitPlayer.getWorld().playSound(hitPlayer.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 1.0f);
hitPlayer.getWorld().spawnParticle(
Particle.DAMAGE_INDICATOR,
hitPlayer.getLocation().add(0, 1, 0), 10, 0.5, 0.5, 0.5, 0.1
);
shooter.playSound(shooter.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 1.5f);
}
@EventHandler
public void onSnowballThrown(ProjectileLaunchEvent event) {
handleSnowballThrown(event, (shooter, shooterTeamPlayer, snowball) -> {
GameClass shooterClass = shooterTeamPlayer.getGameClass();
if (shooterClass == null) {
shooter.sendRichMessage("<red>You appear to not have selected a class, please do so by going to spawn and using /ctf selectclass</red>");
event.setCancelled(true);
return;
}
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
shooterTeamPlayer.increaseStat(Stat.SNOWBALLS_THROWN);
if (shooterClass instanceof Mage mage) {
@@ -72,10 +101,22 @@ public class SnowballEvent implements Listener {
});
}
private boolean blockedAttack(@NotNull Player hitPlayer, @NotNull Snowball snowball) {
private final HashMap<UUID, LinkedList<Instant>> lastTankHits = new HashMap<>();
private boolean blockedAttack(@NotNull Player hitPlayer, @NotNull Player shooter, @NotNull Snowball snowball) {
if (!hitPlayer.isBlocking()) {
return false;
}
if (reachedMaxHits(hitPlayer)) {
hitPlayer.setCooldown(Material.SHIELD, 40);
if (hitPlayer.getInventory().getItemInMainHand().getType().equals(Material.SHIELD)) {
hitPlayer.swingMainHand();
} else if (hitPlayer.getInventory().getItemInOffHand().getType().equals(Material.SHIELD)) {
hitPlayer.swingOffHand();
}
applyBlockBrokenEffects(hitPlayer, shooter);
return false;
}
Location playerLocation = hitPlayer.getLocation();
Vector playerFacing = playerLocation.getDirection().normalize();
Location snowballLocation = snowball.getLocation();
@@ -86,6 +127,37 @@ public class SnowballEvent implements Listener {
return !(Math.toDegrees(angle) > 80); //Blocked if the angle was <= 80
}
private void applyBlockBrokenEffects(@NotNull Player hitPlayer, @NotNull Player shooter) {
hitPlayer.getWorld().playSound(hitPlayer.getLocation(), Sound.ITEM_SHIELD_BREAK, 1.0f, 1.0f);
Location shieldLocation = hitPlayer.getLocation()
.add(hitPlayer.getLocation().getDirection().normalize().multiply(0.5))
.add(0, 0.5, 0);
hitPlayer.getWorld().spawnParticle(
Particle.CRIT,
shieldLocation, 10, 0.5, 0.5, 0.5, 0.1
);
shooter.playSound(shooter.getLocation(), Sound.ITEM_SHIELD_BREAK, 1.0f, 1.5f);
}
private boolean reachedMaxHits(@NotNull Player hitPlayer) {
boolean reachedMaxHits = false;
Instant now = Instant.now();
LinkedList<Instant> lastHits = lastTankHits.getOrDefault(hitPlayer.getUniqueId(), new LinkedList<>());
Instant cutoffTime = now.minusMillis(300);
while (!lastHits.isEmpty() && lastHits.peek().isBefore(cutoffTime)) {
lastHits.poll();
}
lastHits.addLast(now);
if (lastHits.size() >= 3) {
lastHits.clear();
reachedMaxHits = true;
}
lastTankHits.put(hitPlayer.getUniqueId(), lastHits);
return reachedMaxHits;
}
private void handleSnowballThrown(ProjectileLaunchEvent event, SnowballThrownConsumer consumer) {
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
if (optionalGamePhase.isEmpty()) {
@@ -81,10 +81,18 @@ public class Flag implements Runnable {
if (flagCarrier != null) {
return;
}
Team team = teamPlayer.getTeam();
flagLocation.getNearbyPlayers(5).forEach(nearbyPlayer -> {
if (nearbyPlayer.getUniqueId().equals(player.getUniqueId())) {
return;
}
Optional<TeamPlayer> nearByTeamPlayer = gameManager.getTeamPlayer(nearbyPlayer);
if (nearByTeamPlayer.isEmpty()) {
return;
} else if (nearByTeamPlayer.get().getTeam().equals(team)) {
return;
}
Vector direction = nearbyPlayer.getLocation().toVector().subtract(flagLocation.toVector()).normalize();
direction.setY(0.2);
nearbyPlayer.setVelocity(direction.multiply(5));
@@ -1,5 +1,6 @@
package com.alttd.ctf.game;
import com.alttd.ctf.Main;
import com.alttd.ctf.flag.Flag;
import com.alttd.ctf.game.phases.ClassSelectionPhase;
import com.alttd.ctf.game.phases.CombatPhase;
@@ -27,12 +28,12 @@ public class GameManager {
private final HashMap<Integer, Team> teams = new HashMap<>();
private final HashMap<UUID, PlayerStat> playerStats = new HashMap<>();
public GameManager(WorldBorderApi worldBorderApi) {
public GameManager(Main main, WorldBorderApi worldBorderApi) {
phases = new HashMap<>();
phases.put(GamePhase.CLASS_SELECTION, new ClassSelectionPhase(this, FighterCreator::createFighter, worldBorderApi));
phases.put(GamePhase.GATHERING, new GatheringPhase(this, worldBorderApi));
phases.put(GamePhase.COMBAT, new CombatPhase());
phases.put(GamePhase.ENDED, new EndedPhase());
phases.put(GamePhase.ENDED, new EndedPhase(main, this));
}
public Optional<GamePhase> getGamePhase() {
@@ -1,7 +1,9 @@
package com.alttd.ctf.game.phases;
import com.alttd.ctf.Main;
import com.alttd.ctf.config.GameConfig;
import com.alttd.ctf.flag.Flag;
import com.alttd.ctf.game.GameManager;
import com.alttd.ctf.game.GamePhase;
import com.alttd.ctf.game.GamePhaseExecutor;
import com.alttd.ctf.team.Team;
@@ -12,9 +14,11 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.command.ConsoleCommandSender;
import java.util.ArrayList;
import java.util.HashMap;
@@ -25,6 +29,13 @@ import java.util.Map;
public class EndedPhase implements GamePhaseExecutor {
private final MiniMessage miniMessage = MiniMessage.miniMessage();
private final Main main;
private final GameManager gameManager;
public EndedPhase(Main main, GameManager gameManager) {
this.main = main;
this.gameManager = gameManager;
}
@Override
public void start(Flag flag) {
@@ -51,8 +62,8 @@ public class EndedPhase implements GamePhaseExecutor {
player.setHealth(20);
});
flag.resetAll();
gameManager.getTeams().forEach(team -> Bukkit.getOnlinePlayers().forEach(team::removePlayer));
}).start();
// TODO reset world (coreprotect) to prep for next round
}
private List<Component> getWinnerMessages(HashMap<Team, Integer> wins) {
@@ -82,6 +93,18 @@ public class EndedPhase implements GamePhaseExecutor {
messages.add(miniMessage.deserialize("<green><team> has won with <score> captures!</green>",
Placeholder.component("team", winner.getName()),
Placeholder.parsed("score", String.valueOf(highestScore))));
ConsoleCommandSender consoleSender = Bukkit.getConsoleSender();
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
winner.getPlayers().forEach(teamPlayer -> {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(teamPlayer.getUuid());
String name = offlinePlayer.getName();
if (name == null) {
return;
}
Bukkit.dispatchCommand(consoleSender, String.format("lp user %s permission settemp ctf.victory.%s true 14d", name, winner.getId()));
});
});
if (wins.size() <= 1) {
return messages;
}
@@ -1,5 +1,7 @@
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;
@@ -19,6 +21,9 @@ import java.util.UUID;
@RequiredArgsConstructor
@Getter
public final class PlayerStat {
@JsonIgnore
public static Main main;
@JsonIgnore
private boolean touched;
@JsonIgnore
@@ -48,7 +53,12 @@ public final class PlayerStat {
switch (stat) {
case COMPLETED_GAME -> {
if (!completedGame) {
Bukkit.dispatchCommand(commandSender, String.format("lp user %s permission settemp ctf.game.completed true 14d", inGameName));
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;
}
@@ -73,7 +83,10 @@ public final class PlayerStat {
}
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.dispatchCommand(commandSender, String.format("lp user %s permission settemp ctf.game.first_powdered_snow_death true 14d", inGameName));
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()));
@@ -5,7 +5,6 @@ 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.Permission;
import net.dv8tion.jda.api.entities.*;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -68,7 +67,7 @@ public class DiscordTeam {
log.info("Unable to remove team role from server owner");
return;
}
member.getGuild().removeRoleFromMember(member, role).queue(ignored -> kickFromVoiceIfNeeded(member));
member.getGuild().removeRoleFromMember(member, role).queue();
});
}
@@ -82,21 +81,7 @@ public class DiscordTeam {
member.getRoles().stream()
.filter(otherRole -> otherRole.getIdLong() == otherTeam.getDiscordRole())
.forEach(otherRole -> member.getGuild().removeRoleFromMember(member, otherRole).queue()));
member.getGuild().addRoleToMember(member, role).queue(ignored -> kickFromVoiceIfNeeded(member));
member.getGuild().addRoleToMember(member, role).queue();
});
}
private void kickFromVoiceIfNeeded(@NotNull Member member) {
GuildVoiceState voiceState = member.getVoiceState();
if (voiceState == null || !voiceState.inAudioChannel()) {
return;
}
AudioChannel channel = voiceState.getChannel();
if (channel == null) {
return;
}
if (!member.hasPermission(channel, Permission.VOICE_CONNECT)) {
member.getGuild().kickVoiceMember(member).queue();
}
}
}
+2 -2
View File
@@ -1,3 +1,3 @@
#Sat Mar 01 01:12:24 CET 2025
buildNumber=95
#Sun Mar 02 00:42:30 CET 2025
buildNumber=112
version=0.1