diff --git a/CommonCore/SQL/src/de/steamwar/sql/Leaderboard.kt b/CommonCore/SQL/src/de/steamwar/sql/Leaderboard.kt
new file mode 100644
index 00000000..d3399e5d
--- /dev/null
+++ b/CommonCore/SQL/src/de/steamwar/sql/Leaderboard.kt
@@ -0,0 +1,96 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2025 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql
+
+import de.steamwar.sql.internal.useDb
+import org.jetbrains.exposed.v1.core.SortOrder
+import org.jetbrains.exposed.v1.core.and
+import org.jetbrains.exposed.v1.core.count
+import org.jetbrains.exposed.v1.core.dao.id.CompositeID
+import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable
+import org.jetbrains.exposed.v1.core.dao.id.EntityID
+import org.jetbrains.exposed.v1.core.eq
+import org.jetbrains.exposed.v1.core.lessSubQuery
+import org.jetbrains.exposed.v1.dao.CompositeEntity
+import org.jetbrains.exposed.v1.dao.CompositeEntityClass
+import org.jetbrains.exposed.v1.javatime.CurrentTimestamp
+import org.jetbrains.exposed.v1.javatime.timestamp
+import org.jetbrains.exposed.v1.jdbc.select
+
+object LeaderboardTable : CompositeIdTable("Leaderboard") {
+ val userId = reference("UserId", SteamwarUserTable)
+ val name = varchar("Name", 64).entityId()
+ val time = long("Time")
+ val updatedAt = timestamp("UpdatedAt").defaultExpression(CurrentTimestamp)
+ val bestTime = bool("BestTime")
+}
+
+class Leaderboard(id: EntityID) : CompositeEntity(id) {
+ companion object : CompositeEntityClass(LeaderboardTable) {
+ @JvmStatic
+ fun getLeaderboard(name: String) = useDb {
+ find { LeaderboardTable.name eq name }.orderBy(LeaderboardTable.time to SortOrder.ASC).limit(5).toList()
+ }
+
+ @JvmStatic
+ fun getPlayerTime(user: SteamwarUser, name: String) = useDb {
+ findById(CompositeID {
+ it[LeaderboardTable.userId] = user.id.value
+ it[LeaderboardTable.name] = name
+ })
+ }
+
+ @JvmStatic
+ fun getPlayerPlacement(user: SteamwarUser, name: String) = useDb {
+ LeaderboardTable.select(LeaderboardTable.time.count())
+ .where {
+ (LeaderboardTable.name eq name) and (LeaderboardTable.time lessSubQuery LeaderboardTable.select(
+ LeaderboardTable.time
+ ).where { (LeaderboardTable.userId eq user.id.value) and (LeaderboardTable.name eq name) })
+ }
+ .firstOrNull()?.get(LeaderboardTable.time.count())?.toInt() ?: Int.MAX_VALUE
+ }
+
+ @JvmStatic
+ fun upsert(userId: Int, name: String, time: Long, bestTime: Boolean) = useDb {
+ findByIdAndUpdate(CompositeID {
+ it[LeaderboardTable.userId] = userId
+ it[LeaderboardTable.name] = name
+ }) {
+ it.time = time
+ it.bestTime = bestTime
+ } ?: new(
+ CompositeID {
+ it[LeaderboardTable.userId] = userId
+ it[LeaderboardTable.name] = name
+ }
+ ) {
+ this.time = time
+ this.bestTime = bestTime
+ }
+ }
+ }
+
+ val user by LeaderboardTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
+ val name by LeaderboardTable.name
+ var time by LeaderboardTable.time
+ var updatedAt by LeaderboardTable.updatedAt
+ var bestTime by LeaderboardTable.bestTime
+}
\ No newline at end of file
diff --git a/LobbySystem/src/de/steamwar/lobby/boatrace/BoatRace.java b/LobbySystem/src/de/steamwar/lobby/boatrace/BoatRace.java
index 55c6daf7..e61533e0 100644
--- a/LobbySystem/src/de/steamwar/lobby/boatrace/BoatRace.java
+++ b/LobbySystem/src/de/steamwar/lobby/boatrace/BoatRace.java
@@ -22,8 +22,9 @@ package de.steamwar.lobby.boatrace;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.lobby.LobbySystem;
-import de.steamwar.lobby.util.Leaderboard;
-import de.steamwar.sql.UserConfig;
+import de.steamwar.lobby.util.LeaderboardManager;
+import de.steamwar.sql.Leaderboard;
+import de.steamwar.sql.SteamwarUser;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Sound;
@@ -43,16 +44,18 @@ import org.bukkit.scheduler.BukkitTask;
import java.util.EventListener;
-import static de.steamwar.lobby.util.Leaderboard.renderTime;
+import static de.steamwar.lobby.util.LeaderboardManager.renderTime;
public class BoatRace implements EventListener, Listener {
+ private static final String CONFIG_KEY = "lobby@boatrace";
+
private static final double MIN_HEIGHT = 4.3;
public static final REntityServer boatNpcServer;
private static boolean oneNotStarted = false;
- private static final Leaderboard leaderboard;
+ private static final LeaderboardManager leaderboard;
static {
boatNpcServer = new REntityServer();
@@ -65,7 +68,7 @@ public class BoatRace implements EventListener, Listener {
new BoatRace(player);
}
});
- leaderboard = new Leaderboard(boatNpcServer, "lobby@boatrace", BoatRacePositions.LEADERBOARD, 5);
+ leaderboard = new LeaderboardManager(boatNpcServer, CONFIG_KEY, BoatRacePositions.LEADERBOARD);
}
private final Player player;
@@ -123,12 +126,11 @@ public class BoatRace implements EventListener, Listener {
HandlerList.unregisterAll(this);
task.cancel();
LobbySystem.getMessage().send("BOAT_RACE_TIME", player, renderTime(time));
- String conf = UserConfig.getConfig(player.getUniqueId(), "lobby@boatrace");
- long best = Long.parseLong(conf == null ? String.valueOf(Long.MAX_VALUE) : conf);
+ SteamwarUser user = SteamwarUser.get(player.getUniqueId());
+ long best = leaderboard.getPlayerTime(user);
if (time < best) {
LobbySystem.getMessage().send("BOAT_RACE_NEW_BEST", player);
- UserConfig.updatePlayerConfig(player.getUniqueId(), "lobby@boatrace", String.valueOf(time));
- leaderboard.update();
+ leaderboard.updateBestTime(user, time);
}
} else {
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 1);
diff --git a/LobbySystem/src/de/steamwar/lobby/jumpandrun/JumpAndRun.java b/LobbySystem/src/de/steamwar/lobby/jumpandrun/JumpAndRun.java
index adc63951..274a2b7f 100644
--- a/LobbySystem/src/de/steamwar/lobby/jumpandrun/JumpAndRun.java
+++ b/LobbySystem/src/de/steamwar/lobby/jumpandrun/JumpAndRun.java
@@ -22,8 +22,8 @@ package de.steamwar.lobby.jumpandrun;
import de.steamwar.linkage.Linked;
import de.steamwar.lobby.LobbySystem;
import de.steamwar.lobby.listener.PlayerSpawn;
-import de.steamwar.lobby.util.Leaderboard;
-import de.steamwar.sql.UserConfig;
+import de.steamwar.lobby.util.LeaderboardManager;
+import de.steamwar.sql.SteamwarUser;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -58,7 +58,7 @@ public class JumpAndRun implements Listener {
private static final Map CLICKED = new HashMap<>();
private static final Map CLICKED_COUNT = new HashMap<>();
- private static final Leaderboard LEADERBOARD = new Leaderboard(LobbySystem.getEntityServer(false), JUMP_AND_RUN_CONFIG, new Location(Bukkit.getWorlds().get(0), 2338.5, 42.5, 1231.5), 5);
+ private static final LeaderboardManager LEADERBOARD = new LeaderboardManager(LobbySystem.getEntityServer(false), JUMP_AND_RUN_CONFIG, new Location(Bukkit.getWorlds().get(0), 2338.5, 42.5, 1231.5));
{
Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
@@ -161,18 +161,14 @@ public class JumpAndRun implements Listener {
}
private void updateJumpAndRunTime(Player player, long time) {
- String jumpAndRunTimeConfig = UserConfig.getConfig(player.getUniqueId(), JUMP_AND_RUN_CONFIG);
- if (jumpAndRunTimeConfig == null) {
- UserConfig.updatePlayerConfig(player.getUniqueId(), JUMP_AND_RUN_CONFIG, time + "");
- } else {
- long jumpAndRunTime = Long.parseLong(jumpAndRunTimeConfig);
- if (time < jumpAndRunTime) {
+ long best = LEADERBOARD.getPlayerTime(SteamwarUser.get(player.getUniqueId()));
+ if (time < best) {
+ if (best != Long.MAX_VALUE) {
SimpleDateFormat format = new SimpleDateFormat(LobbySystem.getMessage().parse("JUMP_AND_RUN_TIME", player), Locale.ROOT);
- String parsed = format.format(new Date(jumpAndRunTime - time));
+ String parsed = format.format(new Date(best - time));
LobbySystem.getMessage().sendPrefixless("JUMP_AND_RUN_PERSONAL_BEST", player, parsed);
- UserConfig.updatePlayerConfig(player.getUniqueId(), JUMP_AND_RUN_CONFIG, time + "");
- LEADERBOARD.update();
}
+ LEADERBOARD.updateBestTime(SteamwarUser.get(player.getUniqueId()), time);
}
}
diff --git a/LobbySystem/src/de/steamwar/lobby/util/Leaderboard.java b/LobbySystem/src/de/steamwar/lobby/util/LeaderboardManager.java
similarity index 72%
rename from LobbySystem/src/de/steamwar/lobby/util/Leaderboard.java
rename to LobbySystem/src/de/steamwar/lobby/util/LeaderboardManager.java
index f0a33a4a..d5eb8cf9 100644
--- a/LobbySystem/src/de/steamwar/lobby/util/Leaderboard.java
+++ b/LobbySystem/src/de/steamwar/lobby/util/LeaderboardManager.java
@@ -23,9 +23,8 @@ import de.steamwar.entity.RArmorStand;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.lobby.LobbySystem;
+import de.steamwar.sql.Leaderboard;
import de.steamwar.sql.SteamwarUser;
-import de.steamwar.sql.internal.Statement;
-import lombok.AllArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -39,25 +38,18 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-public class Leaderboard implements Listener {
-
- private static final Statement LEADERBOARD = new Statement("SELECT User, CAST(Value as integer) AS Time from UserConfig WHERE Config = ? ORDER BY CAST(Value as integer) ASC LIMIT ?");
- private static final Statement PLAYER_TIME = new Statement("SELECT CAST(Value as integer) AS Time FROM UserConfig WHERE Config = ? AND User = ?");
- private static final Statement PLAYER_PLACEMENT = new Statement("SELECT COUNT(*) AS Placement FROM UserConfig WHERE Config = ? AND CAST(Value as integer) < (SELECT CAST(Value as integer) AS Time FROM UserConfig WHERE Config = ? AND User = ?)");
-
+public class LeaderboardManager implements Listener {
private final REntityServer server;
private final String configKey;
private final Location location;
- private final int best;
private long bestTime;
private final List entities = new ArrayList<>();
private final Map playerPlacements = new HashMap<>();
- public Leaderboard(REntityServer server, String configKey, Location location, int best) {
+ public LeaderboardManager(REntityServer server, String configKey, Location location) {
this.server = server;
this.configKey = configKey;
this.location = location;
- this.best = best;
Bukkit.getPluginManager().registerEvents(this, LobbySystem.getInstance());
update();
}
@@ -65,20 +57,20 @@ public class Leaderboard implements Listener {
public void update() {
entities.forEach(REntity::die);
entities.clear();
- List leaderboard = getLeaderboard();
+ List leaderboard = getLeaderboard();
if (leaderboard.isEmpty()) return;
- bestTime = leaderboard.get(0).time;
+ bestTime = leaderboard.get(0).getTime();
for (int i = 0; i < leaderboard.size(); i++) {
- LeaderboardEntry entry = leaderboard.get(i);
+ Leaderboard entry = leaderboard.get(i);
RArmorStand entity = new RArmorStand(server, location.clone().add(0, (leaderboard.size() - i - 1) * 0.32, 0), RArmorStand.Size.MARKER);
- SteamwarUser user = SteamwarUser.byId(entry.user);
+ SteamwarUser user = SteamwarUser.byId(entry.getUser());
String color = "§7";
if (i == 0) {
color = "§6§l";
} else if (i < 3) {
color = "§e";
}
- entity.setDisplayName(calcName(user, color, i + 1, entry.time));
+ entity.setDisplayName(calcName(user, color, i + 1, entry.getTime()));
entity.setInvisible(true);
entities.add(entity);
}
@@ -135,32 +127,27 @@ public class Leaderboard implements Listener {
return st.toString();
}
- private List getLeaderboard() {
- return LEADERBOARD.select(resultSet -> {
- List leaderboard = new ArrayList<>();
- while (resultSet.next()) {
- leaderboard.add(new LeaderboardEntry(resultSet.getInt("User"), resultSet.getLong("Time")));
- }
- return leaderboard;
- }, configKey, best);
+ private boolean isNewBestTime(long time) {
+ return time < bestTime;
}
- private long getPlayerTime(SteamwarUser user) {
- return PLAYER_TIME.select(resultSet -> {
- if (!resultSet.next()) {
- return Long.MAX_VALUE;
- }
- return resultSet.getLong("Time");
- }, configKey, user.getId());
+ public void updateBestTime(SteamwarUser user, long time) {
+ Leaderboard.upsert(user.getId(), configKey, time, isNewBestTime(time));
+ update();
+ }
+
+ private List getLeaderboard() {
+ return Leaderboard.getLeaderboard(configKey);
+ }
+
+ public long getPlayerTime(SteamwarUser user) {
+ Leaderboard lb = Leaderboard.getPlayerTime(user, configKey);
+ if(lb != null) return lb.getTime();
+ return 0;
}
private int getPlayerPlacement(SteamwarUser user) {
- return PLAYER_PLACEMENT.select(resultSet -> {
- if (!resultSet.next()) {
- return Integer.MAX_VALUE;
- }
- return resultSet.getInt("Placement");
- }, configKey, configKey, user.getId());
+ return Leaderboard.getPlayerPlacement(user, configKey);
}
public static String renderTime(long time) {
@@ -178,13 +165,6 @@ public class Leaderboard implements Listener {
time % 1000);
}
- @AllArgsConstructor
- private class LeaderboardEntry {
-
- private final int user;
- private final long time;
- }
-
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
SteamwarUser steamwarUser = SteamwarUser.get(event.getPlayer().getUniqueId());