Add persistent player statistics tracking
Introduced the `PlayerStat` system to persist player statistics across sessions. Enhanced player management by linking `PlayerStat` with teams and saving/updating stats periodically. Refactored related methods to support the new functionality and ensure data integrity.
This commit is contained in:
parent
e62d0df9df
commit
c738f02d17
|
|
@ -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<PlayerStat> config = new JsonConfigManager<>(JacksonConfig.configureMapper());
|
||||
List<PlayerStat> 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<PlayerStat> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<GamePhase, GamePhaseExecutor> phases;
|
||||
private RunningGame runningGame;
|
||||
private final HashMap<Integer, Team> teams = new HashMap<>();
|
||||
private final HashMap<UUID, PlayerStat> 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<PlayerStat> playerStats) {
|
||||
this.playerStats.clear();
|
||||
playerStats.forEach(playerStat -> this.playerStats.put(playerStat.getUuid(), playerStat));
|
||||
}
|
||||
|
||||
public Collection<PlayerStat> getPlayerStats() {
|
||||
return this.playerStats.values();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <team>!", Placeholder.component("team", team.getName()));
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user