Add support for tracking custom statistics in BattleArena
This commit is contained in:
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>"
|
||||
|
||||
Reference in new issue
Block a user