Become independent from BattleArena
This commit is contained in:
1 parent
bb6cc849e6
commit
9426f631cb
15 files changed
+105
-730
No files matched your search
@@ -2,6 +2,8 @@
|
||||
|
||||
A standalone plugin that tracks PVP & PVE statistics along with a suite of combat features to enhance server gameplay.
|
||||
|
||||
BattleTracker records SMP activity only. Deaths, kills, ratings, KDR changes, and damage recaps from Duels-OG or Spleef-OG are excluded.
|
||||
|
||||
# Features
|
||||
## PVP and PVE Records
|
||||
One of the core features of BattleTracker is its robust tracking system for PVP and PVE statistics. BattleTracker tracks a variety of statistics including kills, deaths, killstreaks, and more. These statistics are saved in MySQL or SQLite, allowing them to be shared across Minecraft servers, or accessed by web applications.
|
||||
|
||||
@@ -27,8 +27,6 @@ repositories {
|
||||
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
maven("https://repo.purpurmc.org/snapshots")
|
||||
maven("https://repo.battleplugins.org/releases/")
|
||||
maven("https://repo.battleplugins.org/snapshots/")
|
||||
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
|
||||
}
|
||||
|
||||
@@ -39,7 +37,6 @@ dependencies {
|
||||
implementation(libs.mysql.connector)
|
||||
|
||||
compileOnlyApi(libs.purpur.api)
|
||||
compileOnlyApi(libs.battlearena)
|
||||
compileOnlyApi(libs.placeholderapi)
|
||||
}
|
||||
|
||||
@@ -143,7 +140,4 @@ modrinth {
|
||||
uploadFile.set(tasks.shadowJar)
|
||||
gameVersions.set(supportedVersions)
|
||||
|
||||
dependencies {
|
||||
optional.project("battlearena")
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,10 @@
|
||||
bstats-bukkit = "3.0.2"
|
||||
purpur-api = "1.19.4-R0.1-SNAPSHOT"
|
||||
commons = "2.12.0"
|
||||
battlearena = "4.0.0-SNAPSHOT"
|
||||
placeholderapi = "2.11.6"
|
||||
mysql-connector = "9.6.0"
|
||||
|
||||
[libraries]
|
||||
battlearena = { group = "org.battleplugins", name = "arena", version.ref = "battlearena" }
|
||||
bstats-bukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats-bukkit" }
|
||||
purpur-api = { group = "org.purpurmc.purpur", name = "purpur-api", version.ref = "purpur-api" }
|
||||
commons-dbcp = { group = "org.apache.commons", name = "commons-dbcp2", version.ref = "commons" }
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.battleplugins.tracker;
|
||||
|
||||
import org.battleplugins.tracker.event.BattleTrackerPreInitializeEvent;
|
||||
import org.battleplugins.tracker.feature.Feature;
|
||||
import org.battleplugins.tracker.feature.battlearena.BattleArenaFeature;
|
||||
import org.battleplugins.tracker.feature.combatlog.CombatLog;
|
||||
import org.battleplugins.tracker.feature.damageindicators.DamageIndicators;
|
||||
import org.battleplugins.tracker.feature.placeholderapi.PlaceholderApiFeature;
|
||||
@@ -55,7 +54,6 @@ public class BattleTracker extends JavaPlugin {
|
||||
private final Map<String, RatingCalculator> ratingCalculators = new HashMap<>();
|
||||
private final Map<Tracker, List<Listener>> trackerListeners = new HashMap<>();
|
||||
|
||||
private BattleArenaFeature battleArenaFeature;
|
||||
private PlaceholderApiFeature placeholderApiFeature;
|
||||
|
||||
private CombatLog combatLog;
|
||||
@@ -89,6 +87,8 @@ public class BattleTracker extends JavaPlugin {
|
||||
Bukkit.getPluginManager().registerEvents(new BattleTrackerListener(this), this);
|
||||
// Captures duel match membership before Duels tears the match down on death.
|
||||
Bukkit.getPluginManager().registerEvents(new org.battleplugins.tracker.util.DuelsHook.DeathGuard(), this);
|
||||
// Captures Spleef membership before Spleef processes an elimination.
|
||||
Bukkit.getPluginManager().registerEvents(new org.battleplugins.tracker.util.SpleefHook.DeathGuard(), this);
|
||||
|
||||
// Register default calculators
|
||||
this.registerCalculator(new EloCalculator(this.config.getRating().elo()));
|
||||
@@ -130,9 +130,6 @@ public class BattleTracker extends JavaPlugin {
|
||||
this.saveResource("messages.yml", false);
|
||||
}
|
||||
|
||||
this.battleArenaFeature = new BattleArenaFeature();
|
||||
this.battleArenaFeature.onEnable(this);
|
||||
|
||||
this.placeholderApiFeature = new PlaceholderApiFeature();
|
||||
this.placeholderApiFeature.onEnable(this);
|
||||
}
|
||||
@@ -157,10 +154,6 @@ public class BattleTracker extends JavaPlugin {
|
||||
this.shuttingDown = true;
|
||||
this.cancelAutoSaveTask();
|
||||
|
||||
if (this.battleArenaFeature != null) {
|
||||
this.unloadFeature(this.battleArenaFeature);
|
||||
}
|
||||
|
||||
if (this.placeholderApiFeature != null) {
|
||||
this.unloadFeature(this.placeholderApiFeature);
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
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;
|
||||
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;
|
||||
|
||||
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,
|
||||
StatType.MAX_STREAK, StatType.MAX_RANKING, StatType.RATING,
|
||||
StatType.MAX_RATING, StatType.MAX_KD_RATIO
|
||||
);
|
||||
|
||||
List<StatType> allStats = new ArrayList<>(additionalStats);
|
||||
allStats.addAll(generalStats);
|
||||
|
||||
return new TrackerSqlSerializer(
|
||||
this,
|
||||
allStats.stream().distinct().toList(),
|
||||
List.of(WINS, LOSSES, StatType.TIES)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StatType> getAdditionalStats() {
|
||||
return this.additionalStats == null ? List.of() : this.additionalStats;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.battleplugins.tracker.feature.battlearena;
|
||||
|
||||
import org.battleplugins.tracker.BattleTracker;
|
||||
import org.battleplugins.tracker.feature.Feature;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class BattleArenaFeature implements Feature {
|
||||
private final boolean enabled;
|
||||
|
||||
private BattleArenaHandler handler;
|
||||
|
||||
public BattleArenaFeature() {
|
||||
this.enabled = Bukkit.getServer().getPluginManager().getPlugin("BattleArena") != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable(BattleTracker battleTracker) {
|
||||
if (!this.enabled) {
|
||||
battleTracker.info("BattleArena not found. Not tracking arena statistics.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.handler = new BattleArenaHandler(battleTracker);
|
||||
battleTracker.info("BattleArena found. Tracking arena statistics.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable(BattleTracker battleTracker) {
|
||||
if (this.handler != null) {
|
||||
this.handler.onDisable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
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);
|
||||
public static final MessageAudience ARENA_AUDIENCE = MessageAudience.create("arena", (player) -> {
|
||||
ArenaPlayer arenaPlayer = ArenaPlayer.getArenaPlayer(player);
|
||||
if (arenaPlayer == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return arenaPlayer.getCompetition().getPlayers()
|
||||
.stream()
|
||||
.map(ArenaPlayer::getPlayer)
|
||||
.toList();
|
||||
});
|
||||
|
||||
private final BattleTracker battleTracker;
|
||||
|
||||
public BattleArenaHandler(BattleTracker battleTracker) {
|
||||
this.battleTracker = battleTracker;
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(new BattleArenaListener(battleTracker), battleTracker);
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
HandlerList.getRegisteredListeners(this.battleTracker).stream()
|
||||
.filter(listener -> listener.getListener() instanceof BattleArenaListener)
|
||||
.forEach(l -> HandlerList.unregisterAll(l.getListener()));
|
||||
}
|
||||
|
||||
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,
|
||||
SET
|
||||
}
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
package org.battleplugins.tracker.feature.battlearena;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import org.battleplugins.arena.Arena;
|
||||
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;
|
||||
import org.battleplugins.arena.event.arena.ArenaDrawEvent;
|
||||
import org.battleplugins.arena.event.arena.ArenaInitializeEvent;
|
||||
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;
|
||||
import org.battleplugins.tracker.Tracker;
|
||||
import org.battleplugins.tracker.stat.Record;
|
||||
import org.battleplugins.tracker.stat.StatType;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BattleArenaListener implements Listener {
|
||||
private final BattleTracker battleTracker;
|
||||
private final Map<Arena, Consumer<Tracker>> pendingExecutors = new HashMap<>();
|
||||
|
||||
private boolean reloaded;
|
||||
|
||||
public BattleArenaListener(BattleTracker battleTracker) {
|
||||
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;
|
||||
|
||||
for (Arena arena : event.getBattleArena().getArenas()) {
|
||||
Tracker tracker = this.battleTracker.getTracker(arena.getName().toLowerCase(Locale.ROOT));
|
||||
if (tracker == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tracker.saveAll().whenComplete((aVoid, e) -> {
|
||||
if (e != null) {
|
||||
this.battleTracker.error("Failed to save tracker data for Arena: {}.", arena.getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
tracker.destroy(false);
|
||||
});
|
||||
|
||||
this.battleTracker.unregisterTracker(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBattleArenaReloaded(BattleArenaReloadedEvent event) {
|
||||
for (Arena arena : event.getBattleArena().getArenas()) {
|
||||
Tracker tracker = this.battleTracker.getTracker(arena.getName().toLowerCase(Locale.ROOT));
|
||||
if (tracker == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure records are created for online players
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
tracker.getOrCreateRecord((OfflinePlayer) player).whenComplete((record, e) -> {
|
||||
if (tracker instanceof SqlTracker sqlTracker) {
|
||||
sqlTracker.getRecords().lock(player.getUniqueId());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onArenaInitialize(ArenaInitializeEvent event) {
|
||||
Arena arena = event.getArena();
|
||||
|
||||
// Statistic tracking is disabled
|
||||
if (!arena.option(BattleArenaHandler.TRACK_STATISTICS_OPTION).map(BooleanArenaOption::isEnabled).orElse(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Consumer<Tracker> pendingExecutor = this.pendingExecutors.remove(arena);
|
||||
Supplier<Tracker> supplier = () -> {
|
||||
Tracker tracker = new ArenaTracker(
|
||||
this.battleTracker,
|
||||
arena.getName(),
|
||||
this.battleTracker.getCalculator("elo"),
|
||||
Set.of(TrackedDataType.PVP),
|
||||
List.of()
|
||||
);
|
||||
|
||||
if (pendingExecutor != null) {
|
||||
pendingExecutor.accept(tracker);
|
||||
}
|
||||
|
||||
return tracker;
|
||||
};
|
||||
|
||||
this.battleTracker.registerTracker(
|
||||
event.getArena().getName().toLowerCase(Locale.ROOT),
|
||||
supplier
|
||||
);
|
||||
|
||||
// This is kind of silly, but in the event that the plugin is reloaded,
|
||||
// we want to ensure our tracker instance gets updated.
|
||||
if (this.reloaded) {
|
||||
this.battleTracker.registerTracker(supplier.get());
|
||||
}
|
||||
|
||||
this.battleTracker.info("Enabled tracking for arena: {}.", arena.getName());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onCreateExecutor(ArenaCreateExecutorEvent event) {
|
||||
Arena arena = event.getArena();
|
||||
|
||||
// Statistic tracking is disabled
|
||||
if (!arena.option(BattleArenaHandler.TRACK_STATISTICS_OPTION).map(BooleanArenaOption::isEnabled).orElse(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingExecutors.put(arena, tracker -> event.registerSubExecutor(new TrackerSubExecutor(arena, tracker)));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onArenaJoin(ArenaPreJoinEvent event) {
|
||||
Tracker tracker = this.battleTracker.getTracker(event.getArena().getName());
|
||||
if (tracker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.battleTracker.getCombatLog().isInCombat(event.getPlayer())) {
|
||||
event.setResult(new JoinResult(false, BattleArenaHandler.convertMessage("combat-log-cannot-join-arena")));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onArenaLeave(ArenaLeaveEvent event) {
|
||||
// If player leaves or quits, we want to decrement their elo
|
||||
if (event.getCause() != ArenaLeaveEvent.Cause.COMMAND && event.getCause() != ArenaLeaveEvent.Cause.DISCONNECT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Player is not in an in-game phase, so we don't want to decrement their elo
|
||||
if (!CompetitionPhaseType.INGAME.equals(event.getCompetition().getPhaseManager().getCurrentPhase().getType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Tracker tracker = this.battleTracker.getTracker(event.getArena().getName());
|
||||
if (tracker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
Record record = tracker.getRecord(player);
|
||||
if (!record.isTracking()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update rating
|
||||
Record[] records = event.getCompetition().getPlayers()
|
||||
.stream()
|
||||
.filter(p -> !p.equals(event.getArenaPlayer()))
|
||||
.map(p -> tracker.getRecord(p.getPlayer()))
|
||||
.toArray(Record[]::new);
|
||||
|
||||
tracker.getRatingCalculator().updateRating(records, new Record[] { record }, false);
|
||||
tracker.setValue(StatType.RATING, record.getRating(), player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onArenaKill(ArenaKillEvent event) {
|
||||
Tracker tracker = this.battleTracker.getTracker(event.getArena().getName());
|
||||
if (tracker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player killer = event.getKiller().getPlayer();
|
||||
Player killed = event.getKilled().getPlayer();
|
||||
|
||||
Record killerRecord = tracker.getRecord(killer);
|
||||
Record killedRecord = tracker.getRecord(killed);
|
||||
|
||||
if (killerRecord.isTracking()) {
|
||||
killerRecord.incrementValue(StatType.KILLS);
|
||||
}
|
||||
|
||||
if (killedRecord.isTracking()) {
|
||||
killedRecord.incrementValue(StatType.DEATHS);
|
||||
}
|
||||
|
||||
// Update ratios
|
||||
tracker.setValue(StatType.KD_RATIO, killerRecord.getStat(StatType.KILLS) / Math.max(1, killerRecord.getStat(StatType.DEATHS)), killer);
|
||||
tracker.setValue(StatType.KD_RATIO, killedRecord.getStat(StatType.KILLS) / Math.max(1, killedRecord.getStat(StatType.DEATHS)), killed);
|
||||
|
||||
float killerKdr = killerRecord.getStat(StatType.KD_RATIO);
|
||||
float killerMaxKdr = killerRecord.getStat(StatType.MAX_KD_RATIO);
|
||||
|
||||
if (killerKdr > killerMaxKdr) {
|
||||
tracker.setValue(StatType.MAX_KD_RATIO, killerKdr, killer);
|
||||
}
|
||||
|
||||
tracker.setValue(StatType.STREAK, 0, killed);
|
||||
tracker.incrementValue(StatType.STREAK, killer);
|
||||
|
||||
float killerStreak = killerRecord.getStat(StatType.STREAK);
|
||||
float killerMaxStreak = killerRecord.getStat(StatType.MAX_STREAK);
|
||||
|
||||
if (killerStreak > killerMaxStreak) {
|
||||
tracker.setValue(StatType.MAX_STREAK, killerStreak, killer);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onArenaVictory(ArenaVictoryEvent event) {
|
||||
// Development note: ArenaVictoryEvent will always be called in conjunction
|
||||
// with the ArenaLoseEvent, so we can process all our logic here
|
||||
|
||||
if (!(event.getCompetition() instanceof LiveCompetition<?> liveCompetition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Tracker tracker = this.battleTracker.getTracker(event.getArena().getName());
|
||||
if (tracker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Record[] victorRecords = event.getVictors()
|
||||
.stream()
|
||||
.map(player -> tracker.getRecord(player.getPlayer()))
|
||||
.toArray(Record[]::new);
|
||||
|
||||
Set<ArenaPlayer> losers = liveCompetition.getPlayers()
|
||||
.stream()
|
||||
.filter(p -> !event.getVictors().contains(p))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Record[] loserRecords = losers.stream()
|
||||
.map(player -> tracker.getRecord(player.getPlayer()))
|
||||
.toArray(Record[]::new);
|
||||
|
||||
// Update ratings
|
||||
tracker.getRatingCalculator().updateRating(victorRecords, loserRecords, false);
|
||||
|
||||
for (ArenaPlayer victor : event.getVictors()) {
|
||||
Player victorPlayer = victor.getPlayer();
|
||||
Record victorRecord = tracker.getRecord(victorPlayer);
|
||||
|
||||
if (!victorRecord.isTracking()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
victorRecord.incrementValue(ArenaTracker.WINS);
|
||||
|
||||
float victorRating = victorRecord.getRating();
|
||||
float victorMaxRating = victorRecord.getStat(StatType.MAX_RATING);
|
||||
|
||||
tracker.setValue(StatType.RATING, victorRecord.getRating(), victorPlayer);
|
||||
|
||||
if (victorRating > victorMaxRating) {
|
||||
tracker.setValue(StatType.MAX_RATING, victorRating, victorPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
for (ArenaPlayer loser : losers) {
|
||||
Player loserPlayer = loser.getPlayer();
|
||||
Record loserRecord = tracker.getRecord(loserPlayer);
|
||||
|
||||
if (!loserRecord.isTracking()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
loserRecord.incrementValue(ArenaTracker.LOSSES);
|
||||
|
||||
float loserRating = loserRecord.getRating();
|
||||
float loserMaxRating = loserRecord.getStat(StatType.MAX_RATING);
|
||||
|
||||
tracker.setValue(StatType.RATING, loserRecord.getRating(), loserPlayer);
|
||||
|
||||
if (loserRating > loserMaxRating) {
|
||||
tracker.setValue(StatType.MAX_RATING, loserRating, loserPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDraw(ArenaDrawEvent event) {
|
||||
if (!(event.getCompetition() instanceof LiveCompetition<?> liveCompetition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Tracker tracker = this.battleTracker.getTracker(event.getArena().getName());
|
||||
if (tracker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Record[] records = liveCompetition.getPlayers()
|
||||
.stream()
|
||||
.map(player -> tracker.getRecord(player.getPlayer()))
|
||||
.toArray(Record[]::new);
|
||||
|
||||
// Update ratings
|
||||
tracker.getRatingCalculator().updateRating(records, true);
|
||||
|
||||
for (ArenaPlayer player : liveCompetition.getPlayers()) {
|
||||
Player bukkitPlayer = player.getPlayer();
|
||||
Record record = tracker.getRecord(bukkitPlayer);
|
||||
|
||||
if (!record.isTracking()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
record.incrementValue(StatType.TIES);
|
||||
|
||||
float victorRating = record.getRating();
|
||||
float victorMaxRating = record.getStat(StatType.MAX_RATING);
|
||||
|
||||
tracker.setValue(StatType.RATING, record.getRating(), bukkitPlayer);
|
||||
|
||||
if (victorRating > victorMaxRating) {
|
||||
tracker.setValue(StatType.MAX_RATING, victorRating, bukkitPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
case SET -> {
|
||||
record.setValue(info.statType(), newValue.floatValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
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;
|
||||
import org.battleplugins.arena.command.SubCommandExecutor;
|
||||
import org.battleplugins.arena.util.PaginationCalculator;
|
||||
import org.battleplugins.tracker.BattleTracker;
|
||||
import org.battleplugins.tracker.Tracker;
|
||||
import org.battleplugins.tracker.message.Messages;
|
||||
import org.battleplugins.tracker.stat.Record;
|
||||
import org.battleplugins.tracker.stat.StatType;
|
||||
import org.battleplugins.tracker.util.Util;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class TrackerSubExecutor implements SubCommandExecutor {
|
||||
private final Arena arena;
|
||||
private final Tracker tracker;
|
||||
|
||||
public TrackerSubExecutor(Arena arena, Tracker tracker) {
|
||||
this.arena = arena;
|
||||
this.tracker = tracker;
|
||||
}
|
||||
|
||||
@ArenaCommand(commands = "top", description = "View the top players in this arena.", permissionNode = "top")
|
||||
public void top(Player player) {
|
||||
this.top(player, 5);
|
||||
}
|
||||
|
||||
@ArenaCommand(commands = "top", description = "View the top players in this arena.", permissionNode = "top")
|
||||
public void top(Player player, @Argument(name = "max", description = "The maximum players to show.") int max) {
|
||||
int amount = max <= 0 ? 5 : Math.min(100, max);
|
||||
Util.getSortedRecords(this.tracker, amount, StatType.RATING).whenComplete((records, e) -> {
|
||||
if (records.isEmpty()) {
|
||||
Messages.send(player, "leaderboard-no-entries");
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendMessage(PaginationCalculator.center(Messages.get("header", this.arena.getName()), Component.space()));
|
||||
|
||||
int ranking = 1;
|
||||
for (Map.Entry<Record, Float> entry : records.entrySet()) {
|
||||
Record record = entry.getKey();
|
||||
|
||||
Util.sendTrackerMessage(player, "leaderboard-arena", ranking++, record);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ArenaCommand(commands = "rank", description = "View the rank of a player.", permissionNode = "rank")
|
||||
public void rank(Player player) {
|
||||
this.rank(player, (String) null);
|
||||
}
|
||||
|
||||
@ArenaCommand(commands = "rank", description = "View the rank of a player.", permissionNode = "rank")
|
||||
public void rank(Player player, @Argument(name = "name", description = "The name of the player.") String playerName) {
|
||||
OfflinePlayer target;
|
||||
if ((playerName == null || playerName.isBlank())) {
|
||||
Messages.send(player, "command-player-not-found", "<blank>");
|
||||
return;
|
||||
} else {
|
||||
target = Bukkit.getServer().getOfflinePlayerIfCached(playerName);
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
CompletableFuture.supplyAsync(() -> Bukkit.getOfflinePlayer(playerName)).thenCompose(this.tracker::getRecord).whenCompleteAsync((record, e) -> {
|
||||
if (record == null) {
|
||||
Messages.send(player, "player-has-no-record", playerName);
|
||||
return;
|
||||
}
|
||||
|
||||
this.rank(player, record);
|
||||
}, Bukkit.getScheduler().getMainThreadExecutor(BattleTracker.getInstance()));
|
||||
} else {
|
||||
this.tracker.getRecord(target).whenCompleteAsync((record, e) -> {
|
||||
if (record == null) {
|
||||
Messages.send(player, "player-has-no-record", playerName);
|
||||
return;
|
||||
}
|
||||
|
||||
this.rank(player, record);
|
||||
}, Bukkit.getScheduler().getMainThreadExecutor(BattleTracker.getInstance()));
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.battleplugins.tracker.feature.recap.RecapEntry;
|
||||
import org.battleplugins.tracker.stat.Record;
|
||||
import org.battleplugins.tracker.stat.StatType;
|
||||
import org.battleplugins.tracker.util.DuelsHook;
|
||||
import org.battleplugins.tracker.util.SpleefHook;
|
||||
import org.battleplugins.tracker.util.Util;
|
||||
import org.bukkit.entity.AnimalTamer;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -46,8 +47,8 @@ public class PvEListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// Duel PvE/world deaths (fall, lava, void, mobs) must not count; use pre-captured marker (live query races Duels).
|
||||
if (DuelsHook.isDuelDeath(killed)) {
|
||||
// Minigame PvE/world deaths must not count; use pre-captured markers because match state can change during death.
|
||||
if (DuelsHook.isDuelDeath(killed) || SpleefHook.isSpleefDeath(killed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,8 +131,8 @@ public class PvEListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// A duelist killing a mob mid-match must not be tracked.
|
||||
if (DuelsHook.isInMatch(killer)) {
|
||||
// A minigame player killing a mob mid-match must not be tracked.
|
||||
if (DuelsHook.isInMatch(killer) || SpleefHook.isInSpleef(killer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -162,8 +163,8 @@ public class PvEListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't build a damage recap from a Duels match (after cheap exits to keep reflection off the common path).
|
||||
if (DuelsHook.isInMatch(player)) {
|
||||
// Don't build a damage recap from a minigame (after cheap exits to keep reflection off the common path).
|
||||
if (DuelsHook.isInMatch(player) || SpleefHook.isInSpleef(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.battleplugins.tracker.stat.Record;
|
||||
import org.battleplugins.tracker.stat.StatType;
|
||||
import org.battleplugins.tracker.stat.TallyEntry;
|
||||
import org.battleplugins.tracker.util.DuelsHook;
|
||||
import org.battleplugins.tracker.util.SpleefHook;
|
||||
import org.bukkit.entity.AnimalTamer;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -42,8 +43,8 @@ public class PvPListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// Duel deaths/kills must not be tracked; use the pre-captured marker (live query races Duels' own death handler).
|
||||
if (DuelsHook.isDuelDeath(killed)) {
|
||||
// Minigame deaths must not be tracked; use pre-captured markers because match state can change during death.
|
||||
if (DuelsHook.isDuelDeath(killed) || SpleefHook.isSpleefDeath(killed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,6 +74,10 @@ public class PvPListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DuelsHook.isInMatch(killer) || SpleefHook.isInSpleef(killer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Record killerRecord = this.tracker.getOrCreateRecord(killer);
|
||||
Record killedRecord = this.tracker.getOrCreateRecord(killed);
|
||||
|
||||
@@ -124,8 +129,8 @@ public class PvPListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't build a damage recap from a Duels match (after cheap exits to keep reflection off the common path).
|
||||
if (DuelsHook.isInMatch(damaged)) {
|
||||
// Don't build a damage recap from a minigame (after cheap exits to keep reflection off the common path).
|
||||
if (DuelsHook.isInMatch(damaged) || SpleefHook.isInSpleef(damaged)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -147,6 +152,10 @@ public class PvPListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DuelsHook.isInMatch(player) || SpleefHook.isInSpleef(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack itemUsed = player.getInventory().getItemInMainHand();
|
||||
recap.getRecap(damaged).record(RecapEntry.builder()
|
||||
.damageCause(event.getCause())
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.battleplugins.tracker.util;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/** Soft integration that keeps Spleef activity out of SMP statistics. */
|
||||
public final class SpleefHook {
|
||||
|
||||
private static final String PLUGIN_NAME = "Spleef-OG";
|
||||
private static final String API_CLASS = "net.trueog.spleefog.api.SpleefAPI";
|
||||
private static final Set<UUID> SPLEEF_DEATHS = ConcurrentHashMap.newKeySet();
|
||||
private static volatile Plugin cachedPlugin;
|
||||
private static volatile Method isInSpleef;
|
||||
private static volatile boolean warned;
|
||||
|
||||
private SpleefHook() {
|
||||
}
|
||||
|
||||
public static final class DeathGuard implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPreDeath(PlayerDeathEvent event) {
|
||||
if (isInSpleef(event.getEntity())) {
|
||||
SPLEEF_DEATHS.add(event.getEntity().getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPostDeath(PlayerDeathEvent event) {
|
||||
SPLEEF_DEATHS.remove(event.getEntity().getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSpleefDeath(Player player) {
|
||||
return player != null && SPLEEF_DEATHS.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
public static boolean isInSpleef(Player player) {
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
Plugin plugin = Bukkit.getPluginManager().getPlugin(PLUGIN_NAME);
|
||||
if (plugin == null || !plugin.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
resolve(plugin);
|
||||
return isInSpleef != null && Boolean.TRUE.equals(isInSpleef.invoke(null, player));
|
||||
} catch (Throwable throwable) {
|
||||
if (!warned) {
|
||||
warned = true;
|
||||
Bukkit.getLogger().log(Level.WARNING,
|
||||
"[BattleTracker] Failed to query Spleef state; Spleef activity may be tracked until resolved.",
|
||||
throwable);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void resolve(Plugin plugin) throws ReflectiveOperationException {
|
||||
if (plugin == cachedPlugin && isInSpleef != null) {
|
||||
return;
|
||||
}
|
||||
Class<?> api = Class.forName(API_CLASS, true, plugin.getClass().getClassLoader());
|
||||
isInSpleef = api.getMethod("isInSpleef", Player.class);
|
||||
cachedPlugin = plugin;
|
||||
warned = false;
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@ description: A standalone plugin that tracks PVP & PVE statistics, provides cust
|
||||
author: BattlePlugins
|
||||
website: https://battleplugins.org
|
||||
api-version: 1.19
|
||||
softdepend: [BattleArena, Duels-OG, Duels]
|
||||
softdepend: [Duels-OG, Duels, Spleef-OG]
|
||||
commands:
|
||||
battletracker:
|
||||
description: The main BattleTracker command.
|
||||
usage: /<command> <subcommand> [args]
|
||||
permission: battletracker.command
|
||||
aliases: [bt]
|
||||
aliases: [bt]
|
||||
@@ -48,7 +48,6 @@ death-messages:
|
||||
# - global: all players
|
||||
# - world: only players in the same world
|
||||
# - local: only the player and the target
|
||||
# - arena: only players in the same arena (requires BattleArena)
|
||||
audience: global
|
||||
# World death messages
|
||||
world:
|
||||
|
||||
@@ -42,7 +42,6 @@ killstreaks:
|
||||
# - global: all players
|
||||
# - world: only players in the same world
|
||||
# - local: only the player and the target
|
||||
# - arena: only players in the same arena (requires BattleArena)
|
||||
audience: global
|
||||
messages:
|
||||
default: "<white><gold>%player%</gold> is on a killstreak of <red>%kills%</red> kills!</white>"
|
||||
@@ -57,7 +56,6 @@ rampage:
|
||||
# - global: all players
|
||||
# - world: only players in the same world
|
||||
# - local: only the player and the target
|
||||
# - arena: only players in the same arena (requires BattleArena)
|
||||
audience: global
|
||||
messages:
|
||||
default: "<white><gold>%player%</gold> is on a rampage!</white>"
|
||||
@@ -71,7 +69,6 @@ death-messages:
|
||||
# - global: all players
|
||||
# - world: only players in the same world
|
||||
# - local: only the player and the target
|
||||
# - arena: only players in the same arena (requires BattleArena)
|
||||
audience: world
|
||||
# World death messages
|
||||
world:
|
||||
|
||||
Reference in new issue
Block a user