Add support for tracking custom statistics in BattleArena

This commit is contained in:
RednedEpic committed 2024-08-28 21:37:46 -05:00
1 parent f793fa21cf
commit ee49462345
11 files changed
+191 -13

No files matched your search

@@ -456,6 +456,9 @@ public class BattleTracker extends JavaPlugin {
Configuration config = YamlConfiguration.loadConfiguration(configFile);
try {
this.config = BattleTrackerConfig.load(config);
if (config.getBoolean("debug-mode", false)) {
this.debugMode = true;
}
} catch (Exception e) {
this.error("Failed to load BattleTracker configuration!", e);
if (!reload) {
@@ -56,7 +56,7 @@ class BattleTrackerListener implements Listener {
if (this.plugin.isDebugMode()) {
long startTimestamp = System.currentTimeMillis();
saveFuture.thenRun(() -> this.plugin.info("Saved data for {} in {}ms", player.getName(), System.currentTimeMillis() - startTimestamp));
saveFuture.thenRun(() -> this.plugin.info("Saved {} data for {} in {}ms", tracker.getName(), player.getName(), System.currentTimeMillis() - startTimestamp));
}
if (Bukkit.isStopping()) {
@@ -27,8 +27,8 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class SqlTracker implements Tracker {
private final BattleTracker plugin;
private final String name;
protected final BattleTracker battleTracker;
protected final String name;
private final RatingCalculator calculator;
private final Set<TrackedDataType> trackedData;
private final List<String> disabledWorlds;
@@ -42,12 +42,12 @@ public class SqlTracker implements Tracker {
private long lastTopLoad = 0;
public SqlTracker(BattleTracker plugin, String name, RatingCalculator calculator, Set<TrackedDataType> trackedData, List<String> disabledWorlds) {
this(plugin, name, calculator, trackedData, disabledWorlds, null);
public SqlTracker(BattleTracker battleTracker, String name, RatingCalculator calculator, Set<TrackedDataType> trackedData, List<String> disabledWorlds) {
this(battleTracker, name, calculator, trackedData, disabledWorlds, null);
}
public SqlTracker(BattleTracker plugin, String name, RatingCalculator calculator, Set<TrackedDataType> trackedData, List<String> disabledWorlds, TrackerSqlSerializer sqlSerializer) {
this.plugin = plugin;
public SqlTracker(BattleTracker battleTracker, String name, RatingCalculator calculator, Set<TrackedDataType> trackedData, List<String> disabledWorlds, TrackerSqlSerializer sqlSerializer) {
this.battleTracker = battleTracker;
this.name = name;
this.calculator = calculator;
this.trackedData = trackedData;
@@ -76,7 +76,7 @@ public class SqlTracker implements Tracker {
@Override
public CompletableFuture<@Nullable Record> getRecord(OfflinePlayer player) {
return this.records.getOrLoad(player.getUniqueId(), this.sqlSerializer.loadRecord(player.getUniqueId())).exceptionally(e -> {
this.plugin.error("Failed to load record for {}", player.getUniqueId(), e);
this.battleTracker.error("Failed to load record for {}", player.getUniqueId(), e);
return null;
});
}
@@ -100,6 +100,11 @@ public class SqlTracker implements Tracker {
);
}
@Override
public List<StatType> getAdditionalStats() {
return List.of();
}
@Override
public Record getRecord(Player player) {
// Online players will always have a record
@@ -125,7 +130,7 @@ public class SqlTracker implements Tracker {
public CompletableFuture<VersusTally> getVersusTally(OfflinePlayer player1, OfflinePlayer player2) {
return this.tallies.getOrLoad(tally -> tally.isTallyFor(player1.getUniqueId(), player2.getUniqueId()), this.sqlSerializer.loadVersusTally(player1.getUniqueId(), player2.getUniqueId())
.exceptionally(e -> {
this.plugin.error("Failed to load tally entries for {} and {}", player1.getUniqueId(), player2.getUniqueId(), e);
this.battleTracker.error("Failed to load tally entries for {} and {}", player1.getUniqueId(), player2.getUniqueId(), e);
return null;
}));
}
@@ -168,7 +173,7 @@ public class SqlTracker implements Tracker {
Record record1 = this.records.getCached(entry.id1());
Record record2 = this.records.getCached(entry.id2());
if (record1 == null || record2 == null) {
this.plugin.warn("Failed to call tally entry event for {} and {} as one of the records was not found!", entry.id1(), entry.id2());
this.battleTracker.warn("Failed to call tally entry event for {} and {} as one of the records was not found!", entry.id1(), entry.id2());
return;
}
@@ -184,7 +189,7 @@ public class SqlTracker implements Tracker {
return entries.stream().filter(entry -> entry.id1().equals(uuid)).toList();
}).exceptionally(e -> {
this.plugin.error("Failed to load tally entries for {}", uuid, e);
this.battleTracker.error("Failed to load tally entries for {}", uuid, e);
return List.of();
});
}
@@ -287,7 +292,7 @@ public class SqlTracker implements Tracker {
this.features.put(feature.getClass(), feature);
if (feature.enabled()) {
feature.onEnable(this.plugin, this);
feature.onEnable(this.battleTracker, this);
}
}
@@ -48,6 +48,15 @@ public interface Tracker {
*/
CompletableFuture<List<Record>> getTopRecords(int limit, StatType orderBy);
/**
* Returns additional stats that are tracked by this tracker. These are
* typically populated by third party trackers that wish to track stats
* that are not included in BattleTracker by default.
*
* @return additional stats that are tracked by this tracker
*/
List<StatType> getAdditionalStats();
/**
* Returns the record for the given Player.
*
@@ -1,5 +1,7 @@
package org.battleplugins.tracker.feature.battlearena;
import org.battleplugins.arena.Arena;
import org.battleplugins.arena.BattleArena;
import org.battleplugins.tracker.BattleTracker;
import org.battleplugins.tracker.SqlTracker;
import org.battleplugins.tracker.TrackedDataType;
@@ -7,6 +9,7 @@ import org.battleplugins.tracker.sql.TrackerSqlSerializer;
import org.battleplugins.tracker.stat.StatType;
import org.battleplugins.tracker.stat.calculator.RatingCalculator;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -14,12 +17,28 @@ public class ArenaTracker extends SqlTracker {
public static StatType WINS = StatType.create("wins", "Wins", true);
public static StatType LOSSES = StatType.create("losses", "Losses", true);
private List<StatType> additionalStats;
public ArenaTracker(BattleTracker plugin, String name, RatingCalculator calculator, Set<TrackedDataType> trackedData, List<String> disabledWorlds) {
super(plugin, name, calculator, trackedData, disabledWorlds);
}
@Override
protected TrackerSqlSerializer createSerializer() {
Arena arena = BattleArena.getInstance().getArena(this.name);
if (arena == null) {
throw new IllegalStateException("Arena " + this.name + " does not exist!");
}
List<StatType> additionalStats = BattleArenaHandler.getTrackedStats(arena)
.stream()
.map(BattleArenaHandler.StatInfo::statType)
.toList();
if (!additionalStats.isEmpty()) {
this.additionalStats = additionalStats;
}
List<StatType> generalStats = List.of(
WINS, LOSSES,
StatType.KILLS, StatType.DEATHS, StatType.TIES,
@@ -27,10 +46,18 @@ public class ArenaTracker extends SqlTracker {
StatType.MAX_RATING, StatType.MAX_KD_RATIO
);
List<StatType> allStats = new ArrayList<>(additionalStats);
allStats.addAll(generalStats);
return new TrackerSqlSerializer(
this,
generalStats,
allStats.stream().distinct().toList(),
List.of(WINS, LOSSES, StatType.TIES)
);
}
@Override
public List<StatType> getAdditionalStats() {
return this.additionalStats == null ? List.of() : this.additionalStats;
}
}
@@ -1,17 +1,24 @@
package org.battleplugins.tracker.feature.battlearena;
import org.battleplugins.arena.Arena;
import org.battleplugins.arena.ArenaPlayer;
import org.battleplugins.arena.messages.Message;
import org.battleplugins.arena.options.ArenaOptionType;
import org.battleplugins.arena.options.types.BooleanArenaOption;
import org.battleplugins.arena.stat.ArenaStat;
import org.battleplugins.arena.stat.ArenaStats;
import org.battleplugins.tracker.BattleTracker;
import org.battleplugins.tracker.feature.message.MessageAudience;
import org.battleplugins.tracker.message.Messages;
import org.battleplugins.tracker.stat.StatType;
import org.battleplugins.tracker.util.MessageUtil;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.HandlerList;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class BattleArenaHandler {
public static final ArenaOptionType<BooleanArenaOption> TRACK_STATISTICS_OPTION = ArenaOptionType.create("track-statistics", BooleanArenaOption::new);
@@ -44,4 +51,36 @@ public class BattleArenaHandler {
public static Message convertMessage(String message) {
return org.battleplugins.arena.messages.Messages.wrap(MessageUtil.serialize(Messages.get(message)));
}
public static List<StatInfo> getTrackedStats(Arena arena) {
List<StatInfo> stats = new ArrayList<>();
ConfigurationSection trackingSection = arena.getConfig().get("tracking");
if (trackingSection != null && trackingSection.isConfigurationSection("stats-tracked")) {
for (String stat : trackingSection.getConfigurationSection("stats-tracked").getKeys(false)) {
ArenaStat<?> arenaStat = ArenaStats.get(stat);
if (arenaStat == null) {
BattleTracker.getInstance().warn("Invalid Arena stat {} for arena " + arena.getName());
continue;
}
StatType statType = StatType.get(trackingSection.getString("stats-tracked." + stat + ".key"));
if (statType != null) {
Type type = Type.valueOf(trackingSection.getString("stats-tracked." + stat + ".type", "add").toUpperCase(Locale.ROOT));
stats.add(new StatInfo(arenaStat, statType, type));
} else {
BattleTracker.getInstance().warn("Invalid StatType {} for arena " + arena.getName());
}
}
}
return stats;
}
public record StatInfo(ArenaStat<?> stat, StatType statType, Type type) {
}
public enum Type {
ADD,
REMOVE
}
}
@@ -6,6 +6,7 @@ import org.battleplugins.arena.ArenaPlayer;
import org.battleplugins.arena.competition.JoinResult;
import org.battleplugins.arena.competition.LiveCompetition;
import org.battleplugins.arena.competition.phase.CompetitionPhaseType;
import org.battleplugins.arena.event.BattleArenaPostInitializeEvent;
import org.battleplugins.arena.event.BattleArenaReloadEvent;
import org.battleplugins.arena.event.BattleArenaReloadedEvent;
import org.battleplugins.arena.event.arena.ArenaCreateExecutorEvent;
@@ -15,7 +16,10 @@ import org.battleplugins.arena.event.arena.ArenaVictoryEvent;
import org.battleplugins.arena.event.player.ArenaKillEvent;
import org.battleplugins.arena.event.player.ArenaLeaveEvent;
import org.battleplugins.arena.event.player.ArenaPreJoinEvent;
import org.battleplugins.arena.event.player.ArenaStatChangeEvent;
import org.battleplugins.arena.options.types.BooleanArenaOption;
import org.battleplugins.arena.stat.ArenaStat;
import org.battleplugins.arena.stat.ArenaStats;
import org.battleplugins.tracker.BattleTracker;
import org.battleplugins.tracker.SqlTracker;
import org.battleplugins.tracker.TrackedDataType;
@@ -46,6 +50,20 @@ public class BattleArenaListener implements Listener {
this.battleTracker = battleTracker;
}
@EventHandler
public void onBattleArenaPostInitialize(BattleArenaPostInitializeEvent event) {
for (ArenaStat<?> stat : ArenaStats.values()) {
String statKey = stat.getKey().replace("-", "_");
// There may be some crossover with stats (i.e. kills, deaths, etc.), so ensure
// we don't have any duplicates before proceeding
if (StatType.get(statKey) != null) {
continue;
}
StatType.create(statKey, stat.getName(), true);
}
}
@EventHandler
public void onBattleArenaReload(BattleArenaReloadEvent event) {
this.reloaded = true;
@@ -340,4 +358,46 @@ public class BattleArenaListener implements Listener {
}
}
}
@EventHandler
public void onStatChange(ArenaStatChangeEvent<?> event) {
if (!(event.getOldValue() instanceof Number) || !(event.getNewValue() instanceof Number)) {
return;
}
Tracker tracker = this.battleTracker.getTracker(event.getArena().getName());
if (tracker == null) {
return;
}
if (!(event.getStatHolder() instanceof ArenaPlayer arenaPlayer)) {
return;
}
Record record = tracker.getRecord(arenaPlayer.getPlayer());
if (!record.isTracking()) {
return;
}
List<BattleArenaHandler.StatInfo> trackedStats = BattleArenaHandler.getTrackedStats(event.getArena());
for (BattleArenaHandler.StatInfo info : trackedStats) {
if (!info.stat().equals(event.getStat())) {
continue;
}
Number oldValue = (Number) event.getOldValue();
Number newValue = (Number) event.getNewValue();
switch (info.type()) {
case ADD -> {
float delta = newValue.floatValue() - oldValue.floatValue();
record.setValue(info.statType(), record.getStat(info.statType()) + delta);
}
case REMOVE -> {
float delta = oldValue.floatValue() - newValue.floatValue();
record.setValue(info.statType(), record.getStat(info.statType()) - delta);
}
}
}
}
}
@@ -1,6 +1,7 @@
package org.battleplugins.tracker.feature.battlearena;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.battleplugins.arena.Arena;
import org.battleplugins.arena.command.ArenaCommand;
import org.battleplugins.arena.command.Argument;
@@ -92,5 +93,11 @@ public class TrackerSubExecutor implements SubCommandExecutor {
private void rank(CommandSender sender, Record record) {
Util.sendTrackerMessage(sender, "rank-arena", -1, record);
if (!this.tracker.getAdditionalStats().isEmpty()) {
Messages.send(sender, "rank-arena-stats");
for (StatType stat : this.tracker.getAdditionalStats()) {
sender.sendMessage(Component.text("- ", NamedTextColor.GRAY).append(Component.text(stat.getName() + ": ", NamedTextColor.RED)).append(Component.text(Util.STAT_FORMAT.format(record.getStat(stat)), NamedTextColor.GOLD)));
}
}
}
}
@@ -7,6 +7,7 @@ import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.battleplugins.tracker.BattleTracker;
import org.battleplugins.tracker.util.MessageUtil;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
@@ -37,6 +38,19 @@ public class Messages {
Component message = MessageUtil.MINI_MESSAGE.deserialize(messageText);
MESSAGES.put(key, message);
}
boolean newMessages = false;
// 08-28-2024
newMessages |= addMessage(messagesConfig, "rank-arena-stats", "<yellow>Arena Statistics:</yellow>");
if (newMessages) {
try {
messagesConfig.save(messagesFile);
} catch (Exception e) {
BattleTracker.getInstance().warn("Failed to save new messages to messages file!");
}
}
}
public static Component get(String key, String... replacements) {
@@ -154,4 +168,13 @@ public class Messages {
component = component.children(children);
return component;
}
private static boolean addMessage(Configuration configuration, String key, String message) {
if (configuration.contains(key)) {
return false;
}
configuration.set(key, message);
return true;
}
}
@@ -54,6 +54,10 @@ public final class StatType {
}
public static StatType create(String key, String name, boolean tracked) {
if (STAT_TYPES.containsKey(key)) {
throw new IllegalArgumentException("Stat type with key " + key + " already exists!");
}
return new StatType(key, name, tracked);
}
+1
View File
@@ -16,6 +16,7 @@ player-has-no-record: "<red>The player <gold>{}</gold> has no record!</red>"
player-has-no-tally: "<red>The players <gold>{}</gold> and <gold>{}</gold> have no tally!</red>"
rank: "<gold>%player%</gold> <gray>-</gray> <aqua>%rating%</aqua> <yellow>(Max Rating: %max_rating%)</yellow> <red>Kills: <gold>%kills%</gold> Deaths: <gold>%deaths%</gold> KDR: <gold>%kd_ratio%</gold></red>"
rank-arena: "<gold>%player%</gold> <gray>-</gray> <aqua>%rating%</aqua> <yellow>(Max Rating: %max_rating%)</yellow> <red>Wins: <gold>%wins%</gold> Losses: <gold>%losses%</gold> <red>Kills: <gold>%kills%</gold> Deaths: <gold>%deaths%</gold> KDR: <gold>%kd_ratio%</gold></red>"
rank-arena-stats: "<yellow>Arena Statistics:</yellow>"
recap: "Recap"
recap-damage-log: "<gold>Damage Log:</gold>"
recap-click-for-more: "<aqua>Click for more information...</aqua>"