From eeed5b4c54180f622a90d6b1d7ed4b4586cb8cd8 Mon Sep 17 00:00:00 2001 From: Teriuihi Date: Sun, 23 Feb 2025 01:14:27 +0100 Subject: [PATCH] Add player statistics tracking and integration. Introduced a comprehensive system for tracking player stats, including kills, flags captured, damage dealt, and more. Integrated stat recording into related game events and ensured proper handling for both individual and team actions. This enhances gameplay analysis and future feature potential. --- .../com/alttd/ctf/events/OnPlayerDeath.java | 11 +++ .../com/alttd/ctf/events/OtherGameEvents.java | 86 +++++++++++++++++++ .../com/alttd/ctf/events/SnowballEvent.java | 23 ++--- src/main/java/com/alttd/ctf/flag/Flag.java | 10 ++- .../java/com/alttd/ctf/stats/PlayerStat.java | 58 +++++++++++++ src/main/java/com/alttd/ctf/stats/Stat.java | 14 +++ src/main/java/com/alttd/ctf/team/Team.java | 2 +- .../java/com/alttd/ctf/team/TeamPlayer.java | 16 +++- version.properties | 4 +- 9 files changed, 203 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/alttd/ctf/events/OtherGameEvents.java create mode 100644 src/main/java/com/alttd/ctf/stats/PlayerStat.java create mode 100644 src/main/java/com/alttd/ctf/stats/Stat.java 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