diff --git a/src/main/java/com/alttd/ctf/Main.java b/src/main/java/com/alttd/ctf/Main.java index 2662d83..7f20c14 100644 --- a/src/main/java/com/alttd/ctf/Main.java +++ b/src/main/java/com/alttd/ctf/Main.java @@ -16,6 +16,7 @@ import com.alttd.ctf.gui.GUIInventory; import com.alttd.ctf.gui.GUIListener; import com.alttd.ctf.json_config.JacksonConfig; import com.alttd.ctf.json_config.JsonConfigManager; +import com.alttd.ctf.stats.PlayerStat; import com.alttd.ctf.team.Team; import com.github.yannicklamprecht.worldborder.api.WorldBorderApi; import lombok.extern.slf4j.Slf4j; @@ -29,6 +30,8 @@ import org.bukkit.plugin.java.JavaPlugin; import java.io.File; import java.io.IOException; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.stream.Collectors; @Slf4j @@ -49,16 +52,11 @@ public class Main extends JavaPlugin { WorldBorderApi worldBorderApi = worldBorder(); this.gameManager = new GameManager(worldBorderApi); registerTeams(); //Skipped in reloadConfig if gameManager is not created yet + loadPlayerStats(); //Skipped in reloadConfig if gameManager is not created yet flag = new Flag(this, gameManager); new CommandManager(this, gameManager, flag, worldBorderApi); //Ensuring immediate respawn is on in all worlds - log.info("Enabling immediate respawn for {}.", GameConfig.FLAG.world); - World world = Bukkit.getWorld(GameConfig.FLAG.world); - if (world != null) { - world.setGameRule(GameRule.DO_IMMEDIATE_RESPAWN, true); - } else { - log.error("No valid flag world defined, unable to modify game rules"); - } + enableImmediateRespawn(); registerEvents(flag, worldBorderApi); } @@ -68,6 +66,7 @@ public class Main extends JavaPlugin { GameConfig.reload(this); if (gameManager != null) { registerTeams(); + loadPlayerStats(); } } @@ -83,6 +82,16 @@ public class Main extends JavaPlugin { return worldBorderApiRegisteredServiceProvider.getProvider(); } + private void enableImmediateRespawn() { + log.info("Enabling immediate respawn for {}.", GameConfig.FLAG.world); + World world = Bukkit.getWorld(GameConfig.FLAG.world); + if (world != null) { + world.setGameRule(GameRule.DO_IMMEDIATE_RESPAWN, true); + } else { + log.error("No valid flag world defined, unable to modify game rules"); + } + } + private void registerEvents(Flag flag, WorldBorderApi worldBorderApi) { PluginManager pluginManager = getServer().getPluginManager(); pluginManager.registerEvents(new SnowballEvent(gameManager), this); @@ -119,4 +128,38 @@ public class Main extends JavaPlugin { teams.forEach(gameManager::registerTeam); } + private void loadPlayerStats() { + JsonConfigManager config = new JsonConfigManager<>(JacksonConfig.configureMapper()); + List playerStats; + + try { + File playerStatsDirectory = new File(getDataFolder(), "player_stats"); + if (!playerStatsDirectory.exists() && !playerStatsDirectory.mkdirs()) { + log.error("Unable to make playerStats directory at {} shutting down plugin", playerStatsDirectory.getAbsolutePath()); + } + playerStats = config.loadConfigs(PlayerStat.class, playerStatsDirectory); + } catch (IOException e) { + log.error("Unable to load teams, shutting down plugin", e); + getServer().getPluginManager().disablePlugin(this); + return; + } + + gameManager.setPlayerStats(playerStats); + final JsonConfigManager jsonConfigManager = new JsonConfigManager<>(JacksonConfig.configureMapper()); + Runnable runnable = () -> { + File playerStatsDirectory = new File(getDataFolder(), "player_stats"); + gameManager.getPlayerStats().stream().filter(PlayerStat::isTouched).forEach(playerStat -> { + try { + jsonConfigManager.saveConfig(playerStat, playerStatsDirectory, String.format("%s.%s", playerStat.getUuid(), "json")); + playerStat.setUnTouched(); + } catch (IOException e) { + log.error("Failed to save player stats for [{}].", playerStat.getUuid(), e); + } + }); + }; + + ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 1, java.util.concurrent.TimeUnit.MINUTES); + } + } \ No newline at end of file diff --git a/src/main/java/com/alttd/ctf/events/OnPlayerOnlineStatus.java b/src/main/java/com/alttd/ctf/events/OnPlayerOnlineStatus.java index 24d5028..699efe6 100644 --- a/src/main/java/com/alttd/ctf/events/OnPlayerOnlineStatus.java +++ b/src/main/java/com/alttd/ctf/events/OnPlayerOnlineStatus.java @@ -61,7 +61,7 @@ public class OnPlayerOnlineStatus implements Listener { log.error("No team found when attempting to add freshly joined player to a team"); return; } - teamPlayer = min.get().addPlayer(player); + teamPlayer = gameManager.registerPlayer(min.get(), player); } else { teamPlayer = optionalTeamPlayer.get(); teamPlayer.getTeam().addToScoreboard(player); diff --git a/src/main/java/com/alttd/ctf/game/GameManager.java b/src/main/java/com/alttd/ctf/game/GameManager.java index f004a43..21e895f 100644 --- a/src/main/java/com/alttd/ctf/game/GameManager.java +++ b/src/main/java/com/alttd/ctf/game/GameManager.java @@ -6,6 +6,7 @@ import com.alttd.ctf.game.phases.CombatPhase; import com.alttd.ctf.game.phases.EndedPhase; import com.alttd.ctf.game.phases.GatheringPhase; import com.alttd.ctf.game_class.creation.FighterCreator; +import com.alttd.ctf.stats.PlayerStat; import com.alttd.ctf.team.Team; import com.alttd.ctf.team.TeamPlayer; import com.github.yannicklamprecht.worldborder.api.WorldBorderApi; @@ -24,6 +25,7 @@ public class GameManager { private final HashMap phases; private RunningGame runningGame; private final HashMap teams = new HashMap<>(); + private final HashMap playerStats = new HashMap<>(); public GameManager(WorldBorderApi worldBorderApi) { phases = new HashMap<>(); @@ -37,9 +39,12 @@ public class GameManager { return runningGame == null ? Optional.empty() : Optional.of(runningGame.getCurrentPhase()); } - public void registerPlayer(Team team, Player player) { + public TeamPlayer registerPlayer(Team team, Player player) { unregisterPlayer(player); - teams.get(team.getId()).addPlayer(player); + UUID uuid = player.getUniqueId(); + PlayerStat playerStat = playerStats + .computeIfAbsent(uuid, (ignored) -> new PlayerStat(uuid, player.getName())); + return teams.get(team.getId()).addPlayer(player, playerStat); } public void unregisterPlayer(Player player) { @@ -107,4 +112,13 @@ public class GameManager { } return runningGame.skipCurrentPhase(); } + + public void setPlayerStats(List playerStats) { + this.playerStats.clear(); + playerStats.forEach(playerStat -> this.playerStats.put(playerStat.getUuid(), playerStat)); + } + + public Collection getPlayerStats() { + return this.playerStats.values(); + } } diff --git a/src/main/java/com/alttd/ctf/game/phases/ClassSelectionPhase.java b/src/main/java/com/alttd/ctf/game/phases/ClassSelectionPhase.java index 87b4719..02bfc68 100644 --- a/src/main/java/com/alttd/ctf/game/phases/ClassSelectionPhase.java +++ b/src/main/java/com/alttd/ctf/game/phases/ClassSelectionPhase.java @@ -62,7 +62,7 @@ public class ClassSelectionPhase implements GamePhaseExecutor { .filter(player -> gameManager.getTeamPlayer(player).isEmpty()) .forEach(player -> { Team team = teamCircularIterator.next(); - team.addPlayer(player); + gameManager.registerPlayer(team, player); player.sendRichMessage("You joined !", Placeholder.component("team", team.getName())); }); } else { diff --git a/src/main/java/com/alttd/ctf/stats/PlayerStat.java b/src/main/java/com/alttd/ctf/stats/PlayerStat.java index c612854..487d72c 100644 --- a/src/main/java/com/alttd/ctf/stats/PlayerStat.java +++ b/src/main/java/com/alttd/ctf/stats/PlayerStat.java @@ -1,15 +1,25 @@ package com.alttd.ctf.stats; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; import java.util.UUID; +@RequiredArgsConstructor @Getter public final class PlayerStat { + @JsonIgnore + private boolean touched; + @JsonIgnore private static final CommandSender commandSender = Bukkit.getConsoleSender(); + + @NotNull private final UUID uuid; + @NotNull private final String inGameName; private boolean completedGame = false; @@ -23,11 +33,6 @@ public final class PlayerStat { 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 -> { @@ -45,6 +50,7 @@ public final class PlayerStat { 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())); } + touched = true; } public void increaseStat(Stat stat, double value) throws IllegalArgumentException { @@ -53,6 +59,11 @@ public final class PlayerStat { case DAMAGE_HEALED -> damageHealed += value; default -> throw new IllegalArgumentException(String.format("%s cannot be passed with a number", stat.name())); } + touched = true; + } + + public void setUnTouched() { + touched = false; } } diff --git a/src/main/java/com/alttd/ctf/team/Team.java b/src/main/java/com/alttd/ctf/team/Team.java index 748abf9..f02fb8e 100644 --- a/src/main/java/com/alttd/ctf/team/Team.java +++ b/src/main/java/com/alttd/ctf/team/Team.java @@ -1,6 +1,7 @@ package com.alttd.ctf.team; import com.alttd.ctf.game.GameManager; +import com.alttd.ctf.stats.PlayerStat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; @@ -73,10 +74,10 @@ public class Team { discordTeam = new DiscordTeam(gameManager); } - public TeamPlayer addPlayer(Player player) { + public TeamPlayer addPlayer(Player player, PlayerStat playerStat) { removeFromScoreBoard(player); UUID uuid = player.getUniqueId(); - TeamPlayer teamPlayer = new TeamPlayer(player, this); + TeamPlayer teamPlayer = new TeamPlayer(player, this, playerStat); 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 a056144..4f60369 100644 --- a/src/main/java/com/alttd/ctf/team/TeamPlayer.java +++ b/src/main/java/com/alttd/ctf/team/TeamPlayer.java @@ -30,10 +30,10 @@ public class TeamPlayer { private GameClass gameClass; private boolean isDead = false; - protected TeamPlayer(Player player, Team team) { + protected TeamPlayer(Player player, Team team, PlayerStat playerStat) { this.uuid = player.getUniqueId(); this.team = team; - this.playerStat = new PlayerStat(uuid, player.getName()); + this.playerStat = playerStat; } public void respawn(@NotNull Player player, @NotNull WorldBorderApi worldBorderApi, @NotNull GamePhase gamePhase) {