Skip to content

Commit debe9fa

Browse files
committed
Implement text display leaderboard
1 parent 3ac2c8d commit debe9fa

File tree

7 files changed

+196
-4
lines changed

7 files changed

+196
-4
lines changed

src/main/java/dev/booky/craftattack/CaMain.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import dev.booky.craftattack.listener.DimensionListener;
66
import dev.booky.craftattack.listener.ElytraListener;
77
import dev.booky.craftattack.listener.ExplosionListener;
8+
import dev.booky.craftattack.listener.LeaderboardListener;
89
import dev.booky.craftattack.listener.MineStatListener;
910
import dev.booky.craftattack.listener.SitListener;
1011
import dev.booky.craftattack.listener.SpawnListener;
@@ -49,6 +50,7 @@ public void onEnable() {
4950
Bukkit.getPluginManager().registerEvents(new SpawnListener(this.manager), this);
5051
Bukkit.getPluginManager().registerEvents(new TeleportListener(this.manager), this);
5152
Bukkit.getPluginManager().registerEvents(new WarpPlateListener(this.manager), this);
53+
Bukkit.getPluginManager().registerEvents(new LeaderboardListener(this.manager), this);
5254

5355
Bukkit.getServicesManager().register(CaManager.class, this.manager, this, ServicePriority.Normal);
5456

@@ -57,10 +59,6 @@ public void onEnable() {
5759

5860
@Override
5961
public void onDisable() {
60-
if (this.manager != null) {
61-
this.manager.saveConfig();
62-
}
63-
6462
if (this.i18n != null) {
6563
this.i18n.unload();
6664
}

src/main/java/dev/booky/craftattack/CaManager.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import dev.booky.cloudcore.config.ConfigurateLoader;
55
import dev.booky.cloudcore.util.BlockBBox;
66
import dev.booky.craftattack.utils.CaConfig;
7+
import dev.booky.craftattack.utils.LeaderboardTasks;
78
import dev.booky.craftattack.utils.TpResult;
89
import io.papermc.paper.entity.TeleportFlag;
910
import net.kyori.adventure.text.Component;
@@ -47,6 +48,7 @@ public final class CaManager {
4748
private final NamespacedKey elytraDataKey;
4849
private final NamespacedKey elytraBoostsKey;
4950

51+
private final LeaderboardTasks leaderboardTasks = new LeaderboardTasks(this);
5052
private final Map<UUID, CompletableFuture<TpResult>> teleports = new HashMap<>();
5153
private final Plugin plugin;
5254

@@ -154,6 +156,7 @@ public void updateConfig(Consumer<CaConfig> updater) {
154156

155157
public void reloadConfig() {
156158
this.config = CONFIG_LOADER.loadObject(this.configPath, CaConfig.class, CaConfig::new);
159+
this.leaderboardTasks.handleReload();
157160
}
158161

159162
public void saveConfig() {
@@ -230,6 +233,10 @@ public OptionalInt consumeElytraBoost(PersistentDataHolder holder) {
230233
return OptionalInt.empty();
231234
}
232235

236+
public LeaderboardTasks getLeaderboardTasks() {
237+
return this.leaderboardTasks;
238+
}
239+
233240
public CaConfig getConfig() {
234241
return Objects.requireNonNull(this.config, "Config has not been loaded yet");
235242
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dev.booky.craftattack.listener;
2+
// Created by booky10 in CraftAttack (03:46 26.10.2025)
3+
4+
import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent;
5+
import dev.booky.craftattack.CaManager;
6+
import dev.booky.craftattack.utils.CaConfig;
7+
import dev.booky.craftattack.utils.LeaderboardTasks;
8+
import org.bukkit.entity.TextDisplay;
9+
import org.bukkit.event.EventHandler;
10+
import org.bukkit.event.Listener;
11+
import org.jspecify.annotations.NullMarked;
12+
13+
@NullMarked
14+
public class LeaderboardListener implements Listener {
15+
16+
private final CaManager manager;
17+
18+
public LeaderboardListener(CaManager manager) {
19+
this.manager = manager;
20+
}
21+
22+
@EventHandler
23+
public void onEntityAdd(EntityAddToWorldEvent event) {
24+
if (!(event.getEntity() instanceof TextDisplay display)) {
25+
return; // not a text display
26+
}
27+
LeaderboardTasks tasks = this.manager.getLeaderboardTasks();
28+
CaConfig.LeaderboardConfig config = tasks.getConfig(display.getUniqueId());
29+
if (config != null) {
30+
tasks.launchUpdater(display, config);
31+
}
32+
}
33+
}

src/main/java/dev/booky/craftattack/utils/CaConfig.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@
44
import dev.booky.cloudcore.util.BlockBBox;
55
import io.papermc.paper.math.BlockPosition;
66
import net.kyori.adventure.key.Key;
7+
import net.kyori.adventure.text.Component;
78
import org.bukkit.Location;
89
import org.bukkit.Material;
910
import org.bukkit.NamespacedKey;
1011
import org.bukkit.block.Block;
1112
import org.jspecify.annotations.Nullable;
1213
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
1314

15+
import java.util.ArrayList;
1416
import java.util.HashMap;
1517
import java.util.LinkedHashMap;
1618
import java.util.List;
1719
import java.util.Map;
1820
import java.util.Set;
21+
import java.util.UUID;
1922

2023
// Can't be final because of object mapping
2124
@SuppressWarnings("FieldMayBeFinal")
@@ -166,6 +169,37 @@ public boolean isPreventAbuse() {
166169

167170
private Map<BlockPosition, String> warpPlates = new HashMap<>();
168171

172+
private List<LeaderboardConfig> leaderboards = new ArrayList<>();
173+
174+
@ConfigSerializable
175+
public static final class LeaderboardConfig {
176+
177+
private String objective;
178+
private UUID entityId;
179+
private int entries = 10;
180+
private Component wrapper = Component.text("");
181+
182+
public String getObjective() {
183+
return this.objective;
184+
}
185+
186+
public UUID getEntityId() {
187+
return this.entityId;
188+
}
189+
190+
public int getEntries() {
191+
return this.entries;
192+
}
193+
194+
public Component getWrapper() {
195+
return this.wrapper;
196+
}
197+
}
198+
199+
public List<LeaderboardConfig> getLeaderboards() {
200+
return this.leaderboards;
201+
}
202+
169203
public @Nullable String getWarpPlateTarget(BlockPosition pos) {
170204
return this.warpPlates.get(pos);
171205
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dev.booky.craftattack.utils;
2+
// Created by booky10 in CraftAttack (02:47 26.10.2025)
3+
4+
import org.bukkit.Bukkit;
5+
import org.bukkit.entity.TextDisplay;
6+
import org.jspecify.annotations.NullMarked;
7+
8+
import java.lang.ref.WeakReference;
9+
10+
@NullMarked
11+
public final class LeaderboardTask implements Runnable {
12+
13+
private final CaConfig.LeaderboardConfig config;
14+
private final WeakReference<TextDisplay> display;
15+
private int taskId;
16+
17+
public LeaderboardTask(CaConfig.LeaderboardConfig config, WeakReference<TextDisplay> display) {
18+
this.config = config;
19+
this.display = display;
20+
}
21+
22+
@Override
23+
public void run() {
24+
TextDisplay display = this.display.get();
25+
if (display == null || !display.isValid()) {
26+
Bukkit.getScheduler().cancelTask(this.taskId); // entity has been removed
27+
} else if (!Bukkit.getOnlinePlayers().isEmpty()) {
28+
LeaderboardUtil.applyLeaderboard(display, this.config);
29+
}
30+
}
31+
32+
public void setTaskId(int taskId) {
33+
this.taskId = taskId;
34+
}
35+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package dev.booky.craftattack.utils;
2+
// Created by booky10 in CraftAttack (03:17 26.10.2025)
3+
4+
import dev.booky.craftattack.CaManager;
5+
import dev.booky.craftattack.utils.CaConfig.LeaderboardConfig;
6+
import net.kyori.adventure.util.Ticks;
7+
import org.bukkit.Bukkit;
8+
import org.bukkit.entity.TextDisplay;
9+
import org.bukkit.scheduler.BukkitTask;
10+
import org.jspecify.annotations.NullMarked;
11+
import org.jspecify.annotations.Nullable;
12+
13+
import java.lang.ref.WeakReference;
14+
import java.util.ArrayList;
15+
import java.util.HashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.UUID;
19+
20+
@NullMarked
21+
public class LeaderboardTasks {
22+
23+
private static final long UPDATE_INTERVAL = Ticks.TICKS_PER_SECOND * 42;
24+
25+
private final CaManager manager;
26+
27+
private final List<Integer> taskIds = new ArrayList<>();
28+
private final Map<UUID, LeaderboardConfig> configs = new HashMap<>();
29+
30+
public LeaderboardTasks(CaManager manager) {
31+
this.manager = manager;
32+
}
33+
34+
public void handleReload() {
35+
for (int taskId : this.taskIds) {
36+
Bukkit.getScheduler().cancelTask(taskId);
37+
}
38+
this.taskIds.clear();
39+
40+
this.configs.clear();
41+
for (LeaderboardConfig config : this.manager.getConfig().getLeaderboards()) {
42+
this.configs.put(config.getEntityId(), config);
43+
if (Bukkit.getEntity(config.getEntityId()) instanceof TextDisplay display) {
44+
this.launchUpdater(display, config);
45+
}
46+
}
47+
}
48+
49+
public void launchUpdater(TextDisplay display, LeaderboardConfig config) {
50+
LeaderboardTask lbTask = new LeaderboardTask(config, new WeakReference<>(display));
51+
BukkitTask task = Bukkit.getScheduler().runTaskTimer(this.manager.getPlugin(), lbTask, 0L, UPDATE_INTERVAL);
52+
// save task id for cancelling later
53+
lbTask.setTaskId(task.getTaskId());
54+
this.taskIds.add(task.getTaskId());
55+
}
56+
57+
public @Nullable LeaderboardConfig getConfig(UUID entityId) {
58+
return this.configs.get(entityId);
59+
}
60+
}

src/main/java/dev/booky/craftattack/utils/LeaderboardUtil.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// Created by booky10 in CraftAttack (02:58 26.10.2025)
33

44
import net.kyori.adventure.text.Component;
5+
import net.kyori.adventure.text.TextComponent;
56
import net.kyori.adventure.text.format.NamedTextColor;
67
import net.minecraft.world.scores.PlayerScoreEntry;
8+
import org.bukkit.Bukkit;
79
import org.bukkit.craftbukkit.scoreboard.CraftScoreboard;
10+
import org.bukkit.entity.TextDisplay;
811
import org.bukkit.scoreboard.Objective;
912
import org.bukkit.scoreboard.Scoreboard;
1013
import org.bukkit.util.NumberConversions;
@@ -43,6 +46,28 @@ private static LeaderboardEntry calcPlacedEntry(UnplacedLeaderboardEntry entry,
4346
return entry.withPlacement(placement);
4447
}
4548

49+
public static void applyLeaderboard(TextDisplay display, CaConfig.LeaderboardConfig config) {
50+
LeaderboardResult leaderboard = buildLeaderboard(config);
51+
if (leaderboard == null) {
52+
return; // objective can't be found
53+
}
54+
TextComponent.Builder builder = text();
55+
for (LeaderboardEntry line : leaderboard.entries()) {
56+
Component wrappedComp = config.getWrapper().append(line.buildLine());
57+
builder.append(wrappedComp).appendNewline();
58+
}
59+
display.text(builder.build());
60+
}
61+
62+
public static @Nullable LeaderboardResult buildLeaderboard(CaConfig.LeaderboardConfig config) {
63+
Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
64+
Objective objective = scoreboard.getObjective(config.getObjective());
65+
if (objective != null) {
66+
return buildLeaderboard(objective, config.getEntries(), null);
67+
}
68+
return null;
69+
}
70+
4671
public static LeaderboardResult buildLeaderboard(Objective objective, int size, @Nullable String self) {
4772
Scoreboard scoreboard = objective.getScoreboard();
4873
if (scoreboard == null) {

0 commit comments

Comments
 (0)