diff --git a/src/main/java/com/alttd/ctf/events/OnPlayerDeath.java b/src/main/java/com/alttd/ctf/events/OnPlayerDeath.java index b92c9ab..a636527 100644 --- a/src/main/java/com/alttd/ctf/events/OnPlayerDeath.java +++ b/src/main/java/com/alttd/ctf/events/OnPlayerDeath.java @@ -5,12 +5,14 @@ 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.stats.Stat; 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.damage.DamageEffect; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -47,6 +49,15 @@ public class OnPlayerDeath implements Listener { gameManager.getTeamPlayer(player) .ifPresent(TeamPlayer::setDead); flag.handleCarrierDeathOrDisconnect(player); + + try { + if (event.getDamageSource().getDamageType().getDamageEffect().equals(DamageEffect.FREEZING)) { + gameManager.getTeamPlayer(player) + .ifPresent(teamPlayer -> teamPlayer.increaseStat(Stat.DEATHS_IN_POWDERED_SNOW)); + } + } catch (Exception e) { + log.warn("Failed to check for death cause due to exception", e); + } } @EventHandler diff --git a/src/main/java/com/alttd/ctf/events/OtherGameEvents.java b/src/main/java/com/alttd/ctf/events/OtherGameEvents.java new file mode 100644 index 0000000..20de13d --- /dev/null +++ b/src/main/java/com/alttd/ctf/events/OtherGameEvents.java @@ -0,0 +1,86 @@ +package com.alttd.ctf.events; + +import com.alttd.ctf.game.GameManager; +import com.alttd.ctf.stats.Stat; +import com.alttd.ctf.team.TeamPlayer; +import lombok.extern.slf4j.Slf4j; +import org.bukkit.Tag; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.Player; +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.entity.PotionSplashEvent; +import org.bukkit.potion.PotionEffectType; + +import java.util.Optional; + +@Slf4j +public class OtherGameEvents implements Listener { + + private final GameManager gameManager; + + public OtherGameEvents(GameManager gameManager) { + this.gameManager = gameManager; + } + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + if (gameManager.getGamePhase().isEmpty()) { + return; + } + if (!Tag.SNOW.isTagged(event.getBlock().getType())) { + event.setCancelled(true); + return; + } + gameManager.getTeamPlayer(event.getPlayer()) + .ifPresent(teamPlayer -> teamPlayer.increaseStat(Stat.SNOW_MINED)); + } + + @EventHandler + public void onBlockPlace(BlockBreakEvent event) { + if (gameManager.getGamePhase().isEmpty()) { + return; + } + if (!Tag.SNOW.isTagged(event.getBlock().getType())) { + event.setCancelled(true); + log.warn("Player {} placed a block that wasn't snow: {}", + event.getPlayer().getName(), event.getBlock().getType()); + return; + } + gameManager.getTeamPlayer(event.getPlayer()) + .ifPresent(teamPlayer -> teamPlayer.increaseStat(Stat.BLOCKS_PLACED)); + } + + @EventHandler + public void onPotionSplash(PotionSplashEvent event) { + ThrownPotion thrownPotion = event.getPotion(); + if (!(thrownPotion.getShooter() instanceof Player player)) + return; + + Optional optionalTeamPlayer = gameManager.getTeamPlayer(player); + if (optionalTeamPlayer.isEmpty()) + return; + + double totalHealing = thrownPotion.getEffects().stream() + .filter(effect -> effect.getType() == PotionEffectType.INSTANT_HEALTH) + .flatMapToDouble(effect -> event.getAffectedEntities().stream() + .filter(livingEntity -> livingEntity instanceof Player) + .map(livingEntity -> (Player) livingEntity) + .mapToDouble(target -> { + AttributeInstance playerMaxHealth = target.getAttribute(Attribute.GENERIC_MAX_HEALTH); + if (playerMaxHealth == null) { + return 0; + } + double missingHealth = playerMaxHealth.getValue() - target.getHealth(); + double potentialHealing = (effect.getAmplifier() + 1) * event.getIntensity(target); + return Math.min(potentialHealing, missingHealth); + })) + .sum(); + + optionalTeamPlayer.get().increaseStat(Stat.DAMAGE_HEALED, totalHealing); + } + +} diff --git a/src/main/java/com/alttd/ctf/events/SnowballEvent.java b/src/main/java/com/alttd/ctf/events/SnowballEvent.java index e7138c4..1fabd2d 100644 --- a/src/main/java/com/alttd/ctf/events/SnowballEvent.java +++ b/src/main/java/com/alttd/ctf/events/SnowballEvent.java @@ -3,16 +3,15 @@ package com.alttd.ctf.events; import com.alttd.ctf.game.GameManager; import com.alttd.ctf.game.GamePhase; import com.alttd.ctf.game_class.GameClass; +import com.alttd.ctf.stats.Stat; import com.alttd.ctf.team.TeamPlayer; import lombok.extern.slf4j.Slf4j; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.Tag; import org.bukkit.entity.Snowball; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.entity.Player; -import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.util.Vector; @@ -48,8 +47,13 @@ public class SnowballEvent implements Listener { GameClass shooterClass = shooterTeamPlayer.getGameClass(); shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed()); - double newHealth = hitPlayer.getHealth() - shooterClass.getDamage(); - hitPlayer.setHealth(Math.max(newHealth, 0)); + double newHealth = Math.max(hitPlayer.getHealth() - shooterClass.getDamage(), 0); + hitPlayer.setHealth(newHealth); + + shooterTeamPlayer.increaseStat(Stat.DAMAGE_DONE, shooterClass.getDamage()); + if (newHealth <= 0) { + shooterTeamPlayer.increaseStat(Stat.KILLS); + } log.debug("{} health was set to {} because of a snowball thrown by {}", hitPlayer.getName(), Math.max(newHealth, 0), shooter.getName()); }); @@ -60,19 +64,10 @@ public class SnowballEvent implements Listener { handleSnowballThrown(event, (shooter, shooterTeamPlayer) -> { GameClass shooterClass = shooterTeamPlayer.getGameClass(); shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed()); + shooterTeamPlayer.increaseStat(Stat.SNOWBALLS_THROWN); }); } - @EventHandler - public void onBlockBreak(BlockBreakEvent event) { - if (gameManager.getGamePhase().isEmpty()) { - return; - } - if (Tag.SNOW.isTagged(event.getBlock().getType())) - return; - event.setCancelled(true); - } - private boolean blockedAttack(@NotNull Player hitPlayer, @NotNull Snowball snowball) { if (!hitPlayer.isBlocking()) { return false; diff --git a/src/main/java/com/alttd/ctf/flag/Flag.java b/src/main/java/com/alttd/ctf/flag/Flag.java index 6e1257b..3f4a36b 100644 --- a/src/main/java/com/alttd/ctf/flag/Flag.java +++ b/src/main/java/com/alttd/ctf/flag/Flag.java @@ -3,6 +3,7 @@ package com.alttd.ctf.flag; import com.alttd.ctf.Main; import com.alttd.ctf.config.GameConfig; import com.alttd.ctf.game.GameManager; +import com.alttd.ctf.stats.Stat; import com.alttd.ctf.team.Team; import com.alttd.ctf.team.TeamColor; import com.alttd.ctf.team.TeamPlayer; @@ -166,7 +167,8 @@ public class Flag implements Runnable { Placeholder.component("player", flagCarrier.displayName()))); Bukkit.getOnlinePlayers().forEach(player -> gameManager.getTeam(player).ifPresent(team -> - player.showTitle(team.getId() == winningTeam.getId() ? capturingTeamTitle : huntingTeamTitle))); + player.showTitle(team.getId().intValue() == winningTeam.getId().intValue() + ? capturingTeamTitle : huntingTeamTitle))); } private void spawnParticlesOnSquareBorder(Location center, double size) { @@ -222,7 +224,10 @@ public class Flag implements Runnable { flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null); gameManager.getTeamPlayer(flagCarrier) - .ifPresent(teamPlayer -> teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer)); + .ifPresent(teamPlayer -> { + teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer); + teamPlayer.increaseStat(Stat.FLAGS_CAPTURED); + }); resetFlagCarrier(); } @@ -319,6 +324,7 @@ public class Flag implements Runnable { return Math.max(updatedValue, 0); }); }); + nearbyPlayers.forEach(teamPlayer -> teamPlayer.increaseStat(Stat.TIME_SPEND_CAPTURING_FLAG)); return true; } diff --git a/src/main/java/com/alttd/ctf/stats/PlayerStat.java b/src/main/java/com/alttd/ctf/stats/PlayerStat.java new file mode 100644 index 0000000..c612854 --- /dev/null +++ b/src/main/java/com/alttd/ctf/stats/PlayerStat.java @@ -0,0 +1,58 @@ +package com.alttd.ctf.stats; + +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; + +import java.util.UUID; + +@Getter +public final class PlayerStat { + private static final CommandSender commandSender = Bukkit.getConsoleSender(); + private final UUID uuid; + private final String inGameName; + + private boolean completedGame = false; + private int flagsCaptured = 0; + private int kills = 0; + private double damageDone = 0; + private double damageHealed = 0; + private int snowMined = 0; + private int blocksPlaced = 0; + private int snowballsThrown = 0; + private long timeSpendCapturingFlag = 0; + private int deathsInPowderedSnow = 0; + + public PlayerStat(UUID uuid, String inGameName) { + this.uuid = uuid; + this.inGameName = inGameName; + } + + public void increaseStat(Stat stat) throws IllegalArgumentException { + switch (stat) { + case COMPLETED_GAME -> { + if (!completedGame) { + Bukkit.dispatchCommand(commandSender, String.format("lp user %s permission set ctf.game.completed", inGameName)); + } + completedGame = true; + } + case FLAGS_CAPTURED -> flagsCaptured++; + case KILLS -> kills++; + case SNOW_MINED -> snowMined++; + case BLOCKS_PLACED -> blocksPlaced++; + case SNOWBALLS_THROWN -> snowballsThrown++; + case TIME_SPEND_CAPTURING_FLAG -> timeSpendCapturingFlag++; + case DEATHS_IN_POWDERED_SNOW -> deathsInPowderedSnow++; //TODO announce if they are the first person to do this and save they are the first + case DAMAGE_DONE, DAMAGE_HEALED -> throw new IllegalArgumentException(String.format("%s requires a number", stat.name())); + } + } + + public void increaseStat(Stat stat, double value) throws IllegalArgumentException { + switch (stat) { + case DAMAGE_DONE -> damageDone += value; + case DAMAGE_HEALED -> damageHealed += value; + default -> throw new IllegalArgumentException(String.format("%s cannot be passed with a number", stat.name())); + } + } + +} diff --git a/src/main/java/com/alttd/ctf/stats/Stat.java b/src/main/java/com/alttd/ctf/stats/Stat.java new file mode 100644 index 0000000..6b62a3a --- /dev/null +++ b/src/main/java/com/alttd/ctf/stats/Stat.java @@ -0,0 +1,14 @@ +package com.alttd.ctf.stats; + +public enum Stat { + COMPLETED_GAME, + FLAGS_CAPTURED, + KILLS, + DAMAGE_DONE, + DAMAGE_HEALED, + SNOW_MINED, + BLOCKS_PLACED, + SNOWBALLS_THROWN, + TIME_SPEND_CAPTURING_FLAG, + DEATHS_IN_POWDERED_SNOW +} diff --git a/src/main/java/com/alttd/ctf/team/Team.java b/src/main/java/com/alttd/ctf/team/Team.java index 4de3a94..748abf9 100644 --- a/src/main/java/com/alttd/ctf/team/Team.java +++ b/src/main/java/com/alttd/ctf/team/Team.java @@ -76,7 +76,7 @@ public class Team { public TeamPlayer addPlayer(Player player) { removeFromScoreBoard(player); UUID uuid = player.getUniqueId(); - TeamPlayer teamPlayer = new TeamPlayer(uuid, this); + TeamPlayer teamPlayer = new TeamPlayer(player, this); players.put(uuid, teamPlayer); addToScoreboard(player); if (discordTeam != null) { diff --git a/src/main/java/com/alttd/ctf/team/TeamPlayer.java b/src/main/java/com/alttd/ctf/team/TeamPlayer.java index a019f4b..a056144 100644 --- a/src/main/java/com/alttd/ctf/team/TeamPlayer.java +++ b/src/main/java/com/alttd/ctf/team/TeamPlayer.java @@ -7,6 +7,8 @@ import com.alttd.ctf.game.GamePhase; import com.alttd.ctf.game_class.GameClass; import com.alttd.ctf.game_class.GameClassRetrieval; import com.alttd.ctf.gui.ClassSelectionGUI; +import com.alttd.ctf.stats.PlayerStat; +import com.alttd.ctf.stats.Stat; import com.github.yannicklamprecht.worldborder.api.WorldBorderApi; import lombok.Getter; import lombok.Setter; @@ -21,15 +23,17 @@ import java.util.*; @Getter public class TeamPlayer { + private final PlayerStat playerStat; private final UUID uuid; private final Team team; @Setter private GameClass gameClass; private boolean isDead = false; - protected TeamPlayer(UUID uuid, Team team) { - this.uuid = uuid; + protected TeamPlayer(Player player, Team team) { + this.uuid = player.getUniqueId(); this.team = team; + this.playerStat = new PlayerStat(uuid, player.getName()); } public void respawn(@NotNull Player player, @NotNull WorldBorderApi worldBorderApi, @NotNull GamePhase gamePhase) { @@ -87,4 +91,12 @@ public class TeamPlayer { public int hashCode() { return Objects.hash(uuid); } + + public void increaseStat(Stat stat) { + playerStat.increaseStat(stat); + } + + public void increaseStat(Stat stat, double amount) { + playerStat.increaseStat(stat, amount); + } } diff --git a/version.properties b/version.properties index 256d771..d2a38f0 100644 --- a/version.properties +++ b/version.properties @@ -1,3 +1,3 @@ -#Sat Feb 15 22:27:11 CET 2025 -buildNumber=66 +#Sun Feb 23 01:14:21 CET 2025 +buildNumber=70 version=0.1