Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e4423e2ce | ||
|
|
ddb65f2f5a | ||
|
|
254faecc90 | ||
|
|
582024eba1 | ||
|
|
e1fe738ffe | ||
|
|
5f3a439113 | ||
|
|
27cca23412 | ||
|
|
2d2c576ad4 | ||
|
|
0adf7b0b6a | ||
|
|
c6ff9370c2 | ||
|
|
e33aac29c5 | ||
|
|
42dc3af6c9 | ||
|
|
ff402d5096 | ||
|
|
06a72bf3bd | ||
|
|
3fb4291163 | ||
|
|
cc57b1d978 | ||
|
|
750b42b5ab | ||
|
|
ad017024ae | ||
|
|
5887d82c19 | ||
|
|
b1e5d01062 | ||
|
|
a50bf45176 | ||
|
|
e0b5ad2a33 | ||
|
|
e5ee94362c | ||
|
|
d877568b38 | ||
|
|
0edec77135 | ||
|
|
54445cdfdb | ||
|
|
8545d60791 | ||
|
|
11327e2890 | ||
|
|
ad511836f3 | ||
|
|
5cb79d3acf | ||
|
|
032dd4f94e | ||
|
|
e831c1a646 | ||
|
|
f50c90a690 | ||
|
|
1e7e4ba5f1 |
No files matched your search
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
contact@jonesdev.xyz.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
+12
-9
@@ -12,12 +12,9 @@ plugins {
|
||||
|
||||
apply(plugin = "io.toolebox.git-versioner")
|
||||
|
||||
rootProject.group = "xyz.jonesdev.sonar"
|
||||
val semanticVersion = "2.0.0"
|
||||
|
||||
versioner {
|
||||
pattern {
|
||||
pattern = "$semanticVersion-%h(-%c)"
|
||||
pattern = "$version-%h-%c-%b"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,17 +32,22 @@ subprojects {
|
||||
apply(plugin = "com.github.johnrengelman.shadow")
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.projectlombok:lombok:1.18.28")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.28")
|
||||
compileOnly("org.projectlombok:lombok:1.18.30")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.30")
|
||||
|
||||
testCompileOnly("org.projectlombok:lombok:1.18.28")
|
||||
testAnnotationProcessor("org.projectlombok:lombok:1.18.28")
|
||||
testCompileOnly("org.projectlombok:lombok:1.18.30")
|
||||
testAnnotationProcessor("org.projectlombok:lombok:1.18.30")
|
||||
|
||||
compileOnly("io.netty:netty-all:4.1.97.Final") // netty
|
||||
// MiniMessage
|
||||
implementation("net.kyori:adventure-text-minimessage:4.14.0")
|
||||
implementation("net.kyori:adventure-text-serializer-gson:4.14.0")
|
||||
implementation("net.kyori:adventure-text-serializer-legacy:4.14.0")
|
||||
|
||||
implementation("net.kyori:adventure-nbt:4.14.0") // nbt
|
||||
implementation("com.j256.ormlite:ormlite-jdbc:6.1") // ORM
|
||||
implementation("xyz.jonesdev:cappuccino:0.1.6-SNAPSHOT") // expiring cache
|
||||
|
||||
compileOnly("io.netty:netty-all:4.1.99.Final") // netty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +64,7 @@ tasks {
|
||||
// Set the implementation version, so we can create exact version
|
||||
// information in-game and make it accessible to the user.
|
||||
attributes["Implementation-Version"] = version
|
||||
attributes["Implementation-Vendor"] = "Jones Development, Sonar Contributors"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
group=xyz.jonesdev.sonar
|
||||
version=2.0.2
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandRegistry;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.controller.VerifiedPlayerController;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEventManager;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.server.ServerWrapper;
|
||||
@@ -53,11 +54,21 @@ public interface Sonar {
|
||||
|
||||
@NotNull Verbose getVerboseHandler();
|
||||
|
||||
/**
|
||||
* Set a custom verbose handler
|
||||
*/
|
||||
void setVerboseHandler(final @NotNull Verbose verboseHandler);
|
||||
|
||||
/**
|
||||
* Reloads the entire plugin
|
||||
*/
|
||||
void reload();
|
||||
|
||||
@NotNull
|
||||
default SonarEventManager getEventManager() {
|
||||
return SonarEventManager.INSTANCE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
default SonarVersion getVersion() {
|
||||
return SonarVersion.GET;
|
||||
|
||||
@@ -24,6 +24,7 @@ public final class SonarVersion {
|
||||
static final SonarVersion GET = new SonarVersion();
|
||||
private final String semanticVersion, full, formatted, commitSHA;
|
||||
private final int build;
|
||||
private final boolean isOnMainBranch;
|
||||
|
||||
SonarVersion() {
|
||||
final Package manifest = Sonar.class.getPackage();
|
||||
@@ -31,11 +32,12 @@ public final class SonarVersion {
|
||||
|
||||
if (versionString == null) {
|
||||
this.full = "<could not retrieve version>";
|
||||
this.semanticVersion = "<unknown>";
|
||||
this.commitSHA = "<unknown>";
|
||||
this.build = 0;
|
||||
this.semanticVersion = "<unknown version>";
|
||||
this.commitSHA = "<unknown commit>";
|
||||
this.build = -1;
|
||||
this.isOnMainBranch = false;
|
||||
this.formatted = full;
|
||||
Sonar.get().getLogger().error("Could not find version information (Is the manifest missing?)");
|
||||
Sonar.get().getLogger().warn("Could not find version information. Is the manifest missing?");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,6 +45,7 @@ public final class SonarVersion {
|
||||
this.semanticVersion = versionString.split("-")[0];
|
||||
this.commitSHA = versionString.split("-")[1];
|
||||
this.build = Integer.parseInt(versionString.split("-")[2]);
|
||||
this.isOnMainBranch = versionString.split("-")[3].equals("main");
|
||||
this.formatted = semanticVersion + " build " + build + " (" + commitSHA + ")";
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class CommandInvocation {
|
||||
private final InvocationSender sender;
|
||||
private final InvocationSource sender;
|
||||
private final Subcommand command;
|
||||
private final String[] rawArguments;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.api.command;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public abstract class InvocationSource {
|
||||
private final String name;
|
||||
private final Audience audience;
|
||||
|
||||
/**
|
||||
* Sends an empty chat message to the command executor
|
||||
*/
|
||||
public final void sendMessage() {
|
||||
sendMessage(Component.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the command executor
|
||||
*/
|
||||
public final void sendMessage(final String legacy) {
|
||||
sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(legacy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the command executor
|
||||
*/
|
||||
public final void sendMessage(final Component component) {
|
||||
audience.sendMessage(component);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.api.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public interface SonarCommand {
|
||||
List<String> TAB_SUGGESTIONS = new ArrayList<>();
|
||||
|
||||
Map<String, List<String>> ARG_TAB_SUGGESTIONS = new HashMap<>();
|
||||
|
||||
ExpiringCache<Object> DELAY = Cappuccino.buildExpiring(500L);
|
||||
|
||||
int COPYRIGHT_YEAR = Calendar.getInstance().get(Calendar.YEAR);
|
||||
|
||||
List<Component> CACHED_HELP_MESSAGE = new Vector<>();
|
||||
|
||||
default void cacheHelpMessage() {
|
||||
CACHED_HELP_MESSAGE.addAll(Arrays.asList(
|
||||
Component.text("Running Sonar " + Sonar.get().getVersion()
|
||||
+ " on " + Sonar.get().getServer().getPlatform().getDisplayName()
|
||||
+ ".", NamedTextColor.YELLOW),
|
||||
Component.text("(C) " + COPYRIGHT_YEAR + " Jones Development and Sonar Contributors", NamedTextColor.YELLOW),
|
||||
Component.text("https://github.com/jonesdevelopment/sonar", NamedTextColor.GREEN)
|
||||
.clickEvent(net.kyori.adventure.text.event.ClickEvent.clickEvent(net.kyori.adventure.text.event.ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar")),
|
||||
Component.empty(),
|
||||
Component.text("Need help or have any questions?", NamedTextColor.YELLOW),
|
||||
Component.textOfChildren(
|
||||
Component.text("Open a ticket on the Discord ", NamedTextColor.YELLOW)
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.hoverEvent(net.kyori.adventure.text.event.HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open Discord)")))
|
||||
.clickEvent(net.kyori.adventure.text.event.ClickEvent.clickEvent(net.kyori.adventure.text.event.ClickEvent.Action.OPEN_URL, "https://jonesdev.xyz/discord/")),
|
||||
Component.text("or open a new issue on GitHub.", NamedTextColor.YELLOW)
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.hoverEvent(net.kyori.adventure.text.event.HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open GitHub)")))
|
||||
.clickEvent(net.kyori.adventure.text.event.ClickEvent.clickEvent(net.kyori.adventure.text.event.ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar" +
|
||||
"/issues"))
|
||||
),
|
||||
Component.empty()
|
||||
));
|
||||
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(sub -> {
|
||||
Component component = Component.textOfChildren(
|
||||
Component.text(" ▪ ", NamedTextColor.GRAY),
|
||||
Component.text("/sonar " + sub.getInfo().name(), NamedTextColor.GREEN),
|
||||
Component.text(" - ", NamedTextColor.GRAY),
|
||||
Component.text(sub.getInfo().description(), NamedTextColor.WHITE)
|
||||
);
|
||||
|
||||
component = component.clickEvent(
|
||||
net.kyori.adventure.text.event.ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND,
|
||||
"/sonar " + sub.getInfo().name() + " ")
|
||||
).hoverEvent(
|
||||
net.kyori.adventure.text.event.HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text(
|
||||
"§7Only players: §f" + (sub.getInfo().onlyPlayers() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Require console: §f" + (sub.getInfo().onlyConsole() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Permission: §f" + sub.getPermission()
|
||||
+ Sonar.LINE_SEPARATOR + "§7Aliases: §f" + sub.getAliases()
|
||||
))
|
||||
);
|
||||
CACHED_HELP_MESSAGE.add(component);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -49,11 +49,9 @@ public abstract class Subcommand {
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
protected final void incorrectUsage(final @NotNull InvocationSender sender) {
|
||||
sender.sendMessage(
|
||||
SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
.replace("%usage%", info.name() + " (" + arguments + ")")
|
||||
);
|
||||
protected final void incorrectUsage(final @NotNull InvocationSource sender) {
|
||||
sender.sendMessage(SONAR.getConfig().getIncorrectCommandUsage()
|
||||
.replace("%usage%", info.name() + " (" + arguments + ")"));
|
||||
}
|
||||
|
||||
public abstract void execute(final @NotNull CommandInvocation invocation);
|
||||
|
||||
@@ -27,8 +27,11 @@ import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.LINE_SEPARATOR;
|
||||
|
||||
@Getter
|
||||
public final class SimpleYamlConfig {
|
||||
private final File file;
|
||||
private final YamlFile yaml;
|
||||
|
||||
private static final List<String> HEADER = Arrays.asList(
|
||||
@@ -48,6 +51,7 @@ public final class SimpleYamlConfig {
|
||||
throw new IllegalStateException("Could not create folder?!");
|
||||
}
|
||||
|
||||
this.file = file;
|
||||
this.yaml = new YamlFile(file.getPath());
|
||||
}
|
||||
|
||||
@@ -59,16 +63,20 @@ public final class SimpleYamlConfig {
|
||||
}
|
||||
}
|
||||
|
||||
public void load() {
|
||||
try {
|
||||
yaml.setCommentFormat(YamlCommentFormat.PRETTY);
|
||||
public void load() throws Exception {
|
||||
// https://github.com/jonesdevelopment/sonar/issues/26
|
||||
// Only load the configuration if the file already exists
|
||||
if (yaml.exists()) {
|
||||
yaml.loadWithComments();
|
||||
} else {
|
||||
yaml.createOrLoadWithComments();
|
||||
|
||||
yaml.options().headerFormatter().commentPrefix("# ");
|
||||
yaml.setHeader(String.join(Sonar.LINE_SEPARATOR, HEADER));
|
||||
} catch (IOException exception) {
|
||||
exception.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
// Always load the comment format and options
|
||||
yaml.setCommentFormat(YamlCommentFormat.PRETTY);
|
||||
|
||||
yaml.options().headerFormatter().commentPrefix("# ");
|
||||
yaml.setHeader(String.join(LINE_SEPARATOR, HEADER));
|
||||
}
|
||||
|
||||
public void set(final String path, final Object v) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+1
-3
@@ -48,10 +48,8 @@ public final class VerifiedPlayerController {
|
||||
private final @NotNull SonarConfiguration.DatabaseType cachedDatabaseType;
|
||||
|
||||
public VerifiedPlayerController() {
|
||||
final SonarConfiguration config = Sonar.get().getConfig();
|
||||
|
||||
// Cache selected database type, so we don't need to call Sonar.get() every time
|
||||
cachedDatabaseType = config.DATABASE_TYPE;
|
||||
cachedDatabaseType = Sonar.get().getConfig().getDatabaseType();
|
||||
|
||||
// Don't establish a database connection if the type is NONE
|
||||
if (cachedDatabaseType == SonarConfiguration.DatabaseType.NONE) {
|
||||
|
||||
@@ -28,7 +28,8 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/Elytrium/LimboAuth/blob/master/src/main/java/net/elytrium/limboauth/dependencies/BaseLibrary.java
|
||||
@@ -77,13 +78,12 @@ public enum Dependency {
|
||||
|
||||
try (final InputStream inputStream = mvnRepoURL.openStream()) {
|
||||
Files.createDirectories(tempFilePath.getParent());
|
||||
Files.copy(inputStream, Files.createFile(tempFilePath), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.copy(inputStream, Files.createFile(tempFilePath), REPLACE_EXISTING);
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
|
||||
Sonar.get().getLogger().info("Finished downloading {} ({}s)!",
|
||||
tempFilePath.getFileName(), timer.formattedDelay());
|
||||
Sonar.get().getLogger().info("Finished downloading {} ({}s)!", tempFilePath.getFileName(), timer);
|
||||
}
|
||||
|
||||
return tempFilePath.toUri().toURL();
|
||||
|
||||
@@ -38,8 +38,8 @@ public class DependencyLoader {
|
||||
public ConnectionSource setUpDriverAndConnect() throws Throwable {
|
||||
final SonarConfiguration config = Sonar.get().getConfig();
|
||||
|
||||
final URL[] urls = new URL[config.DATABASE_TYPE.getDependencies().length];
|
||||
for (final Dependency dependency : config.DATABASE_TYPE.getDependencies()) {
|
||||
final URL[] urls = new URL[config.getDatabaseType().getDependencies().length];
|
||||
for (final Dependency dependency : config.getDatabaseType().getDependencies()) {
|
||||
final URL url = dependency.getClassLoaderURL();
|
||||
final ClassLoader currentClassLoader = DependencyLoader.class.getClassLoader();
|
||||
|
||||
@@ -50,9 +50,9 @@ public class DependencyLoader {
|
||||
urls[dependency.ordinal()] = url;
|
||||
}
|
||||
|
||||
final String type = config.DATABASE_TYPE.name().toLowerCase();
|
||||
final String type = config.getDatabaseType().name().toLowerCase();
|
||||
final String databaseURL =
|
||||
"jdbc:" + type + "://" + config.SQL_URL + ":" + config.SQL_PORT + "/" + config.SQL_DATABASE;
|
||||
"jdbc:" + type + "://" + config.getSqlUrl() + ":" + config.getSqlPort() + "/" + config.getSqlDatabase();
|
||||
|
||||
final ExternalClassLoader classLoader = new ExternalClassLoader(urls);
|
||||
final Connection connection = connect(classLoader, databaseURL, config);
|
||||
@@ -64,17 +64,17 @@ public class DependencyLoader {
|
||||
private Connection connect(final @NotNull ClassLoader classLoader,
|
||||
final @NotNull String databaseURL,
|
||||
final @NotNull SonarConfiguration configuration) throws Throwable {
|
||||
final Class<?> driverClass = classLoader.loadClass(configuration.DATABASE_TYPE.getDriverClassName());
|
||||
final Class<?> driverClass = classLoader.loadClass(configuration.getDatabaseType().getDriverClassName());
|
||||
final Object driver = driverClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
DriverManager.registerDriver((Driver) driver);
|
||||
|
||||
final Properties properties = new Properties();
|
||||
if (!configuration.SQL_USER.isEmpty()) {
|
||||
properties.put("user", configuration.SQL_USER);
|
||||
if (!configuration.getSqlUser().isEmpty()) {
|
||||
properties.put("user", configuration.getSqlUser());
|
||||
}
|
||||
if (!configuration.SQL_PASSWORD.isEmpty()) {
|
||||
properties.put("password", configuration.SQL_PASSWORD);
|
||||
if (!configuration.getSqlPassword().isEmpty()) {
|
||||
properties.put("password", configuration.getSqlPassword());
|
||||
}
|
||||
|
||||
final Method connect = driverClass.getDeclaredMethod("connect", String.class, Properties.class);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.api.event;
|
||||
|
||||
public interface SonarEvent {
|
||||
}
|
||||
+6
-17
@@ -15,26 +15,15 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.command;
|
||||
package xyz.jonesdev.sonar.api.event;
|
||||
|
||||
public interface InvocationSender {
|
||||
@FunctionalInterface
|
||||
public interface SonarEventListener {
|
||||
|
||||
/**
|
||||
* @return The name of the command executor
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Sends a message to the command executor
|
||||
* Handles the publishing of an event
|
||||
*
|
||||
* @param message Deserialized message
|
||||
* @param event [Cancellable] Sonar event
|
||||
*/
|
||||
void sendMessage(final String message);
|
||||
|
||||
/**
|
||||
* Sends an empty chat message to the command executor
|
||||
*/
|
||||
default void sendMessage() {
|
||||
sendMessage("");
|
||||
}
|
||||
void handle(final SonarEvent event);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.api.event;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Vector;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class SonarEventManager {
|
||||
public static final SonarEventManager INSTANCE = new SonarEventManager();
|
||||
private static final Collection<SonarEventListener> EVENT_LISTENERS = new Vector<>(0);
|
||||
|
||||
/**
|
||||
* Publishes the given event to all listeners
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public void publish(final @NotNull SonarEvent event) {
|
||||
for (final SonarEventListener eventListener : EVENT_LISTENERS) {
|
||||
eventListener.handle(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers one (or more) event listeners
|
||||
*
|
||||
* @param listeners One (or more) listeners to register
|
||||
* @see #unregisterListener(SonarEventListener...) Unregister an event listener
|
||||
*/
|
||||
public synchronized void registerListener(final @NotNull SonarEventListener... listeners) {
|
||||
EVENT_LISTENERS.addAll(Arrays.asList(listeners));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters one (or more) event listeners
|
||||
*
|
||||
* @param listeners One (or more) listeners to unregister
|
||||
* @apiNote This does not have any effect if the given listeners are not registered
|
||||
* @see #registerListener(SonarEventListener...) Register an event listener
|
||||
*/
|
||||
public synchronized void unregisterListener(final @NotNull SonarEventListener... listeners) {
|
||||
EVENT_LISTENERS.removeAll(Arrays.asList(listeners));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.api.event.impl;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserBlacklistedEvent implements SonarEvent {
|
||||
@ApiStatus.Experimental
|
||||
private final FallbackUser<?, ?> user;
|
||||
}
|
||||
+14
-11
@@ -15,17 +15,20 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.command;
|
||||
package xyz.jonesdev.sonar.api.event.impl;
|
||||
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public interface SonarBaseCommand {
|
||||
List<String> TAB_SUGGESTIONS = new ArrayList<>();
|
||||
Map<String, List<String>> ARG_TAB_SUGGESTIONS = new HashMap<>();
|
||||
ExpiringCache<Object> DELAY = Cappuccino.buildExpiring(500L);
|
||||
List<Object> CACHED_HELP_MESSAGE = new Vector<>();
|
||||
int COPYRIGHT_YEAR = Calendar.getInstance().get(Calendar.YEAR);
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifyFailedEvent implements SonarEvent {
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final String reason;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.api.event.impl;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifyJoinEvent implements SonarEvent {
|
||||
private final String username;
|
||||
private final FallbackUser<?, ?> user;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.api.event.impl;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifySuccessEvent implements SonarEvent {
|
||||
private final String username;
|
||||
private final UUID offlineUuid;
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final long timeTakenToVerify;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ public final class Fallback {
|
||||
};
|
||||
|
||||
public boolean isPotentiallyUnderAttack() {
|
||||
return connected.size() > SONAR.getConfig().MINIMUM_PLAYERS_FOR_ATTACK
|
||||
|| queue.getQueuedPlayers().size() > SONAR.getConfig().MINIMUM_PLAYERS_FOR_ATTACK;
|
||||
return connected.size() > SONAR.getConfig().getMinimumPlayersForAttack()
|
||||
|| queue.getQueuedPlayers().size() > SONAR.getConfig().getMinimumPlayersForAttack();
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public final class FallbackQueue {
|
||||
}
|
||||
|
||||
public void poll() {
|
||||
final int max = Sonar.get().getConfig().MAXIMUM_QUEUE_POLLS;
|
||||
final int max = Sonar.get().getConfig().getMaximumQueuePolls();
|
||||
int index = 0;
|
||||
|
||||
// We need to be very careful here since we don't want any concurrency issues.
|
||||
|
||||
+26
-6
@@ -19,18 +19,21 @@ package xyz.jonesdev.sonar.api.fallback;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserBlacklistedEvent;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifyFailedEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface FallbackPlayer<X, Y> {
|
||||
public interface FallbackUser<X, Y> {
|
||||
@NotNull Fallback getFallback();
|
||||
|
||||
@NotNull X getPlayer();
|
||||
@@ -49,9 +52,20 @@ public interface FallbackPlayer<X, Y> {
|
||||
* Kicks the player from the server with
|
||||
* the given disconnect message.
|
||||
*
|
||||
* @param reason Disconnect message
|
||||
* @param reason Legacy disconnect message string
|
||||
* @see #disconnect(Component)
|
||||
*/
|
||||
void disconnect(final @NotNull String reason);
|
||||
default void disconnect(final @NotNull String reason) {
|
||||
disconnect(Component.text(reason));
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks the player from the server with
|
||||
* the given disconnect message.
|
||||
*
|
||||
* @param reason Disconnect message component
|
||||
*/
|
||||
void disconnect(final @NotNull Component reason);
|
||||
|
||||
/**
|
||||
* Sends a packet/message to the player
|
||||
@@ -72,10 +86,13 @@ public interface FallbackPlayer<X, Y> {
|
||||
*/
|
||||
default void fail(final @Nullable String reason) {
|
||||
if (getChannel().isActive()) {
|
||||
disconnect(Sonar.get().getConfig().VERIFICATION_FAILED);
|
||||
// Call the VerifyFailedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifyFailedEvent(this, reason));
|
||||
|
||||
disconnect(Sonar.get().getConfig().getVerificationFailed());
|
||||
|
||||
if (reason != null) {
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().VERIFICATION_FAILED_LOG
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().getVerificationFailedLog()
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(getProtocolVersion().getProtocol()))
|
||||
.replace("%reason%", reason));
|
||||
@@ -89,8 +106,11 @@ public interface FallbackPlayer<X, Y> {
|
||||
|
||||
// Check if the player has too many failed attempts
|
||||
if (PREVIOUS_FAILS.has(getInetAddress().toString())) {
|
||||
// Call the BotBlacklistedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserBlacklistedEvent(this));
|
||||
|
||||
getFallback().getBlacklisted().put(getInetAddress().toString());
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().VERIFICATION_BLACKLIST_LOG
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().getVerificationBlacklistLog()
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(getProtocolVersion().getProtocol())));
|
||||
} else {
|
||||
@@ -17,22 +17,26 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.server;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ServerWrapper {
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public abstract class ServerWrapper {
|
||||
|
||||
/**
|
||||
* @return Platform of the server (Velocity, BungeeCord, Spigot)
|
||||
* Platform of the server (Velocity, BungeeCord, Spigot)
|
||||
*/
|
||||
SonarPlatform getPlatform();
|
||||
private final SonarPlatform platform;
|
||||
|
||||
/**
|
||||
* @param username Username of the player
|
||||
* @return Optional player wrapped as InvocationSender
|
||||
* @see xyz.jonesdev.sonar.api.command.InvocationSender
|
||||
* @see InvocationSource
|
||||
*/
|
||||
Optional<InvocationSender> getOnlinePlayer(final String username);
|
||||
public abstract Optional<InvocationSource> getOnlinePlayer(final String username);
|
||||
}
|
||||
@@ -35,7 +35,8 @@ public final class SystemTimer {
|
||||
return delay() >= amount;
|
||||
}
|
||||
|
||||
public String formattedDelay() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%.3f", delay() / 1000D);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public abstract class Verbose implements JVMProfiler {
|
||||
totalJoins = Statistics.TOTAL_TRAFFIC.get();
|
||||
|
||||
// Statistically determine the joins per second without any caches
|
||||
if (totalJoins > 0 && secondTimer.delay() >= 1000L) {
|
||||
if (totalJoins > 0 && secondTimer.elapsed(1000L)) {
|
||||
secondTimer.reset();
|
||||
joinsPerSecond = totalJoins - lastTotalJoins;
|
||||
lastTotalJoins = totalJoins;
|
||||
@@ -57,8 +57,8 @@ public abstract class Verbose implements JVMProfiler {
|
||||
broadcast(prepareActionBarFormat());
|
||||
}
|
||||
|
||||
protected final @NotNull String prepareActionBarFormat() {
|
||||
return Sonar.get().getConfig().ACTION_BAR_LAYOUT
|
||||
protected @NotNull String prepareActionBarFormat() {
|
||||
return Sonar.get().getConfig().getActionBarLayout()
|
||||
.replace("%queued%",
|
||||
DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getQueuedPlayers().size()))
|
||||
.replace("%verifying%", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size()))
|
||||
@@ -82,33 +82,35 @@ public abstract class Verbose implements JVMProfiler {
|
||||
}
|
||||
|
||||
protected final String nextAnimation() {
|
||||
val animations = Sonar.get().getConfig().ANIMATION;
|
||||
val animations = Sonar.get().getConfig().getAnimation();
|
||||
final int nextIndex = ++animationIndex % animations.size();
|
||||
return animations.toArray(new String[0])[nextIndex];
|
||||
}
|
||||
|
||||
// Run action bar verbose
|
||||
protected abstract void broadcast(final String message);
|
||||
protected void broadcast(final String message) {
|
||||
// This should be replaced by the wrapper
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subscriber Name of the player who subscribed
|
||||
* @return Whether the player is subscribed or not
|
||||
*/
|
||||
public boolean isSubscribed(final @NotNull String subscriber) {
|
||||
public final boolean isSubscribed(final @NotNull String subscriber) {
|
||||
return subscribers.contains(subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subscriber Name of the player to subscribe
|
||||
*/
|
||||
public void subscribe(final @NotNull String subscriber) {
|
||||
public final void subscribe(final @NotNull String subscriber) {
|
||||
subscribers.add(subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subscriber Name of the player to unsubscribe
|
||||
*/
|
||||
public void unsubscribe(final @NotNull String subscriber) {
|
||||
public final void unsubscribe(final @NotNull String subscriber) {
|
||||
subscribers.remove(subscriber);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ apply(plugin = "net.minecrell.plugin-yml.bukkit")
|
||||
|
||||
bukkit {
|
||||
name = rootProject.name
|
||||
version = rootProject.version.toString()
|
||||
version = rootProject.version.toString().split("-")[0]
|
||||
main = "xyz.jonesdev.sonar.bukkit.SonarBukkitPlugin"
|
||||
authors = listOf("Jones Development", "Sonar Contributors")
|
||||
website = "https://jonesdev.xyz/discord/"
|
||||
@@ -17,7 +17,7 @@ bukkit {
|
||||
|
||||
commands {
|
||||
register("sonar") {
|
||||
permission = "sonar.command"
|
||||
// No permission check here
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,9 @@ dependencies {
|
||||
compileOnly(project(":api"))
|
||||
compileOnly(project(":common"))
|
||||
|
||||
// MiniMessage platform support
|
||||
implementation("net.kyori:adventure-platform-bukkit:4.3.1")
|
||||
|
||||
// We have to use 1.8 for backwards compatibility
|
||||
compileOnly("org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT")
|
||||
|
||||
|
||||
@@ -21,11 +21,12 @@ import lombok.Getter;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.server.ServerWrapper;
|
||||
import xyz.jonesdev.sonar.bukkit.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.bukkit.command.BukkitInvocationSource;
|
||||
import xyz.jonesdev.sonar.bukkit.command.BukkitSonarCommand;
|
||||
import xyz.jonesdev.sonar.bukkit.verbose.ActionBarVerbose;
|
||||
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
|
||||
|
||||
@@ -72,44 +73,25 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
|
||||
* <br>
|
||||
* We have to do this, so we can access all necessary API functions.
|
||||
*/
|
||||
public final ServerWrapper server = new ServerWrapper() {
|
||||
public final ServerWrapper server = new ServerWrapper(SonarPlatform.BUKKIT) {
|
||||
|
||||
@Override
|
||||
public SonarPlatform getPlatform() {
|
||||
return SonarPlatform.VELOCITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InvocationSender> getOnlinePlayer(final String username) {
|
||||
public Optional<InvocationSource> getOnlinePlayer(final String username) {
|
||||
return getPlugin().getServer().getOnlinePlayers().stream()
|
||||
.filter(player -> player.getName().equalsIgnoreCase(username))
|
||||
.findFirst()
|
||||
.map(player -> new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
player.sendMessage(message);
|
||||
}
|
||||
});
|
||||
.map(BukkitInvocationSource::new);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
|
||||
// Reload configuration
|
||||
reload();
|
||||
|
||||
// Initialize bStats.org metrics
|
||||
new Metrics(getPlugin(), getServer().getPlatform().getMetricsId());
|
||||
|
||||
// Register Sonar command
|
||||
Objects.requireNonNull(getPlugin().getCommand("sonar")).setExecutor(new SonarCommand());
|
||||
Objects.requireNonNull(getPlugin().getCommand("sonar")).setExecutor(new BukkitSonarCommand());
|
||||
|
||||
// Register Fallback queue task
|
||||
getPlugin().getServer().getScheduler().runTaskTimerAsynchronously(getPlugin(), getFallback().getQueue()::poll,
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.bukkit.command;
|
||||
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.bukkit.SonarBukkit;
|
||||
|
||||
public final class BukkitInvocationSource extends InvocationSource {
|
||||
private static final BukkitAudiences AUDIENCES = BukkitAudiences.create(SonarBukkit.INSTANCE.getPlugin());
|
||||
|
||||
public BukkitInvocationSource(final @NotNull CommandSender sender) {
|
||||
super(sender.getName(), AUDIENCES.sender(sender));
|
||||
}
|
||||
}
|
||||
+27
-94
@@ -17,18 +17,14 @@
|
||||
|
||||
package xyz.jonesdev.sonar.bukkit.command;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.command.*;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.SonarBaseCommand;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
@@ -39,62 +35,9 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBaseCommand {
|
||||
private static final TextComponent GITHUB_LINK_COMPONENT = new TextComponent(ChatColor.GREEN + "https://github" +
|
||||
".com/jonesdevelopment/sonar");
|
||||
private static final TextComponent DISCORD_SUPPORT = new TextComponent(ChatColor.YELLOW + "Open a ticket on the " +
|
||||
"Discord ");
|
||||
private static final TextComponent GITHUB_ISSUES = new TextComponent(ChatColor.YELLOW + "or open a new issue on " +
|
||||
"GitHub.");
|
||||
|
||||
static {
|
||||
GITHUB_LINK_COMPONENT.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github" +
|
||||
".com/jonesdevelopment/sonar"));
|
||||
DISCORD_SUPPORT.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://jonesdev.xyz/discord/"));
|
||||
DISCORD_SUPPORT.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("(Click to open " +
|
||||
"Discord)").create()));
|
||||
GITHUB_ISSUES.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar" +
|
||||
"/issues"));
|
||||
GITHUB_ISSUES.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("(Click to open " +
|
||||
"GitHub)").create()));
|
||||
}
|
||||
|
||||
static {
|
||||
CACHED_HELP_MESSAGE.addAll(Arrays.asList(
|
||||
new TextComponent(ChatColor.YELLOW + "Running Sonar " + Sonar.get().getVersion()
|
||||
+ " on " + Sonar.get().getServer().getPlatform().getDisplayName()
|
||||
+ "."),
|
||||
new TextComponent(ChatColor.YELLOW + "(C) " + COPYRIGHT_YEAR + " Jones Development and Sonar Contributors"),
|
||||
GITHUB_LINK_COMPONENT,
|
||||
new TextComponent(""),
|
||||
new TextComponent(ChatColor.YELLOW + "Need help or have any questions?"),
|
||||
new TextComponent(
|
||||
DISCORD_SUPPORT,
|
||||
GITHUB_ISSUES
|
||||
),
|
||||
new TextComponent("")
|
||||
));
|
||||
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(sub -> {
|
||||
final TextComponent component = new TextComponent(
|
||||
new TextComponent(ChatColor.GRAY + " ▪ "),
|
||||
new TextComponent(ChatColor.GREEN + "/sonar " + sub.getInfo().name()),
|
||||
new TextComponent(ChatColor.GRAY + " - "),
|
||||
new TextComponent(ChatColor.WHITE + sub.getInfo().description())
|
||||
);
|
||||
|
||||
component.setClickEvent(
|
||||
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/sonar " + sub.getInfo().name() + " ")
|
||||
);
|
||||
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(
|
||||
"§7Only players: §f" + (sub.getInfo().onlyPlayers() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Require console: §f" + (sub.getInfo().onlyConsole() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Permission: §f" + sub.getPermission()
|
||||
+ Sonar.LINE_SEPARATOR + "§7Aliases: §f" + sub.getAliases()
|
||||
).create())
|
||||
);
|
||||
CACHED_HELP_MESSAGE.add(component);
|
||||
});
|
||||
public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, SonarCommand {
|
||||
{
|
||||
cacheHelpMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,6 +46,11 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
final String label,
|
||||
final String[] args) {
|
||||
if (!(sender instanceof ConsoleCommandSender)) {
|
||||
// Check if the player actually has the permission to run the command
|
||||
if (!sender.hasPermission("sonar.command")) {
|
||||
sender.sendMessage(Sonar.get().getConfig().getNoPermission());
|
||||
return false;
|
||||
}
|
||||
// Checking if it contains will only break more since it can throw
|
||||
// a NullPointerException if the cache is being accessed from parallel threads
|
||||
DELAY.cleanUp(); // Clean up the cache
|
||||
@@ -112,14 +60,14 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
// Spamming should be prevented, especially if some heavy operations are done,
|
||||
// which is not the case here but let's still stay safe!
|
||||
if (mapTimestamp > 0L) {
|
||||
sender.sendMessage(Sonar.get().getConfig().COMMAND_COOL_DOWN);
|
||||
sender.sendMessage(Sonar.get().getConfig().getCommandCoolDown());
|
||||
|
||||
// Format delay
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final double left = 0.5D - (timestamp - mapTimestamp) / 1000D;
|
||||
|
||||
sender.sendMessage(
|
||||
Sonar.get().getConfig().COMMAND_COOL_DOWN_LEFT
|
||||
Sonar.get().getConfig().getCommandCoolDownLeft()
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))
|
||||
);
|
||||
return false;
|
||||
@@ -130,18 +78,7 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
|
||||
Optional<Subcommand> subcommand = Optional.empty();
|
||||
|
||||
final InvocationSender invocationSender = new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return sender.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
};
|
||||
final InvocationSource invocationSource = new BukkitInvocationSource(sender);
|
||||
|
||||
if (args.length > 0) {
|
||||
// Search subcommand if command arguments are present
|
||||
@@ -156,10 +93,8 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
if (!subcommand.get().getInfo().onlyConsole()
|
||||
&& !sender.hasPermission(subcommand.get().getPermission())
|
||||
) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().SUB_COMMAND_NO_PERM
|
||||
.replace("%permission%", subcommand.get().getPermission())
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getSubCommandNoPerm()
|
||||
.replace("%permission%", subcommand.get().getPermission()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -167,24 +102,22 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
|
||||
subcommand.ifPresent(sub -> {
|
||||
if (sub.getInfo().onlyPlayers() && !(sender instanceof Player)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().PLAYERS_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getPlayersOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.getInfo().onlyConsole() && !(sender instanceof ConsoleCommandSender)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().CONSOLE_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getConsoleOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSender, sub, args);
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, sub, args);
|
||||
|
||||
// The subcommands has arguments which are not present in the executed command
|
||||
if (sub.getInfo().arguments().length > 0
|
||||
&& commandInvocation.getRawArguments().length <= 1) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().INCORRECT_COMMAND_USAGE
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")")
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getIncorrectCommandUsage()
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,12 +130,8 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
// Re-use the old, cached help message since we don't want to scan
|
||||
// for each subcommand and it's arguments/attributes every time
|
||||
// someone runs /sonar since the subcommand don't change
|
||||
for (final Object component : CACHED_HELP_MESSAGE) {
|
||||
if (sender instanceof Player) {
|
||||
((Player) sender).spigot().sendMessage((TextComponent) component);
|
||||
} else {
|
||||
sender.sendMessage(((TextComponent) component).getText());
|
||||
}
|
||||
for (final Component component : CACHED_HELP_MESSAGE) {
|
||||
invocationSource.sendMessage(component);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -213,6 +142,10 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
final Command command,
|
||||
final String commandAlias,
|
||||
final String @NotNull [] args) {
|
||||
// Do not allow tab completion if the player does not have the required permission
|
||||
if (!sender.hasPermission("sonar.command")) {
|
||||
return emptyList();
|
||||
}
|
||||
if (args.length <= 1) {
|
||||
if (TAB_SUGGESTIONS.isEmpty()) {
|
||||
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
|
||||
@@ -6,8 +6,8 @@ apply(plugin = "net.minecrell.plugin-yml.bungee")
|
||||
|
||||
bungee {
|
||||
name = rootProject.name
|
||||
version = rootProject.version.toString()
|
||||
main = "xyz.jonesdev.sonar.bukkit.SonarBukkitPlugin"
|
||||
version = rootProject.version.toString().split("-")[0]
|
||||
main = "xyz.jonesdev.sonar.bungee.SonarBungeePlugin"
|
||||
author = "Jones Development, Sonar Contributors"
|
||||
description = "Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)"
|
||||
}
|
||||
@@ -24,6 +24,9 @@ dependencies {
|
||||
|
||||
compileOnly("net.md_5:bungeecord:1.20.2-rc2-SNAPSHOT")
|
||||
|
||||
// MiniMessage platform support
|
||||
implementation("net.kyori:adventure-platform-bungeecord:4.3.1")
|
||||
|
||||
// Implement bStats.org for metrics
|
||||
implementation("org.bstats:bstats-bungeecord:3.0.2")
|
||||
}
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
package xyz.jonesdev.sonar.bungee;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.server.ServerWrapper;
|
||||
import xyz.jonesdev.sonar.bungee.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.bungee.command.BungeeInvocationSource;
|
||||
import xyz.jonesdev.sonar.bungee.command.BungeeSonarCommand;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.FallbackListener;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.injection.BaseInjectionHelper;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.injection.ChildChannelInitializer;
|
||||
@@ -76,44 +76,25 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
* <br>
|
||||
* We have to do this, so we can access all necessary API functions.
|
||||
*/
|
||||
public final ServerWrapper server = new ServerWrapper() {
|
||||
public final ServerWrapper server = new ServerWrapper(SonarPlatform.BUNGEE) {
|
||||
|
||||
@Override
|
||||
public SonarPlatform getPlatform() {
|
||||
return SonarPlatform.BUNGEE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InvocationSender> getOnlinePlayer(final String username) {
|
||||
public Optional<InvocationSource> getOnlinePlayer(final String username) {
|
||||
return getPlugin().getServer().getPlayers().stream()
|
||||
.filter(player -> player.getName().equalsIgnoreCase(username))
|
||||
.findFirst()
|
||||
.map(player -> new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
player.sendMessage(new TextComponent(message));
|
||||
}
|
||||
});
|
||||
.map(BungeeInvocationSource::new);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
|
||||
// Reload configuration
|
||||
reload();
|
||||
|
||||
// Initialize bStats.org metrics
|
||||
new Metrics(getPlugin(), getServer().getPlatform().getMetricsId());
|
||||
|
||||
// Register Sonar command
|
||||
getPlugin().getServer().getPluginManager().registerCommand(getPlugin(), new SonarCommand());
|
||||
getPlugin().getServer().getPluginManager().registerCommand(getPlugin(), new BungeeSonarCommand());
|
||||
|
||||
// Register Fallback listener
|
||||
getPlugin().getServer().getPluginManager().registerListener(getPlugin(), new FallbackListener(getFallback()));
|
||||
@@ -136,8 +117,5 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
|
||||
@Override
|
||||
public void postReload() {
|
||||
|
||||
// Prepare cached messages
|
||||
FallbackListener.CachedMessages.update();
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.bungee.command;
|
||||
|
||||
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.bungee.SonarBungee;
|
||||
|
||||
public final class BungeeInvocationSource extends InvocationSource {
|
||||
private static final BungeeAudiences AUDIENCES = BungeeAudiences.create(SonarBungee.INSTANCE.getPlugin());
|
||||
|
||||
public BungeeInvocationSource(final @NotNull CommandSender sender) {
|
||||
super(sender.getName(), AUDIENCES.sender(sender));
|
||||
}
|
||||
}
|
||||
+29
-94
@@ -17,11 +17,8 @@
|
||||
|
||||
package xyz.jonesdev.sonar.bungee.command;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
@@ -30,8 +27,8 @@ import net.md_5.bungee.command.ConsoleCommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.SonarBaseCommand;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
@@ -42,72 +39,21 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public final class SonarCommand extends Command implements TabExecutor, SonarBaseCommand {
|
||||
public SonarCommand() {
|
||||
super("sonar", "sonar.command");
|
||||
}
|
||||
|
||||
private static final TextComponent GITHUB_LINK_COMPONENT = new TextComponent(ChatColor.GREEN + "https://github" +
|
||||
".com/jonesdevelopment/sonar");
|
||||
private static final TextComponent DISCORD_SUPPORT = new TextComponent(ChatColor.YELLOW + "Open a ticket on the " +
|
||||
"Discord ");
|
||||
private static final TextComponent GITHUB_ISSUES = new TextComponent(ChatColor.YELLOW + "or open a new issue on " +
|
||||
"GitHub.");
|
||||
|
||||
static {
|
||||
GITHUB_LINK_COMPONENT.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github" +
|
||||
".com/jonesdevelopment/sonar"));
|
||||
DISCORD_SUPPORT.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://jonesdev.xyz/discord/"));
|
||||
DISCORD_SUPPORT.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("(Click to open " +
|
||||
"Discord)").create()));
|
||||
GITHUB_ISSUES.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar" +
|
||||
"/issues"));
|
||||
GITHUB_ISSUES.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("(Click to open " +
|
||||
"GitHub)").create()));
|
||||
}
|
||||
|
||||
static {
|
||||
CACHED_HELP_MESSAGE.addAll(Arrays.asList(
|
||||
new TextComponent(ChatColor.YELLOW + "Running Sonar " + Sonar.get().getVersion()
|
||||
+ " on " + Sonar.get().getServer().getPlatform().getDisplayName()
|
||||
+ "."),
|
||||
new TextComponent(ChatColor.YELLOW + "(C) " + COPYRIGHT_YEAR + " Jones Development and Sonar Contributors"),
|
||||
GITHUB_LINK_COMPONENT,
|
||||
new TextComponent(""),
|
||||
new TextComponent(ChatColor.YELLOW + "Need help or have any questions?"),
|
||||
new TextComponent(
|
||||
DISCORD_SUPPORT,
|
||||
GITHUB_ISSUES
|
||||
),
|
||||
new TextComponent("")
|
||||
));
|
||||
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(sub -> {
|
||||
final TextComponent component = new TextComponent(
|
||||
new TextComponent(ChatColor.GRAY + " ▪ "),
|
||||
new TextComponent(ChatColor.GREEN + "/sonar " + sub.getInfo().name()),
|
||||
new TextComponent(ChatColor.GRAY + " - "),
|
||||
new TextComponent(ChatColor.WHITE + sub.getInfo().description())
|
||||
);
|
||||
|
||||
component.setClickEvent(
|
||||
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/sonar " + sub.getInfo().name() + " ")
|
||||
);
|
||||
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(
|
||||
"§7Only players: §f" + (sub.getInfo().onlyPlayers() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Require console: §f" + (sub.getInfo().onlyConsole() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Permission: §f" + sub.getPermission()
|
||||
+ Sonar.LINE_SEPARATOR + "§7Aliases: §f" + sub.getAliases()
|
||||
).create())
|
||||
);
|
||||
CACHED_HELP_MESSAGE.add(component);
|
||||
});
|
||||
public final class BungeeSonarCommand extends Command implements TabExecutor, SonarCommand {
|
||||
public BungeeSonarCommand() {
|
||||
super("sonar");
|
||||
cacheHelpMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"redundantSuppression"})
|
||||
public void execute(final @NotNull CommandSender sender, final String[] args) {
|
||||
if (!(sender instanceof ConsoleCommandSender)) {
|
||||
// Check if the player actually has the permission to run the command
|
||||
if (!sender.hasPermission("sonar.command")) {
|
||||
sender.sendMessage(new TextComponent(Sonar.get().getConfig().getNoPermission()));
|
||||
return;
|
||||
}
|
||||
// Checking if it contains will only break more since it can throw
|
||||
// a NullPointerException if the cache is being accessed from parallel threads
|
||||
DELAY.cleanUp(); // Clean up the cache
|
||||
@@ -117,16 +63,14 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
// Spamming should be prevented, especially if some heavy operations are done,
|
||||
// which is not the case here but let's still stay safe!
|
||||
if (mapTimestamp > 0L) {
|
||||
sender.sendMessage(new TextComponent(Sonar.get().getConfig().COMMAND_COOL_DOWN));
|
||||
sender.sendMessage(new TextComponent(Sonar.get().getConfig().getCommandCoolDown()));
|
||||
|
||||
// Format delay
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final double left = 0.5D - (timestamp - mapTimestamp) / 1000D;
|
||||
|
||||
sender.sendMessage(new TextComponent(
|
||||
Sonar.get().getConfig().COMMAND_COOL_DOWN_LEFT
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))
|
||||
));
|
||||
sender.sendMessage(new TextComponent(Sonar.get().getConfig().getCommandCoolDownLeft()
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,18 +79,7 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
|
||||
Optional<Subcommand> subcommand = Optional.empty();
|
||||
|
||||
final InvocationSender invocationSender = new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return sender.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
sender.sendMessage(new TextComponent(message));
|
||||
}
|
||||
};
|
||||
final InvocationSource invocationSource = new BungeeInvocationSource(sender);
|
||||
|
||||
if (args.length > 0) {
|
||||
// Search subcommand if command arguments are present
|
||||
@@ -161,8 +94,8 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
if (!subcommand.get().getInfo().onlyConsole()
|
||||
&& !sender.hasPermission(subcommand.get().getPermission())
|
||||
) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().SUB_COMMAND_NO_PERM
|
||||
invocationSource.sendMessage(
|
||||
Sonar.get().getConfig().getSubCommandNoPerm()
|
||||
.replace("%permission%", subcommand.get().getPermission())
|
||||
);
|
||||
return;
|
||||
@@ -172,24 +105,22 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
|
||||
subcommand.ifPresent(sub -> {
|
||||
if (sub.getInfo().onlyPlayers() && !(sender instanceof ProxiedPlayer)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().PLAYERS_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getPlayersOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.getInfo().onlyConsole() && !(sender instanceof ConsoleCommandSender)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().CONSOLE_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getConsoleOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSender, sub, args);
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, sub, args);
|
||||
|
||||
// The subcommands has arguments which are not present in the executed command
|
||||
if (sub.getInfo().arguments().length > 0
|
||||
&& commandInvocation.getRawArguments().length <= 1) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().INCORRECT_COMMAND_USAGE
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")")
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getIncorrectCommandUsage()
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,14 +133,18 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
// Re-use the old, cached help message since we don't want to scan
|
||||
// for each subcommand and it's arguments/attributes every time
|
||||
// someone runs /sonar since the subcommand don't change
|
||||
for (final Object component : CACHED_HELP_MESSAGE) {
|
||||
sender.sendMessage((TextComponent) component);
|
||||
for (final Component component : CACHED_HELP_MESSAGE) {
|
||||
invocationSource.sendMessage(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> onTabComplete(final CommandSender sender, final String @NotNull [] args) {
|
||||
// Do not allow tab completion if the player does not have the required permission
|
||||
if (!sender.hasPermission("sonar.command")) {
|
||||
return emptyList();
|
||||
}
|
||||
if (args.length <= 1) {
|
||||
if (TAB_SUGGESTIONS.isEmpty()) {
|
||||
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
|
||||
+27
-24
@@ -21,8 +21,11 @@ import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.compress.PacketCompressor;
|
||||
@@ -30,17 +33,18 @@ import net.md_5.bungee.compress.PacketDecompressor;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.protocol.packet.Kick;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.compress.FallbackPacketCompressor;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.compress.FallbackPacketDecompressor;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.handler.FallbackInitialHandler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static net.md_5.bungee.netty.PipelineUtils.FRAME_PREPENDER;
|
||||
import static xyz.jonesdev.sonar.bungee.fallback.FallbackListener.CachedMessages.LOCKDOWN_DISCONNECT;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackListener implements Listener {
|
||||
@@ -57,36 +61,35 @@ public final class FallbackListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
public static class CachedMessages {
|
||||
static TextComponent LOCKDOWN_DISCONNECT;
|
||||
|
||||
public static void update() {
|
||||
LOCKDOWN_DISCONNECT = new TextComponent(Sonar.get().getConfig().LOCKDOWN_DISCONNECT);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@SuppressWarnings("deprecation")
|
||||
public void handle(final @NotNull PostLoginEvent event) throws Throwable {
|
||||
if (Sonar.get().getConfig().LOCKDOWN_ENABLED) {
|
||||
if (Sonar.get().getConfig().isLockdownEnabled()) {
|
||||
if (!event.getPlayer().hasPermission("sonar.lockdown")) {
|
||||
event.getPlayer().disconnect(LOCKDOWN_DISCONNECT);
|
||||
final PendingConnection pendingConnection = event.getPlayer().getPendingConnection();
|
||||
// Try to close the channel with a custom serialized disconnect component
|
||||
if (pendingConnection instanceof FallbackInitialHandler) {
|
||||
final FallbackInitialHandler fallbackInitialHandler = (FallbackInitialHandler) pendingConnection;
|
||||
final Component component = Sonar.get().getConfig().getLockdownDisconnect();
|
||||
final String serialized = JSONComponentSerializer.json().serialize(component);
|
||||
fallbackInitialHandler.closeWith(new Kick(serialized));
|
||||
} else {
|
||||
// Fallback by disconnecting without a message
|
||||
pendingConnection.disconnect();
|
||||
Sonar.get().getLogger().warn("Fallback handler of {} is missing", event.getPlayer().getName());
|
||||
}
|
||||
|
||||
if (Sonar.get().getConfig().LOCKDOWN_LOG_ATTEMPTS) {
|
||||
Sonar.get().getLogger().info(
|
||||
Sonar.get().getConfig().LOCKDOWN_CONSOLE_LOG
|
||||
.replace("%player%", event.getPlayer().getName())
|
||||
.replace("%ip%", Sonar.get().getConfig()
|
||||
.formatAddress(event.getPlayer().getAddress().getAddress()))
|
||||
.replace("%protocol%",
|
||||
String.valueOf(event.getPlayer().getPendingConnection().getVersion()))
|
||||
);
|
||||
if (Sonar.get().getConfig().isLockdownLogAttempts()) {
|
||||
Sonar.get().getLogger().info(Sonar.get().getConfig().getLockdownConsoleLog()
|
||||
.replace("%player%", event.getPlayer().getName())
|
||||
.replace("%ip%", Sonar.get().getConfig()
|
||||
.formatAddress(event.getPlayer().getAddress().getAddress()))
|
||||
.replace("%protocol%",
|
||||
String.valueOf(event.getPlayer().getPendingConnection().getVersion())));
|
||||
}
|
||||
return;
|
||||
} else if (Sonar.get().getConfig().LOCKDOWN_ENABLE_NOTIFY) {
|
||||
event.getPlayer().sendMessage(
|
||||
new TextComponent(Sonar.get().getConfig().LOCKDOWN_NOTIFICATION)
|
||||
);
|
||||
} else if (Sonar.get().getConfig().isLockdownEnableNotify()) {
|
||||
event.getPlayer().sendMessage(new TextComponent(Sonar.get().getConfig().getLockdownNotification()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -22,11 +22,11 @@ import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackPlayer;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.handler.FallbackInitialHandler;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.Disconnect;
|
||||
@@ -35,7 +35,7 @@ import java.net.InetAddress;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackPlayerWrapper implements FallbackPlayer<ChannelWrapper, FallbackInitialHandler> {
|
||||
public final class FallbackUserWrapper implements FallbackUser<ChannelWrapper, FallbackInitialHandler> {
|
||||
private final Fallback fallback;
|
||||
private final ChannelWrapper player;
|
||||
private final FallbackInitialHandler connection;
|
||||
@@ -45,8 +45,8 @@ public final class FallbackPlayerWrapper implements FallbackPlayer<ChannelWrappe
|
||||
private final ProtocolVersion protocolVersion;
|
||||
|
||||
@Override
|
||||
public void disconnect(final @NotNull String reason) {
|
||||
connection.closeWith(Disconnect.create(TextComponent.toLegacyText(new TextComponent(reason))));
|
||||
public void disconnect(final @NotNull Component reason) {
|
||||
connection.closeWith(Disconnect.create(reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
+3
-3
@@ -29,7 +29,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.FallbackPlayerWrapper;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.FallbackUserWrapper;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
@@ -43,7 +43,7 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
private static final @NotNull Fallback FALLBACK = Objects.requireNonNull(Sonar.get().getFallback());
|
||||
private ChannelWrapper channelWrapper;
|
||||
@SuppressWarnings("unused") // TODO: remove later
|
||||
private @Nullable FallbackPlayerWrapper player;
|
||||
private @Nullable FallbackUserWrapper player;
|
||||
|
||||
@Override
|
||||
public void connected(final ChannelWrapper channelWrapper) throws Exception {
|
||||
@@ -58,7 +58,7 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
if (player == null && !Sonar.get().getVerifiedPlayerController().has(inetAddress)) {
|
||||
final Channel channel = channelWrapper.getHandle();
|
||||
|
||||
player = new FallbackPlayerWrapper(
|
||||
player = new FallbackUserWrapper(
|
||||
FALLBACK, channelWrapper, this,
|
||||
channel, channel.pipeline(), inetAddress,
|
||||
ProtocolVersion.fromId(getHandshake().getProtocolVersion())
|
||||
|
||||
+2
-6
@@ -27,13 +27,10 @@ import net.md_5.bungee.netty.PipelineUtils;
|
||||
import net.md_5.bungee.protocol.Varint21LengthFieldPrepender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.varint.Varint21FrameDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackTimeoutHandler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static net.md_5.bungee.netty.PipelineUtils.*;
|
||||
@@ -42,8 +39,7 @@ import static net.md_5.bungee.netty.PipelineUtils.*;
|
||||
public final class BaseChannelInitializer extends ChannelInitializer<Channel> {
|
||||
public static final BaseChannelInitializer INSTANCE = new BaseChannelInitializer();
|
||||
|
||||
// cached object of our configuration to instantly access it
|
||||
private static final SonarConfiguration CONFIG = Objects.requireNonNull(Sonar.get().getConfig());
|
||||
private static final boolean REPLACE_VAR_INT_DECODER = !Boolean.getBoolean("sonar.do-not-replace-decoder");
|
||||
|
||||
private static final WriteBufferWaterMark MARK;
|
||||
private static final Varint21LengthFieldPrepender FRAME_ENCODER;
|
||||
@@ -75,7 +71,7 @@ public final class BaseChannelInitializer extends ChannelInitializer<Channel> {
|
||||
channel.config().setAllocator(PooledByteBufAllocator.DEFAULT);
|
||||
channel.config().setWriteBufferWaterMark(MARK);
|
||||
|
||||
channel.pipeline().addLast(FRAME_DECODER, CONFIG.REPLACE_VAR_INT_DECODER
|
||||
channel.pipeline().addLast(FRAME_DECODER, REPLACE_VAR_INT_DECODER
|
||||
? new Varint21FrameDecoder() : new net.md_5.bungee.protocol.Varint21FrameDecoder());
|
||||
channel.pipeline().addLast(TIMEOUT_HANDLER, new FallbackTimeoutHandler(
|
||||
BungeeCord.getInstance().config.getTimeout(), TimeUnit.MILLISECONDS
|
||||
|
||||
@@ -19,6 +19,7 @@ package xyz.jonesdev.sonar.common.boot;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
@@ -41,6 +42,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@RequiredArgsConstructor
|
||||
public abstract class SonarBootstrap<T> implements Sonar {
|
||||
private T plugin;
|
||||
@Setter
|
||||
private Verbose verboseHandler;
|
||||
private SonarConfiguration config;
|
||||
private VerifiedPlayerController verifiedPlayerController;
|
||||
@@ -63,13 +65,20 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
}
|
||||
|
||||
public final void initialize() {
|
||||
getLogger().info("Successfully booted in {}s!", launchTimer.formattedDelay());
|
||||
// Check if the branch is not the main branch to warn about unstable versions
|
||||
if (!getVersion().isOnMainBranch()) {
|
||||
getLogger().warn("You are currently using an unofficial experimental branch.");
|
||||
getLogger().warn("It is highly recommended to use the latest stable release of Sonar:");
|
||||
getLogger().warn("https://github.com/jonesdevelopment/sonar/releases");
|
||||
}
|
||||
|
||||
getLogger().info("Successfully booted in {}s!", launchTimer);
|
||||
getLogger().info("Initializing shared components...");
|
||||
|
||||
// Reload configuration
|
||||
reload();
|
||||
|
||||
getLogger().info("Successfully initialized components in {}s!", launchTimer.formattedDelay());
|
||||
getLogger().info("Successfully initialized components in {}s!", launchTimer);
|
||||
getLogger().info("Enabling all tasks and features...");
|
||||
|
||||
try {
|
||||
@@ -77,10 +86,11 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
enable();
|
||||
|
||||
// Done
|
||||
getLogger().info("Done ({}s)!", launchTimer.formattedDelay());
|
||||
getLogger().info("Done ({}s)!", launchTimer);
|
||||
} catch (Throwable throwable) {
|
||||
// An error has occurred
|
||||
getLogger().error("An error has occurred while launching Sonar: {}", throwable);
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +102,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
|
||||
// Warn player if they reloaded and changed the database type
|
||||
if (getVerifiedPlayerController() != null
|
||||
&& getVerifiedPlayerController().getCachedDatabaseType() != getConfig().DATABASE_TYPE) {
|
||||
&& getVerifiedPlayerController().getCachedDatabaseType() != getConfig().getDatabaseType()) {
|
||||
Sonar.get().getLogger().warn("Reloading the server after changing the database type"
|
||||
+ " is generally not recommended as it can sometimes cause data loss.");
|
||||
}
|
||||
@@ -102,7 +112,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
|
||||
// Update ratelimiter
|
||||
final ExpiringCache<InetAddress> expiringCache = Cappuccino.buildExpiring(
|
||||
getConfig().VERIFICATION_DELAY, TimeUnit.MILLISECONDS, 250L
|
||||
getConfig().getVerificationDelay(), TimeUnit.MILLISECONDS, 250L
|
||||
);
|
||||
FallbackRatelimiter.INSTANCE.setExpiringCache(expiringCache);
|
||||
|
||||
|
||||
+56
-55
@@ -24,7 +24,8 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackPlayer;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifySuccessEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
@@ -41,7 +42,7 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
public final class FallbackVerificationHandler implements FallbackPacketListener {
|
||||
private final @NotNull FallbackPlayer<?, ?> player;
|
||||
private final @NotNull FallbackUser<?, ?> user;
|
||||
private final String username;
|
||||
private final UUID uuid;
|
||||
private final short transactionId;
|
||||
@@ -72,10 +73,10 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
private final boolean canMove;
|
||||
}
|
||||
|
||||
public FallbackVerificationHandler(final @NotNull FallbackPlayer<?, ?> player,
|
||||
public FallbackVerificationHandler(final @NotNull FallbackUser<?, ?> user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid) {
|
||||
this.player = player;
|
||||
this.user = user;
|
||||
this.username = username;
|
||||
this.uuid = uuid;
|
||||
this.transactionId = (short) RANDOM.nextInt();
|
||||
@@ -83,14 +84,14 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
this.state = State.KEEP_ALIVE;
|
||||
|
||||
// Send LoginSuccess packet to make the client think they are joining the server
|
||||
player.write(new ServerLoginSuccess(username, uuid));
|
||||
user.write(new ServerLoginSuccess(username, uuid));
|
||||
|
||||
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
|
||||
// 1.7 players don't have KeepAlive packets in the login process
|
||||
sendJoinGamePacket();
|
||||
} else {
|
||||
// Send first KeepAlive to check if the connection is somewhat responsive
|
||||
player.write(new KeepAlive(verifyKeepAliveId));
|
||||
user.write(new KeepAlive(verifyKeepAliveId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// and go on with the flow of the verification.
|
||||
state = State.TRANSACTION;
|
||||
// Send a transaction with a
|
||||
player.write(new Transaction(
|
||||
user.write(new Transaction(
|
||||
0, transactionId, false
|
||||
));
|
||||
}
|
||||
@@ -109,7 +110,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// and go on with the flow of the verification.
|
||||
state = State.CLIENT_SETTINGS;
|
||||
// Select the JoinGame packet for the respective protocol version
|
||||
player.write(getJoinPacketForVersion(player.getProtocolVersion()));
|
||||
user.write(getJoinPacketForVersion(user.getProtocolVersion()));
|
||||
}
|
||||
|
||||
private void sendChunkData() {
|
||||
@@ -117,38 +118,36 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// and go on with the flow of the verification.
|
||||
state = State.POSITION;
|
||||
// Teleport player into the fake lobby by sending an empty chunk
|
||||
player.write(EMPTY_CHUNK_DATA);
|
||||
user.write(EMPTY_CHUNK_DATA);
|
||||
// Send an UpdateSectionBlocks packet with a platform of blocks
|
||||
// to check if the player collides with the solid platform.
|
||||
player.write(UPDATE_SECTION_BLOCKS);
|
||||
user.write(UPDATE_SECTION_BLOCKS);
|
||||
}
|
||||
|
||||
private static boolean validateClientLocale(final @NotNull FallbackPlayer<?, ?> player, final String locale) {
|
||||
private static boolean validateClientLocale(final @SuppressWarnings("unused") @NotNull FallbackUser<?, ?> user,
|
||||
final String locale) {
|
||||
// Check the client locale by performing a simple regex check on it
|
||||
final Pattern pattern = Sonar.get().getConfig().VALID_LOCALE_REGEX;
|
||||
final Pattern pattern = Sonar.get().getConfig().getValidLocaleRegex();
|
||||
return pattern.matcher(locale).matches(); // Disallow non-ascii characters (by default)
|
||||
}
|
||||
|
||||
private static boolean validateClientBrand(final @NotNull FallbackPlayer<?, ?> player, final ByteBuf content) {
|
||||
private static boolean validateClientBrand(final @NotNull FallbackUser<?, ?> user, final ByteBuf content) {
|
||||
// We have to catch every DecoderException, so we can fail and punish
|
||||
// the player instead of only disconnecting them due to an exception.
|
||||
try {
|
||||
// Regex pattern for validating client brands
|
||||
final Pattern pattern = Sonar.get().getConfig().VALID_BRAND_REGEX;
|
||||
// 1.7 has some very weird issues when trying to decode the client brand
|
||||
final boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0;
|
||||
final int cap = Sonar.get().getConfig().MAXIMUM_BRAND_LENGTH;
|
||||
// Read the client brand using our custom readString method that supports 1.7.
|
||||
// The legacy version of readString does not compare the string length
|
||||
// with the VarInt sent by the client.
|
||||
final String read = ProtocolUtil.readString(content, cap, legacy);
|
||||
// TODO: fix this?
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) return true;
|
||||
// No need to check for empty or too long client brands since
|
||||
// ProtocolUtil#readString already does exactly that.
|
||||
final String read = ProtocolUtil.readString(content, Sonar.get().getConfig().getMaximumBrandLength());
|
||||
// Regex pattern for validating client brands
|
||||
final Pattern pattern = Sonar.get().getConfig().getValidBrandRegex();
|
||||
return !read.equals("Vanilla") // The normal brand is always lowercase
|
||||
&& pattern.matcher(read).matches(); // Disallow non-ascii characters (by default)
|
||||
} catch (DecoderException exception) {
|
||||
// Fail if the string (client brand) could not be decoded properly
|
||||
player.fail("could not decode string");
|
||||
user.fail("could not decode string");
|
||||
// Throw the exception so we don't continue checking
|
||||
throw exception;
|
||||
}
|
||||
@@ -157,24 +156,27 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
@Override
|
||||
public void handle(final @NotNull FallbackPacket packet) {
|
||||
// Check if the player is not sending a ton of packets to the server
|
||||
final int maxPackets = Sonar.get().getConfig().MAXIMUM_LOGIN_PACKETS
|
||||
+ Sonar.get().getConfig().MAXIMUM_MOVEMENT_TICKS;
|
||||
final int maxPackets = Sonar.get().getConfig().getMaximumLoginPackets()
|
||||
+ Sonar.get().getConfig().getMaximumMovementTicks();
|
||||
checkFrame(++receivedPackets < maxPackets, "too many packets");
|
||||
|
||||
// Check for timeout since the player could be sending packets but not important ones
|
||||
final long timeout = Sonar.get().getConfig().VERIFICATION_TIMEOUT;
|
||||
final long timeout = Sonar.get().getConfig().getVerificationTimeout();
|
||||
// Check if the time limit has exceeded
|
||||
if (login.delay() > timeout) {
|
||||
player.getChannel().close();
|
||||
if (login.elapsed(timeout)) {
|
||||
user.getChannel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet instanceof KeepAlive) {
|
||||
final KeepAlive keepAlive = (KeepAlive) packet;
|
||||
|
||||
// 1.7-1.8.9 are sending a KeepAlive packet with the ID 0 every 20 ticks
|
||||
if (keepAlive.getId() == 0 && user.getProtocolVersion().compareTo(MINECRAFT_1_8) <= 0) return;
|
||||
|
||||
// Check if we are currently expecting a KeepAlive packet
|
||||
assertState(State.KEEP_ALIVE);
|
||||
|
||||
final KeepAlive keepAlive = (KeepAlive) packet;
|
||||
|
||||
checkFrame(keepAlive.getId() == verifyKeepAliveId, "invalid KeepAlive ID");
|
||||
|
||||
// The correct KeepAlive packet has been received
|
||||
@@ -185,7 +187,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
final ClientSettings clientSettings = (ClientSettings) packet;
|
||||
|
||||
// Validate the locale using a regex to filter unwanted characters.
|
||||
checkFrame(validateClientLocale(player, clientSettings.getLocale()), "invalid locale");
|
||||
checkFrame(validateClientLocale(user, clientSettings.getLocale()), "invalid locale");
|
||||
|
||||
// Clients sometimes mess up the ClientSettings or PluginMessage packet.
|
||||
if (state == State.CLIENT_SETTINGS) {
|
||||
@@ -203,11 +205,11 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// Check if the channel is correct - 1.13 uses the new namespace
|
||||
// system ('minecraft:' + channel) and anything below 1.13 uses
|
||||
// the legacy namespace system ('MC|' + channel).
|
||||
final boolean v1_13 = player.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0;
|
||||
final boolean v1_13 = user.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0;
|
||||
checkFrame(pluginMessage.getChannel().equals("MC|Brand") || v1_13, "invalid channel");
|
||||
|
||||
// Validate the client branding using a regex to filter unwanted characters.
|
||||
checkFrame(validateClientBrand(player, pluginMessage.content()), "invalid client brand");
|
||||
checkFrame(validateClientBrand(user, pluginMessage.content()), "invalid client brand");
|
||||
|
||||
// Clients sometimes mess up the ClientSettings or PluginMessage packet.
|
||||
if (state == State.PLUGIN_MESSAGE) {
|
||||
@@ -229,18 +231,18 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
checkFrame(transaction.getId() == transactionId, "invalid transaction id");
|
||||
|
||||
// Checking gravity is disabled, just finish verification
|
||||
if (!Sonar.get().getConfig().CHECK_GRAVITY) {
|
||||
if (!Sonar.get().getConfig().isCheckGravity()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the player is unable to fly (the player is in spectator mode)
|
||||
player.write(DEFAULT_ABILITIES);
|
||||
user.write(DEFAULT_ABILITIES);
|
||||
// Teleport the player to the spawn position
|
||||
player.write(SPAWN_TELEPORT);
|
||||
user.write(SPAWN_TELEPORT);
|
||||
|
||||
// 1.7-1.8 clients do not have a TeleportConfirm packet
|
||||
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) <= 0) {
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) <= 0) {
|
||||
// Immediately send the chunk data as 1.7-1.8 can't confirm teleports
|
||||
sendChunkData();
|
||||
} else {
|
||||
@@ -304,12 +306,11 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// The onGround property can never be true when we aren't checking for collisions
|
||||
checkFrame(!ground || state == State.COLLISIONS, "invalid ground state");
|
||||
|
||||
// Skip teleports using this small check
|
||||
// Check if the chunk might be unloaded
|
||||
if (deltaY > 0.07) {
|
||||
|
||||
// Verify the player if they sent correct movement packets
|
||||
if (movementTick++ >= MAX_MOVEMENT_TICK) {
|
||||
if (Sonar.get().getConfig().CHECK_COLLISIONS) {
|
||||
if (Sonar.get().getConfig().isCheckCollisions()) {
|
||||
if (state != State.COLLISIONS) {
|
||||
// Set the state to COLLISIONS to avoid false positives
|
||||
// and go on with the flow of the verification.
|
||||
@@ -334,26 +335,27 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// Checking collisions is disabled, just finish verification
|
||||
finish();
|
||||
}
|
||||
} else if (y >= DEFAULT_Y_COLLIDE_POSITION && y <= DYNAMIC_SPAWN_Y_POSITION) {
|
||||
} else if (y <= DYNAMIC_SPAWN_Y_POSITION && y >= DEFAULT_Y_COLLIDE_POSITION) {
|
||||
// This is a basic gravity check that predicts the next y position
|
||||
final double predictedY = PREPARED_MOVEMENT_PACKETS[movementTick];
|
||||
final double offsetY = Math.abs(deltaY - predictedY);
|
||||
|
||||
// Check if the y motion is roughly equal to the predicted value
|
||||
checkFrame(offsetY < 0.01, "invalid offset: " + y + ", " + offsetY);
|
||||
final String verbose = String.format("%d: %.7f %.10f %.10f!%.10f", movementTick, y, offsetY, deltaY, predictedY);
|
||||
checkFrame(offsetY < 0.01, verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertState(final @NotNull State expectedState) {
|
||||
if (expectedState != state) {
|
||||
player.fail("expected " + expectedState + ", got " + state);
|
||||
user.fail("expected " + expectedState + ", got " + state);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFrame(final boolean condition, final String message) {
|
||||
if (!condition) {
|
||||
player.fail(message);
|
||||
user.fail(message);
|
||||
throw new CorruptedFrameException(message);
|
||||
}
|
||||
}
|
||||
@@ -361,21 +363,20 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
private void finish() {
|
||||
state = State.SUCCESS;
|
||||
|
||||
player.getFallback().getConnected().remove(username);
|
||||
user.getFallback().getConnected().remove(username);
|
||||
|
||||
// Add verified player to the database
|
||||
final VerifiedPlayer verifiedPlayer = new VerifiedPlayer(
|
||||
player.getInetAddress().toString(), uuid, login.getStart()
|
||||
);
|
||||
final VerifiedPlayer verifiedPlayer = new VerifiedPlayer(user.getInetAddress().toString(), uuid, login.getStart());
|
||||
Sonar.get().getVerifiedPlayerController().add(verifiedPlayer);
|
||||
|
||||
// Disconnect player with the verification success message
|
||||
player.disconnect(Sonar.get().getConfig().VERIFICATION_SUCCESS);
|
||||
// Call the VerifySuccessEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(username, uuid, user, login.delay()));
|
||||
|
||||
player.getFallback().getLogger().info(
|
||||
Sonar.get().getConfig().VERIFICATION_SUCCESSFUL_LOG
|
||||
.replace("%name%", username)
|
||||
.replace("%time%", login.formattedDelay())
|
||||
);
|
||||
// Disconnect player with the verification success message
|
||||
user.disconnect(Sonar.get().getConfig().getVerificationSuccess());
|
||||
|
||||
user.getFallback().getLogger().info(Sonar.get().getConfig().getVerificationSuccessfulLog()
|
||||
.replace("%name%", username)
|
||||
.replace("%time%", login.toString()));
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -23,21 +23,21 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackPlayer;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
private final FallbackPlayer<?, ?> connection;
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
private final FallbackPacketListener listener;
|
||||
|
||||
public FallbackPacketDecoder(final @NotNull FallbackPlayer<?, ?> connection,
|
||||
public FallbackPacketDecoder(final @NotNull FallbackUser<?, ?> user,
|
||||
final @NotNull FallbackPacketListener listener) {
|
||||
this.connection = connection;
|
||||
this.user = user;
|
||||
this.registry = FallbackPacketRegistry.SONAR.getProtocolRegistry(
|
||||
FallbackPacketRegistry.Direction.SERVERBOUND, connection.getProtocolVersion()
|
||||
FallbackPacketRegistry.Direction.SERVERBOUND, user.getProtocolVersion()
|
||||
);
|
||||
this.listener = listener;
|
||||
}
|
||||
@@ -64,14 +64,14 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
doLengthSanityChecks(byteBuf, packet);
|
||||
|
||||
try {
|
||||
packet.decode(byteBuf, connection.getProtocolVersion());
|
||||
packet.decode(byteBuf, user.getProtocolVersion());
|
||||
} catch (Throwable throwable) {
|
||||
connection.fail("could not decode packet");
|
||||
user.fail("could not decode packet");
|
||||
throw new CorruptedFrameException("Failed to decode packet");
|
||||
}
|
||||
|
||||
if (byteBuf.isReadable()) {
|
||||
connection.fail("could not read packet to end");
|
||||
user.fail("could not read packet to end (" + byteBuf.readableBytes() + " bytes left)");
|
||||
throw new CorruptedFrameException("Could not read packet to end");
|
||||
}
|
||||
|
||||
@@ -85,15 +85,15 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private void doLengthSanityChecks(final ByteBuf byteBuf,
|
||||
final @NotNull FallbackPacket packet) throws Exception {
|
||||
final int expectedMaxLen = packet.expectedMaxLength(byteBuf, connection.getProtocolVersion());
|
||||
final int expectedMaxLen = packet.expectedMaxLength(byteBuf, user.getProtocolVersion());
|
||||
if (expectedMaxLen != -1 && byteBuf.readableBytes() > expectedMaxLen) {
|
||||
connection.fail("packet too large");
|
||||
user.fail("packet too large");
|
||||
throw new CorruptedFrameException("Packet too large");
|
||||
}
|
||||
|
||||
final int expectedMinLen = packet.expectedMinLength(byteBuf, connection.getProtocolVersion());
|
||||
final int expectedMinLen = packet.expectedMinLength(byteBuf, user.getProtocolVersion());
|
||||
if (byteBuf.readableBytes() < expectedMinLen) {
|
||||
connection.fail("packet too small");
|
||||
user.fail("packet too small");
|
||||
throw new CorruptedFrameException("Packet too small");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -123,7 +123,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x20, MINECRAFT_1_19_3, true),
|
||||
map(0x24, MINECRAFT_1_19_4, true));
|
||||
clientbound.register(UpdateSectionBlocks.class, UpdateSectionBlocks::new,
|
||||
map(34, MINECRAFT_1_7_2, true),
|
||||
map(0x22, MINECRAFT_1_7_2, true),
|
||||
map(16, MINECRAFT_1_9, true),
|
||||
map(15, MINECRAFT_1_13, true),
|
||||
map(16, MINECRAFT_1_15, true),
|
||||
|
||||
+4
-4
@@ -99,7 +99,7 @@ public class FallbackPreparer {
|
||||
JOIN_GAME_1_19_4 = createJoinGamePacket(MINECRAFT_1_19_4);
|
||||
JOIN_GAME_1_20 = createJoinGamePacket(MINECRAFT_1_20);
|
||||
|
||||
MAX_MOVEMENT_TICK = Sonar.get().getConfig().MAXIMUM_MOVEMENT_TICKS;
|
||||
MAX_MOVEMENT_TICK = Sonar.get().getConfig().getMaximumMovementTicks();
|
||||
PREPARED_MOVEMENT_PACKETS = new double[MAX_MOVEMENT_TICK + 1];
|
||||
|
||||
for (int i = 0; i < MAX_MOVEMENT_TICK + 1; i++) {
|
||||
@@ -122,7 +122,7 @@ public class FallbackPreparer {
|
||||
8, DYNAMIC_SPAWN_Y_POSITION, 8,
|
||||
0f, 0f,
|
||||
RANDOM.nextInt(Short.MAX_VALUE),
|
||||
true
|
||||
false // 1.7 support
|
||||
);
|
||||
|
||||
// Prepare collision platform positions
|
||||
@@ -184,7 +184,7 @@ public class FallbackPreparer {
|
||||
final JoinGame joinGame = new JoinGame();
|
||||
|
||||
joinGame.setLevelType("flat");
|
||||
joinGame.setGamemode(Sonar.get().getConfig().GAMEMODE_ID);
|
||||
joinGame.setGamemode(Sonar.get().getConfig().getGamemodeId());
|
||||
joinGame.setDimension(0);
|
||||
joinGame.setReducedDebugInfo(true);
|
||||
return joinGame;
|
||||
@@ -196,7 +196,7 @@ public class FallbackPreparer {
|
||||
final JoinGame joinGame = new JoinGame();
|
||||
|
||||
joinGame.setLevelType("flat");
|
||||
joinGame.setGamemode(Sonar.get().getConfig().GAMEMODE_ID);
|
||||
joinGame.setGamemode(Sonar.get().getConfig().getGamemodeId());
|
||||
joinGame.setDimension(0);
|
||||
joinGame.setReducedDebugInfo(true);
|
||||
joinGame.setDimensionInfo(DIMENSION);
|
||||
|
||||
+12
-6
@@ -38,14 +38,20 @@ public final class DefaultSpawnPosition implements FallbackPacket {
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
final long encoded = protocolVersion.compareTo(MINECRAFT_1_14) < 0
|
||||
? ((x & 0x3FFFFFFL) << 38) | ((y & 0xFFFL) << 26) | (z & 0x3FFFFFFL)
|
||||
: ((x & 0x3FFFFFFL) << 38) | ((y & 0x3FFFFFFL) << 12) | (z & 0xFFFL);
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
|
||||
byteBuf.writeInt(x);
|
||||
byteBuf.writeInt(y);
|
||||
byteBuf.writeInt(z);
|
||||
} else {
|
||||
final long encoded = protocolVersion.compareTo(MINECRAFT_1_14) < 0
|
||||
? ((x & 0x3FFFFFFL) << 38) | ((y & 0xFFFL) << 26) | (z & 0x3FFFFFFL)
|
||||
: ((x & 0x3FFFFFFL) << 38) | ((y & 0x3FFFFFFL) << 12) | (z & 0xFFFL);
|
||||
|
||||
byteBuf.writeLong(encoded);
|
||||
byteBuf.writeLong(encoded);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
|
||||
byteBuf.writeFloat(angle);
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
|
||||
byteBuf.writeFloat(angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -22,6 +22,8 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
@@ -48,7 +50,7 @@ public final class Disconnect implements FallbackPacket {
|
||||
writeString(byteBuf, Objects.requireNonNull(reason));
|
||||
}
|
||||
|
||||
public static Disconnect create(final String serialized) {
|
||||
return new Disconnect(serialized);
|
||||
public static Disconnect create(final Component component) {
|
||||
return new Disconnect(JSONComponentSerializer.json().serialize(component));
|
||||
}
|
||||
}
|
||||
+33
-24
@@ -48,10 +48,20 @@ public final class EmptyChunkData implements FallbackPacket {
|
||||
private static final byte[] LEGACY_FILLER_BYTES = new byte[256];
|
||||
private static final byte[] MODERN_FILLER_BYTES = new byte[1024];
|
||||
|
||||
private static final long[] MASK;
|
||||
|
||||
// Prepare nbt for 1.18 and pre-1.18
|
||||
private static final CompoundBinaryTag MODERN_TAG, LEGACY_TAG;
|
||||
|
||||
static {
|
||||
final BitSet bitSet = new BitSet();
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
bitSet.set(i, false);
|
||||
}
|
||||
|
||||
MASK = bitSet.toLongArray();
|
||||
|
||||
MODERN_TAG = prepareNBT(false);
|
||||
LEGACY_TAG = prepareNBT(true);
|
||||
}
|
||||
@@ -73,32 +83,26 @@ public final class EmptyChunkData implements FallbackPacket {
|
||||
byteBuf.writeInt(x);
|
||||
byteBuf.writeInt(z);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
|
||||
byteBuf.writeBoolean(true);
|
||||
}
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) {
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17_1) <= 0) {
|
||||
writeVarInt(byteBuf, MASK.length);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0 && protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
|
||||
byteBuf.writeBoolean(true);
|
||||
}
|
||||
for (final long l : MASK) {
|
||||
byteBuf.writeLong(l);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
byteBuf.writeBoolean(true); // Full chunk
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) == 0) {
|
||||
byteBuf.writeShort(1);
|
||||
} else {
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0
|
||||
&& protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_16_2) < 0) {
|
||||
byteBuf.writeBoolean(true); // Ignore old data
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) > 0) {
|
||||
writeVarInt(byteBuf, 0);
|
||||
}
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_18) < 0) {
|
||||
final BitSet bitSet = new BitSet();
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
bitSet.set(i, false);
|
||||
}
|
||||
|
||||
long[] mask = bitSet.toLongArray();
|
||||
writeVarInt(byteBuf, mask.length);
|
||||
|
||||
for (long l : mask) {
|
||||
byteBuf.writeLong(l);
|
||||
} else {
|
||||
byteBuf.writeShort(1); // Fix void chunk
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +125,12 @@ public final class EmptyChunkData implements FallbackPacket {
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_13) < 0) {
|
||||
writeArray(byteBuf, LEGACY_FILLER_BYTES); // 1.8 - 1.12.2
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
writeArray(byteBuf, LEGACY_FILLER_BYTES); // 1.8 - 1.12.2
|
||||
} else {
|
||||
byteBuf.writeInt(0); // Compressed size.
|
||||
byteBuf.writeBytes(new byte[2]); // 1.7
|
||||
}
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_15) < 0) {
|
||||
writeArray(byteBuf, MODERN_FILLER_BYTES); // 1.13 - 1.14.4
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_18) < 0) {
|
||||
|
||||
+14
@@ -36,6 +36,10 @@ public final class Position implements FallbackPacket {
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
x = byteBuf.readDouble();
|
||||
// https://github.com/jonesdevelopment/sonar/issues/20
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
|
||||
byteBuf.skipBytes(8);
|
||||
}
|
||||
y = byteBuf.readDouble();
|
||||
z = byteBuf.readDouble();
|
||||
onGround = byteBuf.readBoolean();
|
||||
@@ -45,4 +49,14 @@ public final class Position implements FallbackPacket {
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
return protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? 33 : 25;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
return 25;
|
||||
}
|
||||
}
|
||||
+26
-6
@@ -41,6 +41,10 @@ public final class PositionLook implements FallbackPacket {
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
x = byteBuf.readDouble();
|
||||
// https://github.com/jonesdevelopment/sonar/issues/20
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
|
||||
byteBuf.skipBytes(8);
|
||||
}
|
||||
y = byteBuf.readDouble();
|
||||
z = byteBuf.readDouble();
|
||||
yaw = byteBuf.readFloat();
|
||||
@@ -55,14 +59,30 @@ public final class PositionLook implements FallbackPacket {
|
||||
byteBuf.writeDouble(z);
|
||||
byteBuf.writeFloat(yaw);
|
||||
byteBuf.writeFloat(pitch);
|
||||
byteBuf.writeByte(0x00);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_9) >= 0) {
|
||||
writeVarInt(byteBuf, teleportId);
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
|
||||
byteBuf.writeBoolean(onGround);
|
||||
} else {
|
||||
byteBuf.writeByte(0x00);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0 && protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
|
||||
byteBuf.writeBoolean(true); // Dismount vehicle
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_9) >= 0) {
|
||||
writeVarInt(byteBuf, teleportId);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
|
||||
byteBuf.writeBoolean(true); // Always dismount vehicle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
return protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? 41 : 33;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
return 33;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -41,7 +41,7 @@ public final class Transaction implements FallbackPacket {
|
||||
byteBuf.writeByte(windowId);
|
||||
byteBuf.writeShort((short) id);
|
||||
// The "accepted" field is actually really unnecessary since
|
||||
// the client must accept the transaction either way.
|
||||
// it's never even used in the client.
|
||||
byteBuf.writeBoolean(accepted);
|
||||
} else {
|
||||
byteBuf.writeInt(id);
|
||||
|
||||
+17
-7
@@ -26,15 +26,14 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.block.ChangedBlock;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_16_2;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarLong;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ToString(exclude = {"changedBlocks"})
|
||||
public final class UpdateSectionBlocks implements FallbackPacket {
|
||||
private int sectionX, sectionZ;
|
||||
private ChangedBlock[] changedBlocks;
|
||||
@@ -44,11 +43,22 @@ public final class UpdateSectionBlocks implements FallbackPacket {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
|
||||
byteBuf.writeInt(sectionX);
|
||||
byteBuf.writeInt(sectionZ);
|
||||
writeVarInt(byteBuf, changedBlocks.length);
|
||||
|
||||
for (final ChangedBlock block : changedBlocks) {
|
||||
byteBuf.writeShort(block.getLegacyChunkPosCrammed());
|
||||
writeVarInt(byteBuf, block.getType().getId(protocolVersion));
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
|
||||
byteBuf.writeShort((short) changedBlocks.length);
|
||||
byteBuf.writeInt(4 * changedBlocks.length);
|
||||
|
||||
for (final ChangedBlock block : changedBlocks) {
|
||||
byteBuf.writeShort(block.getLegacyChunkPosCrammed());
|
||||
byteBuf.writeShort(block.getType().getId(protocolVersion));
|
||||
}
|
||||
} else {
|
||||
writeVarInt(byteBuf, changedBlocks.length);
|
||||
|
||||
for (final ChangedBlock block : changedBlocks) {
|
||||
byteBuf.writeShort(block.getLegacyChunkPosCrammed());
|
||||
writeVarInt(byteBuf, block.getType().getId(protocolVersion));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final ChangedBlock lastChangedBlock = changedBlocks[changedBlocks.length - 1];
|
||||
|
||||
+17
-22
@@ -44,15 +44,14 @@ public final class BlacklistCommand extends Subcommand {
|
||||
switch (invocation.getRawArguments()[1].toLowerCase()) {
|
||||
case "add": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "blacklist add <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
|
||||
if (!IP_PATTERN.matcher(rawInetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_IP_ADDRESS);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIncorrectIpAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,41 +60,40 @@ public final class BlacklistCommand extends Subcommand {
|
||||
inetAddress = InetAddress.getByName(rawInetAddress);
|
||||
|
||||
if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().ILLEGAL_IP_ADDRESS);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIllegalIpAddress());
|
||||
return;
|
||||
}
|
||||
} catch (UnknownHostException exception) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().ILLEGAL_IP_ADDRESS);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIllegalIpAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
if (SONAR.getFallback().getBlacklisted().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_DUPLICATE);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getBlacklistDuplicate());
|
||||
return;
|
||||
}
|
||||
|
||||
if (SONAR.getVerifiedPlayerController().has(inetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_ADD_WARNING
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getBlacklistAddWarning()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
}
|
||||
|
||||
SONAR.getFallback().getBlacklisted().put(inetAddress.toString());
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_ADD
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getBlacklistAdd()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
break;
|
||||
}
|
||||
|
||||
case "remove": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "blacklist remove <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
|
||||
if (!IP_PATTERN.matcher(rawInetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_IP_ADDRESS);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIncorrectIpAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,21 +102,21 @@ public final class BlacklistCommand extends Subcommand {
|
||||
inetAddress = InetAddress.getByName(rawInetAddress);
|
||||
|
||||
if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().ILLEGAL_IP_ADDRESS);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIllegalIpAddress());
|
||||
return;
|
||||
}
|
||||
} catch (UnknownHostException exception) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().ILLEGAL_IP_ADDRESS);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIllegalIpAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SONAR.getFallback().getBlacklisted().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_NOT_FOUND);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getBlacklistNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
SONAR.getFallback().getBlacklisted().invalidate(inetAddress.toString());
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_REMOVE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getBlacklistRemove()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
break;
|
||||
}
|
||||
@@ -127,21 +125,18 @@ public final class BlacklistCommand extends Subcommand {
|
||||
final int blacklisted = SONAR.getFallback().getBlacklisted().estimatedSize();
|
||||
|
||||
if (blacklisted == 0) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_EMPTY);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getBlacklistEmpty());
|
||||
return;
|
||||
}
|
||||
|
||||
SONAR.getFallback().getBlacklisted().invalidateAll();
|
||||
|
||||
invocation.getSender().sendMessage(
|
||||
SONAR.getConfig().BLACKLIST_CLEARED
|
||||
.replace("%removed%", Sonar.DECIMAL_FORMAT.format(blacklisted))
|
||||
);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getBlacklistCleared()
|
||||
.replace("%removed%", Sonar.DECIMAL_FORMAT.format(blacklisted)));
|
||||
break;
|
||||
}
|
||||
|
||||
case "size": {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_SIZE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getBlacklistSize()
|
||||
.replace("%amount%", Sonar.DECIMAL_FORMAT.format(SONAR.getFallback().getBlacklisted().estimatedSize())));
|
||||
break;
|
||||
}
|
||||
|
||||
+5
-7
@@ -30,14 +30,12 @@ public final class LockdownCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull CommandInvocation invocation) {
|
||||
SONAR.getConfig().LOCKDOWN_ENABLED = !SONAR.getConfig().LOCKDOWN_ENABLED;
|
||||
SONAR.getConfig().getGeneralConfig().set("general.lockdown.enabled", SONAR.getConfig().LOCKDOWN_ENABLED);
|
||||
final boolean newState = !SONAR.getConfig().isLockdownEnabled();
|
||||
SONAR.getConfig().setLockdownEnabled(newState);
|
||||
SONAR.getConfig().getGeneralConfig().set("general.lockdown.enabled", newState);
|
||||
SONAR.getConfig().getGeneralConfig().save();
|
||||
|
||||
invocation.getSender().sendMessage(
|
||||
SONAR.getConfig().LOCKDOWN_ENABLED
|
||||
? SONAR.getConfig().LOCKDOWN_ACTIVATED
|
||||
: SONAR.getConfig().LOCKDOWN_DEACTIVATED
|
||||
);
|
||||
invocation.getSender().sendMessage(newState
|
||||
? SONAR.getConfig().getLockdownActivated() : SONAR.getConfig().getLockdownDeactivated());
|
||||
}
|
||||
}
|
||||
+5
-7
@@ -21,6 +21,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "reload",
|
||||
@@ -30,15 +31,12 @@ public final class ReloadCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull CommandInvocation invocation) {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final SystemTimer timer = new SystemTimer();
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().RELOADING);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getReloading());
|
||||
SONAR.reload();
|
||||
|
||||
final long timeTaken = System.currentTimeMillis() - startTime;
|
||||
invocation.getSender().sendMessage(
|
||||
SONAR.getConfig().RELOADED
|
||||
.replace("%taken%", String.valueOf(timeTaken))
|
||||
);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getReloaded()
|
||||
.replace("%taken%", String.valueOf(timer.delay())));
|
||||
}
|
||||
}
|
||||
+9
-13
@@ -19,7 +19,7 @@ package xyz.jonesdev.sonar.common.subcommand.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
|
||||
@@ -34,33 +34,29 @@ public final class VerboseCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull CommandInvocation invocation) {
|
||||
InvocationSender verboseSubscriber = invocation.getSender();
|
||||
InvocationSource verboseSubscriber = invocation.getSender();
|
||||
|
||||
// Support for '/sonar verbose [username]'
|
||||
if (invocation.getRawArguments().length >= 2) {
|
||||
final Optional<InvocationSender> optional = SONAR.getServer().getOnlinePlayer(invocation.getRawArguments()[1]);
|
||||
final Optional<InvocationSource> optional = SONAR.getServer().getOnlinePlayer(invocation.getRawArguments()[1]);
|
||||
if (optional.isPresent()) verboseSubscriber = optional.get();
|
||||
}
|
||||
|
||||
if (SONAR.getVerboseHandler().isSubscribed(verboseSubscriber.getName())) {
|
||||
SONAR.getVerboseHandler().unsubscribe(verboseSubscriber.getName());
|
||||
if (verboseSubscriber != invocation.getSender()) {
|
||||
verboseSubscriber.sendMessage(
|
||||
SONAR.getConfig().VERBOSE_UNSUBSCRIBED_OTHER
|
||||
.replace("%player%", verboseSubscriber.getName())
|
||||
);
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().getVerboseUnsubscribedOther()
|
||||
.replace("%player%", verboseSubscriber.getName()));
|
||||
}
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().VERBOSE_UNSUBSCRIBED);
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().getVerboseUnsubscribed());
|
||||
return;
|
||||
}
|
||||
|
||||
if (verboseSubscriber != invocation.getSender()) {
|
||||
verboseSubscriber.sendMessage(
|
||||
SONAR.getConfig().VERBOSE_SUBSCRIBED_OTHER
|
||||
.replace("%player%", verboseSubscriber.getName())
|
||||
);
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().getVerboseSubscribedOther()
|
||||
.replace("%player%", verboseSubscriber.getName()));
|
||||
}
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().VERBOSE_SUBSCRIBED);
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().getVerboseSubscribed());
|
||||
SONAR.getVerboseHandler().subscribe(verboseSubscriber.getName());
|
||||
}
|
||||
}
|
||||
+19
-22
@@ -46,27 +46,26 @@ public final class VerifiedCommand extends Subcommand {
|
||||
switch (invocation.getRawArguments()[1].toLowerCase()) {
|
||||
case "history": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "verified history <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String inetAddress = invocation.getRawArguments()[2];
|
||||
|
||||
if (!IP_PATTERN.matcher(inetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_IP_ADDRESS);
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
if (!IP_PATTERN.matcher(rawInetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIncorrectIpAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't need to parse this, so we can just use the string.
|
||||
final String realInetAddress = "/" + inetAddress;
|
||||
final String realInetAddress = "/" + rawInetAddress;
|
||||
|
||||
if (!SONAR.getVerifiedPlayerController().has(realInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_NOT_FOUND);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getVerifiedNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
invocation.getSender().sendMessage("§ePrevious UUIDs for " + inetAddress + ":");
|
||||
invocation.getSender().sendMessage("§ePrevious UUIDs for " + rawInetAddress + ":");
|
||||
|
||||
for (final UUID uuid : SONAR.getVerifiedPlayerController().getUUIDs(realInetAddress)) {
|
||||
invocation.getSender().sendMessage(" §7▪ §f" + uuid.toString());
|
||||
@@ -76,28 +75,27 @@ public final class VerifiedCommand extends Subcommand {
|
||||
|
||||
case "remove": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "verified remove <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String inetAddress = invocation.getRawArguments()[2];
|
||||
|
||||
if (!IP_PATTERN.matcher(inetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_IP_ADDRESS);
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
if (!IP_PATTERN.matcher(rawInetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getIncorrectIpAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't need to parse this, so we can just use the string.
|
||||
final String realInetAddress = "/" + inetAddress;
|
||||
final String realInetAddress = "/" + rawInetAddress;
|
||||
|
||||
if (LOCK.contains(realInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_BLOCKED);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getVerifiedBlocked());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SONAR.getVerifiedPlayerController().has(realInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_NOT_FOUND);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getVerifiedNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,8 +104,8 @@ public final class VerifiedCommand extends Subcommand {
|
||||
SONAR.getVerifiedPlayerController().remove(realInetAddress);
|
||||
LOCK.remove(realInetAddress);
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_REMOVE
|
||||
.replace("%ip%", inetAddress));
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getVerifiedRemove()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -115,19 +113,18 @@ public final class VerifiedCommand extends Subcommand {
|
||||
final int verified = SONAR.getVerifiedPlayerController().estimatedSize();
|
||||
|
||||
if (verified == 0) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_EMPTY);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getVerifiedEmpty());
|
||||
return;
|
||||
}
|
||||
|
||||
SONAR.getVerifiedPlayerController().clearAll();
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_CLEARED
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getVerifiedCleared()
|
||||
.replace("%removed%", Sonar.DECIMAL_FORMAT.format(verified)));
|
||||
break;
|
||||
}
|
||||
|
||||
case "size": {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_SIZE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getVerifiedSize()
|
||||
.replace("%amount%", Sonar.DECIMAL_FORMAT.format(SONAR.getVerifiedPlayerController().estimatedSize())));
|
||||
break;
|
||||
}
|
||||
|
||||
+6
-19
@@ -53,34 +53,21 @@ public class ProtocolUtil {
|
||||
|
||||
public static @NotNull String readString(final ByteBuf buf,
|
||||
final int cap) throws CorruptedFrameException {
|
||||
return readString(buf, cap, false);
|
||||
}
|
||||
|
||||
public static @NotNull String readString(final ByteBuf buf,
|
||||
final int cap,
|
||||
final boolean legacy) throws CorruptedFrameException {
|
||||
final int length = VarIntUtil.readVarInt(buf);
|
||||
|
||||
checkFrame(length >= 0, "Got a negative-length string");
|
||||
checkFrame(length <= cap * 3, "Bad string size");
|
||||
String str;
|
||||
if (legacy) {
|
||||
// TODO: length checking?
|
||||
str = buf.toString(StandardCharsets.UTF_8);
|
||||
buf.skipBytes(buf.readableBytes());
|
||||
} else {
|
||||
checkFrame(buf.isReadable(length), "Got an invalid string length");
|
||||
str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
buf.skipBytes(length);
|
||||
}
|
||||
checkFrame(buf.isReadable(length), "Got an invalid string length");
|
||||
final String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
buf.skipBytes(length);
|
||||
checkFrame(str.length() <= cap, "Got a too-long string");
|
||||
return str;
|
||||
}
|
||||
|
||||
public static void writeString(final ByteBuf buf, final CharSequence str) {
|
||||
public static void writeString(final ByteBuf byteBuf, final CharSequence str) {
|
||||
final int size = ByteBufUtil.utf8Bytes(str);
|
||||
VarIntUtil.writeVarInt(buf, size);
|
||||
buf.writeCharSequence(str, StandardCharsets.UTF_8);
|
||||
VarIntUtil.writeVarInt(byteBuf, size);
|
||||
byteBuf.writeCharSequence(str, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static void writeUUID(final @NotNull ByteBuf byteBuf, final @NotNull UUID uuid) {
|
||||
|
||||
@@ -4,17 +4,34 @@ dependencies {
|
||||
compileOnly(project(":api"))
|
||||
compileOnly(project(":common"))
|
||||
|
||||
compileOnly("com.velocitypowered:velocity-proxy:$velocityVersion") // Proxy module
|
||||
compileOnly("com.velocitypowered:velocity-proxy:$velocityVersion")
|
||||
testCompileOnly("com.velocitypowered:velocity-proxy:$velocityVersion")
|
||||
|
||||
compileOnly("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
annotationProcessor("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
//annotationProcessor("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
|
||||
testCompileOnly("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
testAnnotationProcessor("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
//testAnnotationProcessor("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
|
||||
// Implement bStats.org for metrics
|
||||
implementation("org.bstats:bstats-velocity:3.0.2")
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
val props = mapOf(
|
||||
"name" to rootProject.name,
|
||||
"version" to rootProject.version.toString().split("-")[0],
|
||||
"description" to "Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)",
|
||||
"url" to "https://jonesdev.xyz/discord/",
|
||||
"main" to "xyz.jonesdev.sonar.velocity.SonarVelocityPlugin"
|
||||
)
|
||||
inputs.properties(props)
|
||||
filesMatching("velocity-plugin.json") {
|
||||
expand(props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
java.targetCompatibility = JavaVersion.VERSION_11
|
||||
@@ -18,15 +18,15 @@
|
||||
package xyz.jonesdev.sonar.velocity;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.server.ServerWrapper;
|
||||
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
|
||||
import xyz.jonesdev.sonar.velocity.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.velocity.command.VelocityInvocationSource;
|
||||
import xyz.jonesdev.sonar.velocity.command.VelocitySonarCommand;
|
||||
import xyz.jonesdev.sonar.velocity.fallback.FallbackListener;
|
||||
import xyz.jonesdev.sonar.velocity.verbose.VerboseWrapper;
|
||||
|
||||
@@ -72,30 +72,14 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
* <br>
|
||||
* We have to do this, so we can access all necessary API functions.
|
||||
*/
|
||||
public final ServerWrapper server = new ServerWrapper() {
|
||||
public final ServerWrapper server = new ServerWrapper(SonarPlatform.VELOCITY) {
|
||||
|
||||
@Override
|
||||
public SonarPlatform getPlatform() {
|
||||
return SonarPlatform.VELOCITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InvocationSender> getOnlinePlayer(final String username) {
|
||||
public Optional<InvocationSource> getOnlinePlayer(final String username) {
|
||||
return getPlugin().getServer().getAllPlayers().stream()
|
||||
.filter(player -> player.getUsername().equalsIgnoreCase(username))
|
||||
.findFirst()
|
||||
.map(player -> new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return player.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
player.sendMessage(Component.text(message));
|
||||
}
|
||||
});
|
||||
.map(VelocityInvocationSource::new);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -106,7 +90,7 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
getPlugin().getMetricsFactory().make(getPlugin(), getServer().getPlatform().getMetricsId());
|
||||
|
||||
// Register Sonar command
|
||||
getPlugin().getServer().getCommandManager().register("sonar", new SonarCommand());
|
||||
getPlugin().getServer().getCommandManager().register("sonar", new VelocitySonarCommand());
|
||||
|
||||
// Register Fallback listener
|
||||
getPlugin().getServer().getEventManager().register(getPlugin(), new FallbackListener(getFallback()));
|
||||
@@ -129,8 +113,5 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
|
||||
@Override
|
||||
public void postReload() {
|
||||
|
||||
// Prepare cached messages
|
||||
FallbackListener.CachedMessages.update();
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,10 @@ import org.slf4j.Logger;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Getter
|
||||
@Plugin(id = "sonar",
|
||||
@Plugin(
|
||||
id = "sonar",
|
||||
name = "Sonar",
|
||||
version = "2.0.0",
|
||||
version = "$version",
|
||||
authors = {"Jones Development", "Sonar Contributors"},
|
||||
url = "https://jonesdev.xyz/discord/",
|
||||
description = "Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)"
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.velocity.command;
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
|
||||
public final class VelocityInvocationSource extends InvocationSource {
|
||||
public VelocityInvocationSource(final @NotNull CommandSource sender) {
|
||||
super(sender instanceof Player
|
||||
? ((Player) sender).getUsername()
|
||||
: "Console", sender);
|
||||
}
|
||||
}
|
||||
+27
-83
@@ -21,14 +21,11 @@ import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.SonarBaseCommand;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
@@ -39,54 +36,19 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
static {
|
||||
CACHED_HELP_MESSAGE.addAll(Arrays.asList(
|
||||
Component.text("Running Sonar " + Sonar.get().getVersion()
|
||||
+ " on " + Sonar.get().getServer().getPlatform().getDisplayName()
|
||||
+ ".", NamedTextColor.YELLOW),
|
||||
Component.text("(C) " + COPYRIGHT_YEAR + " Jones Development and Sonar Contributors", NamedTextColor.YELLOW),
|
||||
Component.text("https://github.com/jonesdevelopment/sonar", NamedTextColor.GREEN)
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar")),
|
||||
Component.empty(),
|
||||
Component.text("Need help or have any questions?", NamedTextColor.YELLOW),
|
||||
Component.textOfChildren(
|
||||
Component.text("Open a ticket on the Discord ", NamedTextColor.YELLOW)
|
||||
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open Discord)")))
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://jonesdev.xyz/discord/")),
|
||||
Component.text("or open a new issue on GitHub.", NamedTextColor.YELLOW)
|
||||
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open GitHub)")))
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar" +
|
||||
"/issues"))
|
||||
),
|
||||
Component.empty()
|
||||
));
|
||||
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(sub -> {
|
||||
var component = Component.textOfChildren(
|
||||
Component.text(" ▪ ", NamedTextColor.GRAY),
|
||||
Component.text("/sonar " + sub.getInfo().name(), NamedTextColor.GREEN),
|
||||
Component.text(" - ", NamedTextColor.GRAY),
|
||||
Component.text(sub.getInfo().description(), NamedTextColor.WHITE)
|
||||
);
|
||||
|
||||
component = component.clickEvent(
|
||||
ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/sonar " + sub.getInfo().name() + " ")
|
||||
).hoverEvent(
|
||||
HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text(
|
||||
"§7Only players: §f" + (sub.getInfo().onlyPlayers() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Require console: §f" + (sub.getInfo().onlyConsole() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Permission: §f" + sub.getPermission()
|
||||
+ Sonar.LINE_SEPARATOR + "§7Aliases: §f" + sub.getAliases()
|
||||
))
|
||||
);
|
||||
CACHED_HELP_MESSAGE.add(component);
|
||||
});
|
||||
public final class VelocitySonarCommand implements SimpleCommand, SonarCommand {
|
||||
{
|
||||
cacheHelpMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull Invocation invocation) {
|
||||
if (!(invocation.source() instanceof ConsoleCommandSource)) {
|
||||
// Check if the player actually has the permission to run the command
|
||||
if (!invocation.source().hasPermission("sonar.command")) {
|
||||
invocation.source().sendMessage(Component.text(Sonar.get().getConfig().getNoPermission()));
|
||||
return;
|
||||
}
|
||||
// Checking if it contains will only break more since it can throw
|
||||
// a NullPointerException if the cache is being accessed from parallel threads
|
||||
DELAY.cleanUp(); // Clean up the cache
|
||||
@@ -96,16 +58,14 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
// Spamming should be prevented, especially if some heavy operations are done,
|
||||
// which is not the case here but let's still stay safe!
|
||||
if (mapTimestamp > 0L) {
|
||||
invocation.source().sendMessage(Component.text(Sonar.get().getConfig().COMMAND_COOL_DOWN));
|
||||
invocation.source().sendMessage(Component.text(Sonar.get().getConfig().getCommandCoolDown()));
|
||||
|
||||
// Format delay
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final double left = 0.5D - (timestamp - mapTimestamp) / 1000D;
|
||||
|
||||
invocation.source().sendMessage(Component.text(
|
||||
Sonar.get().getConfig().COMMAND_COOL_DOWN_LEFT
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))
|
||||
));
|
||||
invocation.source().sendMessage(Component.text(Sonar.get().getConfig().getCommandCoolDownLeft()
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,20 +74,7 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
|
||||
Optional<Subcommand> subcommand = Optional.empty();
|
||||
|
||||
final InvocationSender invocationSender = new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return invocation.source() instanceof Player
|
||||
? ((Player) invocation.source()).getUsername()
|
||||
: "Console";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
invocation.source().sendMessage(Component.text(message));
|
||||
}
|
||||
};
|
||||
final InvocationSource invocationSource = new VelocityInvocationSource(invocation.source());
|
||||
|
||||
if (invocation.arguments().length > 0) {
|
||||
// Search subcommand if command arguments are present
|
||||
@@ -142,8 +89,8 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
if (!subcommand.get().getInfo().onlyConsole()
|
||||
&& !invocation.source().hasPermission(subcommand.get().getPermission())
|
||||
) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().SUB_COMMAND_NO_PERM
|
||||
invocationSource.sendMessage(
|
||||
Sonar.get().getConfig().getSubCommandNoPerm()
|
||||
.replace("%permission%", subcommand.get().getPermission())
|
||||
);
|
||||
return;
|
||||
@@ -153,25 +100,23 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
|
||||
subcommand.ifPresent(sub -> {
|
||||
if (sub.getInfo().onlyPlayers() && !(invocation.source() instanceof Player)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().PLAYERS_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getPlayersOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.getInfo().onlyConsole() && !(invocation.source() instanceof ConsoleCommandSource)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().CONSOLE_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getConsoleOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSender, sub, invocation.arguments());
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, sub, invocation.arguments());
|
||||
|
||||
// The subcommands has arguments which are not present in the executed command
|
||||
if (sub.getInfo().arguments().length > 0
|
||||
&& commandInvocation.getRawArguments().length <= 1
|
||||
&& sub.getInfo().argumentsRequired()) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().INCORRECT_COMMAND_USAGE
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")")
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getIncorrectCommandUsage()
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,14 +129,18 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
// Re-use the old, cached help message since we don't want to scan
|
||||
// for each subcommand and it's arguments/attributes every time
|
||||
// someone runs /sonar since the subcommand don't change
|
||||
for (final Object component : CACHED_HELP_MESSAGE) {
|
||||
invocation.source().sendMessage((Component) component);
|
||||
for (final Component component : CACHED_HELP_MESSAGE) {
|
||||
invocationSource.sendMessage(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggest(final @NotNull Invocation invocation) {
|
||||
// Do not allow tab completion if the player does not have the required permission
|
||||
if (!invocation.source().hasPermission("sonar.command")) {
|
||||
return emptyList();
|
||||
}
|
||||
if (invocation.arguments().length <= 1) {
|
||||
if (TAB_SUGGESTIONS.isEmpty()) {
|
||||
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
|
||||
@@ -221,9 +170,4 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
}
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(final @NotNull Invocation invocation) {
|
||||
return invocation.source().hasPermission("sonar.command");
|
||||
}
|
||||
}
|
||||
+23
-55
@@ -55,34 +55,11 @@ import java.util.concurrent.TimeUnit;
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_HANDLER;
|
||||
import static xyz.jonesdev.sonar.common.utility.geyser.GeyserUtil.isGeyserConnection;
|
||||
import static xyz.jonesdev.sonar.velocity.fallback.FallbackListener.CachedMessages.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackListener {
|
||||
private final @NotNull Fallback fallback;
|
||||
|
||||
public static class CachedMessages {
|
||||
static Component LOCKDOWN_DISCONNECT;
|
||||
static Component TOO_MANY_ONLINE_PER_IP;
|
||||
static Component TOO_MANY_PLAYERS;
|
||||
static Component BLACKLISTED;
|
||||
static Component ALREADY_VERIFYING;
|
||||
static Component ALREADY_QUEUED;
|
||||
static Component TOO_FAST_RECONNECT;
|
||||
public static Component INVALID_USERNAME;
|
||||
|
||||
public static void update() {
|
||||
ALREADY_VERIFYING = Component.text(Sonar.get().getConfig().ALREADY_VERIFYING);
|
||||
ALREADY_QUEUED = Component.text(Sonar.get().getConfig().ALREADY_QUEUED);
|
||||
TOO_MANY_PLAYERS = Component.text(Sonar.get().getConfig().TOO_MANY_PLAYERS);
|
||||
BLACKLISTED = Component.text(Sonar.get().getConfig().BLACKLISTED);
|
||||
TOO_FAST_RECONNECT = Component.text(Sonar.get().getConfig().TOO_FAST_RECONNECT);
|
||||
INVALID_USERNAME = Component.text(Sonar.get().getConfig().INVALID_USERNAME);
|
||||
TOO_MANY_ONLINE_PER_IP = Component.text(Sonar.get().getConfig().TOO_MANY_ONLINE_PER_IP);
|
||||
LOCKDOWN_DISCONNECT = Component.text(Sonar.get().getConfig().LOCKDOWN_DISCONNECT);
|
||||
}
|
||||
}
|
||||
|
||||
private static final DummyConnection CLOSED_MINECRAFT_CONNECTION;
|
||||
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
@@ -133,14 +110,14 @@ public final class FallbackListener {
|
||||
if (fallback.getBlacklisted().has(inetAddress.toString())) {
|
||||
markConnectionAsDead(mcConnection.getSessionHandler());
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
BLACKLISTED,
|
||||
Sonar.get().getConfig().getBlacklisted(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getConfig().ENABLE_VERIFICATION) return;
|
||||
if (!Sonar.get().getConfig().isEnableVerification()) return;
|
||||
|
||||
// Check if the player is already verified.
|
||||
// No one wants to be verified over and over again.
|
||||
@@ -162,7 +139,7 @@ public final class FallbackListener {
|
||||
if (fallback.getConnected().containsKey(event.getUsername())
|
||||
|| fallback.getConnected().containsValue(inetAddress)) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
ALREADY_VERIFYING,
|
||||
Sonar.get().getConfig().getAlreadyVerifying(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
@@ -170,9 +147,9 @@ public final class FallbackListener {
|
||||
|
||||
// We cannot allow too many players on our Fallback server
|
||||
// There's technically no reason for limiting this, but we'll better stay safe.
|
||||
if (fallback.getConnected().size() > Sonar.get().getConfig().MAXIMUM_VERIFYING_PLAYERS) {
|
||||
if (fallback.getConnected().size() > Sonar.get().getConfig().getMaximumVerifyingPlayers()) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
TOO_MANY_PLAYERS,
|
||||
Sonar.get().getConfig().getTooManyPlayers(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
@@ -181,7 +158,7 @@ public final class FallbackListener {
|
||||
// Check if the IP address is currently being rate-limited
|
||||
if (!fallback.getRatelimiter().attempt(inetAddress)) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
TOO_FAST_RECONNECT,
|
||||
Sonar.get().getConfig().getTooFastReconnect(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
@@ -190,7 +167,7 @@ public final class FallbackListener {
|
||||
// Check if the player is already queued since we don't want bots to flood the queue
|
||||
if (fallback.getQueue().getQueuedPlayers().containsKey(inetAddress)) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
ALREADY_QUEUED,
|
||||
Sonar.get().getConfig().getAlreadyQueued(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
@@ -214,9 +191,8 @@ public final class FallbackListener {
|
||||
|
||||
// Check if the username matches the valid name regex in order to prevent
|
||||
// UTF-16 names or other types of flood attacks
|
||||
if (!Sonar.get().getConfig().VALID_NAME_REGEX
|
||||
.matcher(event.getUsername()).matches()) {
|
||||
mcConnection.closeWith(Disconnect.create(INVALID_USERNAME, mcConnection.getProtocolVersion()));
|
||||
if (!Sonar.get().getConfig().getValidNameRegex().matcher(event.getUsername()).matches()) {
|
||||
mcConnection.closeWith(Disconnect.create(Sonar.get().getConfig().getInvalidUsername(), mcConnection.getProtocolVersion()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -225,10 +201,7 @@ public final class FallbackListener {
|
||||
pipeline.replace(
|
||||
READ_TIMEOUT,
|
||||
READ_TIMEOUT,
|
||||
new FallbackTimeoutHandler(
|
||||
Sonar.get().getConfig().VERIFICATION_READ_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
new FallbackTimeoutHandler(Sonar.get().getConfig().getVerificationReadTimeout(), TimeUnit.MILLISECONDS)
|
||||
);
|
||||
|
||||
// We need to determine if the player is premium before we handle the connection,
|
||||
@@ -250,27 +223,22 @@ public final class FallbackListener {
|
||||
public void handle(final @NotNull LoginEvent event) {
|
||||
val connectedPlayer = (ConnectedPlayer) event.getPlayer();
|
||||
|
||||
if (Sonar.get().getConfig().LOCKDOWN_ENABLED) {
|
||||
if (Sonar.get().getConfig().isLockdownEnabled()) {
|
||||
if (!event.getPlayer().hasPermission("sonar.lockdown")) {
|
||||
connectedPlayer.getConnection().closeWith(Disconnect.create(
|
||||
LOCKDOWN_DISCONNECT, connectedPlayer.getProtocolVersion()
|
||||
));
|
||||
Sonar.get().getConfig().getLockdownDisconnect(), connectedPlayer.getProtocolVersion()));
|
||||
|
||||
if (Sonar.get().getConfig().LOCKDOWN_LOG_ATTEMPTS) {
|
||||
Sonar.get().getLogger().info(
|
||||
Sonar.get().getConfig().LOCKDOWN_CONSOLE_LOG
|
||||
.replace("%player%", event.getPlayer().getUsername())
|
||||
.replace("%ip%", Sonar.get().getConfig()
|
||||
.formatAddress(event.getPlayer().getRemoteAddress().getAddress()))
|
||||
.replace("%protocol%",
|
||||
String.valueOf(event.getPlayer().getProtocolVersion().getProtocol()))
|
||||
);
|
||||
if (Sonar.get().getConfig().isLockdownLogAttempts()) {
|
||||
Sonar.get().getLogger().info(Sonar.get().getConfig().getLockdownConsoleLog()
|
||||
.replace("%player%", event.getPlayer().getUsername())
|
||||
.replace("%ip%", Sonar.get().getConfig()
|
||||
.formatAddress(event.getPlayer().getRemoteAddress().getAddress()))
|
||||
.replace("%protocol%",
|
||||
String.valueOf(event.getPlayer().getProtocolVersion().getProtocol())));
|
||||
}
|
||||
return;
|
||||
} else if (Sonar.get().getConfig().LOCKDOWN_ENABLE_NOTIFY) {
|
||||
event.getPlayer().sendMessage(
|
||||
Component.text(Sonar.get().getConfig().LOCKDOWN_NOTIFICATION)
|
||||
);
|
||||
} else if (Sonar.get().getConfig().isLockdownEnableNotify()) {
|
||||
event.getPlayer().sendMessage(Component.text(Sonar.get().getConfig().getLockdownNotification()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +246,7 @@ public final class FallbackListener {
|
||||
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().MAXIMUM_ONLINE_PER_IP;
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().getMaximumOnlinePerIp();
|
||||
|
||||
if (maxOnlinePerIp > 0) {
|
||||
final long onlinePerIp = SonarVelocity.INSTANCE.getPlugin().getServer().getAllPlayers().stream()
|
||||
@@ -288,7 +256,7 @@ public final class FallbackListener {
|
||||
// We use '>=' because the player connecting to the server hasn't joined yet
|
||||
if (onlinePerIp >= maxOnlinePerIp) {
|
||||
connectedPlayer.getConnection().closeWith(Disconnect.create(
|
||||
TOO_MANY_ONLINE_PER_IP, connectedPlayer.getProtocolVersion()
|
||||
Sonar.get().getConfig().getTooManyOnlinePerIp(), connectedPlayer.getProtocolVersion()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -29,6 +29,7 @@ import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifyJoinEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
@@ -106,7 +107,7 @@ public final class FallbackSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
// Create an instance for the Fallback connection
|
||||
final FallbackPlayerWrapper fallbackPlayer = new FallbackPlayerWrapper(
|
||||
final FallbackUserWrapper fallbackPlayer = new FallbackUserWrapper(
|
||||
fallback, connectedPlayer, mcConnection, mcConnection.getChannel(),
|
||||
mcConnection.getChannel().pipeline(), inetAddress,
|
||||
ProtocolVersion.fromId(connectedPlayer.getProtocolVersion().getProtocol())
|
||||
@@ -121,17 +122,20 @@ public final class FallbackSessionHandler implements MinecraftSessionHandler {
|
||||
// The player joined the verification
|
||||
Statistics.REAL_TRAFFIC.increment();
|
||||
|
||||
if (Sonar.get().getConfig().LOG_CONNECTIONS) {
|
||||
if (Sonar.get().getConfig().isLogConnections()) {
|
||||
// Only log the processing message if the server isn't under attack.
|
||||
// We let the user override this through the configuration.
|
||||
if (!fallback.isPotentiallyUnderAttack() || Sonar.get().getConfig().LOG_DURING_ATTACK) {
|
||||
fallback.getLogger().info(Sonar.get().getConfig().VERIFICATION_CONNECT_LOG
|
||||
if (!fallback.isPotentiallyUnderAttack() || Sonar.get().getConfig().isLogDuringAttack()) {
|
||||
fallback.getLogger().info(Sonar.get().getConfig().getVerificationConnectLog()
|
||||
.replace("%name%", connectedPlayer.getUsername())
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(fallbackPlayer.getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(fallbackPlayer.getProtocolVersion().getProtocol())));
|
||||
}
|
||||
}
|
||||
|
||||
// Call the VerifyJoinEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(gameProfile.getName(), fallbackPlayer));
|
||||
|
||||
// Mark the player as connected → verifying players
|
||||
fallback.getConnected().put(gameProfile.getName(), inetAddress);
|
||||
|
||||
|
||||
+6
-7
@@ -19,25 +19,26 @@ package xyz.jonesdev.sonar.velocity.fallback;
|
||||
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackPlayer;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.Disconnect;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
@Getter
|
||||
@ToString(of = {"protocolVersion"})
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackPlayerWrapper implements FallbackPlayer<ConnectedPlayer, MinecraftConnection> {
|
||||
public final class FallbackUserWrapper implements FallbackUser<ConnectedPlayer, MinecraftConnection> {
|
||||
private final Fallback fallback;
|
||||
private final ConnectedPlayer player;
|
||||
private final MinecraftConnection connection;
|
||||
@@ -47,10 +48,8 @@ public final class FallbackPlayerWrapper implements FallbackPlayer<ConnectedPlay
|
||||
private final ProtocolVersion protocolVersion;
|
||||
|
||||
@Override
|
||||
public void disconnect(final @NotNull String reason) {
|
||||
final String serialized = ProtocolUtils.getJsonChatSerializer(connection.getProtocolVersion())
|
||||
.serialize(Component.text(reason));
|
||||
connection.closeWith(Disconnect.create(serialized));
|
||||
public void disconnect(final @NotNull Component reason) {
|
||||
connection.closeWith(Disconnect.create(reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "sonar",
|
||||
"name": "$name",
|
||||
"version": "$version",
|
||||
"description": "$description",
|
||||
"url": "$url",
|
||||
"authors": [
|
||||
"Jones Development",
|
||||
"Sonar Contributors"
|
||||
],
|
||||
"dependencies": [],
|
||||
"main": "$main"
|
||||
}
|
||||
Reference in new issue
Block a user