Compare commits

...

2 Commits

Author SHA1 Message Date
Teriuihi 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
Teriuihi 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
8 changed files with 85 additions and 12 deletions

View File

@ -4,10 +4,7 @@ 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;
@ -94,6 +91,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);

View File

@ -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;
}

View File

@ -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() {

View File

@ -30,8 +30,10 @@ public class OtherGameEvents implements Listener {
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
if (gameManager.getGamePhase().isEmpty()) {
log.info("no game phases");
return;
}
log.info(event.getBlock().getType().toString());
if (!Tag.SNOW.isTagged(event.getBlock().getType())) {
event.setCancelled(true);
return;

View File

@ -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,9 +64,26 @@ 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) -> {
@ -72,10 +96,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 +122,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()) {

View File

@ -32,7 +32,7 @@ public class GameManager {
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(this));
}
public Optional<GamePhase> getGamePhase() {

View File

@ -2,6 +2,7 @@ package com.alttd.ctf.game.phases;
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;
@ -25,6 +26,11 @@ import java.util.Map;
public class EndedPhase implements GamePhaseExecutor {
private final MiniMessage miniMessage = MiniMessage.miniMessage();
private final GameManager gameManager;
public EndedPhase(GameManager gameManager) {
this.gameManager = gameManager;
}
@Override
public void start(Flag flag) {
@ -51,8 +57,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) {

View File

@ -1,3 +1,3 @@
#Sat Mar 01 01:12:24 CET 2025
buildNumber=95
#Sat Mar 01 19:29:28 CET 2025
buildNumber=102
version=0.1