mirror of
https://github.com/jonesdevelopment/sonar.git
synced 2026-11-05 09:20:15 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70506aaf64 | ||
|
|
873d9f8c10 | ||
|
|
8f40552dc5 | ||
|
|
6302888a33 | ||
|
|
ea7cc9a9f3 | ||
|
|
ea928ce6db | ||
|
|
d19ee78c41 | ||
|
|
924f4e7344 | ||
|
|
6e4c938767 | ||
|
|
e534738336 | ||
|
|
92d40ca4cf | ||
|
|
0fd7f0c173 | ||
|
|
f197260426 | ||
|
|
cbae477982 | ||
|
|
878789b2c1 | ||
|
|
2a98d4bdb0 | ||
|
|
790d233870 | ||
|
|
e732da6af2 |
No files matched your search
@@ -1 +0,0 @@
|
||||
patreon: jonesdev
|
||||
+2
-3
@@ -55,12 +55,11 @@ Sonar is unlikely to ever falsely prevent a player from joining the server since
|
||||
If you or one of your players experiences a false positive, make sure to report it by opening a [GitHub issue](https://github.com/jonesdevelopment/sonar/issues/new/choose) or a ticket on the [Discord server](https://jonesdev.xyz/discord/).
|
||||
|
||||
## Sponsors
|
||||
Massive thanks to the sponsors of Sonar who help keep this project running:
|
||||
|
||||
<a href="https://github.com/Chumb3x"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/79834154?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
|
||||
If you are interested in sponsoring this project, please contact me via [Discord](https://jonesdev.xyz/discord/).
|
||||
|
||||
### Past Sponsors
|
||||
|
||||
<a href="https://github.com/Chumb3x"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/79834154?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
|
||||
<a href="https://github.com/Hydoxl"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/107579333?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
|
||||
<a href="https://github.com/ItzErpandX"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/84748484?v=4?s=400?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
|
||||
@@ -30,6 +30,8 @@ public enum SonarPlatform {
|
||||
pipeline -> pipeline.context("outbound_config") != null ? "outbound_config" : "encoder"),
|
||||
BUNGEE("BungeeCord", 19109, "inbound-boss",
|
||||
pipeline -> "packet-encoder"),
|
||||
PAPER("Paper", 19110, "packet_handler",
|
||||
pipeline -> pipeline.context("outbound_config") != null ? "outbound_config" : "encoder"),
|
||||
VELOCITY("Velocity", 19107, "handler",
|
||||
pipeline -> "minecraft-encoder");
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@ public final class SonarConfiguration {
|
||||
verification.validNameRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-name-regex"));
|
||||
|
||||
verification.checkGeyser = generalConfig.getBoolean("verification.check-geyser-players");
|
||||
verification.checkEagler = generalConfig.getBoolean("verification.check-eagler-players");
|
||||
verification.logConnections = generalConfig.getBoolean("verification.log-connections");
|
||||
verification.logDuringAttack = generalConfig.getBoolean("verification.log-during-attack");
|
||||
verification.debugXYZPositions = generalConfig.getBoolean("verification.debug-xyz-positions");
|
||||
@@ -415,6 +416,7 @@ public final class SonarConfiguration {
|
||||
|
||||
private int timeOfDay;
|
||||
private boolean checkGeyser;
|
||||
private boolean checkEagler;
|
||||
private boolean logConnections;
|
||||
private boolean logDuringAttack;
|
||||
private boolean debugXYZPositions;
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ package xyz.jonesdev.sonar.api.database.controller;
|
||||
import com.alessiodp.libby.LibraryManager;
|
||||
import com.j256.ormlite.dao.Dao;
|
||||
import com.j256.ormlite.dao.DaoManager;
|
||||
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||
import com.j256.ormlite.jdbc.JdbcPooledConnectionSource;
|
||||
import com.j256.ormlite.stmt.QueryBuilder;
|
||||
import com.j256.ormlite.support.ConnectionSource;
|
||||
import com.j256.ormlite.table.TableUtils;
|
||||
@@ -93,7 +93,7 @@ public final class VerifiedPlayerController {
|
||||
"?autoReconnect=true");
|
||||
}
|
||||
|
||||
connectionSource = new JdbcConnectionSource(jdbcURL,
|
||||
connectionSource = new JdbcPooledConnectionSource(jdbcURL,
|
||||
Sonar.get0().getConfig().getGeneralConfig().getString("database.username"),
|
||||
Sonar.get0().getConfig().getGeneralConfig().getString("database.password"),
|
||||
cachedDatabaseType.getDatabaseType());
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven(url = "https://repo.jonesdev.xyz/releases/") // Bungee & Velocity proxy module
|
||||
maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/") // libby
|
||||
maven(url = "https://repo.alessiodp.com/snapshots/") // libby
|
||||
}
|
||||
|
||||
apply(plugin = "java")
|
||||
@@ -102,7 +102,7 @@ allprojects {
|
||||
tasks {
|
||||
// This is a small wrapper tasks to simplify the building process
|
||||
register("build-sonar") {
|
||||
val subprojects = listOf("api", "captcha", "common", "bukkit", "bungeecord", "velocity")
|
||||
val subprojects = listOf("api", "captcha", "common", "bukkit", "bungeecord", "paper", "velocity")
|
||||
val buildTasks = subprojects.flatMap { listOf("$it:clean", "$it:spotlessApply", "$it:shadowJar") }
|
||||
dependsOn(buildTasks)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ dependencies {
|
||||
implementation(project(":common"))
|
||||
|
||||
compileOnly(rootProject.libs.spigot)
|
||||
testCompileOnly(rootProject.libs.spigot)
|
||||
|
||||
implementation(rootProject.libs.adventure.platform.bukkit) {
|
||||
exclude(module = "adventure-nbt")
|
||||
|
||||
@@ -14,7 +14,6 @@ dependencies {
|
||||
implementation(project(":common"))
|
||||
|
||||
compileOnly(rootProject.libs.bungeecord)
|
||||
testCompileOnly(rootProject.libs.bungeecord)
|
||||
|
||||
implementation(rootProject.libs.adventure.platform.bungee) {
|
||||
exclude(module = "adventure-nbt")
|
||||
|
||||
@@ -30,6 +30,7 @@ import xyz.jonesdev.sonar.common.protocol.SonarPacket;
|
||||
import xyz.jonesdev.sonar.common.protocol.SonarPacketEncoder;
|
||||
import xyz.jonesdev.sonar.common.protocol.SonarPacketRegistry;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
import xyz.jonesdev.sonar.common.util.EaglerUtil;
|
||||
import xyz.jonesdev.sonar.common.util.GeyserUtil;
|
||||
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
|
||||
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
|
||||
@@ -132,6 +133,13 @@ public abstract class InboundHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
return;
|
||||
}
|
||||
|
||||
// Completely skip EaglercraftX connections if configured
|
||||
if (EaglerUtil.isEaglerConnection(ctx.channel())
|
||||
&& !Sonar.get0().getConfig().getVerification().isCheckEagler()) {
|
||||
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we actually have to verify the player
|
||||
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
|
||||
if (Sonar.get0().getVerifiedPlayerController().getCache().contains(fingerprint)) {
|
||||
@@ -191,6 +199,7 @@ public abstract class InboundHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
// Don't remove floodgate's pipelines
|
||||
|| entry.getKey().startsWith("geyser")
|
||||
|| entry.getKey().startsWith("floodgate")
|
||||
|| entry.getKey().startsWith("eagler")
|
||||
|| entry.getKey().equals("DefaultChannelPipeline$TailContext#0")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class LibraryLoader {
|
||||
Library.builder()
|
||||
.groupId("com{}github{}ben-manes{}caffeine")
|
||||
.artifactId("caffeine")
|
||||
.version("3.2.0")
|
||||
.version("3.2.3")
|
||||
.relocate("com{}github{}benmanes{}caffeine", "xyz{}jonesdev{}sonar{}libs{}caffeine")
|
||||
.build(),
|
||||
// ORMLite
|
||||
@@ -68,26 +68,38 @@ class LibraryLoader {
|
||||
|
||||
// Only load adventure if not on Velocity
|
||||
if (platform != SonarPlatform.VELOCITY) {
|
||||
libraryManager.loadLibraries(
|
||||
Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-text-minimessage")
|
||||
.version("4.13.1") // adventure-platforms hasn't updated in years... Please update! :'(
|
||||
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
|
||||
.build(),
|
||||
Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-text-serializer-gson")
|
||||
.version("4.18.0")
|
||||
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
|
||||
.build(),
|
||||
Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-nbt")
|
||||
.version("4.18.0")
|
||||
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
|
||||
.build()
|
||||
);
|
||||
if (platform != SonarPlatform.PAPER) {
|
||||
libraryManager.loadLibraries(
|
||||
Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-text-minimessage")
|
||||
.version("4.21.0")
|
||||
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
|
||||
.build(),
|
||||
Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-text-serializer-gson")
|
||||
.version("4.21.0")
|
||||
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
|
||||
.build(),
|
||||
Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-nbt")
|
||||
.version("4.21.0")
|
||||
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
|
||||
.build()
|
||||
);
|
||||
} else {
|
||||
// On Paper, we only relocate the nbt package since the rest of adventure is bundled with the server.
|
||||
libraryManager.loadLibrary(
|
||||
Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-nbt")
|
||||
.version("4.21.0")
|
||||
.relocate("net{}kyori{}adventure{}nbt", "xyz{}jonesdev{}sonar{}libs{}kyori{}adventure{}nbt")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.util;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@UtilityClass
|
||||
public class EaglerUtil {
|
||||
// Detect EaglerXServer WebSocket connections by checking for Eagler-specific pipeline handlers.
|
||||
private static final String H_EAGLER_HANDSHAKE = "eagler-handshake";
|
||||
private static final String H_EAGLER_FRAME_CODEC = "eagler-frame-codec";
|
||||
|
||||
public boolean isEaglerConnection(final @NotNull Channel channel) {
|
||||
return channel.pipeline().get(H_EAGLER_HANDSHAKE) != null
|
||||
|| channel.pipeline().get(H_EAGLER_FRAME_CODEC) != null;
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,9 @@ verification:
|
||||
# Tato funkce je experimentální a může způsobit problémy
|
||||
# Pokud je toto zakázáno, hráči Bedrock budou přeskočeni
|
||||
check-geyser-players: false
|
||||
# Měl by Sonar kontrolovat také hráče EaglercraftX?
|
||||
# Tato funkce je experimentální a může způsobit problémy.
|
||||
check-eagler-players: false
|
||||
# Doba, za kterou hráč přestal odesílat pakety před odpojením
|
||||
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
|
||||
read-timeout: 8000
|
||||
|
||||
@@ -184,6 +184,9 @@ verification:
|
||||
# Diese Funktion ist experimentell und kann Probleme verursachen
|
||||
# Wenn dies deaktiviert ist, werden Bedrock-Spieler übersprungen
|
||||
check-geyser-players: false
|
||||
# Sollte Sonar auch EaglercraftX-Spieler überprüfen?
|
||||
# Diese Funktion ist experimentell und kann zu Problemen führen.
|
||||
check-eagler-players: false
|
||||
# Die Zeit, seit der der Spieler keine Pakete mehr sendet, bevor er die Verbindung trennt
|
||||
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
|
||||
read-timeout: 8000
|
||||
|
||||
@@ -187,6 +187,9 @@ verification:
|
||||
# This feature is experimental and might cause issues
|
||||
# If this is disabled, Bedrock players will be skipped
|
||||
check-geyser-players: false
|
||||
# Should Sonar also check EaglercraftX players?
|
||||
# This feature is experimental and might cause issues
|
||||
check-eagler-players: false
|
||||
# The time since the player stopped sending packets before disconnecting
|
||||
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
|
||||
read-timeout: 8000
|
||||
|
||||
@@ -186,6 +186,9 @@ verification:
|
||||
# Cette fonctionnalité est expérimentale et peut causer des problèmes
|
||||
# Si cela est désactivé, les joueurs Bedrock seront ignorés
|
||||
check-geyser-players: false
|
||||
# Sonar devrait-il également vérifier les joueurs EaglercraftX ?
|
||||
# Cette fonctionnalité est expérimentale et peut causer des problèmes.
|
||||
check-eagler-players: false
|
||||
# Temps écoulé depuis que le joueur a cessé d'envoyer des paquets avant de se déconnecter
|
||||
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
|
||||
read-timeout: 8000
|
||||
|
||||
@@ -185,6 +185,9 @@ verification:
|
||||
# ეს ფუნქცია ექსპერიმენტულია და შეიძლება გამოიწვიოს პრობლემები
|
||||
# თუ ეს გამორთულია, Bedrock მოთამაშეები გამოვლენილი იქნება
|
||||
check-geyser-players: false
|
||||
# უნდა შეამოწმოს Sonar-მა EaglercraftX-ის მოთამაშეებიც?
|
||||
# ეს ფუნქცია ექსპერიმენტულია და შესაძლოა პრობლემები გამოიწვიოს
|
||||
check-eagler-players: false
|
||||
# დროის ოდენობა, რომელიც უნდა გავიდეს, სერვერთან კავშირის გარეშე, სანამ მოთამაშე დროებით გავარდება სერვერიდან.
|
||||
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
|
||||
read-timeout: 8000
|
||||
|
||||
@@ -182,6 +182,9 @@ verification:
|
||||
# Moet Sonar elke beweging/positieverandering tijdens de verificatie registreren?
|
||||
# Dit wordt niet aanbevolen voor productieservers, maar kan nuttig zijn bij het opsporen van fouten.
|
||||
debug-xyz-positions: false
|
||||
# Moet Sonar ook EaglercraftX-spelers controleren?
|
||||
# Deze functie is experimenteel en kan problemen veroorzaken.
|
||||
check-eagler-players: false
|
||||
# Moet Sonar ook Geyser (Bedrock) spelers controleren?
|
||||
# Deze functie is experimenteel en kan problemen veroorzaken
|
||||
# Als dit is uitgeschakeld, worden Bedrock-spelers overgeslagen
|
||||
|
||||
@@ -186,6 +186,9 @@ verification:
|
||||
# Ta funkcja jest eksperymentalna i może powodować problemy
|
||||
# Jeśli ta opcja jest wyłączona, gracze Bedrock będą pomijani
|
||||
check-geyser-players: false
|
||||
# Czy Sonar powinien również sprawdzać graczy EaglercraftX?
|
||||
# Ta funkcja jest eksperymentalna i może powodować problemy.
|
||||
check-eagler-players: false
|
||||
# Czas, jaki ma upłynąć od zatrzymania wysyłania pakietów przez gracza do wyrzucenia.
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
read-timeout: 8000
|
||||
|
||||
@@ -186,6 +186,9 @@ verification:
|
||||
# Este recurso é experimental e pode causar problemas
|
||||
# Se isso estiver desativado, jogadores Bedrock serão ignorados
|
||||
check-geyser-players: false
|
||||
# O Sonar também deve verificar os jogadores do EaglercraftX?
|
||||
# Este recurso é experimental e pode causar problemas.
|
||||
check-eagler-players: false
|
||||
# O tempo desde que o jogador parou de enviar pacotes antes de desconectar
|
||||
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
|
||||
read-timeout: 8000
|
||||
|
||||
@@ -185,6 +185,9 @@ verification:
|
||||
# Эта функция экспериментальна и может вызывать проблемы
|
||||
# Если это отключено, игроки Bedrock будут пропущены
|
||||
check-geyser-players: false
|
||||
# Должен ли Sonar также проверять игроков EaglercraftX?
|
||||
# Эта функция является экспериментальной и может вызывать проблемы.
|
||||
check-eagler-players: false
|
||||
# Время, прошедшее с момента прекращения отправки пакетов игроком до отключения.
|
||||
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
|
||||
read-timeout: 8000
|
||||
|
||||
@@ -108,9 +108,9 @@ verification:
|
||||
# 如果未找到指定的图片, 则自动通过噪音生成一个随机的背景图片.
|
||||
# 如果图片在该插件的配置文件夹下, 则可以简单地输入图片的文件名.
|
||||
background: ''
|
||||
# 地图验证码的风格(默认:'modern').
|
||||
# 地图验证码的风格(默认: 'modern').
|
||||
# 要使用旧设计, 请将其设置为 'legacy'.
|
||||
# To use an experimental design, set this to 'complex'.
|
||||
# 要使用实验设计, 请将此项设置为 'complex'.
|
||||
style: 'modern'
|
||||
# Sonar 应该一次准备多少个验证码?
|
||||
precompute: 500
|
||||
@@ -182,6 +182,9 @@ verification:
|
||||
# 此功能是实验性的. 请报告任何因为误判而导致无法通过检查的问题
|
||||
# 如果关闭该选项 Sonar 将不检查这些玩家以便直接连接到服务器.
|
||||
check-geyser-players: false
|
||||
# 声纳是否也应检测 EaglercraftX 玩家?
|
||||
# 此功能尚在试验阶段, 可能引发问题
|
||||
check-eagler-players: false
|
||||
# 播放器断开连接前停止发送数据包的时间
|
||||
# (该值为毫秒. 1秒等于1000毫秒.)
|
||||
read-timeout: 8000
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
group=xyz.jonesdev
|
||||
version=2.1.43
|
||||
version=2.1.45
|
||||
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
|
||||
@@ -9,9 +9,10 @@ pluginyml = "0.6.0"
|
||||
[plugins]
|
||||
indra-git = "net.kyori.indra.git:3.2.0"
|
||||
shadow = "com.gradleup.shadow:8.3.8"
|
||||
spotless = "com.diffplug.spotless:7.2.1"
|
||||
spotless = "com.diffplug.spotless:8.2.1"
|
||||
pluginyml-bungee = { id = "net.minecrell.plugin-yml.bungee", version.ref = "pluginyml" }
|
||||
pluginyml-bukkit = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" }
|
||||
pluginyml-paper = { id = "net.minecrell.plugin-yml.paper", version.ref = "pluginyml" }
|
||||
|
||||
[libraries]
|
||||
netty = { module = "io.netty:netty-all", version.ref = "netty" }
|
||||
@@ -23,17 +24,19 @@ adventure-platform-bungee = { module = "net.kyori:adventure-platform-bungeecord"
|
||||
libby-core = { module = "com.alessiodp.libby:libby-core", version.ref = "libby" }
|
||||
libby-bungee = { module = "com.alessiodp.libby:libby-bungee", version.ref = "libby" }
|
||||
libby-bukkit = { module = "com.alessiodp.libby:libby-bukkit", version.ref = "libby" }
|
||||
libby-paper = { module = "com.alessiodp.libby:libby-paper", version.ref = "libby" }
|
||||
libby-velocity = { module = "com.alessiodp.libby:libby-velocity", version.ref = "libby" }
|
||||
bstats-bungee = { module = "org.bstats:bstats-bungeecord", version.ref = "bstats" }
|
||||
bstats-bukkit = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" }
|
||||
bstats-velocity = { module = "org.bstats:bstats-velocity", version.ref = "bstats" }
|
||||
imagefilters = "com.jhlabs:filters:2.0.235-1"
|
||||
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.2"
|
||||
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.3"
|
||||
annotations = "org.jetbrains:annotations:26.0.2"
|
||||
simpleyaml = "com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4"
|
||||
ormlite = "com.j256.ormlite:ormlite-jdbc:6.1"
|
||||
lombok = "org.projectlombok:lombok:1.18.42"
|
||||
bungeecord = "net.md-5:bungeecord-proxy:1.21-SNAPSHOT"
|
||||
velocity = "io.papermc.velocity:velocity-proxy:3.4.0-SNAPSHOT"
|
||||
velocity = "io.papermc.velocity:velocity-proxy:3.5.0-SNAPSHOT"
|
||||
paper = "io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT"
|
||||
# We have to use 1.16.5 for backwards compatibility
|
||||
spigot = "org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT"
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -172,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
|
||||
+93
-94
@@ -1,94 +1,93 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,52 @@
|
||||
import net.minecrell.pluginyml.bukkit.BukkitPluginDescription
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.pluginyml.paper) apply true
|
||||
}
|
||||
|
||||
paper {
|
||||
name = rootProject.name
|
||||
main = "xyz.jonesdev.sonar.paper.SonarPaperPlugin"
|
||||
authors = listOf("Jones Development", "Sonar Contributors")
|
||||
website = "https://jonesdev.xyz/discord/"
|
||||
load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD
|
||||
apiVersion = "1.21" // ignore legacy plugin warning
|
||||
foliaSupported = true
|
||||
|
||||
serverDependencies {
|
||||
listOf("Geyser-Spigot", "floodgate", "Protocolize", "ProtocolSupport",
|
||||
"ViaVersion", "packetevents", "ProtocolLib", "FastLogin").forEach {
|
||||
register(it) {
|
||||
required = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
// Modern Paper requires Java 21
|
||||
java.sourceCompatibility = JavaVersion.VERSION_21
|
||||
java.targetCompatibility = JavaVersion.VERSION_21
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven(url = "https://repo.papermc.io/repository/maven-public/") // Paper
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":api"))
|
||||
implementation(project(":common"))
|
||||
implementation(project(":bukkit"))
|
||||
|
||||
compileOnly(rootProject.libs.paper)
|
||||
|
||||
implementation(rootProject.libs.bstats.bukkit)
|
||||
implementation(rootProject.libs.libby.paper)
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
relocate("net.kyori.adventure.nbt", "xyz.jonesdev.sonar.libs.kyori.adventure.nbt")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.paper;
|
||||
|
||||
import com.alessiodp.libby.PaperLibraryManager;
|
||||
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.paper.command.PaperSonarCommand;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.bukkit.SonarBukkit;
|
||||
import xyz.jonesdev.sonar.bukkit.antibot.BukkitInjector;
|
||||
import xyz.jonesdev.sonar.bukkit.listener.BukkitJoinListener;
|
||||
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public final class SonarPaper extends SonarBootstrap<SonarPaperPlugin> {
|
||||
private final LoggerWrapper logger = new LoggerWrapper() {
|
||||
|
||||
@Override
|
||||
public void info(final String message, final Object... args) {
|
||||
getPlugin().getLogger().info(buildFullMessage(message, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(final String message, final Object... args) {
|
||||
getPlugin().getLogger().warning(buildFullMessage(message, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(final String message, final Object... args) {
|
||||
getPlugin().getLogger().severe(buildFullMessage(message, args));
|
||||
}
|
||||
};
|
||||
private Metrics metrics;
|
||||
|
||||
public SonarPaper(final @NotNull SonarPaperPlugin plugin) {
|
||||
super(plugin, SonarPlatform.PAPER, plugin.getDataFolder(), new PaperLibraryManager(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Audience audience(@Nullable final UUID uniqueId) {
|
||||
if (uniqueId == null) return null;
|
||||
return Bukkit.getPlayer(uniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Audience sender(@NotNull final Object object) {
|
||||
return (Audience) object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LoggerWrapper getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
// Initialize bStats.org metrics
|
||||
metrics = new Metrics(getPlugin(), getPlatform().getMetricsId());
|
||||
|
||||
// Add charts for some configuration options
|
||||
metrics.addCustomChart(new SimplePie("verification",
|
||||
() -> getConfig().getVerification().getTiming().getDisplayName()));
|
||||
metrics.addCustomChart(new SimplePie("captcha",
|
||||
() -> getConfig().getVerification().getMap().getTiming().getDisplayName()));
|
||||
metrics.addCustomChart(new SimplePie("language",
|
||||
() -> getConfig().getLanguage().getName()));
|
||||
metrics.addCustomChart(new SimplePie("database_type",
|
||||
() -> getConfig().getDatabase().getType().getDisplayName()));
|
||||
|
||||
// Register Sonar command
|
||||
getPlugin().getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->
|
||||
event.registrar().register("sonar", new PaperSonarCommand()));
|
||||
|
||||
// Try to inject into the server
|
||||
if (BukkitInjector.isLateBindEnabled()) {
|
||||
getPlugin().getServer().getScheduler().runTask(getPlugin(), BukkitInjector::inject);
|
||||
} else {
|
||||
getPlugin().getServer().getPluginManager().registerEvents(new BukkitJoinListener(), getPlugin());
|
||||
}
|
||||
|
||||
// Let the injector know that the plugin has been enabled
|
||||
SonarBukkit.INITIALIZE_LISTENER.complete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
// Make sure to properly stop the metrics
|
||||
if (metrics != null) {
|
||||
metrics.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.paper;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import xyz.jonesdev.sonar.bukkit.antibot.BukkitInjector;
|
||||
|
||||
public final class SonarPaperPlugin extends JavaPlugin {
|
||||
private SonarPaper bootstrap;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
// Inject early if late-bind is disabled
|
||||
if (!BukkitInjector.isLateBindEnabled()) {
|
||||
BukkitInjector.inject();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
bootstrap = new SonarPaper(this);
|
||||
bootstrap.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
bootstrap.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.paper.command;
|
||||
|
||||
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public final class PaperSonarCommand implements BasicCommand, SonarCommand {
|
||||
@Override
|
||||
public void execute(final CommandSourceStack commandSourceStack, final String @NonNull [] args) {
|
||||
final CommandSender sender = commandSourceStack.getSender();
|
||||
final InvocationSource invocationSource = new InvocationSource(
|
||||
sender instanceof Player ? ((Player) sender).getUniqueId() : null,
|
||||
sender,
|
||||
sender::hasPermission
|
||||
);
|
||||
handle(invocationSource, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<String> suggest(final CommandSourceStack commandSourceStack, final String @NonNull [] args) {
|
||||
// Do not allow tab completion if the player does not have the required permission
|
||||
return commandSourceStack.getSender().hasPermission("sonar.command") ?
|
||||
getCachedTabSuggestions(args) : Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ sequenceOf(
|
||||
"common",
|
||||
"bukkit",
|
||||
"bungeecord",
|
||||
"paper",
|
||||
"velocity"
|
||||
).forEach {
|
||||
include(":$it")
|
||||
|
||||
@@ -3,7 +3,6 @@ dependencies {
|
||||
implementation(project(":common"))
|
||||
|
||||
compileOnly(rootProject.libs.velocity)
|
||||
testCompileOnly(rootProject.libs.velocity)
|
||||
|
||||
implementation(rootProject.libs.bstats.velocity)
|
||||
implementation(rootProject.libs.libby.velocity)
|
||||
|
||||
Reference in new issue
Block a user