Compare commits

..
25 Commits
Author SHA1 Message Date
Jones de5750e4eb Release 2.1.42 (#535) 2025-12-09 18:17:10 +00:00
Jones 71b4bad41c 1.21.11 Support (#534)
* [wip] Add 1.21.11 protocol version

* [wip] Add 1.21.11 codec

* [wip] Add 1.21.11 entity mappings

* [wip] Add 1.21.11 item component mappings
2025-12-09 18:13:32 +00:00
Jones 05d2e55d69 Minor codestyle improvements 2025-12-02 21:36:36 +01:00
Jones 41fe39cf0f Fix incorrect file license header 2025-12-02 21:35:24 +01:00
Jones fa2e68be20 Implement complex CAPTCHA design (PoC) 2025-12-02 21:34:23 +01:00
Jones 2139147952 Update sponsors 2025-11-16 13:47:40 +01:00
Jones df049a7042 Release 2.1.41 (#528) 2025-10-27 13:53:35 +00:00
Jones 8df7874cab Don't invoke initChannel if the channel is closed (https://github.com/jonesdevelopment/sonar/pull/527) 2025-10-27 14:24:29 +01:00
Jones b132fe839e Disable max-online-per-ip if it's 0 or negative 2025-10-23 16:47:28 +02:00
Jones f0bdfa8ef1 Fix filled_map item type id on 1.21.9+ 2025-10-23 13:59:01 +02:00
dependabot[bot] 7899569970 Bump io.netty:netty-all from 4.2.6.Final to 4.2.7.Final (#526)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.6.Final to 4.2.7.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.6.Final...netty-4.2.7.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.7.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 15:24:04 +00:00
Jones 16d72fc682 Release 2.1.40 (#524) 2025-10-17 12:51:28 +00:00
Giuseppe PerrottaandJones 425a902ec8 Introduce PostgreSQL (#522)
* Introduce PostgreSQL

* update comment

* Mention `POSTGRESQL` in the configuration

---------

Co-authored-by: Jones <admin@jonesdev.xyz>
2025-10-07 13:05:35 +00:00
Jones fcb52fc6c6 Release 2.1.39 (#521) 2025-10-04 08:08:05 +00:00
Jones ac4d566627 Use radial gradient for CAPTCHA text 2025-10-03 11:32:05 +02:00
Jones 0944701797 1.21.9 Support (#520)
* [wip] 1.21.9 Support

* Implement missing BlockType mappings

* Implement missing EntityType mappings

* Implement less-precision vectors for SpawnEntityPacket

* Fix SpawnEntityPacket encoding the wrong values
2025-10-03 08:44:16 +00:00
Jones 9464bffd8e Use Spigot 1.16.5 for better compatibility 2025-10-03 10:26:53 +02:00
Jones 9299edc839 Include font license for kingthings fonts 2025-09-30 20:01:26 +02:00
dependabot[bot] 41eb6d9767 Bump org.projectlombok:lombok from 1.18.40 to 1.18.42 (#517)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.40 to 1.18.42.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.40...v1.18.42)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-version: 1.18.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-22 19:15:17 +00:00
dependabot[bot] 4289b92ab6 Bump io.netty:netty-all from 4.2.5.Final to 4.2.6.Final (#516)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.5.Final to 4.2.6.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.5.Final...netty-4.2.6.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.6.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 10:55:41 +00:00
Jones b2dddfb0a4 Release 2.1.38 (#515) 2025-09-13 15:12:37 +00:00
Jones ce2fca72f0 Update Dutch translations 2025-09-13 17:05:39 +02:00
Greened 87da706508 Update ka.yml (#514)
* Update ka.yml

* Update ka.yml
2025-09-13 09:30:40 +00:00
Jones 3480bf9332 Make max packet count configurable 2025-09-12 16:59:46 +02:00
Jones 745d01b4d2 Disable expected position but got client tick end check 2025-09-12 16:23:11 +02:00
42 changed files with 517 additions and 256 deletions

No files matched your search

+2 -1
View File
@@ -57,10 +57,11 @@ If you or one of your players experiences a false positive, make sure to report
## Sponsors
Massive thanks to the sponsors of Sonar who help keep this project running:
<a href="https://github.com/Hydoxl"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/107579333?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
<a href="https://github.com/Chumb3x"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/79834154?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
### Past Sponsors
<a href="https://github.com/Hydoxl"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/107579333?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
<a href="https://github.com/ItzErpandX"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/84748484?v=4?s=400?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
## Building
@@ -30,6 +30,7 @@ import xyz.jonesdev.sonar.api.command.SonarCommand;
import xyz.jonesdev.sonar.api.database.ormlite.H2DatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.database.ormlite.MariaDbDatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.database.ormlite.MysqlDatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.database.ormlite.PostgresDatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.webhook.DiscordWebhook;
import java.io.File;
@@ -139,7 +140,7 @@ public final class SonarConfiguration {
// General settings
logPlayerAddresses = generalConfig.getBoolean("general.log-player-addresses");
maxOnlinePerIp = clamp(generalConfig.getInt("general.max-online-per-ip"), 0, 99);
maxOnlinePerIp = clamp(generalConfig.getInt("general.max-online-per-ip"), -1, 99);
// Attack tracker
minPlayersForAttack = clamp(generalConfig.getInt("attack-tracker.min-players-for-attack"), 2, 1024);
@@ -205,6 +206,7 @@ public final class SonarConfiguration {
verification.debugXYZPositions = generalConfig.getBoolean("verification.debug-xyz-positions");
verification.readTimeout = clamp(generalConfig.getInt("verification.read-timeout"), 1000, 30000);
verification.writeTimeout = clamp(generalConfig.getInt("verification.write-timeout"), 1000, 30000);
verification.maxPacketCount = clamp(generalConfig.getInt("verification.max-packets"), 200, 9999);
verification.reconnectDelay = clamp(generalConfig.getInt("verification.rejoin-delay"), 0, 100000);
verification.rememberTime = clamp(generalConfig.getInt("verification.remember-time"), 0, 86400000);
verification.blacklistTime = clamp(generalConfig.getInt("verification.blacklist-time"), 0, 86400000);
@@ -420,6 +422,7 @@ public final class SonarConfiguration {
private int readTimeout;
private int writeTimeout;
private int maxPacketCount;
private int reconnectDelay;
private int rememberTime;
private int blacklistTime;
@@ -466,6 +469,13 @@ public final class SonarConfiguration {
.version("2.2.220")
.relocate("org{}h2", "xyz{}jonesdev{}sonar{}libs{}h2")
.build()),
POSTGRESQL("PostgreSQL", "jdbc:postgresql://%s:%d/%s%s", new PostgresDatabaseTypeAdapter(),
Library.builder()
.groupId("org{}postgresql")
.artifactId("postgresql")
.version("42.7.8")
.relocate("org{}postgresql", "xyz{}jonesdev{}sonar{}libs{}postgresql")
.build()),
NONE("None", null, null, null);
private final String displayName;
@@ -0,0 +1,14 @@
package xyz.jonesdev.sonar.api.database.ormlite;
import com.j256.ormlite.jdbc.db.PostgresDatabaseType;
import org.jetbrains.annotations.NotNull;
public final class PostgresDatabaseTypeAdapter extends PostgresDatabaseType {
// We need to override the default driver class name
// to use the custom relocated Postgres driver
@Override
protected String @NotNull [] getDriverClassNames() {
return new String[]{"xyz.jonesdev.sonar.libs.postgresql.Driver"};
}
}
@@ -73,7 +73,9 @@ public enum ProtocolVersion {
MINECRAFT_1_21_4(769, "1.21.4"),
MINECRAFT_1_21_5(770, "1.21.5"),
MINECRAFT_1_21_6(771, "1.21.6"),
MINECRAFT_1_21_7(772, "1.21.7");
MINECRAFT_1_21_7(772, "1.21.7"),
MINECRAFT_1_21_9(773, "1.21.9"),
MINECRAFT_1_21_11(774, "1.21.11");
private final int protocol;
private final String name;
+1
View File
@@ -62,6 +62,7 @@ allprojects {
relocate("org.mariadb", "xyz.jonesdev.sonar.libs.mariadb")
relocate("org.h2", "xyz.jonesdev.sonar.libs.h2")
relocate("com.jhlabs", "xyz.jonesdev.sonar.libs.jhlabs")
relocate("org.postgresql", "xyz.jonesdev.sonar.libs.postgresql")
// Exclude unnecessary metadata information
exclude("META-INF/*/**")
@@ -78,7 +78,10 @@ public enum BukkitServerVersion {
MINECRAFT_1_21_5,
MINECRAFT_1_21_6,
MINECRAFT_1_21_7,
MINECRAFT_1_21_8;
MINECRAFT_1_21_8,
MINECRAFT_1_21_9,
MINECRAFT_1_21_10,
MINECRAFT_1_21_11;
static final BukkitServerVersion[] REVERSED_VALUES;
@@ -49,8 +49,11 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
private static final Random RANDOM = new Random();
private static final Color[] COLORS = new Color[4];
private static final float[] COLOR_FRACTIONS = new float[COLORS.length];
static final Font FONT;
static {
FONT = TTFFontProvider.loadFont("/assets/fonts/Kingthings_Trypewriter_2.ttf");
FBM.setAmount(0.6f);
FBM.setScale(15);
@@ -113,8 +116,8 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
}
// Apply the random gradient effect
graphics.setPaint(new LinearGradientPaint(0, 0, width, height,
COLOR_FRACTIONS, COLORS, MultipleGradientPaint.CycleMethod.REPEAT));
graphics.setPaint(new RadialGradientPaint(0, 0, width,
COLOR_FRACTIONS, COLORS, MultipleGradientPaint.CycleMethod.REFLECT));
}
private void drawCharacters(final @NotNull Graphics2D graphics,
@@ -124,8 +127,7 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
for (int i = 0; i < answer.length; i++) {
// Create a glyph vector for the character with a random font
final Font font = StandardTTFFontProvider.FONTS[RANDOM.nextInt(StandardTTFFontProvider.FONTS.length)];
glyphs[i] = font.createGlyphVector(ctx, String.valueOf(answer[i]));
glyphs[i] = FONT.createGlyphVector(ctx, String.valueOf(answer[i]));
}
final double scalingXY = 5 - Math.min(answer.length, 5) * 0.65;
@@ -135,7 +137,7 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
.mapToDouble(glyph -> glyph.getLogicalBounds().getWidth() * scalingXY - 1)
.sum();
double beginX = Math.max(Math.min(width / 2D - totalWidth / 2D, totalWidth), 0);
double beginY = (height + StandardTTFFontProvider.STANDARD_FONT_SIZE / 2D) / 2D + scalingXY;
double beginY = (height + TTFFontProvider.STANDARD_FONT_SIZE / 2D) / 2D + scalingXY;
// Draw each glyph one by one
for (final GlyphVector glyph : glyphs) {
@@ -17,26 +17,16 @@
package xyz.jonesdev.sonar.captcha;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.io.InputStream;
import java.util.Objects;
@UtilityClass
class StandardTTFFontProvider {
private static final String[] FONT_NAMES = {"Kingthings_Trypewriter_2"};
static final Font[] FONTS = new Font[FONT_NAMES.length];
public final class TTFFontProvider {
static final int STANDARD_FONT_SIZE = 25;
static {
for (int i = 0; i < FONT_NAMES.length; i++) {
FONTS[i] = loadFont(String.format("/assets/fonts/%s.ttf", FONT_NAMES[i]));
}
}
private static Font loadFont(final @NotNull String path) {
public static Font loadFont(final @NotNull String path) {
try (final InputStream inputStream = StandardCaptchaGenerator.class.getResourceAsStream(path)) {
// Load the font from the TTF file
final Font customFont = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(inputStream));
@@ -0,0 +1,210 @@
/*
* Copyright (C) 2025 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.complex;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.captcha.TTFFontProvider;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;
@RequiredArgsConstructor
public final class ComplexCaptchaGenerator implements CaptchaGenerator {
private final File background;
private final int width = 128, height = 128;
private BufferedImage backgroundImage;
private static final Font[] FONTS = {
TTFFontProvider.loadFont("/assets/fonts/dotness.ttf"), // by http://bythebutterfly.com/
TTFFontProvider.loadFont("/assets/fonts/DotMatrix.ttf"),
};
private static final Random RANDOM = new SecureRandom();
private static final double MAX_ROTATION_RADIANS = Math.toRadians(15);
private static final int LINE_COUNT = 8;
private static final int MIN_LINE_COLOR = 100;
private static final int MAX_TEXT_COLOR = 150;
private static final int DOT_COUNT = 100;
private @NotNull BufferedImage createBackgroundImage() {
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
final Graphics2D graphics = image.createGraphics();
if (background == null) {
graphics.setBackground(Color.WHITE);
graphics.clearRect(0, 0, image.getWidth(), image.getHeight());
return image;
}
if (backgroundImage == null) {
try {
backgroundImage = ImageIO.read(background);
} catch (IOException exception) {
throw new IllegalStateException("Could not read background image", exception);
}
}
graphics.drawImage(backgroundImage, 0, 0, Color.WHITE, null);
return image;
}
@Override
public @NotNull BufferedImage createImage(char @NotNull [] answer) {
final BufferedImage image = createBackgroundImage();
final Graphics2D graphics = image.createGraphics();
final String text = new String(answer);
final int padding = 12;
final double maxWidth = width - 2D * padding;
final double maxHeight = height - 2D * padding;
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
final FontRenderContext frc = graphics.getFontRenderContext();
float optimalSize = 24;
final float trialSize = 100;
final Rectangle2D trialBounds = FONTS[0].deriveFont(trialSize).getStringBounds(text, frc);
if (trialBounds.getWidth() > 0 && trialBounds.getHeight() > 0) {
final double scaleFactorWidth = maxWidth / trialBounds.getWidth();
final double scaleFactorHeight = maxHeight / trialBounds.getHeight();
optimalSize = (float) (trialSize * Math.min(scaleFactorWidth, scaleFactorHeight));
}
if (optimalSize < 1f) {
optimalSize = 1f;
}
final Font finalFont = FONTS[0].deriveFont(optimalSize);
graphics.setFont(finalFont);
final Rectangle2D fullBounds = finalFont.getStringBounds(text, frc);
double currentX = (width - fullBounds.getWidth()) / 2D;
final double baselineY = (height / 2D) - (fullBounds.getHeight() / 2D) - fullBounds.getY();
double currentY = baselineY;
for (final char c : answer) {
final Font font = FONTS[RANDOM.nextInt(FONTS.length)].deriveFont(optimalSize);
graphics.setFont(font);
final String character = String.valueOf(c);
final Rectangle2D charBounds = font.getStringBounds(character, frc);
final double charWidth = charBounds.getWidth();
final double charHeight = charBounds.getHeight();
final double charCenterX = currentX + charWidth / 2D;
final double charCenterY = baselineY + charBounds.getY() + charHeight / 2D;
final Color[] colors = new Color[2 + RANDOM.nextInt(2)];
final float[] colorFractions = new float[colors.length];
for (int i = 0; i < colors.length; i++) {
colors[i] = getRandomTextColor();
colorFractions[i] = i / (float) colors.length;
}
graphics.setPaint(new RadialGradientPaint((float) currentX, (float) currentY, (float) charWidth,
colorFractions, colors, MultipleGradientPaint.CycleMethod.REFLECT));
final double rotation = (RANDOM.nextDouble() * 2 * MAX_ROTATION_RADIANS) - MAX_ROTATION_RADIANS;
final AffineTransform originalTransform = graphics.getTransform();
graphics.rotate(rotation, charCenterX, charCenterY);
final TextLayout layout = new TextLayout(character, font, frc);
final AffineTransform translate = AffineTransform.getTranslateInstance(currentX, currentY);
final Shape outline = layout.getOutline(translate);
graphics.fill(outline);
graphics.setTransform(originalTransform);
currentX += charWidth;
currentY += RANDOM.nextDouble(charHeight / 4) - charHeight / 8;
}
drawNoiseLines(graphics);
drawNoiseDots(graphics);
graphics.dispose();
return image;
}
private @NotNull Color getRandomTextColor() {
final int r = Math.min(RANDOM.nextInt(256), MAX_TEXT_COLOR);
final int g = Math.min(RANDOM.nextInt(256), MAX_TEXT_COLOR);
final int b = Math.min(RANDOM.nextInt(256), MAX_TEXT_COLOR);
return new Color(r, g, b, 220);
}
private @NotNull Color getRandomColor() {
final int r = Math.max(RANDOM.nextInt(256), MIN_LINE_COLOR);
final int g = Math.max(RANDOM.nextInt(256), MIN_LINE_COLOR);
final int b = Math.max(RANDOM.nextInt(256), MIN_LINE_COLOR);
final int a = Math.max(Math.min(RANDOM.nextInt(256), 200), MIN_LINE_COLOR);
return new Color(r, g, b, a);
}
private void drawNoiseLines(final @NotNull Graphics2D graphics) {
for (int i = 0; i < LINE_COUNT; i++) {
graphics.setColor(getRandomColor());
graphics.setStroke(new BasicStroke(1f + RANDOM.nextFloat() * 2f,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
if (RANDOM.nextBoolean()) {
float x1 = RANDOM.nextInt(width);
float y1 = RANDOM.nextInt(height);
float ctrlX = RANDOM.nextInt(width);
float ctrlY = RANDOM.nextInt(height);
float x2 = RANDOM.nextInt(width);
float y2 = RANDOM.nextInt(height);
graphics.draw(new QuadCurve2D.Float(x1, y1, ctrlX, ctrlY, x2, y2));
} else {
int x1 = RANDOM.nextInt(width);
int y1 = RANDOM.nextInt(height);
int x2 = RANDOM.nextInt(width);
int y2 = RANDOM.nextInt(height);
graphics.drawLine(x1, y1, x2, y2);
}
}
}
private void drawNoiseDots(final @NotNull Graphics2D graphics) {
for (int i = 0; i < DOT_COUNT; i++) {
graphics.setColor(getRandomColor());
final int x = RANDOM.nextInt(width);
final int y = RANDOM.nextInt(height);
final int radius = 1 + RANDOM.nextInt(5);
graphics.fillOval(x, y, radius, radius);
}
}
}
@@ -1,86 +0,0 @@
/*
* Copyright (C) 2025 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.filters;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class CircleInverseFilter {
private final int amount, minRadius, maxRadiusExpansion;
private static final Random RANDOM = new Random();
public void transform(final @NotNull BufferedImage image) {
final int minX = image.getWidth() / 4;
final int minY = image.getHeight() / 4;
for (int i = 0; i < amount; i++) {
// Generate random position and radius for the circle
final float centerX = minX + RANDOM.nextInt(image.getWidth() - minX * 2);
final float centerY = minY + RANDOM.nextInt(image.getHeight() - minY * 2);
final int radius = minRadius + RANDOM.nextInt(maxRadiusExpansion);
final Circle circle = new Circle(centerX, centerY, radius);
// Draw inverted circle
_addInvertedCircle(image, circle);
}
}
private static void _addInvertedCircle(final @NotNull BufferedImage bufferedImage,
final @NotNull Circle circle) {
for (int x = 0; x < bufferedImage.getWidth(); x++) {
for (int y = 0; y < bufferedImage.getHeight(); y++) {
if (isWithinCircle(x, y, circle, circle.radius)) {
// Invert colors within the circle
final int rgb = bufferedImage.getRGB(x, y);
final int invertedRGB = invertColorAndFilter(rgb);
bufferedImage.setRGB(x, y, invertedRGB);
}
}
}
}
private static boolean isWithinCircle(final float x, final float y, final @NotNull Circle circle, int radius) {
final float distanceX = x - circle.centerX;
final float distanceY = y - circle.centerY;
return distanceX * distanceX + distanceY * distanceY <= radius * radius;
}
@Getter
@RequiredArgsConstructor
static final class Circle {
private final float centerX;
private final float centerY;
private final int radius;
}
private static int invertColorAndFilter(final int rgb) {
final Color color = new Color(rgb);
final int red = Math.max(Color.LIGHT_GRAY.getRed() - color.getRed(), 0);
final int green = Math.max(Color.LIGHT_GRAY.getGreen() - color.getGreen(), 0);
final int blue = Math.max(Color.LIGHT_GRAY.getBlue() - color.getBlue(), 0);
return new Color(red, green, blue).getRGB();
}
}
@@ -1,51 +0,0 @@
/*
* Copyright (C) 2025 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.filters;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class GridOverlayFilter {
private final int amount, lineWidth, randomOffset;
private static final Random RANDOM = new Random();
public void transform(final @NotNull BufferedImage image,
final @NotNull Graphics2D graphics) {
final int spacingX = image.getWidth() / amount + lineWidth;
final int spacingY = image.getHeight() / amount + lineWidth;
int currentX = spacingX / 2;
int currentY = spacingY / 2;
for (int i = 0; i < amount; i++) {
graphics.setStroke(new BasicStroke(lineWidth * 0.5f + lineWidth * RANDOM.nextFloat()));
graphics.drawLine(currentX, 0, currentX + RANDOM.nextInt(randomOffset), image.getHeight());
graphics.drawLine(0, currentY, image.getWidth(), currentY + RANDOM.nextInt(randomOffset));
currentX += spacingX + lineWidth;
currentY += spacingY + lineWidth;
}
}
}
Binary file not shown.
@@ -0,0 +1,5 @@
My fonts are free for personal use. You also MAY redistribute the fonts on your website as long as you guve credit to http://bythebutterfly.com as the creator of the fonts. If you are interested in commercial use, please email me at BYTHEBUTTERFLY@GMAIL.COM
Thanks, and enjoy :)
Vanessa
@@ -0,0 +1,12 @@
Kingthings EULA - (End User License Agreement).
All the fonts currently published on my website (www.kingthingsfonts.co.uk) are free for you to use for any purpose you wish. Please do not change my original font files (.TTF).
There are some paid-for versions of my fonts (with much enhanced character sets) available from to buy from www.cheapprofonts.com, these contain their own EULA and are not included or implied in this notice.
Please enjoy all your creative work - and if you come up with anything you like, I would appreciate a picture.
Regards
Kevin King
Exeter, England - February 2009
+3 -2
View File
@@ -16,7 +16,8 @@
*/
import lombok.experimental.UtilityClass;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.captcha.complex.ComplexCaptchaGenerator;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
@@ -28,7 +29,7 @@ import java.util.Random;
public class Main {
public void main(final String... args) throws IOException {
final Random random = new Random();
final StandardCaptchaGenerator standardCaptchaGenerator = new StandardCaptchaGenerator(null);
final CaptchaGenerator standardCaptchaGenerator = new ComplexCaptchaGenerator(null);
final long start = System.currentTimeMillis();
final char[] dictionary = {'a', 'b', 'c', 'd', 'e', 'f'/*, 'g'*/, 'h'/*, 'i'*/, 'j',
@@ -169,11 +169,13 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
final @NotNull InetAddress inetAddress,
final @NotNull Runnable loginPacket) throws Exception {
final int maxOnlinePerIp = Sonar.get0().getConfig().getMaxOnlinePerIp();
final int newCount = Sonar.get0().getFallback().getOnline().compute(inetAddress,
(__, count) -> count == null ? 1 : count + 1);
if (newCount > maxOnlinePerIp) {
customDisconnect(channel, tooManyOnlinePerIP, protocolVersion);
return;
if (maxOnlinePerIp > 0) {
final int newCount = Sonar.get0().getFallback().getOnline().compute(inetAddress,
(__, count) -> count == null ? 1 : count + 1);
if (newCount > maxOnlinePerIp) {
customDisconnect(channel, tooManyOnlinePerIP, protocolVersion);
return;
}
}
loginPacket.run();
}
@@ -53,6 +53,10 @@ public final class FallbackInjectedChannelInitializer extends ChannelInitializer
@Override
protected void initChannel(final Channel channel) throws Exception {
if (!channel.isActive()) {
return;
}
// Invoke the original method
try {
INIT_CHANNEL_METHOD.invokeExact(originalChannelInitializer, channel);
@@ -108,7 +108,8 @@ public enum FallbackPacketRegistry {
map(0x29, MINECRAFT_1_20_2, true),
map(0x2B, MINECRAFT_1_20_5, true),
map(0x2C, MINECRAFT_1_21_2, true),
map(0x2B, MINECRAFT_1_21_5, true));
map(0x2B, MINECRAFT_1_21_5, true),
map(0x30, MINECRAFT_1_21_9, true));
// minecraft:keep_alive
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x00, MINECRAFT_1_7_2, true),
@@ -126,7 +127,8 @@ public enum FallbackPacketRegistry {
map(0x24, MINECRAFT_1_20_2, true),
map(0x26, MINECRAFT_1_20_5, true),
map(0x27, MINECRAFT_1_21_2, true),
map(0x26, MINECRAFT_1_21_5, true));
map(0x26, MINECRAFT_1_21_5, true),
map(0x2B, MINECRAFT_1_21_9, true));
// minecraft:disconnect
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
map(0x40, MINECRAFT_1_7_2, true),
@@ -143,7 +145,8 @@ public enum FallbackPacketRegistry {
map(0x1A, MINECRAFT_1_19_4, true),
map(0x1B, MINECRAFT_1_20_2, true),
map(0x1D, MINECRAFT_1_20_5, true),
map(0x1C, MINECRAFT_1_21_5, true));
map(0x1C, MINECRAFT_1_21_5, true),
map(0x20, MINECRAFT_1_21_9, true));
// minecraft:player_position
clientbound.register(SetPlayerPositionRotationPacket.class, SetPlayerPositionRotationPacket::new,
map(0x08, MINECRAFT_1_7_2, true),
@@ -162,7 +165,8 @@ public enum FallbackPacketRegistry {
map(0x3E, MINECRAFT_1_20_2, true),
map(0x40, MINECRAFT_1_20_5, true),
map(0x42, MINECRAFT_1_21_2, true),
map(0x41, MINECRAFT_1_21_5, true));
map(0x41, MINECRAFT_1_21_5, true),
map(0x46, MINECRAFT_1_21_9, true));
// minecraft:player_abilities
clientbound.register(PlayerAbilitiesPacket.class, PlayerAbilitiesPacket::new,
map(0x39, MINECRAFT_1_7_2, true),
@@ -181,7 +185,8 @@ public enum FallbackPacketRegistry {
map(0x36, MINECRAFT_1_20_2, true),
map(0x38, MINECRAFT_1_20_5, true),
map(0x3A, MINECRAFT_1_21_2, true),
map(0x39, MINECRAFT_1_21_5, true));
map(0x39, MINECRAFT_1_21_5, true),
map(0x3E, MINECRAFT_1_21_9, true));
// minecraft:level_chunk_with_light
clientbound.register(ChunkDataPacket.class, ChunkDataPacket::new,
map(0x21, MINECRAFT_1_7_2, true),
@@ -199,7 +204,8 @@ public enum FallbackPacketRegistry {
map(0x25, MINECRAFT_1_20_2, true),
map(0x27, MINECRAFT_1_20_5, true),
map(0x28, MINECRAFT_1_21_2, true),
map(0x27, MINECRAFT_1_21_5, true));
map(0x27, MINECRAFT_1_21_5, true),
map(0x2C, MINECRAFT_1_21_9, true));
// minecraft:remove_entities
clientbound.register(RemoveEntitiesPacket.class, RemoveEntitiesPacket::new,
map(0x13, MINECRAFT_1_7_2, true),
@@ -220,7 +226,8 @@ public enum FallbackPacketRegistry {
map(0x40, MINECRAFT_1_20_2, true),
map(0x42, MINECRAFT_1_20_5, true),
map(0x47, MINECRAFT_1_21_2, true),
map(0x46, MINECRAFT_1_21_5, true));
map(0x46, MINECRAFT_1_21_5, true),
map(0x4B, MINECRAFT_1_21_9, true));
// minecraft:set_passengers
clientbound.register(SetPassengersPacket.class, SetPassengersPacket::new,
map(0x1B, MINECRAFT_1_7_2, true),
@@ -238,7 +245,8 @@ public enum FallbackPacketRegistry {
map(0x5D, MINECRAFT_1_20_3, true),
map(0x5F, MINECRAFT_1_20_5, true),
map(0x65, MINECRAFT_1_21_2, true),
map(0x64, MINECRAFT_1_21_5, true));
map(0x64, MINECRAFT_1_21_5, true),
map(0x69, MINECRAFT_1_21_9, true));
// minecraft:add_entity
clientbound.register(SpawnEntityPacket.class, SpawnEntityPacket::new,
map(0x0E, MINECRAFT_1_7_2, true),
@@ -261,7 +269,8 @@ public enum FallbackPacketRegistry {
map(0x47, MINECRAFT_1_20_3, true),
map(0x49, MINECRAFT_1_20_5, true),
map(0x4E, MINECRAFT_1_21_2, true),
map(0x4D, MINECRAFT_1_21_5, true));
map(0x4D, MINECRAFT_1_21_5, true),
map(0x52, MINECRAFT_1_21_9, true));
// minecraft:ping
clientbound.register(TransactionPacket.class, TransactionPacket::new,
map(0x32, MINECRAFT_1_7_2, true),
@@ -278,7 +287,8 @@ public enum FallbackPacketRegistry {
map(0x33, MINECRAFT_1_20_2, true),
map(0x35, MINECRAFT_1_20_5, true),
map(0x37, MINECRAFT_1_21_2, true),
map(0x36, MINECRAFT_1_21_5, true));
map(0x36, MINECRAFT_1_21_5, true),
map(0x3B, MINECRAFT_1_21_9, true));
// minecraft:set_default_spawn_position
clientbound.register(SetDefaultSpawnPositionPacket.class, SetDefaultSpawnPositionPacket::new,
map(0x05, MINECRAFT_1_7_2, true),
@@ -298,7 +308,8 @@ public enum FallbackPacketRegistry {
map(0x54, MINECRAFT_1_20_3, true),
map(0x56, MINECRAFT_1_20_5, true),
map(0x5B, MINECRAFT_1_21_2, true),
map(0x5A, MINECRAFT_1_21_5, true));
map(0x5A, MINECRAFT_1_21_5, true),
map(0x5F, MINECRAFT_1_21_9, true));
// minecraft:map_item_data
clientbound.register(MapDataPacket.class, MapDataPacket::new,
map(0x34, MINECRAFT_1_7_2, true),
@@ -315,7 +326,8 @@ public enum FallbackPacketRegistry {
map(0x2A, MINECRAFT_1_20_2, true),
map(0x2C, MINECRAFT_1_20_5, true),
map(0x2D, MINECRAFT_1_21_2, true),
map(0x2C, MINECRAFT_1_21_5, true));
map(0x2C, MINECRAFT_1_21_5, true),
map(0x31, MINECRAFT_1_21_9, true));
// minecraft:container_set_slot
clientbound.register(SetContainerSlotPacket.class, SetContainerSlotPacket::new,
map(0x2F, MINECRAFT_1_7_2, true),
@@ -348,7 +360,8 @@ public enum FallbackPacketRegistry {
map(0x5A, MINECRAFT_1_20_3, true),
map(0x5C, MINECRAFT_1_20_5, true),
map(0x61, MINECRAFT_1_21_2, true),
map(0x60, MINECRAFT_1_21_5, true));
map(0x60, MINECRAFT_1_21_5, true),
map(0x65, MINECRAFT_1_21_9, true));
// minecraft:system_chat
clientbound.register(SystemChatPacket.class, SystemChatPacket::new,
map(0x02, MINECRAFT_1_7_2, true),
@@ -365,17 +378,20 @@ public enum FallbackPacketRegistry {
map(0x69, MINECRAFT_1_20_3, true),
map(0x6C, MINECRAFT_1_20_5, true),
map(0x73, MINECRAFT_1_21_2, true),
map(0x72, MINECRAFT_1_21_5, true));
map(0x72, MINECRAFT_1_21_5, true),
map(0x77, MINECRAFT_1_21_9, true));
// minecraft:game_event
clientbound.register(GameEventPacket.class, GameEventPacket::new,
map(0x20, MINECRAFT_1_20_3, true),
map(0x22, MINECRAFT_1_20_5, true),
map(0x23, MINECRAFT_1_21_2, true),
map(0x22, MINECRAFT_1_21_5, true));
map(0x22, MINECRAFT_1_21_5, true),
map(0x26, MINECRAFT_1_21_9, true));
// minecraft:transfer
clientbound.register(TransferPacket.class, TransferPacket::new,
map(0x73, MINECRAFT_1_20_5, true),
map(0x7A, MINECRAFT_1_21_2, true));
map(0x7A, MINECRAFT_1_21_2, true),
map(0x7F, MINECRAFT_1_21_9, true));
// minecraft:set_held_slot
clientbound.register(SetHeldItemPacket.class, SetHeldItemPacket::new,
map(0x09, MINECRAFT_1_7_2, true),
@@ -395,7 +411,8 @@ public enum FallbackPacketRegistry {
map(0x51, MINECRAFT_1_20_3, true),
map(0x53, MINECRAFT_1_20_5, true),
map(0x63, MINECRAFT_1_21_2, true),
map(0x62, MINECRAFT_1_21_5, true));
map(0x62, MINECRAFT_1_21_5, true),
map(0x67, MINECRAFT_1_21_9, true));
// minecraft:animate
clientbound.register(EntityAnimationPacket.class, EntityAnimationPacket::new,
map(0x0B, MINECRAFT_1_7_2, true),
@@ -425,7 +442,8 @@ public enum FallbackPacketRegistry {
map(0x62, MINECRAFT_1_20_3, true),
map(0x64, MINECRAFT_1_20_5, true),
map(0x6B, MINECRAFT_1_21_2, true),
map(0x6A, MINECRAFT_1_21_5, true));
map(0x6A, MINECRAFT_1_21_5, true),
map(0x6F, MINECRAFT_1_21_9, true));
// minecraft:client_tick_end
serverbound.register(ClientTickEndPacket.class, ClientTickEndPacket::new,
@@ -28,6 +28,7 @@ import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.event.impl.CaptchaGenerationStartEvent;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import xyz.jonesdev.sonar.captcha.complex.ComplexCaptchaGenerator;
import xyz.jonesdev.sonar.captcha.legacy.LegacyCaptchaGenerator;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockType;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
@@ -96,6 +97,7 @@ public class FallbackPreparer {
public final FallbackPacket[] REGISTRY_SYNC_1_21_2 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_2);
public final FallbackPacket[] REGISTRY_SYNC_1_21_4 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_4);
public final FallbackPacket[] REGISTRY_SYNC_1_21_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_5);
public final FallbackPacket[] REGISTRY_SYNC_1_21_11 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_11);
public final FallbackPacket START_WRITING_CHUNKS = new GameEventPacket(13, 0);
public final static FallbackPacket INVALID_HELD_ITEM_SLOT = new SetHeldItemPacket(-1);
public final FallbackPacket RANDOM_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(RANDOM.nextInt()));
@@ -168,6 +170,7 @@ public class FallbackPreparer {
// Set the dynamic block and collide Y position based on the maximum fall distance
dynamicSpawnYPosition = PLATFORM_Y_POSITION + (int) Math.ceil(fallDistance);
defaultSpawnPosition = new FallbackPacketSnapshot(new SetDefaultSpawnPositionPacket(
"minecraft:overworld",
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION));
spawnPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
@@ -233,7 +236,6 @@ public class FallbackPreparer {
if (Sonar.get0().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get0().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
// Prepare CAPTCHA messages
enterCodeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(
Sonar.get0().getConfig().getMessagesConfig().getString("verification.captcha.enter"),
@@ -246,7 +248,6 @@ public class FallbackPreparer {
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("attempts-left", String.valueOf(i + 1))))));
}
// Prepare countdown
xpCountdown = new FallbackPacket[Sonar.get0().getConfig().getVerification().getMap().getMaxDuration() / 1000];
for (int i = 0; i < xpCountdown.length; i++) {
@@ -254,27 +255,33 @@ public class FallbackPreparer {
xpCountdown[i] = new FallbackPacketSnapshot(new SetExperiencePacket(bar, i, 0));
}
// Update the CAPTCHA generator if necessary
final CaptchaGenerationStartEvent generationStartEvent = new CaptchaGenerationStartEvent(
Sonar.get0().getFallback().getCaptchaGenerator());
Sonar.get0().getEventManager().publish(generationStartEvent);
if (generationStartEvent.getCaptchaGenerator() == null
|| generationStartEvent.getCaptchaGenerator() instanceof StandardCaptchaGenerator
|| generationStartEvent.getCaptchaGenerator() instanceof LegacyCaptchaGenerator) {
|| generationStartEvent.getCaptchaGenerator() instanceof LegacyCaptchaGenerator
|| generationStartEvent.getCaptchaGenerator() instanceof ComplexCaptchaGenerator) {
final File backgroundImage = Sonar.get0().getConfig().getVerification().getMap().getBackgroundImage();
final boolean useLegacyCaptchaGenerator = Sonar.get0().getConfig().getGeneralConfig()
.getString("verification.checks.map-captcha.style").equalsIgnoreCase("legacy");
Sonar.get0().getFallback().setCaptchaGenerator(useLegacyCaptchaGenerator
? new LegacyCaptchaGenerator(backgroundImage) : new StandardCaptchaGenerator(backgroundImage));
switch (Sonar.get0().getConfig().getGeneralConfig()
.getString("verification.checks.map-captcha.style").toLowerCase()) {
case "legacy":
Sonar.get0().getFallback().setCaptchaGenerator(new LegacyCaptchaGenerator(backgroundImage));
break;
case "complex":
Sonar.get0().getFallback().setCaptchaGenerator(new ComplexCaptchaGenerator(backgroundImage));
break;
default:
Sonar.get0().getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(backgroundImage));
break;
}
} else {
Sonar.get0().getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
}
// Prepare CAPTCHA answers
CaptchaPreparer.prepare();
} else {
// Throw away if not needed
enterCodeMessage = null;
incorrectCaptcha = null;
xpCountdown = null;
@@ -285,11 +292,13 @@ public class FallbackPreparer {
+ (xpCountdown == null ? 0 : xpCountdown.length) * 20 + 5
+ Sonar.get0().getConfig().getVerification().getVehicle().getMinimumPackets() * 4
+ Sonar.get0().getConfig().getVerification().getMap().getMaxTries()
+ 150 /* some arbitrary leeway */;
+ Sonar.get0().getConfig().getVerification().getMaxPacketCount();
}
public static FallbackPacket[] getRegistryPackets(final @NotNull ProtocolVersion protocolVersion) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_5)) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_11)) {
return REGISTRY_SYNC_1_21_11;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_5)) {
return REGISTRY_SYNC_1_21_5;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_4)) {
return REGISTRY_SYNC_1_21_4;
@@ -34,7 +34,6 @@ public enum BlockType {
// - https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/
// - https://pokechu22.github.io/Burger/
// - https://github.com/ViaVersion/Mappings/tree/main/mappings
//STONE(protocolVersion -> 1, 1),
ENCHANTMENT_TABLE(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
return 116;
@@ -75,7 +74,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 8163;
}
return 8173;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 8173;
}
return 9250;
}, protocolVersion -> (double) 0.75f),
TRAPDOOR(protocolVersion -> {
// We have to use wooden trapdoors for 1.7 since 1.7 doesn't have iron trapdoors
@@ -124,7 +126,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 11293;
}
return 11303;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 11303;
}
return 12380;
}, protocolVersion -> 0.1875),
END_PORTAL_FRAME(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -166,7 +171,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 8185;
}
return 8195;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 8195;
}
return 9272;
}, protocolVersion -> 0.8125),
DAYLIGHT_SENSOR(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -211,7 +219,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 10006;
}
return 10016;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 10016;
}
return 11093;
}, protocolVersion -> 0.375),
COBBLESTONE_WALL(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -253,7 +264,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 8696;
}
return 8706;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 8706;
}
return 9783;
}, protocolVersion -> 1.5),
STONE_SLABS(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -298,7 +312,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 12110;
}
return 12120;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 12120;
}
return 13197;
}, protocolVersion -> 0.5),
WHITE_CARPET(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -343,7 +360,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 11607;
}
return 11617;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 11617;
}
return 12694;
}, protocolVersion -> protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 0 : 0.0625);
private final Function<ProtocolVersion, Integer> id;
@@ -40,6 +40,7 @@ public final class DimensionRegistry {
public final CompoundBinaryTag CODEC_1_21_2;
public final CompoundBinaryTag CODEC_1_21_4;
public final CompoundBinaryTag CODEC_1_21_5;
public final CompoundBinaryTag CODEC_1_21_11;
static {
CODEC_1_16 = getCodec("codec_1_16.nbt");
@@ -54,6 +55,7 @@ public final class DimensionRegistry {
CODEC_1_21_2 = getCodec("codec_1_21_2.nbt");
CODEC_1_21_4 = getCodec("codec_1_21_4.nbt");
CODEC_1_21_5 = getCodec("codec_1_21_5.nbt");
CODEC_1_21_11 = getCodec("codec_1_21_11.nbt");
}
private @NotNull CompoundBinaryTag getCodec(final @NotNull String fileName) {
@@ -61,7 +61,13 @@ public enum EntityType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_5)) {
return 84;
}
return 85;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 85;
}
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_9)) {
return 87;
}
return 89;
}),
MINECART(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
@@ -100,7 +106,13 @@ public enum EntityType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_5)) {
return 81;
}
return 82;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 82;
}
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_9)) {
return 84;
}
return 85;
});
private final Function<ProtocolVersion, Integer> id;
@@ -79,7 +79,10 @@ public enum ItemType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_5)) {
return 1042;
}
return 1059;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_7)) {
return 1059;
}
return 1104;
}, protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 26;
@@ -87,7 +90,10 @@ public enum ItemType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 36;
}
return 37;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_9)) {
return 37;
}
return 44;
});
private final Function<ProtocolVersion, Integer> id;
@@ -60,10 +60,10 @@ public final class SetContainerSlotPacket implements FallbackPacket {
ProtocolUtil.writeVarInt(byteBuf, count);
}
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_13_2)) {
byteBuf.writeShort(itemType.getId().apply(protocolVersion));
} else {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_13_2)) {
ProtocolUtil.writeVarInt(byteBuf, itemType.getId().apply(protocolVersion));
} else {
byteBuf.writeShort(itemType.getId().apply(protocolVersion));
}
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
@@ -83,8 +83,8 @@ public final class SetContainerSlotPacket implements FallbackPacket {
} else if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, compoundBinaryTag);
} else {
ProtocolUtil.writeVarInt(byteBuf, 1); // component count to add
ProtocolUtil.writeVarInt(byteBuf, 0); // component count to remove
ProtocolUtil.writeVarInt(byteBuf, 1); // addedComponentCount
ProtocolUtil.writeVarInt(byteBuf, 0); // removedComponentCount
ProtocolUtil.writeVarInt(byteBuf, itemType.getComponents().apply(protocolVersion));
ProtocolUtil.writeVarInt(byteBuf, 0); // data
}
@@ -25,12 +25,14 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class SetDefaultSpawnPositionPacket implements FallbackPacket {
private String dimensionName;
private int x, y, z;
@Override
@@ -40,6 +42,10 @@ public final class SetDefaultSpawnPositionPacket implements FallbackPacket {
byteBuf.writeInt(y);
byteBuf.writeInt(z);
} else {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_9)) {
ProtocolUtil.writeString(byteBuf, dimensionName);
}
final long encoded = protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_14)
? ((x & 0x3FFFFFFL) << 38) | ((y & 0xFFFL) << 26) | (z & 0x3FFFFFFL)
: ((x & 0x3FFFFFFL) << 38) | ((y & 0x3FFFFFFL) << 12) | (z & 0xFFFL);
@@ -48,6 +54,10 @@ public final class SetDefaultSpawnPositionPacket implements FallbackPacket {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_17)) {
byteBuf.writeFloat(0f);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_9)) {
byteBuf.writeFloat(0f); // pitch
}
}
}
}
@@ -59,6 +59,10 @@ public final class SpawnEntityPacket implements FallbackPacket {
byteBuf.writeDouble(x);
byteBuf.writeDouble(y);
byteBuf.writeDouble(z);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_9)) {
encodeVelocity(byteBuf, velocityX, velocityY, velocityZ);
}
} else {
byteBuf.writeInt((int) (x * 32D));
byteBuf.writeInt((int) (y * 32D));
@@ -75,13 +79,43 @@ public final class SpawnEntityPacket implements FallbackPacket {
byteBuf.writeInt(data); // data
}
if (data > 0 || protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_9)) {
byteBuf.writeShort((int) (velocityX * 8000D));
byteBuf.writeShort((int) (velocityY * 8000D));
byteBuf.writeShort((int) (velocityZ * 8000D));
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_21_9)) {
if (data > 0 || protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_9)) {
byteBuf.writeShort((int) (velocityX * 8000D));
byteBuf.writeShort((int) (velocityY * 8000D));
byteBuf.writeShort((int) (velocityZ * 8000D));
}
}
}
private static void encodeVelocity(final ByteBuf byteBuf, final double x, final double y, final double z) {
final double maxVal = Math.max(Math.abs(x), Math.max(Math.abs(y), Math.abs(z)));
if (maxVal < 3.051944088384301E-5) {
byteBuf.writeByte(0);
return;
}
final long scale = (long) Math.ceil(maxVal);
final boolean scaleTooLargeForBits = (scale & 3L) != scale;
final long scaleBits = scaleTooLargeForBits ? scale & 3L | 4L : scale;
final long encodedX = packLpVec3Component(x / scale) << 3;
final long encodedY = packLpVec3Component(y / scale) << 18;
final long encodedZ = packLpVec3Component(z / scale) << 33;
final long packed = scaleBits | encodedX | encodedY | encodedZ;
byteBuf.writeByte((byte) packed);
byteBuf.writeByte((byte) (packed >> 8));
byteBuf.writeInt((int) (packed >> 16));
if (scaleTooLargeForBits) {
ProtocolUtil.writeVarInt(byteBuf, (int) (scale >> 2));
}
}
private static long packLpVec3Component(double d) {
return Math.round((d * 0.5 + 0.5) * 32766.0);
}
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
@@ -161,7 +161,7 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
if (!expectClientTick) {
// Is it impossible for the client to not move during the gravity check?
if (++tickWithoutMove >= 20) {
fail("expected position but got client tick end.");
//failOrShowCaptcha("expected position but got client tick end.");
}
} else {
tickWithoutMove = 0;
@@ -44,7 +44,7 @@ attack-tracker:
# Nicméně, IP adresy nemůžou být vystopovány zpátky k hráči, protože Sonar používá UUID na ukládní místo jmeno hráčů
database:
# Typy datábázi se kterýma Sonar bude pracovat
# Typy: NONE, MYSQL, MARIADB, H2
# Typy: NONE, MYSQL, MARIADB, POSTGRESQL, H2
type: NONE
# Soubor v adresáři zásuvných modulů Sonaru použitý pro databázi H2
filename: verified_players
@@ -112,6 +112,7 @@ verification:
background: ''
# Styl mapy CAPTCHA (Výchozí: 'modern').
# Chcete-li použít starý design, nastavte tuto hodnotu na 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# Kolik odpovědí by měl Sonar předgenerovat?
# Tento úkol běží souběžně v pozadí
@@ -192,6 +193,8 @@ verification:
# Doba od okamžiku, kdy server přestal posílat pakety před odpojením hráče
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
write-timeout: 10000
# The maximum number of packets the client is allowed to send in total (during verification).
max-packets: 325
# Jak dlouho by měl hráč čekat, než se při ověřování znovu připojí
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
rejoin-delay: 5000
@@ -41,7 +41,7 @@ attack-tracker:
# Beachte: IP adressen werden im Klartext gespeichert. Du bist für die Sicherheit der Datenbank verantwortlich
database:
# Typ von Datenbank die Sonar verwenden soll, um verifizierte Spielerdaten zu speichern
# Mögliche Typen: NONE, MYSQL, MARIADB, H2
# Mögliche Typen: NONE, MYSQL, MARIADB, POSTGRESQL, H2
type: NONE
# Datei im Plugin-Verzeichnis von Sonar, die für die H2-Datenbank verwendet wird
filename: verified_players
@@ -109,6 +109,7 @@ verification:
background: ''
# Stil der Karte CAPTCHA (Standard: 'modern').
# Um das alte Design zu verwenden, setzen Sie dies auf 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# Wie viele Antworten soll Sonar vorbereiten?
# Dieser Vorgang erfolgt asynchron im Hintergrund
@@ -189,6 +190,8 @@ verification:
# Die Zeit, seit der der Server keine Pakete mehr sendet, bevor er die Verbindung zum Spieler trennt
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
write-timeout: 10000
# The maximum number of packets the client is allowed to send in total (during verification).
max-packets: 325
# Wie lange sollte ein Spieler warten, bevor er während der Überprüfung erneut verbinden kann
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
rejoin-delay: 5000
@@ -43,7 +43,7 @@ attack-tracker:
# Note: IP addresses are saved in plain text. You are responsible for keeping your database safe!
database:
# Type of database Sonar uses to store verified players
# Possible types: NONE, MYSQL, MARIADB, H2
# Possible types: NONE, MYSQL, MARIADB, POSTGRESQL, H2
type: NONE
# File in Sonar's plugin directory used for the H2 database
filename: verified_players
@@ -111,6 +111,7 @@ verification:
background: ''
# Style of the map CAPTCHA (Default: 'modern').
# To use the old design, set this to 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# How many CAPTCHA answers should Sonar prepare?
# This task happens asynchronously in the background
@@ -192,6 +193,8 @@ verification:
# The time since the server stopped sending packets before disconnecting the player
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
write-timeout: 10000
# The maximum number of packets the client is allowed to send in total (during verification).
max-packets: 325
# How long should a player wait before reconnecting during verification
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
rejoin-delay: 5000
@@ -43,7 +43,7 @@ attack-tracker:
# Remarque : Les adresses IP sont enregistrées en texte brut. Vous êtes responsable de la sécurité de votre base de données !
database:
# Type de base de données utilisé par Sonar pour stocker les joueurs vérifiés
# Types possibles : NONE, MYSQL, MARIADB, H2
# Types possibles : NONE, MYSQL, MARIADB, POSTGRESQL, H2
type: NONE
# Fichier dans le répertoire des plugins de Sonar utilisé pour la base de données H2
filename: verified_players
@@ -111,6 +111,7 @@ verification:
background: ''
# Style de la carte CAPTCHA (par défaut : 'moderne').
# Pour utiliser l'ancien design, mettez ce paramètre à 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# Combien de réponses Sonar devrait-il préparer ?
# Cette tâche se déroule de manière asynchrone en arrière-plan
@@ -191,6 +192,8 @@ verification:
# Temps écoulé depuis que le serveur a cessé d'envoyer des paquets avant de se déconnecter
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
write-timeout: 10000
# The maximum number of packets the client is allowed to send in total (during verification).
max-packets: 325
# Combien de temps un joueur doit-il attendre avant de se reconnecter pendant la vérification
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
rejoin-delay: 5000
@@ -43,7 +43,7 @@ attack-tracker:
# შენიშვნა: IP მისამართები ინახება უბრალო ტექსტში. თქვენ ხართ პასუხისმგებელი თქვენი მონაცემთა ბაზის უსაფრთხოებაზე!
database:
# მონაცემთა ბაზის ტიპები, რომლებიც Sonar-ს შეუძლია გამოიყენოს უკვე შემოწმებული მოთამაშეების შესანახად
# შესაძლო ტიპები: NONE, MYSQL, MARIADB, H2
# შესაძლო ტიპები: NONE, MYSQL, MARIADB, POSTGRESQL, H2
type: NONE
# ფაილი სონარ_ის ფოლდერში რომელიც ინახავს H2 database_ს
filename: verified_players
@@ -111,6 +111,7 @@ verification:
background: ''
# რუკის სტილი CAPTCHA (ნაგულისხმევი: 'modern').
# ძველი დიზაინის გამოსაყენებლად დააყენეთ ეს 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# რამდენი პასუხი უნდა მოამზადოს Sonar-მა?
# ეს ხდება ასინქრონულად (ანუ ამის გამოთვლას სერვერი არ დაელოდება) უკანა ფონზე
@@ -190,6 +191,8 @@ verification:
# დრო როდიდანაც სერვერმა შეწყვიტა პაკეტების გაგზავნა სანამ გააგდებს მოთამაშეს.
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
write-timeout: 10000
# პაკეტების მაქსიმალური ოდენობა, რომელიც შეუძლია მოთამაშებ გაგზავნოს (შემოწმების დროს).
max-packets: 325
# რამდენ ხანს უნდა დაელოდოს მოთამაშე ვერიფიკაციის დროს ხელახლა დაკავშირებამდე?
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
rejoin-delay: 5000
@@ -43,7 +43,7 @@ attack-tracker:
# Let op: IP-adressen worden opgeslagen in platte tekst. U bent verantwoordelijk voor het veilig houden van uw database!
database:
# Type database dat Sonar gebruikt om geverifieerde spelers op te slaan
# Mogelijke typen: NONE, MYSQL, MARIADB, H2
# Mogelijke typen: NONE, MYSQL, MARIADB, POSTGRESQL, H2
type: NONE
# Bestand in de plugin-directory van Sonar dat wordt gebruikt voor de H2-database
filename: verified_players
@@ -111,6 +111,7 @@ verification:
background: ''
# Stijl van de CAPTCHA kaart (Standaard: 'modern').
# Om het oude ontwerp te gebruiken, stel je dit in op 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# Hoeveel antwoorden moet Sonar voorbereiden?
# Deze taak gebeurt asynchroon op de achtergrond
@@ -188,12 +189,14 @@ verification:
# De hoeveelheid tijd die moet verstrijken voordat een speler een time-out krijgt
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
read-timeout: 8000
# De tijd sinds de speler stopte met het verzenden van pakketten voordat de verbinding werd verbroken
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
rejoin-delay: 5000
# De tijd sinds de server stopte met het versturen van pakketten voordat de verbinding met de speler werd verbroken
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
write-timeout: 10000
# Het maximale aantal pakketten dat de client in totaal mag verzenden (tijdens verificatie).
max-packets: 325
# De tijd sinds de speler stopte met het verzenden van pakketten voordat de verbinding werd verbroken
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
rejoin-delay: 5000
# Hoe lang moet Sonar het aantal mislukte verificaties voor een speler onthouden?
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
remember-time: 120000
@@ -43,7 +43,7 @@ attack-tracker:
# Uwaga: Adresy IP są zapisywane w postaci zwykłego tekstu. Jesteś odpowiedzialny za bezpieczeństwo swojej bazy danych!
database:
# Typ bazy danych używanej przez Sonar.
# Dostępne rodzaje: NONE, MYSQL, MARIADB, H2
# Dostępne rodzaje: NONE, MYSQL, MARIADB, POSTGRESQL, H2
type: NONE
# Plik w folderze pluginu używany dla bazy danych H2
filename: verified_players
@@ -111,6 +111,7 @@ verification:
background: ''
# Styl mapy CAPTCHA (domyślnie: 'nowoczesny').
# Aby użyć starego projektu, ustaw to na 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# Ile możliwych odpowiedzi powinien przygotować Sonar?
# Zadanie to wykonywane jest asynchronicznie w tle
@@ -191,6 +192,8 @@ verification:
# Czas, jaki ma upłynąć od zatrzymania wysyłania pakietów przez serwer do wyrzucenia.
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
write-timeout: 10000
# The maximum number of packets the client is allowed to send in total (during verification).
max-packets: 325
# Jak długo niezweryfikowany gracz powinien czekać przed ponownym połączeniem do serwera?
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
rejoin-delay: 5000
@@ -42,7 +42,7 @@ attack-tracker:
# Nota: Endereços IP são salvos em texto simples. Você é responsável por manter seu banco de dados seguro!
database:
# Tipo de banco de dados que o Sonar usa para armazenar jogadores verificados
# Tipos possíveis: NONE, MYSQL, MARIADB, H2
# Tipos possíveis: NONE, MYSQL, MARIADB, POSTGRESQL, H2
type: NONE
# Arquivo no diretório de plugins do Sonar usado para o banco de dados H2
filename: verified_players
@@ -110,6 +110,7 @@ verification:
background: ''
# Estilo do CAPTCHA do mapa (Padrão: 'modern').
# Para usar o design antigo, defina-o como 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# Quantas respostas de CAPTCHA o Sonar deve preparar?
# Esta tarefa acontece de forma assíncrona em segundo plano
@@ -191,6 +192,8 @@ verification:
# O tempo desde que o servidor parou de enviar pacotes antes de desconectar o jogador
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
write-timeout: 10000
# The maximum number of packets the client is allowed to send in total (during verification).
max-packets: 325
# Quanto tempo um jogador deve esperar antes de se reconectar durante a verificação
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
rejoin-delay: 5000
@@ -43,7 +43,7 @@ attack-tracker:
# Примечание: IP-адреса сохраняются в виде обычного текста. Вы сами отвечаете за сохранность своей базы данных!
database:
# Тип базы данных, которую Sonar использует для хранения проверенных игроков.
# Возможные типы: NONE, MYSQL, MARIADB, H2
# Возможные типы: NONE, MYSQL, MARIADB, POSTGRESQL, H2
type: NONE
# Файл в каталоге плагинов Sonar, используемый для базы данных H2
filename: verified_players
@@ -110,6 +110,7 @@ verification:
background: ''
# Стиль капч (по умолчанию: 'modern').
# Чтобы использовать старый дизайн, установите значение 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# Сколько капч должен подготовить Sonar?
# Эта задача выполняется асинхронно в фоновом режиме
@@ -190,6 +191,8 @@ verification:
# Время, прошедшее с момента прекращения отправки сервером пакетов до отключения игрока
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
write-timeout: 10000
# The maximum number of packets the client is allowed to send in total (during verification).
max-packets: 325
# Сколько времени игрок должен ждать перед повторным подключением во время проверки
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
rejoin-delay: 5000
@@ -110,6 +110,7 @@ verification:
background: ''
# 地图验证码的风格(默认:'modern').
# 要使用旧设计, 请将其设置为 'legacy'.
# To use an experimental design, set this to 'complex'.
style: 'modern'
# Sonar 应该一次准备多少个验证码?
precompute: 500
@@ -187,6 +188,8 @@ verification:
# 服务器在断开玩家连接前停止发送数据包的时间
# (该值为毫秒. 1秒等于1000毫秒.)
write-timeout: 10000
# The maximum number of packets the client is allowed to send in total (during verification).
max-packets: 325
# 当玩家在还未通过检查的情况下断开连接后需要等待多少毫秒才可以重新连接?
# (该值为毫秒. 1秒等于1000毫秒.)
rejoin-delay: 5000
@@ -61,9 +61,9 @@ commands:
tick: '<green>✔</green>'
cross: '<red>✗</red>'
# Formatting for the version information
outdated-version: ' <red><click:open_url:''https://github.com/jonesdevelopment/sonar/releases/latest''><hover:show_text:''(Click to download the latest version of Sonar)''>(Outdated)</hover></click></red>'
unstable-version: ' <gold>(Unstable)</gold>'
api-error: ' <dark_red><hover:show_text:''Check the server logs for more information''>(API error)</hover></dark_red>'
outdated-version: ' <red><click:open_url:''https://github.com/jonesdevelopment/sonar/releases/latest''><hover:show_text:''(დააჭირე, რომ გადმოწერო Sonar_ის უახლესი ვერსია)''>(Outdated)</hover></click></red>'
unstable-version: ' <gold>(არასტაბილური)</gold>'
api-error: ' <dark_red><hover:show_text:''შეამოწმე სერვერის Log_ები მეტი ინფორმაციისთვის''>(API error)</hover></dark_red>'
# თარგმანი '/sonar reload' სთვის
reload:
@@ -97,7 +97,7 @@ commands:
# აღწერა ამ ქვე-ბრძანებისთვის
description: "შეცვალე აკრძალული (დაბლოკილი) IP მისამართები"
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე ცდის შავი ნუსხის გასუფთავებას თუმცა იგი ცარიელია.
empty: '<prefix>შავი ნუსხა ამჟამად ცარიელია. ასე რომ არცერთი IP მისამართი არ მოშორებულა.'
empty: '<prefix>შავი ნუსხა ამჟამად ცარიელია. ასე რომ, არცერთი IP მისამართი არ მოშორებულა.'
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე გაასუფთავებს შავ ნუსხას.
clear: '<prefix>თქვენ წარმატებით გაასუფთავეთ <amount> IP მისამართ(ებ)ი შავი ნუსხიდან.'
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე შეამოწმებს შავი ნუსხის ზომას.
@@ -114,9 +114,9 @@ commands:
# თარგმანი '/sonar verified' სთვის
verified:
# აღწერა ამ ქვე-ბრძანებისთვის
description: "Manage verified IP მისამართები"
description: "განაგე შემოწმებული IP მისამართები"
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე ცდის გაასუფთავოს ნუსხა შემოწმებული მოთამაშეებისა თუმცა იგი ისედაც ცარიელია.
empty: '<prefix>ნუსხა შემოწმებული მოთამაშეებისა ისედაც ცარიელია. ასე რომ არ მოშორებულა არცერთი IP მისამართი იქიდან.'
empty: '<prefix>ნუსხა შემოწმებული მოთამაშეებისა ისედაც ცარიელია. ასე რომ, არ მოშორებულა არცერთი IP მისამართი იქიდან.'
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე გაასუფთავებს ნუსხას შემოწმებული მოთამაშეებისა.
clear: '<prefix>თქვენ წარმატებით გაასუფთავეთ <amount> მოთამაშე შემოწმებულების ნუსხიდან.'
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე შეამოწმებს სიდიდეს შემოწმებული მოთამაშეების ნუსხისა.
@@ -124,7 +124,7 @@ commands:
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე მოშლის IP მისამართს შემოწმებულების სიიდან.
remove: '<prefix>წარმატებით მოშორდა <ip>.'
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე დაამატებს IP მისამართს შემოწმებულების ნუსხაში.
add: '<prefix>Successfully verified <ip>.'
add: '<prefix>წარმატებით შემოწმდა <ip>.'
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე ცდის რომ დაამატოს IP/UUID შემოწმებულების ნუსხაში რაც უკვე დამატებულია.
already: '<prefix>IP მისამართი/UUID რაც მოგვაწოდეთ ისედაც დამატებულია.'
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე მოშლის IP მისამართს შემოწმებულების ნუსხიდან მაგრამ ის იქ არ არის.
@@ -136,7 +136,7 @@ commands:
# თარგმანი '/sonar statistics' სთვის
statistics:
# აღწერა ამ ქვე-ბრძანებისთვის
description: "აჩვენე სერვერის ამჟამინდელი სტატისტიკები"
description: "აჩვენე სერვერის ამჟამინდელი სტატისტიკა"
# ინფორმაციული მესიჯი რაც არის ნაჩვენები ყველაფრის ზემოთ როცა ნახულობთ სტატისტიკებს.
header: '<prefix>ვაჩვენებთ <statistics-type> სტატისტიკას:'
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა მოთამაშე ცდის იხილოს უცნობი სტატისტიკა.
@@ -174,7 +174,7 @@ commands:
# თარგმანი '/sonar dump' სთვის
dump:
# აღწერა ამ ქვე-ბრძანებისთვის
description: "Print developer information"
description: "აჩვენებს დეველოპერის ინფორმაციას"
# ფორმატი ინფორმაციისთვის რაც კონსოლს ეგზავნება
log: "გენერირდა: <dumped-json-data>"
@@ -194,48 +194,48 @@ verification:
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა მათ უნდა დაწერონ CAPTCHA
captcha:
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა მათ უნდა მიუთითონ CAPTCHA ს პასუხი
enter: '<prefix><green>გთხოვთ დაწეროთ ჩატში ის კოდი რომელიც არის ნაჩვენები რუკაზე.'
enter: '<prefix><green>გთხოვთ დაწეროთ ჩატში კოდი, რომელიც არის ნაჩვენები რუკაზე.'
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა ისინი ჩაწერენ არასწორ პასუხს ჩატში
incorrect: '<prefix><red>თქვენ მიუთითეთ არასწორი კოდი. თქვენ დაგრჩათ <attempts-left> მცდელობა(ები).'
incorrect: '<prefix><red>კოდი არასწორია. დაგრჩათ <attempts-left> მცდელობა(ები).'
# Message shown to the player when they are being verified
# Set this to '' to disable the welcome message entirely
welcome: '<prefix><green>You are being verified. Please do not move; this is an automatic process.'
welcome: '<prefix><green>მოწმდებით. გთხოვთ არ გაინძრეთ; ეს ავტომატური პროცესია.'
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა მაგრამ CAPTCHA არ არის მზად
currently-preparing:
- <header>
- <yellow>ანტიბოტი ჯერ არ მომზადებულა.
- <gray>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა შემოხვალთ.
- <gray>გთხოვთ დაიცადოთ რამდენიმე წამი სანამ ხელახლა შემოხვალთ.
- <footer>
# Disconnect message that is shown when someone joins with an unsupported Minecraft version
unsupported-version:
- <header>
- <red>This server does not support your version of Minecraft.
- <red>თქვენი Minecraft_ის ვერსია არ არის მხარდაჭერილი.
- <footer>
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე ხელახლა შემოდის ძალიან სწრაფად შემოწმების დროს.
too-fast-reconnect:
- <header>
- <gold>თქვენ კავშირდებით ხელახლა ძალიან სწრაფად.
- <gray>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
- <gray>გთხოვთ დაიცადოთ რამდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
- <footer>
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე ცდის შემოსვლას თუმცა იგი უკვე მოწმდება
already-verifying:
- <header>
- <red>თქვენი IP მისამართი ამჟამად უკვე მოწმდება.
- <red>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
- <red>გთხოვთ დაიცადოთ რამდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
- <footer>
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა თუმცა იგი უკვე არის შემოწმების რიგში
already-queued:
- <header>
- <red>თქვენი IP მისამართი უკვე არის რიგში შემოწმებისთვის.
- <red>გთხოვთ დაიცადოთ რამოდენიმე წუთი სანამ ხელახლა ცდით შემოსვლას.
- <red>გთხოვთ დაიცადოთ რამდენიმე წუთი სანამ ხელახლა ცდით შემოსვლას.
- <footer>
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა თუმცა დროებით დაბლოკილია
blacklisted:
- <header>
- <red>თქვენ ამჟამად დაბლოკილი ხართ სერვერიდან.
- <red>გთხოვთ დაიცადოთ რამოდენიმე წუთი რომ ისევ შეგეძლოტ სერვერზე შემოსვლა.
- <red>გთხოვთ დაიცადოთ რამდენიმე წუთი, რომ ისევ შეგეძლოთ სერვერზე შემოსვლა.
- <gold>შეცდომა? <gray><support-url>
- <footer>
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა არასწორი სახელით
@@ -253,12 +253,12 @@ verification:
success:
- <header>
- <green>თქვენ წარმატებით გაიარეთ შემოწმება.
- <white>თქვენ უკვე შეგიძლიათ შეხვიდეთ სერვერზე.
- <white>უკვე შეგიძლიათ შეხვიდეთ სერვერზე.
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე წარუმატებლად გაივლის შემოწმებას
failed:
- <header>
- <red>თქვენ ვერ გაიარეთ შემოწმება.
- <gray>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
- <gray>გთხოვთ დაიცადოთ რამდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
- <gold>გჭირდებათ დახმარება შესვლაში? <gray><support-url>
- <footer>
@@ -266,9 +266,9 @@ verification:
verbose:
layout:
# ჩვენება Sonar_ის actionbar დეტალური ინფორმაციისთვის
normal: '<prefix><gray>კთწ <white><connections-per-second> <dark_aqua>╺ <gray>შემოსვლა/წ <white><logins-per-second> <dark_aqua>╺ <gray>მოწმდება <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
normal: '<prefix><gray>კ/წ <white><connections-per-second> <dark_aqua>╺ <gray>შემოსვლა/წ <white><logins-per-second> <dark_aqua>╺ <gray>მოწმდება <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
# Layout for Sonar's actionbar verbose during an attack
attack: '<prefix><gray>კთწ <white><connections-per-second> <dark_aqua>╺ <gray>შემოსვლა/წ <white><logins-per-second> <dark_aqua>╺ <gray>რიგი <white><queued> <dark_aqua>╺ <gray>მოწმდება <white><verifying> <dark_aqua>╺ <gray>ხანგრძლივობა <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
attack: '<prefix><gray>კ/წ <white><connections-per-second> <dark_aqua>╺ <gray>შემოსვლა/წ <white><logins-per-second> <dark_aqua>╺ <gray>რიგი <white><queued> <dark_aqua>╺ <gray>მოწმდება <white><verifying> <dark_aqua>╺ <gray>ხანგრძლივობა <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
# Animation for the action bar
# Alternatives:
# - ▙, ▛, ▜, ▟
+1 -1
View File
@@ -1,3 +1,3 @@
group=xyz.jonesdev
version=2.1.37
version=2.1.42
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
+4 -4
View File
@@ -1,5 +1,5 @@
[versions]
netty = "4.2.5.Final"
netty = "4.2.7.Final"
adventure = "4.24.0"
adventure-platform = "4.4.1"
bstats = "3.1.0"
@@ -32,8 +32,8 @@ caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.2"
annotations = "org.jetbrains:annotations:26.0.2"
simpleyaml = "com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4"
ormlite = "com.j256.ormlite:ormlite-jdbc:6.1"
lombok = "org.projectlombok:lombok:1.18.40"
lombok = "org.projectlombok:lombok:1.18.42"
bungeecord = "net.md-5:bungeecord-proxy:1.21-SNAPSHOT"
velocity = "io.papermc.velocity:velocity-proxy:3.4.0-SNAPSHOT"
# We have to use 1.8.8 for backwards compatibility
spigot = "org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT"
# We have to use 1.16.5 for backwards compatibility
spigot = "org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT"