diff --git a/.github/javadoc-publish-clear b/.github/javadoc-publish-clear new file mode 100644 index 000000000..1a5d6285a --- /dev/null +++ b/.github/javadoc-publish-clear @@ -0,0 +1,3 @@ +**/* +!.git +!CNAME diff --git a/.github/workflows/check-pr-style.yml b/.github/workflows/check-pr-style.yml index 7ac69d24b..2efc6f6b2 100644 --- a/.github/workflows/check-pr-style.yml +++ b/.github/workflows/check-pr-style.yml @@ -14,9 +14,9 @@ jobs: - name: Set up JDK 11 uses: actions/setup-java@v1 with: - java-version: 1.11 + java-version: 11 - name: Run java checkstyle - uses: nikitasavinov/checkstyle-action@d87d526a914fc5cb0b003908e35038dbb2d6e1b7 + uses: nikitasavinov/checkstyle-action@0.3.1 with: # Report level for reviewdog [info,warning,error] level: info @@ -28,4 +28,4 @@ jobs: fail_on_error: false # Checkstyle config file checkstyle_config: minestom_checks.xml - checkstyle_version: "8.37" + checkstyle_version: "8.42" diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml index d3b71f032..c0b3b1bc4 100644 --- a/.github/workflows/javadoc.yml +++ b/.github/workflows/javadoc.yml @@ -14,7 +14,7 @@ jobs: - name: Set up JDK 11 uses: actions/setup-java@v1 with: - java-version: 1.11 + java-version: 11 - name: Build javadoc run: gradle javadoc @@ -25,3 +25,4 @@ jobs: BRANCH: javadoc FOLDER: docs GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CLEAR_GLOBS_FILE: ".github/javadoc-publish-clear" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c6d8bda7c..176c7e3cb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: - name: Set up JDK 11 uses: actions/setup-java@v1 with: - java-version: 1.11 + java-version: 11 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Setup gradle cache diff --git a/build.gradle b/build.gradle index d754a86b8..2b26a2602 100644 --- a/build.gradle +++ b/build.gradle @@ -2,9 +2,7 @@ import org.gradle.internal.os.OperatingSystem plugins { id 'java-library' - id 'java' id 'maven-publish' - id 'net.ltgt.apt' version '0.10' id 'org.jetbrains.kotlin.jvm' version '1.5.0' id 'checkstyle' } @@ -12,7 +10,7 @@ plugins { group 'net.minestom.server' version '1.0' -sourceCompatibility = 1.11 +sourceCompatibility = 11 project.ext.lwjglVersion = "3.2.3" switch (OperatingSystem.current()) { @@ -33,7 +31,6 @@ switch (OperatingSystem.current()) { allprojects { repositories { mavenCentral() - maven { url 'https://libraries.minecraft.net' } maven { url 'https://jitpack.io' } maven { name 'sponge' @@ -54,7 +51,7 @@ allprojects { // see https://stackoverflow.com/a/56641766 doLast { // Append the fix to the file - def searchScript = new File(destinationDir.getAbsolutePath() + '/search.js') + def searchScript = new File(destinationDir, '/search.js') searchScript.append '\n\n' + 'getURLPrefix = function(ui) {\n' + ' return \'\';\n' + @@ -63,7 +60,7 @@ allprojects { } checkstyle { - toolVersion "8.37" + toolVersion "8.42" configFile file("${projectDir}/minestom_checks.xml") } } @@ -115,6 +112,10 @@ test { useJUnitPlatform() } +tasks.withType(Zip).configureEach { + duplicatesStrategy DuplicatesStrategy.EXCLUDE +} + dependencies { // Junit Testing Framework testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' @@ -130,9 +131,6 @@ dependencies { api 'io.netty:netty-transport-native-kqueue:4.1.63.Final:osx-x86_64' api 'io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.5.Final:linux-x86_64' - // https://mvnrepository.com/artifact/org.apache.commons/commons-text - compile group: 'org.apache.commons', name: 'commons-text', version: '1.9' - // https://mvnrepository.com/artifact/it.unimi.dsi/fastutil api 'it.unimi.dsi:fastutil:8.5.4' @@ -154,9 +152,12 @@ dependencies { // https://mvnrepository.com/artifact/org.jline/jline implementation group: 'org.jline', name: 'jline', version: '3.20.0' - // Guava 21.0+ required for Mixin, but Authlib imports 17.0 + // Jline compatibility for windows + // https://search.maven.org/artifact/org.fusesource.jansi/jansi/2.3.2/jar + implementation 'org.fusesource.jansi:jansi:2.3.2' + + // Guava 21.0+ required for Mixin api 'com.google.guava:guava:30.1-jre' - api 'com.mojang:authlib:1.5.21' // Code modification api "org.ow2.asm:asm:${asmVersion}" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 28ff446a2..0f80bbf51 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/minestom_checks.xml b/minestom_checks.xml index eb513ae72..082918382 100644 --- a/minestom_checks.xml +++ b/minestom_checks.xml @@ -277,7 +277,7 @@ value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/> - + diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 1a8a71b8a..4057bace8 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -826,6 +826,6 @@ public final class MinecraftServer { } private static int getThreadCount(@NotNull String property, int count) { - return Integer.getInteger(property, Math.min(1, count)); + return Integer.getInteger(property, Math.max(1, count)); } } diff --git a/src/main/java/net/minestom/server/adventure/audience/PacketGroupingAudience.java b/src/main/java/net/minestom/server/adventure/audience/PacketGroupingAudience.java index f391c8765..2a2329374 100644 --- a/src/main/java/net/minestom/server/adventure/audience/PacketGroupingAudience.java +++ b/src/main/java/net/minestom/server/adventure/audience/PacketGroupingAudience.java @@ -12,7 +12,8 @@ import net.kyori.adventure.title.Title; import net.minestom.server.MinecraftServer; import net.minestom.server.adventure.AdventurePacketConvertor; import net.minestom.server.entity.Player; -import net.minestom.server.network.packet.server.play.ChatMessagePacket; +import net.minestom.server.message.ChatPosition; +import net.minestom.server.message.Messenger; import net.minestom.server.network.packet.server.play.PlayerListHeaderAndFooterPacket; import net.minestom.server.network.packet.server.play.TitlePacket; import net.minestom.server.utils.PacketUtils; @@ -46,7 +47,7 @@ public interface PacketGroupingAudience extends ForwardingAudience { @Override default void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) { - PacketUtils.sendGroupedPacket(this.getPlayers(), new ChatMessagePacket(message, ChatMessagePacket.Position.fromMessageType(type), source.uuid())); + Messenger.sendMessage(this.getPlayers(), message, ChatPosition.fromMessageType(type), source.uuid()); } @Override diff --git a/src/main/java/net/minestom/server/color/Color.java b/src/main/java/net/minestom/server/color/Color.java index 9fc613792..7eddf0614 100644 --- a/src/main/java/net/minestom/server/color/Color.java +++ b/src/main/java/net/minestom/server/color/Color.java @@ -2,7 +2,8 @@ package net.minestom.server.color; import net.kyori.adventure.util.RGBLike; import net.minestom.server.chat.ChatColor; -import org.apache.commons.lang3.Validate; +import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -38,17 +39,15 @@ public class Color implements RGBLike { /** * Creates a color from red, green, and blue components. * - * @param red the red component + * @param red the red component * @param green the green component - * @param blue the blue component - * + * @param blue the blue component * @throws IllegalArgumentException if any component value is not between 0-255 (inclusive) */ public Color(int red, int green, int blue) { - Validate.isTrue(red >= 0 && red <= 255, "Red is not between 0-255: ", red); - Validate.isTrue(green >= 0 && green <= 255, "Green is not between 0-255: ", green); - Validate.isTrue(blue >= 0 && blue <= 255, "Blue is not between 0-255: ", blue); - + Check.argCondition(!MathUtils.isBetween(red, 0, 255), "Red is not between 0-255: {0}", red); + Check.argCondition(!MathUtils.isBetween(green, 0, 255), "Green is not between 0-255: {0}", green); + Check.argCondition(!MathUtils.isBetween(blue, 0, 255), "Blue is not between 0-255: {0}", blue); this.red = red; this.green = green; this.blue = blue; @@ -69,7 +68,7 @@ public class Color implements RGBLike { * @param red the red component, from 0 to 255 */ public void setRed(int red) { - Validate.isTrue(red >= 0 && red <= 255, "Red is not between 0-255: ", red); + Check.argCondition(!MathUtils.isBetween(red, 0, 255), "Red is not between 0-255: {0}", red); this.red = red; } @@ -88,7 +87,7 @@ public class Color implements RGBLike { * @param green the red component, from 0 to 255 */ public void setGreen(int green) { - Validate.isTrue(green >= 0 && green <= 255, "Green is not between 0-255: ", green); + Check.argCondition(!MathUtils.isBetween(green, 0, 255), "Green is not between 0-255: {0}", green); this.green = green; } @@ -107,7 +106,7 @@ public class Color implements RGBLike { * @param blue the red component, from 0 to 255 */ public void setBlue(int blue) { - Validate.isTrue(blue >= 0 && blue <= 255, "Blue is not between 0-255: ", blue); + Check.argCondition(!MathUtils.isBetween(blue, 0, 255), "Blue is not between 0-255: {0}", blue); this.blue = blue; } @@ -132,8 +131,6 @@ public class Color implements RGBLike { * @param colors the colors */ public void mixWith(@NotNull RGBLike... colors) { - Validate.noNullElements(colors, "Colors cannot be null"); - // store the current highest component int max = Math.max(Math.max(this.red, this.green), this.blue); diff --git a/src/main/java/net/minestom/server/command/builder/Command.java b/src/main/java/net/minestom/server/command/builder/Command.java index 14b3252f8..e84cce5c3 100644 --- a/src/main/java/net/minestom/server/command/builder/Command.java +++ b/src/main/java/net/minestom/server/command/builder/Command.java @@ -7,7 +7,7 @@ import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.arguments.*; import net.minestom.server.command.builder.arguments.minecraft.SuggestionType; import net.minestom.server.command.builder.condition.CommandCondition; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; diff --git a/src/main/java/net/minestom/server/command/builder/CommandContext.java b/src/main/java/net/minestom/server/command/builder/CommandContext.java index 4eedfef6b..ddbec96b8 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandContext.java +++ b/src/main/java/net/minestom/server/command/builder/CommandContext.java @@ -1,7 +1,7 @@ package net.minestom.server.command.builder; import net.minestom.server.command.builder.arguments.Argument; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -44,10 +44,16 @@ public class CommandContext { } public T get(@NotNull String identifier) { - return (T) args.computeIfAbsent(identifier, s -> { - throw new NullPointerException( - "The argument with the id '" + identifier + "' has no value assigned, be sure to check your arguments id, your syntax, and that you do not change the argument id dynamically."); - }); + return (T) args.get(identifier); + } + + public T getOrDefault(@NotNull Argument argument, T defaultValue) { + return getOrDefault(argument.getId(), defaultValue); + } + + public T getOrDefault(@NotNull String identifier, T defaultValue) { + T value; + return (value = get(identifier)) != null ? value : defaultValue; } public boolean has(@NotNull Argument argument) { diff --git a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java index 730919479..dc930af34 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java +++ b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java @@ -10,7 +10,7 @@ import net.minestom.server.command.builder.parser.CommandParser; import net.minestom.server.command.builder.parser.CommandQueryResult; import net.minestom.server.command.builder.parser.CommandSuggestionHolder; import net.minestom.server.command.builder.parser.ValidSyntaxHolder; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/minestom/server/command/builder/CommandSyntax.java b/src/main/java/net/minestom/server/command/builder/CommandSyntax.java index 5a5a7a5cc..a076ce956 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandSyntax.java +++ b/src/main/java/net/minestom/server/command/builder/CommandSyntax.java @@ -3,7 +3,7 @@ package net.minestom.server.command.builder; import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.condition.CommandCondition; import net.minestom.server.entity.Player; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/minestom/server/command/builder/ParsedCommand.java b/src/main/java/net/minestom/server/command/builder/ParsedCommand.java index cc3059af2..496f4023b 100644 --- a/src/main/java/net/minestom/server/command/builder/ParsedCommand.java +++ b/src/main/java/net/minestom/server/command/builder/ParsedCommand.java @@ -1,5 +1,6 @@ package net.minestom.server.command.builder; +import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.condition.CommandCondition; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; @@ -54,11 +55,19 @@ public class ParsedCommand { final CommandCondition commandCondition = syntax.getCommandCondition(); if (commandCondition == null || commandCondition.canUse(source, commandString)) { context.retrieveDefaultValues(syntax.getDefaultValuesMap()); - executor.apply(source, context); + try { + executor.apply(source, context); + } catch (Exception exception) { + MinecraftServer.getExceptionManager().handleException(exception); + } } } else { // The executor is probably the default one - executor.apply(source, context); + try { + executor.apply(source, context); + } catch (Exception exception) { + MinecraftServer.getExceptionManager().handleException(exception); + } } } else if (callback != null && argumentSyntaxException != null) { // No syntax has been validated but the faulty argument with a callback has been found diff --git a/src/main/java/net/minestom/server/command/builder/arguments/Argument.java b/src/main/java/net/minestom/server/command/builder/arguments/Argument.java index 4cff3f232..2b32ffa00 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/Argument.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/Argument.java @@ -195,23 +195,48 @@ public abstract class Argument { return this; } + /** + * Sets the default value supplier of the argument. + * + * @param defaultValue the default argument value + * @return 'this' for chaining + */ @NotNull - public Argument setDefaultValue(@Nullable T defaultValue) { + public Argument setDefaultValue(@NotNull T defaultValue) { this.defaultValue = () -> defaultValue; return this; } + /** + * Gets the suggestion callback of the argument + * + * @see #setSuggestionCallback + * @return the suggestion callback of the argument, null if it doesn't exist + */ @Nullable public SuggestionCallback getSuggestionCallback() { return suggestionCallback; } + /** + * Sets the suggestion callback (for dynamic tab completion) of this argument. + *

+ * Note: This will not automatically filter arguments by user input. + * + * @param suggestionCallback The suggestion callback to set. + * @return 'this' for chaining + */ @Beta public Argument setSuggestionCallback(@NotNull SuggestionCallback suggestionCallback) { this.suggestionCallback = suggestionCallback; return this; } + /** + * Check if the argument has a suggestion. + * + * @return If this argument has a suggestion. + */ public boolean hasSuggestion() { return suggestionCallback != null; } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentCommand.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentCommand.java index d6179499a..335be5300 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentCommand.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentCommand.java @@ -7,7 +7,7 @@ import net.minestom.server.command.builder.CommandResult; import net.minestom.server.command.builder.NodeMaker; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; public class ArgumentCommand extends Argument { diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicStringArray.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicStringArray.java index 3ede1e208..c9084f849 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicStringArray.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicStringArray.java @@ -7,7 +7,7 @@ import net.minestom.server.command.builder.suggestion.SuggestionCallback; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.callback.validator.StringArrayValidator; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java index d72e1c6ca..2143e83e5 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java @@ -6,9 +6,9 @@ import net.minestom.server.command.builder.arguments.minecraft.SuggestionType; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.command.builder.suggestion.SuggestionCallback; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; +import net.minestom.server.utils.StringUtils; import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.callback.validator.StringValidator; -import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentGroup.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentGroup.java index 513839b60..9d4c8f659 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentGroup.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentGroup.java @@ -5,7 +5,7 @@ import net.minestom.server.command.builder.NodeMaker; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.command.builder.parser.CommandParser; import net.minestom.server.command.builder.parser.ValidSyntaxHolder; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentLoop.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentLoop.java index 0bbadee1c..5387bae0f 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentLoop.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentLoop.java @@ -3,7 +3,7 @@ package net.minestom.server.command.builder.arguments; import net.minestom.server.command.builder.NodeMaker; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentString.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentString.java index 0bca51aa8..f5b9dcd0d 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentString.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentString.java @@ -4,8 +4,8 @@ import io.netty.util.internal.StringUtil; import net.minestom.server.command.builder.NodeMaker; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; +import net.minestom.server.utils.StringUtils; import net.minestom.server.utils.binary.BinaryWriter; -import org.apache.commons.text.StringEscapeUtils; import org.jetbrains.annotations.NotNull; /** @@ -75,7 +75,7 @@ public class ArgumentString extends Argument { } } - return StringEscapeUtils.unescapeJava(input); + return StringUtils.unescapeJavaString(input); } @Override diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentStringArray.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentStringArray.java index e56781dbc..e6429a4c1 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentStringArray.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentStringArray.java @@ -2,8 +2,8 @@ package net.minestom.server.command.builder.arguments; import net.minestom.server.command.builder.NodeMaker; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; +import net.minestom.server.utils.StringUtils; import net.minestom.server.utils.binary.BinaryWriter; -import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import java.util.regex.Pattern; diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentWord.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentWord.java index 5f2deb5b3..91da3861e 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentWord.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentWord.java @@ -5,7 +5,7 @@ import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.validate.Check; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentEntity.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentEntity.java index 11e51b08b..84cd7d2a2 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentEntity.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentEntity.java @@ -10,7 +10,7 @@ import net.minestom.server.registry.Registries; import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.entity.EntityFinder; import net.minestom.server.utils.math.IntRange; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import java.util.Arrays; diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentResourceLocation.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentResourceLocation.java index 44d41ecc7..666e467e9 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentResourceLocation.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentResourceLocation.java @@ -4,7 +4,7 @@ import net.minestom.server.command.builder.NodeMaker; import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; public class ArgumentResourceLocation extends Argument { diff --git a/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeBlockPosition.java b/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeBlockPosition.java index bb0a7f4d1..eeda34d76 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeBlockPosition.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeBlockPosition.java @@ -5,7 +5,7 @@ import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.location.RelativeBlockPosition; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; /** diff --git a/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeVec2.java b/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeVec2.java index 9aa894b37..904179195 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeVec2.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeVec2.java @@ -5,7 +5,7 @@ import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; import net.minestom.server.utils.Vector; import net.minestom.server.utils.location.RelativeVec; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; /** diff --git a/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeVec3.java b/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeVec3.java index 6f632b6a7..6e6f8a4fe 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeVec3.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/relative/ArgumentRelativeVec3.java @@ -5,7 +5,7 @@ import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; import net.minestom.server.utils.Vector; import net.minestom.server.utils.location.RelativeVec; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; /** diff --git a/src/main/java/net/minestom/server/command/builder/parser/ArgumentParser.java b/src/main/java/net/minestom/server/command/builder/parser/ArgumentParser.java index 2aceda1de..0c51c8aa5 100644 --- a/src/main/java/net/minestom/server/command/builder/parser/ArgumentParser.java +++ b/src/main/java/net/minestom/server/command/builder/parser/ArgumentParser.java @@ -11,7 +11,7 @@ import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeBl import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec2; import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec3; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/minestom/server/command/builder/parser/CommandParser.java b/src/main/java/net/minestom/server/command/builder/parser/CommandParser.java index 98316d5e9..f0e1333cb 100644 --- a/src/main/java/net/minestom/server/command/builder/parser/CommandParser.java +++ b/src/main/java/net/minestom/server/command/builder/parser/CommandParser.java @@ -7,7 +7,7 @@ import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.CommandContext; import net.minestom.server.command.builder.CommandSyntax; import net.minestom.server.command.builder.arguments.Argument; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 87b539f61..d30c80fc0 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -268,7 +268,7 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, final ChunkCallback endCallback = (chunk) -> { refreshPosition(teleportPosition); - synchronizePosition(); + synchronizePosition(true); OptionalCallback.execute(callback); }; @@ -552,7 +552,10 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, // Apply the position if changed if (!finalVelocityPosition.isSimilar(position)) { - refreshPosition(finalVelocityPosition); + refreshPosition(finalVelocityPosition.getX(), + finalVelocityPosition.getY(), + finalVelocityPosition.getZ()); + sendPositionUpdate(true); } @@ -594,7 +597,7 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, // Synchronization and packets... if (!isNettyClient) { - synchronizePosition(); + synchronizePosition(true); } // Verify if velocity packet has to be sent if (hasVelocity() || (!isNettyClient && gravityTickCount > 0)) { @@ -665,7 +668,7 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, // Scheduled synchronization if (!Cooldown.hasCooldown(time, lastAbsoluteSynchronizationTime, getSynchronizationCooldown())) { - synchronizePosition(); + synchronizePosition(false); } if (shouldRemove() && !MinecraftServer.isStopping()) { @@ -677,28 +680,28 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, * Sends the correct packets to update the entity's position, should be called * every tick. The movement is checked inside the method! *

- * The following packets are sent to viewers (check are performed in this order): - *

    - *
  1. {@link EntityTeleportPacket} if {@code distanceX > 8 || distanceY > 8 || distanceZ > 8} - * (performed using {@link #synchronizePosition()})
  2. - *
  3. {@link EntityPositionAndRotationPacket} if {@code positionChange && viewChange}
  4. - *
  5. {@link EntityPositionPacket} if {@code positionChange}
  6. - *
  7. {@link EntityRotationPacket} and {@link EntityHeadLookPacket} if {@code viewChange}
  8. - *
- * In case of a player's position and/or view change an additional {@link PlayerPositionAndLookPacket} - * is sent to self. + * The following packets are sent to viewers (check are performed in this order): + *
    + *
  1. {@link EntityTeleportPacket} if {@code distanceX > 8 || distanceY > 8 || distanceZ > 8} + * (performed using {@link #synchronizePosition(boolean)})
  2. + *
  3. {@link EntityPositionAndRotationPacket} if {@code positionChange && viewChange}
  4. + *
  5. {@link EntityPositionPacket} if {@code positionChange}
  6. + *
  7. {@link EntityRotationPacket} and {@link EntityHeadLookPacket} if {@code viewChange}
  8. + *
+ * In case of a player's position and/or view change an additional {@link PlayerPositionAndLookPacket} + * is sent to self. * * @param clientSide {@code true} if the client triggered this action */ protected void sendPositionUpdate(final boolean clientSide) { final boolean viewChange = !position.hasSimilarView(lastSyncedPosition); - final double distanceX = Math.abs(position.getX()-lastSyncedPosition.getX()); - final double distanceY = Math.abs(position.getY()-lastSyncedPosition.getY()); - final double distanceZ = Math.abs(position.getZ()-lastSyncedPosition.getZ()); - final boolean positionChange = (distanceX+distanceY+distanceZ) > 0; + final double distanceX = Math.abs(position.getX() - lastSyncedPosition.getX()); + final double distanceY = Math.abs(position.getY() - lastSyncedPosition.getY()); + final double distanceZ = Math.abs(position.getZ() - lastSyncedPosition.getZ()); + final boolean positionChange = (distanceX + distanceY + distanceZ) > 0; if (distanceX > 8 || distanceY > 8 || distanceZ > 8) { - synchronizePosition(); + synchronizePosition(true); // #synchronizePosition sets sync fields, it's safe to return return; } else if (positionChange && viewChange) { @@ -742,7 +745,7 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, final PlayerPositionAndLookPacket playerPositionAndLookPacket = new PlayerPositionAndLookPacket(); playerPositionAndLookPacket.flags = 0b111; playerPositionAndLookPacket.position = position.clone().subtract(lastSyncedPosition.getX(), lastSyncedPosition.getY(), lastSyncedPosition.getZ()); - playerPositionAndLookPacket.teleportId = ((Player)this).getNextTeleportId(); + playerPositionAndLookPacket.teleportId = ((Player) this).getNextTeleportId(); ((Player) this).getPlayerConnection().sendPacket(playerPositionAndLookPacket); } @@ -1393,8 +1396,7 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, /** * Updates internal fields and sends updates * - * @param position the new position - * + * @param position the new position * @see #refreshPosition(double, double, double) * @see #refreshView(float, float) * @see #sendPositionUpdate(boolean) @@ -1583,9 +1585,12 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, * {@link EntityTeleportPacket} to viewers, in case of a player this is * overridden in order to send an additional {@link PlayerPositionAndLookPacket} * to itself. + * + * @param includeSelf if {@code true} and this is a {@link Player} an additional {@link PlayerPositionAndLookPacket} + * will be sent to the player itself */ @ApiStatus.Internal - protected void synchronizePosition() { + protected void synchronizePosition(boolean includeSelf) { final Position pos = position.clone(); final EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket(); entityTeleportPacket.entityId = getEntityId(); diff --git a/src/main/java/net/minestom/server/entity/EquipmentSlot.java b/src/main/java/net/minestom/server/entity/EquipmentSlot.java new file mode 100644 index 000000000..27a7cf02d --- /dev/null +++ b/src/main/java/net/minestom/server/entity/EquipmentSlot.java @@ -0,0 +1,42 @@ +package net.minestom.server.entity; + +import net.minestom.server.event.item.EntityEquipEvent; +import net.minestom.server.item.attribute.AttributeSlot; +import org.jetbrains.annotations.NotNull; + +public enum EquipmentSlot { + MAIN_HAND, + OFF_HAND, + BOOTS, + LEGGINGS, + CHESTPLATE, + HELMET; + + public boolean isHand() { + return this == MAIN_HAND || this == OFF_HAND; + } + + public boolean isArmor() { + return !isHand(); + } + + @NotNull + public static EquipmentSlot fromAttributeSlot(AttributeSlot attributeSlot) { + switch (attributeSlot) { + case MAINHAND: + return MAIN_HAND; + case OFFHAND: + return OFF_HAND; + case FEET: + return BOOTS; + case LEGS: + return LEGGINGS; + case CHEST: + return CHESTPLATE; + case HEAD: + return HELMET; + } + throw new IllegalStateException("Something weird happened"); + } + +} diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 1c9a27c7b..3267e0337 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -10,7 +10,7 @@ import net.minestom.server.entity.metadata.LivingEntityMeta; import net.minestom.server.event.entity.EntityDamageEvent; import net.minestom.server.event.entity.EntityDeathEvent; import net.minestom.server.event.entity.EntityFireEvent; -import net.minestom.server.event.item.ArmorEquipEvent; +import net.minestom.server.event.item.EntityEquipEvent; import net.minestom.server.event.item.PickupItemEvent; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.block.Block; @@ -127,8 +127,8 @@ public class LivingEntity extends Entity implements EquipmentHandler { @Override public void setItemInMainHand(@NotNull ItemStack itemStack) { - this.mainHandItem = itemStack; - syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND); + this.mainHandItem = getEquipmentItem(itemStack, EquipmentSlot.MAIN_HAND); + syncEquipment(EquipmentSlot.MAIN_HAND); } @NotNull @@ -139,8 +139,8 @@ public class LivingEntity extends Entity implements EquipmentHandler { @Override public void setItemInOffHand(@NotNull ItemStack itemStack) { - this.offHandItem = itemStack; - syncEquipment(EntityEquipmentPacket.Slot.OFF_HAND); + this.offHandItem = getEquipmentItem(itemStack, EquipmentSlot.OFF_HAND); + syncEquipment(EquipmentSlot.OFF_HAND); } @NotNull @@ -151,8 +151,8 @@ public class LivingEntity extends Entity implements EquipmentHandler { @Override public void setHelmet(@NotNull ItemStack itemStack) { - this.helmet = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.HELMET); - syncEquipment(EntityEquipmentPacket.Slot.HELMET); + this.helmet = getEquipmentItem(itemStack, EquipmentSlot.HELMET); + syncEquipment(EquipmentSlot.HELMET); } @NotNull @@ -163,8 +163,8 @@ public class LivingEntity extends Entity implements EquipmentHandler { @Override public void setChestplate(@NotNull ItemStack itemStack) { - this.chestplate = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE); - syncEquipment(EntityEquipmentPacket.Slot.CHESTPLATE); + this.chestplate = getEquipmentItem(itemStack, EquipmentSlot.CHESTPLATE); + syncEquipment(EquipmentSlot.CHESTPLATE); } @NotNull @@ -175,8 +175,8 @@ public class LivingEntity extends Entity implements EquipmentHandler { @Override public void setLeggings(@NotNull ItemStack itemStack) { - this.leggings = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS); - syncEquipment(EntityEquipmentPacket.Slot.LEGGINGS); + this.leggings = getEquipmentItem(itemStack, EquipmentSlot.LEGGINGS); + syncEquipment(EquipmentSlot.LEGGINGS); } @NotNull @@ -187,14 +187,14 @@ public class LivingEntity extends Entity implements EquipmentHandler { @Override public void setBoots(@NotNull ItemStack itemStack) { - this.boots = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.BOOTS); - syncEquipment(EntityEquipmentPacket.Slot.BOOTS); + this.boots = getEquipmentItem(itemStack, EquipmentSlot.BOOTS); + syncEquipment(EquipmentSlot.BOOTS); } - private ItemStack getEquipmentItem(@NotNull ItemStack itemStack, @NotNull ArmorEquipEvent.ArmorSlot armorSlot) { - ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(this, itemStack, armorSlot); - callEvent(ArmorEquipEvent.class, armorEquipEvent); - return armorEquipEvent.getArmorItem(); + private ItemStack getEquipmentItem(@NotNull ItemStack itemStack, @NotNull EquipmentSlot slot) { + EntityEquipEvent entityEquipEvent = new EntityEquipEvent(this, itemStack, slot); + callEvent(EntityEquipEvent.class, entityEquipEvent); + return entityEquipEvent.getEquippedItem(); } @Override @@ -595,6 +595,14 @@ public class LivingEntity extends Entity implements EquipmentHandler { } } + public boolean isFlyingWithElytra() { + return this.entityMeta.isFlyingWithElytra(); + } + + public void setFlyingWithElytra(boolean isFlying) { + this.entityMeta.setFlyingWithElytra(isFlying); + } + /** * Used to change the {@code isDead} internal field. * diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 8a7ecad0c..49bd4ec3f 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -38,12 +38,15 @@ import net.minestom.server.event.player.*; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.CustomBlock; +import net.minestom.server.message.ChatMessageType; +import net.minestom.server.message.ChatPosition; import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.item.metadata.WrittenBookMeta; import net.minestom.server.listener.PlayerDiggingListener; +import net.minestom.server.message.Messenger; import net.minestom.server.network.ConnectionManager; import net.minestom.server.network.ConnectionState; import net.minestom.server.network.PlayerProvider; @@ -661,7 +664,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, } if (dimensionChange || firstSpawn) { - synchronizePosition(); // So the player doesn't get stuck + synchronizePosition(true); // So the player doesn't get stuck this.inventory.update(); } @@ -730,8 +733,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, @Override public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) { - ChatMessagePacket chatMessagePacket = new ChatMessagePacket(message, ChatMessagePacket.Position.fromMessageType(type), source.uuid()); - playerConnection.sendPacket(chatMessagePacket); + Messenger.sendMessage(this, message, ChatPosition.fromMessageType(type), source.uuid()); } /** @@ -1984,18 +1986,20 @@ public class Player extends LivingEntity implements CommandSender, Localizable, } /** - * @see Entity#synchronizePosition() + * @see Entity#synchronizePosition(boolean) */ @Override @ApiStatus.Internal - protected void synchronizePosition() { - final PlayerPositionAndLookPacket positionAndLookPacket = new PlayerPositionAndLookPacket(); - positionAndLookPacket.position = position.clone(); - positionAndLookPacket.flags = 0x00; - positionAndLookPacket.teleportId = teleportId.incrementAndGet(); - playerConnection.sendPacket(positionAndLookPacket); + protected void synchronizePosition(boolean includeSelf) { + if (includeSelf) { + final PlayerPositionAndLookPacket positionAndLookPacket = new PlayerPositionAndLookPacket(); + positionAndLookPacket.position = position.clone(); + positionAndLookPacket.flags = 0x00; + positionAndLookPacket.teleportId = teleportId.incrementAndGet(); + playerConnection.sendPacket(positionAndLookPacket); + } - super.synchronizePosition(); + super.synchronizePosition(includeSelf); } /** @@ -2223,6 +2227,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable, public void refreshOnGround(boolean onGround) { this.onGround = onGround; + if(this.onGround && this.isFlyingWithElytra()) { + this.setFlyingWithElytra(false); + this.callEvent(PlayerStopFlyingWithElytraEvent.class, new PlayerStopFlyingWithElytraEvent(this)); + } } /** @@ -2256,7 +2264,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, */ public void refreshHeldSlot(byte slot) { this.heldSlot = slot; - syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND); + syncEquipment(EquipmentSlot.MAIN_HAND); refreshEating(null); } @@ -2578,6 +2586,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable, RIGHT } + /** + * @deprecated See {@link ChatMessageType} + */ + @Deprecated public enum ChatMode { ENABLED, COMMANDS_ONLY, @@ -2588,7 +2600,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, private String locale; private byte viewDistance; - private ChatMode chatMode; + private ChatMessageType chatMessageType; private boolean chatColors; private byte displayedSkinParts; private MainHand mainHand; @@ -2619,9 +2631,20 @@ public class Player extends LivingEntity implements CommandSender, Localizable, * Gets the player chat mode. * * @return the player chat mode + * @deprecated Use {@link #getChatMessageType()} */ + @Deprecated public ChatMode getChatMode() { - return chatMode; + return ChatMode.values()[chatMessageType.ordinal()]; + } + + /** + * Gets the messages this player wants to receive. + * + * @return the messages + */ + public @Nullable ChatMessageType getChatMessageType() { + return chatMessageType; } /** @@ -2653,19 +2676,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable, * * @param locale the player locale * @param viewDistance the player view distance - * @param chatMode the player chat mode - * @param chatColors the player chat colors + * @param chatMessageType the chat messages the player wishes to receive + * @param chatColors if chat colors should be displayed * @param displayedSkinParts the player displayed skin parts * @param mainHand the player main hand */ - public void refresh(String locale, byte viewDistance, ChatMode chatMode, boolean chatColors, + public void refresh(String locale, byte viewDistance, ChatMessageType chatMessageType, boolean chatColors, byte displayedSkinParts, MainHand mainHand) { final boolean viewDistanceChanged = this.viewDistance != viewDistance; this.locale = locale; this.viewDistance = viewDistance; - this.chatMode = chatMode; + this.chatMessageType = chatMessageType; this.chatColors = chatColors; this.displayedSkinParts = displayedSkinParts; this.mainHand = mainHand; diff --git a/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java b/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java index f1b174834..52acf40c6 100644 --- a/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java +++ b/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java @@ -124,17 +124,22 @@ public class FakePlayer extends Player implements NavigableEntity { super.setInstance(instance); } + @Override + protected boolean addViewer0(@NotNull Player player) { + final boolean result = super.addViewer0(player); + if (result) { + handleTabList(player.getPlayerConnection()); + } + return result; + } + /** * {@inheritDoc} */ @Override protected void showPlayer(@NotNull PlayerConnection connection) { super.showPlayer(connection); - if (!option.isInTabList()) { - // Remove from tab-list - MinecraftServer.getSchedulerManager().buildTask(() -> connection.sendPacket(getRemovePlayerToList())).delay(20, TimeUnit.TICK).schedule(); - } - + handleTabList(connection); } @NotNull @@ -142,4 +147,11 @@ public class FakePlayer extends Player implements NavigableEntity { public Navigator getNavigator() { return navigator; } + + private void handleTabList(PlayerConnection connection) { + if (!option.isInTabList()) { + // Remove from tab-list + MinecraftServer.getSchedulerManager().buildTask(() -> connection.sendPacket(getRemovePlayerToList())).delay(20, TimeUnit.TICK).schedule(); + } + } } diff --git a/src/main/java/net/minestom/server/entity/metadata/animal/FoxMeta.java b/src/main/java/net/minestom/server/entity/metadata/animal/FoxMeta.java index cc45e76e4..e91063e3d 100644 --- a/src/main/java/net/minestom/server/entity/metadata/animal/FoxMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/animal/FoxMeta.java @@ -3,8 +3,8 @@ package net.minestom.server.entity.metadata.animal; import net.minestom.server.entity.Entity; import net.minestom.server.entity.Metadata; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.UUID; public class FoxMeta extends AnimalMeta { diff --git a/src/main/java/net/minestom/server/entity/type/decoration/EntityArmorStand.java b/src/main/java/net/minestom/server/entity/type/decoration/EntityArmorStand.java index 89a600d02..121146e7b 100644 --- a/src/main/java/net/minestom/server/entity/type/decoration/EntityArmorStand.java +++ b/src/main/java/net/minestom/server/entity/type/decoration/EntityArmorStand.java @@ -1,13 +1,9 @@ package net.minestom.server.entity.type.decoration; -import net.minestom.server.entity.EntityType; -import net.minestom.server.entity.Metadata; -import net.minestom.server.entity.ObjectEntity; -import net.minestom.server.entity.Player; -import net.minestom.server.event.item.ArmorEquipEvent; +import net.minestom.server.entity.*; +import net.minestom.server.event.item.EntityEquipEvent; import net.minestom.server.inventory.EquipmentHandler; import net.minestom.server.item.ItemStack; -import net.minestom.server.network.packet.server.play.EntityEquipmentPacket; import net.minestom.server.utils.Position; import net.minestom.server.utils.Vector; import net.minestom.server.utils.binary.BitmaskUtil; @@ -73,8 +69,8 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { @Override public void setItemInMainHand(@NotNull ItemStack itemStack) { - this.mainHandItem = itemStack; - syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND); + this.mainHandItem = getEquipmentItem(itemStack, EquipmentSlot.MAIN_HAND); + syncEquipment(EquipmentSlot.MAIN_HAND); } @NotNull @@ -85,8 +81,8 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { @Override public void setItemInOffHand(@NotNull ItemStack itemStack) { - this.offHandItem = itemStack; - syncEquipment(EntityEquipmentPacket.Slot.OFF_HAND); + this.offHandItem = getEquipmentItem(itemStack, EquipmentSlot.OFF_HAND); + syncEquipment(EquipmentSlot.OFF_HAND); } @NotNull @@ -97,8 +93,8 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { @Override public void setHelmet(@NotNull ItemStack itemStack) { - this.helmet = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.HELMET); - syncEquipment(EntityEquipmentPacket.Slot.HELMET); + this.helmet = getEquipmentItem(itemStack, EquipmentSlot.HELMET); + syncEquipment(EquipmentSlot.HELMET); } @NotNull @@ -109,8 +105,8 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { @Override public void setChestplate(@NotNull ItemStack itemStack) { - this.chestplate = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE); - syncEquipment(EntityEquipmentPacket.Slot.CHESTPLATE); + this.chestplate = getEquipmentItem(itemStack, EquipmentSlot.CHESTPLATE); + syncEquipment(EquipmentSlot.CHESTPLATE); } @NotNull @@ -121,8 +117,8 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { @Override public void setLeggings(@NotNull ItemStack itemStack) { - this.leggings = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS); - syncEquipment(EntityEquipmentPacket.Slot.LEGGINGS); + this.leggings = getEquipmentItem(itemStack, EquipmentSlot.LEGGINGS); + syncEquipment(EquipmentSlot.LEGGINGS); } @NotNull @@ -133,8 +129,8 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { @Override public void setBoots(@NotNull ItemStack itemStack) { - this.boots = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.BOOTS); - syncEquipment(EntityEquipmentPacket.Slot.BOOTS); + this.boots = getEquipmentItem(itemStack, EquipmentSlot.BOOTS); + syncEquipment(EquipmentSlot.BOOTS); } public boolean isSmall() { @@ -239,9 +235,9 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { // Equipments - private ItemStack getEquipmentItem(@NotNull ItemStack itemStack, @NotNull ArmorEquipEvent.ArmorSlot armorSlot) { - ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(this, itemStack, armorSlot); - callEvent(ArmorEquipEvent.class, armorEquipEvent); - return armorEquipEvent.getArmorItem(); + private ItemStack getEquipmentItem(@NotNull ItemStack itemStack, @NotNull EquipmentSlot slot) { + EntityEquipEvent entityEquipEvent = new EntityEquipEvent(this, itemStack, slot); + callEvent(EntityEquipEvent.class, entityEquipEvent); + return entityEquipEvent.getEquippedItem(); } } diff --git a/src/main/java/net/minestom/server/event/handler/EventHandler.java b/src/main/java/net/minestom/server/event/handler/EventHandler.java index 2b8a1b4ad..36d19851d 100644 --- a/src/main/java/net/minestom/server/event/handler/EventHandler.java +++ b/src/main/java/net/minestom/server/event/handler/EventHandler.java @@ -108,22 +108,27 @@ public interface EventHandler extends IExtensionObserver { */ default void callEvent(@NotNull Class eventClass, @NotNull E event) { - // Global listeners - if (!(this instanceof GlobalEventHandler)) { - final GlobalEventHandler globalEventHandler = MinecraftServer.getGlobalEventHandler(); - runEvent(globalEventHandler.getEventCallbacks(eventClass), event); - } + try { - // Local listeners - final Collection eventCallbacks = getEventCallbacks(eventClass); - runEvent(eventCallbacks, event); - - // Call the same event for the current entity instance - if (this instanceof Entity) { - final Instance instance = ((Entity) this).getInstance(); - if (instance != null) { - runEvent(instance.getEventCallbacks(eventClass), event); + // Global listeners + if (!(this instanceof GlobalEventHandler)) { + final GlobalEventHandler globalEventHandler = MinecraftServer.getGlobalEventHandler(); + runEvent(globalEventHandler.getEventCallbacks(eventClass), event); } + + // Local listeners + final Collection eventCallbacks = getEventCallbacks(eventClass); + runEvent(eventCallbacks, event); + + // Call the same event for the current entity instance + if (this instanceof Entity) { + final Instance instance = ((Entity) this).getInstance(); + if (instance != null) { + runEvent(instance.getEventCallbacks(eventClass), event); + } + } + } catch (Exception exception) { + MinecraftServer.getExceptionManager().handleException(exception); } } diff --git a/src/main/java/net/minestom/server/event/item/ArmorEquipEvent.java b/src/main/java/net/minestom/server/event/item/ArmorEquipEvent.java deleted file mode 100644 index f54ba4d32..000000000 --- a/src/main/java/net/minestom/server/event/item/ArmorEquipEvent.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.minestom.server.event.item; - -import net.minestom.server.entity.Entity; -import net.minestom.server.event.Event; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -public class ArmorEquipEvent extends Event { - - private final Entity entity; - private ItemStack armorItem; - private final ArmorSlot armorSlot; - - public ArmorEquipEvent(@NotNull Entity entity, @NotNull ItemStack armorItem, @NotNull ArmorSlot armorSlot) { - this.entity = entity; - this.armorItem = armorItem; - this.armorSlot = armorSlot; - } - - @NotNull - public Entity getEntity() { - return entity; - } - - @NotNull - public ItemStack getArmorItem() { - return armorItem; - } - - public void setArmorItem(@NotNull ItemStack armorItem) { - this.armorItem = armorItem; - } - - @NotNull - public ArmorSlot getArmorSlot() { - return armorSlot; - } - - public enum ArmorSlot { - HELMET, - CHESTPLATE, - LEGGINGS, - BOOTS - } -} diff --git a/src/main/java/net/minestom/server/event/item/EntityEquipEvent.java b/src/main/java/net/minestom/server/event/item/EntityEquipEvent.java new file mode 100644 index 000000000..501273eaf --- /dev/null +++ b/src/main/java/net/minestom/server/event/item/EntityEquipEvent.java @@ -0,0 +1,39 @@ +package net.minestom.server.event.item; + +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EquipmentSlot; +import net.minestom.server.event.Event; +import net.minestom.server.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class EntityEquipEvent extends Event { + + private final Entity entity; + private ItemStack equippedItem; + private final EquipmentSlot slot; + + public EntityEquipEvent(@NotNull Entity entity, @NotNull ItemStack equippedItem, @NotNull EquipmentSlot slot) { + this.entity = entity; + this.equippedItem = equippedItem; + this.slot = slot; + } + + @NotNull + public Entity getEntity() { + return entity; + } + + @NotNull + public ItemStack getEquippedItem() { + return equippedItem; + } + + public void setEquippedItem(@NotNull ItemStack armorItem) { + this.equippedItem = armorItem; + } + + @NotNull + public EquipmentSlot getSlot() { + return slot; + } +} diff --git a/src/main/java/net/minestom/server/event/player/PlayerStartFlyingWithElytraEvent.java b/src/main/java/net/minestom/server/event/player/PlayerStartFlyingWithElytraEvent.java new file mode 100644 index 000000000..28ca41575 --- /dev/null +++ b/src/main/java/net/minestom/server/event/player/PlayerStartFlyingWithElytraEvent.java @@ -0,0 +1,12 @@ +package net.minestom.server.event.player; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +public class PlayerStartFlyingWithElytraEvent extends PlayerEvent { + + public PlayerStartFlyingWithElytraEvent(@NotNull Player player) { + super(player); + } +} diff --git a/src/main/java/net/minestom/server/event/player/PlayerStopFlyingWithElytraEvent.java b/src/main/java/net/minestom/server/event/player/PlayerStopFlyingWithElytraEvent.java new file mode 100644 index 000000000..314f16e22 --- /dev/null +++ b/src/main/java/net/minestom/server/event/player/PlayerStopFlyingWithElytraEvent.java @@ -0,0 +1,12 @@ +package net.minestom.server.event.player; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +public class PlayerStopFlyingWithElytraEvent extends PlayerEvent { + + public PlayerStopFlyingWithElytraEvent(@NotNull Player player) { + super(player); + } +} diff --git a/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java b/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java index 6318e3de7..04f779880 100644 --- a/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java +++ b/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java @@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.net.URL; +import java.nio.file.Path; import java.util.LinkedList; import java.util.List; @@ -75,6 +76,8 @@ public final class DiscoveredExtension { /** The original jar this is from. */ transient private File originalJar; + transient private Path dataDirectory; + /** The class loader that powers it. */ transient private MinestomExtensionClassLoader minestomExtensionClassLoader; @@ -130,6 +133,14 @@ public final class DiscoveredExtension { return originalJar; } + public @NotNull Path getDataDirectory() { + return dataDirectory; + } + + public void setDataDirectory(@NotNull Path dataDirectory) { + this.dataDirectory = dataDirectory; + } + MinestomExtensionClassLoader removeMinestomExtensionClassLoader() { MinestomExtensionClassLoader oldClassLoader = getMinestomExtensionClassLoader(); setMinestomExtensionClassLoader(null); diff --git a/src/main/java/net/minestom/server/extensions/Extension.java b/src/main/java/net/minestom/server/extensions/Extension.java index cd9b52028..1272751cc 100644 --- a/src/main/java/net/minestom/server/extensions/Extension.java +++ b/src/main/java/net/minestom/server/extensions/Extension.java @@ -1,11 +1,19 @@ package net.minestom.server.extensions; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; +import java.io.IOException; +import java.io.InputStream; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @@ -77,6 +85,118 @@ public abstract class Extension { return logger; } + public @NotNull Path getDataDirectory() { + return getOrigin().getDataDirectory(); + } + + /** + * Gets a resource from the extension directory, or from inside the jar if it does not + * exist in the extension directory. + *

+ * If it does not exist in the extension directory, it will be copied from inside the jar. + *

+ * The caller is responsible for closing the returned {@link InputStream}. + * + * @param fileName The file to read + * @return The file contents, or null if there was an issue reading the file. + */ + public @Nullable InputStream getResource(@NotNull String fileName) { + return getResource(Paths.get(fileName)); + } + + /** + * Gets a resource from the extension directory, or from inside the jar if it does not + * exist in the extension directory. + *

+ * If it does not exist in the extension directory, it will be copied from inside the jar. + *

+ * The caller is responsible for closing the returned {@link InputStream}. + * + * @param target The file to read + * @return The file contents, or null if there was an issue reading the file. + */ + public @Nullable InputStream getResource(@NotNull Path target) { + final Path targetFile = getDataDirectory().resolve(target); + try { + // Copy from jar if the file does not exist in the extension data directory. + if (!Files.exists(targetFile)) { + savePackagedResource(target); + } + + return Files.newInputStream(targetFile); + } catch (IOException ex) { + getLogger().info("Failed to read resource {}.", target, ex); + return null; + } + } + + /** + * Gets a resource from inside the extension jar. + *

+ * The caller is responsible for closing the returned {@link InputStream}. + * + * @param fileName The file to read + * @return The file contents, or null if there was an issue reading the file. + */ + public @Nullable InputStream getPackagedResource(@NotNull String fileName) { + return getPackagedResource(Paths.get(fileName)); + } + + /** + * Gets a resource from inside the extension jar. + *

+ * The caller is responsible for closing the returned {@link InputStream}. + * + * @param target The file to read + * @return The file contents, or null if there was an issue reading the file. + */ + public @Nullable InputStream getPackagedResource(@NotNull Path target) { + try { + final URL url = getOrigin().getMinestomExtensionClassLoader().getResource(target.toString()); + if (url == null) { + getLogger().debug("Resource not found: {}", target); + return null; + } + + return url.openConnection().getInputStream(); + } catch (IOException ex) { + getLogger().debug("Failed to load resource {}.", target, ex); + return null; + } + } + + /** + * Copies a resource file to the extension directory, replacing any existing copy. + * + * @param fileName The resource to save + * @return True if the resource was saved successfully, null otherwise + */ + public boolean savePackagedResource(@NotNull String fileName) { + return savePackagedResource(Paths.get(fileName)); + } + + /** + * Copies a resource file to the extension directory, replacing any existing copy. + * + * @param target The resource to save + * @return True if the resource was saved successfully, null otherwise + */ + public boolean savePackagedResource(@NotNull Path target) { + final Path targetFile = getDataDirectory().resolve(target); + try (InputStream is = getPackagedResource(target)) { + if (is == null) { + return false; + } + + Files.createDirectories(targetFile.getParent()); + Files.copy(is, targetFile, StandardCopyOption.REPLACE_EXISTING); + return true; + } catch (IOException ex) { + getLogger().debug("Failed to save resource {}.", target, ex); + return false; + } + } + /** * Adds a new observer to this extension. * Will be kept as a WeakReference. diff --git a/src/main/java/net/minestom/server/extensions/ExtensionManager.java b/src/main/java/net/minestom/server/extensions/ExtensionManager.java index 2de27ece5..95ca6452e 100644 --- a/src/main/java/net/minestom/server/extensions/ExtensionManager.java +++ b/src/main/java/net/minestom/server/extensions/ExtensionManager.java @@ -24,6 +24,8 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; import java.util.zip.ZipFile; @@ -44,6 +46,7 @@ public class ExtensionManager { private final File extensionFolder = new File("extensions"); private final File dependenciesFolder = new File(extensionFolder, ".libs"); + private Path extensionDataRoot = extensionFolder.toPath(); private boolean loaded; // Option @@ -336,6 +339,7 @@ public class ExtensionManager { DiscoveredExtension extension = GSON.fromJson(reader, DiscoveredExtension.class); extension.files.add(new File(extensionClasses).toURI().toURL()); extension.files.add(new File(extensionResources).toURI().toURL()); + extension.setDataDirectory(getExtensionDataRoot().resolve(extension.getName())); // Verify integrity and ensure defaults DiscoveredExtension.verifyIntegrity(extension); @@ -365,6 +369,7 @@ public class ExtensionManager { DiscoveredExtension extension = GSON.fromJson(reader, DiscoveredExtension.class); extension.setOriginalJar(file); extension.files.add(file.toURI().toURL()); + extension.setDataDirectory(getExtensionDataRoot().resolve(extension.getName())); // Verify integrity and ensure defaults DiscoveredExtension.verifyIntegrity(extension); @@ -569,6 +574,14 @@ public class ExtensionManager { return extensionFolder; } + public @NotNull Path getExtensionDataRoot() { + return extensionDataRoot; + } + + public void setExtensionDataRoot(@NotNull Path dataRoot) { + this.extensionDataRoot = dataRoot; + } + @NotNull public Collection getExtensions() { return immutableExtensions.values(); diff --git a/src/main/java/net/minestom/server/extras/MojangAuth.java b/src/main/java/net/minestom/server/extras/MojangAuth.java index 02831864c..d322ef4fe 100644 --- a/src/main/java/net/minestom/server/extras/MojangAuth.java +++ b/src/main/java/net/minestom/server/extras/MojangAuth.java @@ -1,14 +1,10 @@ package net.minestom.server.extras; -import com.mojang.authlib.AuthenticationService; -import com.mojang.authlib.minecraft.MinecraftSessionService; -import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; import net.minestom.server.MinecraftServer; import net.minestom.server.extras.mojangAuth.MojangCrypt; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.Nullable; -import java.net.Proxy; import java.security.KeyPair; public final class MojangAuth { @@ -16,8 +12,6 @@ public final class MojangAuth { private static volatile boolean enabled = false; private static KeyPair keyPair; - private static AuthenticationService authService; - private static MinecraftSessionService sessionService; /** * Enables mojang authentication on the server. @@ -32,8 +26,6 @@ public final class MojangAuth { // Generate necessary fields... keyPair = MojangCrypt.generateKeyPair(); - authService = new YggdrasilAuthenticationService(Proxy.NO_PROXY, ""); - sessionService = authService.createMinecraftSessionService(); } public static boolean isEnabled() { @@ -44,14 +36,4 @@ public final class MojangAuth { public static KeyPair getKeyPair() { return keyPair; } - - @Nullable - public static AuthenticationService getAuthService() { - return authService; - } - - @Nullable - public static MinecraftSessionService getSessionService() { - return sessionService; - } } diff --git a/src/main/java/net/minestom/server/instance/block/rule/vanilla/StairsPlacementRule.java b/src/main/java/net/minestom/server/instance/block/rule/vanilla/StairsPlacementRule.java index 4a53096f2..6499fd96c 100644 --- a/src/main/java/net/minestom/server/instance/block/rule/vanilla/StairsPlacementRule.java +++ b/src/main/java/net/minestom/server/instance/block/rule/vanilla/StairsPlacementRule.java @@ -1,5 +1,6 @@ package net.minestom.server.instance.block.rule.vanilla; +import it.unimi.dsi.fastutil.Pair; import net.minestom.server.entity.Player; import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; @@ -7,7 +8,6 @@ import net.minestom.server.instance.block.BlockAlternative; import net.minestom.server.instance.block.BlockFace; import net.minestom.server.instance.block.rule.BlockPlacementRule; import net.minestom.server.utils.BlockPosition; -import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -113,10 +113,10 @@ public class StairsPlacementRule extends BlockPlacementRule { @Nullable private Shape getShapeFromSide(@NotNull Pair side, @NotNull Facing facing, @NotNull Shape right, @NotNull Shape left) { - if (side.getLeft() == null) { + if (side.left() == null) { return null; } - Facing sideFacing = side.getRight(); + Facing sideFacing = side.right(); if (facing.equals(Facing.NORTH)) { if (sideFacing.equals(Facing.EAST)) { return right; diff --git a/src/main/java/net/minestom/server/instance/palette/Section.java b/src/main/java/net/minestom/server/instance/palette/Section.java index b95ae63c7..1976b930f 100644 --- a/src/main/java/net/minestom/server/instance/palette/Section.java +++ b/src/main/java/net/minestom/server/instance/palette/Section.java @@ -53,6 +53,8 @@ public class Section implements PublicCloneable

{ private int valuesPerLong; private boolean hasPalette; + private short blockCount = 0; + protected Section(int bitsPerEntry, int bitsIncrement) { this.bitsPerEntry = bitsPerEntry; this.bitsIncrement = bitsIncrement; @@ -74,6 +76,9 @@ public class Section implements PublicCloneable
{ blocks = new long[getSize(valuesPerLong)]; } + // Check if the new block is air, used for counting none air blocks. + final boolean isAir = Block.fromStateId(blockId).isAir(); + // Change to palette value blockId = getPaletteIndex(blockId); @@ -87,10 +92,20 @@ public class Section implements PublicCloneable
{ { final long clear = MAGIC_MASKS[bitsPerEntry]; + final long value = block >> bitIndex & clear; + final boolean isCurrentAir = Block.fromStateId( + hasPalette ? paletteBlockMap.get((short) value) : (short) value).isAir(); + block |= clear << bitIndex; block ^= clear << bitIndex; block |= (long) blockId << bitIndex; + if (!isCurrentAir && isAir) { // The old block isn't air & the new block is. + this.blockCount--; + } else if (isCurrentAir && !isAir) { // The old block is air & the new block isn't. + this.blockCount++; + } // If both block are air or not air then don't change the value. + blocks[index] = block; } } @@ -143,6 +158,7 @@ public class Section implements PublicCloneable
{ this.hasPalette = section.hasPalette; this.blocks = section.blocks; + this.blockCount = section.blockCount; } /** @@ -171,12 +187,22 @@ public class Section implements PublicCloneable
{ this.blocks = new long[0]; this.paletteBlockMap = createPaletteBlockMap(); this.blockPaletteMap = createBlockPaletteMap(); + this.blockCount = 0; } public long[] getBlocks() { return blocks; } + /** + * Get the amount of non air blocks in this section. + * + * @return The amount of blocks in this section. + */ + public short getBlockCount() { + return blockCount; + } + public Short2ShortLinkedOpenHashMap getPaletteBlockMap() { return paletteBlockMap; } @@ -288,6 +314,7 @@ public class Section implements PublicCloneable
{ section.blocks = blocks.clone(); section.paletteBlockMap = paletteBlockMap.clone(); section.blockPaletteMap = blockPaletteMap.clone(); + section.blockCount = blockCount; return section; } catch (CloneNotSupportedException e) { MinecraftServer.getExceptionManager().handleException(e); diff --git a/src/main/java/net/minestom/server/inventory/EquipmentHandler.java b/src/main/java/net/minestom/server/inventory/EquipmentHandler.java index e515112aa..8d6562132 100644 --- a/src/main/java/net/minestom/server/inventory/EquipmentHandler.java +++ b/src/main/java/net/minestom/server/inventory/EquipmentHandler.java @@ -1,6 +1,7 @@ package net.minestom.server.inventory; import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.entity.Player; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.EntityEquipmentPacket; @@ -139,7 +140,7 @@ public interface EquipmentHandler { * @param slot the equipment to get the item from * @return the equipment {@link ItemStack} */ - default @NotNull ItemStack getEquipment(@NotNull EntityEquipmentPacket.Slot slot) { + default @NotNull ItemStack getEquipment(@NotNull EquipmentSlot slot) { switch (slot) { case MAIN_HAND: return getItemInMainHand(); @@ -157,7 +158,7 @@ public interface EquipmentHandler { throw new IllegalStateException("Something weird happened"); } - default void setEquipment(@NotNull EntityEquipmentPacket.Slot slot, @NotNull ItemStack itemStack) { + default void setEquipment(@NotNull EquipmentSlot slot, @NotNull ItemStack itemStack) { switch (slot) { case MAIN_HAND: setItemInMainHand(itemStack); @@ -187,7 +188,7 @@ public interface EquipmentHandler { * * @param slot the slot of the equipment */ - default void syncEquipment(@NotNull EntityEquipmentPacket.Slot slot) { + default void syncEquipment(@NotNull EquipmentSlot slot) { Check.stateCondition(!(this instanceof Entity), "Only accessible for Entity"); Entity entity = (Entity) this; @@ -196,7 +197,7 @@ public interface EquipmentHandler { EntityEquipmentPacket entityEquipmentPacket = new EntityEquipmentPacket(); entityEquipmentPacket.entityId = entity.getEntityId(); - entityEquipmentPacket.slots = new EntityEquipmentPacket.Slot[]{slot}; + entityEquipmentPacket.slots = new EquipmentSlot[]{slot}; entityEquipmentPacket.itemStacks = new ItemStack[]{itemStack}; entity.sendPacketToViewers(entityEquipmentPacket); @@ -213,12 +214,12 @@ public interface EquipmentHandler { final Entity entity = (Entity) this; - final EntityEquipmentPacket.Slot[] slots = EntityEquipmentPacket.Slot.values(); + final EquipmentSlot[] slots = EquipmentSlot.values(); List itemStacks = new ArrayList<>(slots.length); // Fill items - for (EntityEquipmentPacket.Slot slot : slots) { + for (EquipmentSlot slot : slots) { final ItemStack equipment = getEquipment(slot); itemStacks.add(equipment); } diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index 840850d5b..a4723b8fd 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -1,12 +1,12 @@ package net.minestom.server.inventory; +import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.entity.Player; -import net.minestom.server.event.item.ArmorEquipEvent; +import net.minestom.server.event.item.EntityEquipEvent; import net.minestom.server.inventory.click.ClickType; import net.minestom.server.inventory.click.InventoryClickResult; import net.minestom.server.inventory.condition.InventoryCondition; import net.minestom.server.item.ItemStack; -import net.minestom.server.network.packet.server.play.EntityEquipmentPacket; import net.minestom.server.network.packet.server.play.SetSlotPacket; import net.minestom.server.network.packet.server.play.WindowItemsPacket; import net.minestom.server.utils.MathUtils; @@ -170,33 +170,27 @@ public class PlayerInventory extends AbstractInventory implements EquipmentHandl "The slot {0} does not exist for player", slot); Check.notNull(itemStack, "The ItemStack cannot be null, you can set air instead"); - EntityEquipmentPacket.Slot equipmentSlot; + EquipmentSlot equipmentSlot = null; if (slot == player.getHeldSlot()) { - equipmentSlot = EntityEquipmentPacket.Slot.MAIN_HAND; + equipmentSlot = EquipmentSlot.MAIN_HAND; } else if (slot == OFFHAND_SLOT) { - equipmentSlot = EntityEquipmentPacket.Slot.OFF_HAND; - } else { - ArmorEquipEvent armorEquipEvent = null; + equipmentSlot = EquipmentSlot.OFF_HAND; + } else if (slot == HELMET_SLOT) { + equipmentSlot = EquipmentSlot.HELMET; + } else if (slot == CHESTPLATE_SLOT) { + equipmentSlot = EquipmentSlot.CHESTPLATE; + } else if (slot == LEGGINGS_SLOT) { + equipmentSlot = EquipmentSlot.LEGGINGS; + } else if (slot == BOOTS_SLOT) { + equipmentSlot = EquipmentSlot.BOOTS; + } - if (slot == HELMET_SLOT) { - armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.HELMET); - } else if (slot == CHESTPLATE_SLOT) { - armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE); - } else if (slot == LEGGINGS_SLOT) { - armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS); - } else if (slot == BOOTS_SLOT) { - armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.BOOTS); - } + if (equipmentSlot != null) { + EntityEquipEvent entityEquipEvent = new EntityEquipEvent(player, itemStack, equipmentSlot); - if (armorEquipEvent != null) { - ArmorEquipEvent.ArmorSlot armorSlot = armorEquipEvent.getArmorSlot(); - equipmentSlot = EntityEquipmentPacket.Slot.fromArmorSlot(armorSlot); - player.callEvent(ArmorEquipEvent.class, armorEquipEvent); - itemStack = armorEquipEvent.getArmorItem(); - } else { - equipmentSlot = null; - } + player.callEvent(EntityEquipEvent.class, entityEquipEvent); + itemStack = entityEquipEvent.getEquippedItem(); } this.itemStacks[slot] = itemStack; diff --git a/src/main/java/net/minestom/server/inventory/type/VillagerInventory.java b/src/main/java/net/minestom/server/inventory/type/VillagerInventory.java index 4812fe9e9..e7a521376 100644 --- a/src/main/java/net/minestom/server/inventory/type/VillagerInventory.java +++ b/src/main/java/net/minestom/server/inventory/type/VillagerInventory.java @@ -1,9 +1,11 @@ package net.minestom.server.inventory.type; +import net.minestom.server.entity.Player; import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.InventoryType; import net.minestom.server.network.packet.server.play.TradeListPacket; import net.minestom.server.utils.ArrayUtils; +import org.jetbrains.annotations.NotNull; public class VillagerInventory extends Inventory { @@ -23,7 +25,7 @@ public class VillagerInventory extends Inventory { final int length = oldTrades.length + 1; TradeListPacket.Trade[] trades = new TradeListPacket.Trade[length]; System.arraycopy(oldTrades, 0, trades, 0, oldTrades.length); - trades[length] = trade; + trades[length - 1] = trade; this.tradeListPacket.trades = trades; update(); } @@ -79,6 +81,15 @@ public class VillagerInventory extends Inventory { sendPacketToViewers(tradeListPacket); // Refresh window } + @Override + public boolean addViewer(@NotNull Player player) { + final boolean result = super.addViewer(player); + if (result) { + player.getPlayerConnection().sendPacket(tradeListPacket); + } + return result; + } + private void setupPacket() { this.tradeListPacket = new TradeListPacket(); this.tradeListPacket.windowId = getWindowId(); diff --git a/src/main/java/net/minestom/server/item/ItemMeta.java b/src/main/java/net/minestom/server/item/ItemMeta.java index 04d0023ba..2128a180a 100644 --- a/src/main/java/net/minestom/server/item/ItemMeta.java +++ b/src/main/java/net/minestom/server/item/ItemMeta.java @@ -4,6 +4,8 @@ import io.netty.buffer.ByteBuf; import net.kyori.adventure.text.Component; import net.minestom.server.instance.block.Block; import net.minestom.server.item.attribute.ItemAttribute; +import net.minestom.server.tag.Tag; +import net.minestom.server.tag.TagReadable; import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.binary.Writeable; import org.jetbrains.annotations.Contract; @@ -13,8 +15,9 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound; import java.util.*; import java.util.function.Consumer; +import java.util.function.Supplier; -public class ItemMeta implements Writeable { +public class ItemMeta implements TagReadable, Writeable { private final int damage; private final boolean unbreakable; @@ -109,18 +112,14 @@ public class ItemMeta implements Writeable { return Collections.unmodifiableSet(canPlaceOn); } - @Contract(pure = true) - public T getOrDefault(@NotNull ItemTag tag, @Nullable T defaultValue) { - var key = tag.getKey(); - if (nbt.containsKey(key)) { - return tag.read(toNBT()); - } else { - return defaultValue; - } + @Override + public @Nullable T getTag(@NotNull Tag tag) { + return tag.read(nbt); } - public @Nullable T get(@NotNull ItemTag tag) { - return tag.read(toNBT()); + @Override + public boolean hasTag(@NotNull Tag tag) { + return nbt.containsKey(tag.getKey()); } public @NotNull NBTCompound toNBT() { @@ -163,4 +162,26 @@ public class ItemMeta implements Writeable { writer.write(cachedBuffer); this.cachedBuffer.resetReaderIndex(); } + + /** + * @deprecated use {@link #getTag(Tag)} with {@link Tag#defaultValue(Supplier)} + */ + @Deprecated + @Contract(pure = true) + public T getOrDefault(@NotNull Tag tag, @Nullable T defaultValue) { + var key = tag.getKey(); + if (nbt.containsKey(key)) { + return tag.read(toNBT()); + } else { + return defaultValue; + } + } + + /** + * @deprecated use {@link #getTag(Tag)} + */ + @Deprecated + public @Nullable T get(@NotNull Tag tag) { + return getTag(tag); + } } diff --git a/src/main/java/net/minestom/server/item/ItemMetaBuilder.java b/src/main/java/net/minestom/server/item/ItemMetaBuilder.java index 58b23f92e..c0d8f9529 100644 --- a/src/main/java/net/minestom/server/item/ItemMetaBuilder.java +++ b/src/main/java/net/minestom/server/item/ItemMetaBuilder.java @@ -5,6 +5,8 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.adventure.AdventureSerializer; import net.minestom.server.instance.block.Block; import net.minestom.server.item.attribute.ItemAttribute; +import net.minestom.server.tag.Tag; +import net.minestom.server.tag.TagWritable; import net.minestom.server.utils.NBTUtils; import net.minestom.server.utils.Utils; import org.jetbrains.annotations.Contract; @@ -16,7 +18,7 @@ import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; -public abstract class ItemMetaBuilder { +public abstract class ItemMetaBuilder implements TagWritable { protected NBTCompound nbt = new NBTCompound(); @@ -183,12 +185,13 @@ public abstract class ItemMetaBuilder { return canDestroy(Set.of(blocks)); } - public @NotNull ItemMetaBuilder set(@NotNull ItemTag tag, @Nullable T value) { - if (value != null) { - tag.write(nbt, value); - } else { - this.nbt.removeTag(tag.getKey()); - } + @Override + public void setTag(@NotNull Tag tag, @Nullable T value) { + tag.write(nbt, value); + } + + public @NotNull ItemMetaBuilder set(@NotNull Tag tag, @Nullable T value) { + setTag(tag, value); return this; } diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index f1e1b7f7b..b5d6a0a80 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -4,6 +4,8 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; import net.minestom.server.item.rule.VanillaStackingRule; +import net.minestom.server.tag.Tag; +import net.minestom.server.tag.TagReadable; import net.minestom.server.utils.NBTUtils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -22,7 +24,7 @@ import java.util.function.UnaryOperator; *

* An item stack cannot be null, {@link ItemStack#AIR} should be used instead. */ -public final class ItemStack implements HoverEventSource { +public final class ItemStack implements TagReadable, HoverEventSource { /** * Constant AIR item. Should be used instead of 'null'. @@ -191,6 +193,21 @@ public final class ItemStack implements HoverEventSource { .stackingRule(stackingRule); } + @Contract(value = "_, _ -> new", pure = true) + public @NotNull ItemStack withTag(@NotNull Tag tag, @Nullable T value) { + return builder().meta(metaBuilder -> metaBuilder.set(tag, value)).build(); + } + + @Override + public @Nullable T getTag(@NotNull Tag tag) { + return meta.getTag(tag); + } + + @Override + public boolean hasTag(@NotNull Tag tag) { + return meta.hasTag(tag); + } + @Override public @NotNull HoverEvent asHoverEvent(@NotNull UnaryOperator op) { return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(this.material, diff --git a/src/main/java/net/minestom/server/item/ItemTag.java b/src/main/java/net/minestom/server/item/ItemTag.java index 6f0c02a65..70ad3b527 100644 --- a/src/main/java/net/minestom/server/item/ItemTag.java +++ b/src/main/java/net/minestom/server/item/ItemTag.java @@ -1,112 +1,65 @@ package net.minestom.server.item; +import net.minestom.server.tag.Tag; import org.jetbrains.annotations.NotNull; import org.jglrxavpok.hephaistos.nbt.NBT; import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import org.jglrxavpok.hephaistos.nbt.NBTList; import java.util.function.BiConsumer; import java.util.function.Function; -public class ItemTag { +/** + * @deprecated use {@link Tag}. + */ +@Deprecated +public class ItemTag extends Tag { - private final String key; - private final Function readFunction; - private final BiConsumer writeConsumer; - - private ItemTag(@NotNull String key, - @NotNull Function readFunction, - @NotNull BiConsumer writeConsumer) { - this.key = key; - this.readFunction = readFunction; - this.writeConsumer = writeConsumer; + protected ItemTag(@NotNull String key, @NotNull Function readFunction, @NotNull BiConsumer writeConsumer) { + super(key, readFunction, writeConsumer); } - public @NotNull String getKey() { - return key; + public static @NotNull Tag Byte(@NotNull String key) { + return Tag.Byte(key); } - protected T read(@NotNull NBTCompound nbtCompound) { - return readFunction.apply(nbtCompound); + public static @NotNull Tag Short(@NotNull String key) { + return Tag.Short(key); } - protected void write(@NotNull NBTCompound nbtCompound, @NotNull T value) { - this.writeConsumer.accept(nbtCompound, value); + public static @NotNull Tag Integer(@NotNull String key) { + return Tag.Integer(key); } - public static @NotNull ItemTag Byte(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getByte(key), - (nbtCompound, value) -> nbtCompound.setByte(key, value)); + public static @NotNull Tag Long(@NotNull String key) { + return Tag.Long(key); } - public static @NotNull ItemTag Short(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getShort(key), - (nbtCompound, value) -> nbtCompound.setShort(key, value)); + public static @NotNull Tag Float(@NotNull String key) { + return Tag.Float(key); } - public static @NotNull ItemTag Integer(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getInt(key), - (nbtCompound, integer) -> nbtCompound.setInt(key, integer)); + public static @NotNull Tag Double(@NotNull String key) { + return Tag.Double(key); } - public static @NotNull ItemTag Long(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getLong(key), - (nbtCompound, value) -> nbtCompound.setLong(key, value)); + public static @NotNull Tag ByteArray(@NotNull String key) { + return Tag.ByteArray(key); } - public static @NotNull ItemTag Float(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getFloat(key), - (nbtCompound, value) -> nbtCompound.setFloat(key, value)); + public static @NotNull Tag String(@NotNull String key) { + return Tag.String(key); } - public static @NotNull ItemTag Double(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getDouble(key), - (nbtCompound, value) -> nbtCompound.setDouble(key, value)); + public static @NotNull Tag NBT(@NotNull String key) { + return Tag.NBT(key); } - public static @NotNull ItemTag ByteArray(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getByteArray(key), - (nbtCompound, value) -> nbtCompound.setByteArray(key, value)); + public static @NotNull Tag IntArray(@NotNull String key) { + return Tag.IntArray(key); } - public static @NotNull ItemTag String(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getString(key), - (nbtCompound, value) -> nbtCompound.setString(key, value)); - } - - public static @NotNull ItemTag NBT(@NotNull String key) { - return new ItemTag<>(key, - nbt -> { - var currentNBT = nbt.get(key); - - // Avoid a NPE when cloning a null variable. - if (currentNBT == null) { - return null; - } - - return currentNBT.deepClone(); - }, - ((nbt, value) -> nbt.set(key, value.deepClone()))); - } - - public static @NotNull ItemTag IntArray(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getIntArray(key), - (nbtCompound, value) -> nbtCompound.setIntArray(key, value)); - } - - public static @NotNull ItemTag LongArray(@NotNull String key) { - return new ItemTag<>(key, - nbtCompound -> nbtCompound.getLongArray(key), - (nbtCompound, value) -> nbtCompound.setLongArray(key, value)); + public static @NotNull Tag LongArray(@NotNull String key) { + return Tag.LongArray(key); } } diff --git a/src/main/java/net/minestom/server/listener/ChatMessageListener.java b/src/main/java/net/minestom/server/listener/ChatMessageListener.java index 1f5e56a49..cc1f491df 100644 --- a/src/main/java/net/minestom/server/listener/ChatMessageListener.java +++ b/src/main/java/net/minestom/server/listener/ChatMessageListener.java @@ -6,10 +6,10 @@ import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandManager; import net.minestom.server.entity.Player; import net.minestom.server.event.player.PlayerChatEvent; +import net.minestom.server.message.ChatPosition; +import net.minestom.server.message.Messenger; import net.minestom.server.network.ConnectionManager; import net.minestom.server.network.packet.client.play.ClientChatMessagePacket; -import net.minestom.server.network.packet.server.play.ChatMessagePacket; -import net.minestom.server.utils.PacketUtils; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -26,21 +26,30 @@ public class ChatMessageListener { final String cmdPrefix = CommandManager.COMMAND_PREFIX; if (message.startsWith(cmdPrefix)) { // The message is a command - message = message.replaceFirst(cmdPrefix, ""); + final String command = message.replaceFirst(cmdPrefix, ""); - COMMAND_MANAGER.execute(player, message); + // check if we can receive commands + if (Messenger.canReceiveCommand(player)) { + COMMAND_MANAGER.execute(player, command); + } else { + Messenger.sendRejectionMessage(player); + } // Do not call chat event return; } + // check if we can receive messages + if (!Messenger.canReceiveMessage(player)) { + Messenger.sendRejectionMessage(player); + return; + } + final Collection players = CONNECTION_MANAGER.getOnlinePlayers(); - String finalMessage = message; - PlayerChatEvent playerChatEvent = new PlayerChatEvent(player, players, () -> buildDefaultChatMessage(player, finalMessage), message); + PlayerChatEvent playerChatEvent = new PlayerChatEvent(player, players, () -> buildDefaultChatMessage(player, message), message); // Call the event player.callCancellableEvent(PlayerChatEvent.class, playerChatEvent, () -> { - final Function formatFunction = playerChatEvent.getChatFormatFunction(); Component textObject; @@ -55,15 +64,10 @@ public class ChatMessageListener { final Collection recipients = playerChatEvent.getRecipients(); if (!recipients.isEmpty()) { - // Send the message with the correct player UUID - ChatMessagePacket chatMessagePacket = - new ChatMessagePacket(textObject, ChatMessagePacket.Position.CHAT, player.getUuid()); - - PacketUtils.sendGroupedPacket(recipients, chatMessagePacket); + // delegate to the messenger to avoid sending messages we shouldn't be + Messenger.sendMessage(recipients, textObject, ChatPosition.CHAT, player.getUuid()); } - }); - } private static @NotNull Component buildDefaultChatMessage(@NotNull Player player, @NotNull String message) { diff --git a/src/main/java/net/minestom/server/listener/EntityActionListener.java b/src/main/java/net/minestom/server/listener/EntityActionListener.java index e99cffc75..dea235c90 100644 --- a/src/main/java/net/minestom/server/listener/EntityActionListener.java +++ b/src/main/java/net/minestom/server/listener/EntityActionListener.java @@ -1,10 +1,7 @@ package net.minestom.server.listener; import net.minestom.server.entity.Player; -import net.minestom.server.event.player.PlayerStartSneakingEvent; -import net.minestom.server.event.player.PlayerStartSprintingEvent; -import net.minestom.server.event.player.PlayerStopSneakingEvent; -import net.minestom.server.event.player.PlayerStopSprintingEvent; +import net.minestom.server.event.player.*; import net.minestom.server.network.packet.client.play.ClientEntityActionPacket; public class EntityActionListener { @@ -24,6 +21,9 @@ public class EntityActionListener { case STOP_SPRINTING: EntityActionListener.setSprinting(player, false); break; + case START_FLYING_ELYTRA: + EntityActionListener.startFlyingElytra(player); + break; // TODO do remaining actions } } @@ -55,4 +55,9 @@ public class EntityActionListener { } } } + + private static void startFlyingElytra(Player player) { + player.setFlyingWithElytra(true); + player.callEvent(PlayerStartFlyingWithElytraEvent.class, new PlayerStartFlyingWithElytraEvent(player)); + } } diff --git a/src/main/java/net/minestom/server/listener/SettingsListener.java b/src/main/java/net/minestom/server/listener/SettingsListener.java index 7f23840be..3099a8226 100644 --- a/src/main/java/net/minestom/server/listener/SettingsListener.java +++ b/src/main/java/net/minestom/server/listener/SettingsListener.java @@ -8,7 +8,7 @@ public class SettingsListener { public static void listener(ClientSettingsPacket packet, Player player) { Player.PlayerSettings settings = player.getSettings(); - settings.refresh(packet.locale, packet.viewDistance, packet.chatMode, packet.chatColors, packet.displayedSkinParts, packet.mainHand); + settings.refresh(packet.locale, packet.viewDistance, packet.chatMessageType, packet.chatColors, packet.displayedSkinParts, packet.mainHand); PlayerSettingsChangeEvent playerSettingsChangeEvent = new PlayerSettingsChangeEvent(player); player.callEvent(PlayerSettingsChangeEvent.class, playerSettingsChangeEvent); diff --git a/src/main/java/net/minestom/server/listener/TabCompleteListener.java b/src/main/java/net/minestom/server/listener/TabCompleteListener.java index 73e95dc91..462978a50 100644 --- a/src/main/java/net/minestom/server/listener/TabCompleteListener.java +++ b/src/main/java/net/minestom/server/listener/TabCompleteListener.java @@ -11,9 +11,10 @@ import net.minestom.server.command.builder.suggestion.SuggestionCallback; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.client.play.ClientTabCompletePacket; import net.minestom.server.network.packet.server.play.TabCompletePacket; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import java.util.Arrays; +import java.util.regex.Pattern; public class TabCompleteListener { @@ -23,8 +24,7 @@ public class TabCompleteListener { String commandString = packet.text.replaceFirst(CommandManager.COMMAND_PREFIX, ""); String[] split = commandString.split(StringUtils.SPACE); String commandName = split[0]; - - String args = commandString.replaceFirst(commandName, ""); + String args = commandString.replaceFirst(Pattern.quote(commandName), ""); final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandString); if (commandQueryResult == null) { @@ -48,7 +48,7 @@ public class TabCompleteListener { final int inputLength = input.length(); final int commandLength = Arrays.stream(split).map(String::length).reduce(0, Integer::sum) + - StringUtils.countMatches(args, StringUtils.SPACE); + StringUtils.countMatches(args, StringUtils.SPACE_CHAR); final int trailingSpaces = !input.isEmpty() ? text.length() - text.trim().length() : 0; final int start = commandLength - inputLength + 1 - trailingSpaces; diff --git a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java index 6471b4f0e..72f944ca0 100644 --- a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java +++ b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java @@ -84,7 +84,12 @@ public final class PacketListenerManager { // Finally execute the listener if (packetListenerConsumer != null) { - packetListenerConsumer.accept(packet, player); + try { + packetListenerConsumer.accept(packet, player); + } catch (Exception e) { + // Packet is likely invalid + MinecraftServer.getExceptionManager().handleException(e); + } } } diff --git a/src/main/java/net/minestom/server/message/ChatMessageType.java b/src/main/java/net/minestom/server/message/ChatMessageType.java new file mode 100644 index 000000000..73bf2fffd --- /dev/null +++ b/src/main/java/net/minestom/server/message/ChatMessageType.java @@ -0,0 +1,65 @@ +package net.minestom.server.message; + +import org.jetbrains.annotations.NotNull; + +import java.util.EnumSet; + +/** + * The messages that a player is willing to receive. + */ +public enum ChatMessageType { + /** + * The client wants all chat messages. + */ + FULL(EnumSet.allOf(ChatPosition.class)), + + /** + * The client only wants messages from commands, or system messages. + */ + SYSTEM(EnumSet.of(ChatPosition.SYSTEM_MESSAGE, ChatPosition.GAME_INFO)), + + /** + * The client doesn't want any messages. + */ + NONE(EnumSet.of(ChatPosition.GAME_INFO)); + + private final EnumSet acceptedPositions; + + ChatMessageType(@NotNull EnumSet acceptedPositions) { + this.acceptedPositions = acceptedPositions; + } + + /** + * Checks if this message type is accepting of messages from a given position. + * + * @param chatPosition the position + * @return if the message is accepted + */ + public boolean accepts(@NotNull ChatPosition chatPosition) { + return this.acceptedPositions.contains(chatPosition); + } + + /** + * Gets the packet ID for this chat message type. + * + * @return the packet ID + */ + public int getPacketID() { + return this.ordinal(); + } + + /** + * Gets a chat message type from a packet ID. + * + * @param id the packet ID + * @return the chat message type + */ + public static @NotNull ChatMessageType fromPacketID(int id) { + switch (id) { + case 0: return FULL; + case 1: return SYSTEM; + case 2: return NONE; + default: throw new IllegalArgumentException("id must be between 0-2 (inclusive)"); + } + } +} diff --git a/src/main/java/net/minestom/server/message/ChatPosition.java b/src/main/java/net/minestom/server/message/ChatPosition.java new file mode 100644 index 000000000..345fa0677 --- /dev/null +++ b/src/main/java/net/minestom/server/message/ChatPosition.java @@ -0,0 +1,79 @@ +package net.minestom.server.message; + +import net.kyori.adventure.audience.MessageType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * The different positions for chat messages. + */ +public enum ChatPosition { + /** + * A player-initiated chat message. + */ + CHAT(MessageType.CHAT), + + /** + * Feedback from running a command or other system messages. + */ + SYSTEM_MESSAGE(MessageType.SYSTEM), + + /** + * Game state information displayed above the hot bar. + */ + GAME_INFO(null); + + private final MessageType messageType; + + ChatPosition(@NotNull MessageType messageType) { + this.messageType = messageType; + } + + /** + * Gets the Adventure message type from this position. Note that there is no + * message type for {@link #GAME_INFO}, as Adventure uses the title methods for this. + * + * @return the message type, if any + */ + public @Nullable MessageType getMessageType() { + return this.messageType; + } + + /** + * Gets the packet ID of this chat position. + * + * @return the ID + */ + public byte getID() { + return (byte) this.ordinal(); + } + + /** + * Gets a position from an Adventure message type. + * + * @param messageType the message type + * @return the position + */ + public static @NotNull ChatPosition fromMessageType(@NotNull MessageType messageType) { + switch (messageType) { + case CHAT: return CHAT; + case SYSTEM: return SYSTEM_MESSAGE; + } + throw new IllegalArgumentException("Cannot get position from message type!"); + } + + /** + * Gets a position from a packet ID. + * + * @param id the id + * @return the chat position + */ + public static @NotNull ChatPosition fromPacketID(byte id) { + switch (id) { + case 0: return CHAT; + case 1: return SYSTEM_MESSAGE; + case 2: return GAME_INFO; + default: throw new IllegalArgumentException("id must be between 0-2 (inclusive)"); + } + } +} diff --git a/src/main/java/net/minestom/server/message/Messenger.java b/src/main/java/net/minestom/server/message/Messenger.java new file mode 100644 index 000000000..c4dc90ba1 --- /dev/null +++ b/src/main/java/net/minestom/server/message/Messenger.java @@ -0,0 +1,94 @@ +package net.minestom.server.message; + +import java.util.*; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.minestom.server.entity.Player; +import net.minestom.server.network.packet.server.play.ChatMessagePacket; +import net.minestom.server.utils.PacketUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Utility class to handle client chat settings. + */ +public class Messenger { + /** + * The message sent to the client if they send a chat message but it is rejected by the server. + */ + public static final Component CANNOT_SEND_MESSAGE = Component.translatable("chat.cannotSend", NamedTextColor.RED); + + private static final ChatMessagePacket CANNOT_SEND_PACKET = new ChatMessagePacket(CANNOT_SEND_MESSAGE, ChatPosition.SYSTEM_MESSAGE, null); + + /** + * Sends a message to a player, respecting their chat settings. + * + * @param player the player + * @param message the message + * @param position the position + * @param uuid the UUID of the sender, if any + * @return if the message was sent + */ + public static boolean sendMessage(@NotNull Player player, @NotNull Component message, @NotNull ChatPosition position, @Nullable UUID uuid) { + if (getChatMessageType(player).accepts(position)) { + player.getPlayerConnection().sendPacket(new ChatMessagePacket(message, position, uuid)); + return true; + } + + return false; + } + + /** + * Sends a message to some players, respecting their chat settings. + * + * @param players the players + * @param message the message + * @param position the position + * @param uuid the UUID of the sender, if any + */ + public static void sendMessage(@NotNull Collection players, @NotNull Component message, + @NotNull ChatPosition position, @Nullable UUID uuid) { + PacketUtils.sendGroupedPacket(players, new ChatMessagePacket(message, position, uuid), + player -> getChatMessageType(player).accepts(position)); + } + + /** + * Checks if the server should receive messages from a player, given their chat settings. + * + * @param player the player + * @return if the server should receive messages from them + */ + public static boolean canReceiveMessage(@NotNull Player player) { + return getChatMessageType(player) == ChatMessageType.FULL; + } + + /** + * Checks if the server should receive commands from a player, given their chat settings. + * + * @param player the player + * @return if the server should receive commands from them + */ + public static boolean canReceiveCommand(@NotNull Player player) { + return getChatMessageType(player) != ChatMessageType.NONE; + } + + /** + * Sends a message to the player informing them we are rejecting their message or command. + * + * @param player the player + */ + public static void sendRejectionMessage(@NotNull Player player) { + player.getPlayerConnection().sendPacket(CANNOT_SEND_PACKET, false); + } + + /** + * Gets the chat message type for a player, returning {@link ChatMessageType#FULL} if not set. + * + * @param player the player + * @return the chat message type + */ + private static @NotNull ChatMessageType getChatMessageType(@NotNull Player player) { + return Objects.requireNonNullElse(player.getSettings().getChatMessageType(), ChatMessageType.FULL); + } +} diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index 932a6c0b7..67ae73556 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -20,10 +20,10 @@ import net.minestom.server.network.packet.server.play.DisconnectPacket; import net.minestom.server.network.packet.server.play.KeepAlivePacket; import net.minestom.server.network.player.NettyPlayerConnection; import net.minestom.server.network.player.PlayerConnection; +import net.minestom.server.utils.StringUtils; import net.minestom.server.utils.async.AsyncUtils; import net.minestom.server.utils.callback.validator.PlayerValidator; import net.minestom.server.utils.validate.Check; -import org.apache.commons.text.similarity.JaroWinklerDistance; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,6 +33,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -83,7 +84,6 @@ public final class ConnectionManager { /** * Finds the closest player matching a given username. - *

* * @param username the player username (can be partial) * @return the closest match, null if no players are online @@ -91,18 +91,17 @@ public final class ConnectionManager { public @Nullable Player findPlayer(@NotNull String username) { Player exact = getPlayer(username); if (exact != null) return exact; + final String username1 = username.toLowerCase(Locale.ROOT); - String lowercase = username.toLowerCase(); - double currentDistance = 0; - for (Player player : getOnlinePlayers()) { - final JaroWinklerDistance jaroWinklerDistance = new JaroWinklerDistance(); - final double distance = jaroWinklerDistance.apply(lowercase, player.getUsername().toLowerCase()); - if (distance > currentDistance) { - currentDistance = distance; - exact = player; - } - } - return exact; + Function distanceFunction = player -> { + final String username2 = player.getUsername().toLowerCase(Locale.ROOT); + return StringUtils.jaroWinklerScore(username1, username2); + }; + return getOnlinePlayers() + .stream() + .min(Comparator.comparingDouble(distanceFunction::apply)) + .filter(player -> distanceFunction.apply(player) > 0) + .orElse(null); } /** @@ -188,8 +187,12 @@ public final class ConnectionManager { * Gets all the listeners which are called for each packet received. * * @return a list of packet's consumers + * @deprecated all packet listening methods will ultimately be removed. + * May or may not work depending on the packet. + * It is instead recommended to use a proxy, improving scalability and increasing server performance */ @NotNull + @Deprecated public List getReceivePacketConsumers() { return receiveClientPacketConsumers; } @@ -198,7 +201,11 @@ public final class ConnectionManager { * Adds a consumer to call once a packet is received. * * @param clientPacketConsumer the packet consumer + * @deprecated all packet listening methods will ultimately be removed. + * May or may not work depending on the packet. + * It is instead recommended to use a proxy, improving scalability and increasing server performance */ + @Deprecated public void onPacketReceive(@NotNull ClientPacketConsumer clientPacketConsumer) { this.receiveClientPacketConsumers.add(clientPacketConsumer); } @@ -207,8 +214,12 @@ public final class ConnectionManager { * Gets all the listeners which are called for each packet sent. * * @return a list of packet's consumers + * @deprecated all packet listening methods will ultimately be removed. + * May or may not work depending on the packet. + * It is instead recommended to use a proxy, improving scalability and increasing server performance */ @NotNull + @Deprecated public List getSendPacketConsumers() { return sendClientPacketConsumers; } @@ -217,7 +228,11 @@ public final class ConnectionManager { * Adds a consumer to call once a packet is sent. * * @param serverPacketConsumer the packet consumer + * @deprecated all packet listening methods will ultimately be removed. + * May or may not work depending on the packet. + * It is instead recommended to use a proxy, improving scalability and increasing server performance */ + @Deprecated public void onPacketSend(@NotNull ServerPacketConsumer serverPacketConsumer) { this.sendClientPacketConsumers.add(serverPacketConsumer); } diff --git a/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java b/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java index c98217f55..83d5ef855 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java @@ -1,7 +1,7 @@ package net.minestom.server.network.packet.client.login; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.exceptions.AuthenticationUnavailableException; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import net.minestom.server.MinecraftServer; import net.minestom.server.data.type.array.ByteArrayData; import net.minestom.server.extras.MojangAuth; @@ -15,11 +15,16 @@ import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; import javax.crypto.SecretKey; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.math.BigInteger; +import java.net.URL; import java.util.Arrays; +import java.util.UUID; public class EncryptionResponsePacket implements ClientPreplayPacket { - + private static final Gson GSON = new Gson(); private byte[] sharedSecret; private byte[] verifyToken; @@ -44,7 +49,7 @@ public class EncryptionResponsePacket implements ClientPreplayPacket { MinecraftServer.LOGGER.error("{} tried to login with an invalid nonce!", loginUsername); return; } - if (!loginUsername.isEmpty()) { + if (loginUsername != null && !loginUsername.isEmpty()) { final byte[] digestedData = MojangCrypt.digestData("", MojangAuth.getKeyPair().getPublic(), getSecretKey()); @@ -55,14 +60,24 @@ public class EncryptionResponsePacket implements ClientPreplayPacket { return; } - final String string3 = new BigInteger(digestedData).toString(16); - final GameProfile gameProfile = MojangAuth.getSessionService().hasJoinedServer(new GameProfile(null, loginUsername), string3); - nettyConnection.setEncryptionKey(getSecretKey()); + // Query Mojang's sessionserver. + final String serverId = new BigInteger(digestedData).toString(16); + InputStream gameProfileStream = new URL( + "https://sessionserver.mojang.com/session/minecraft/hasJoined?" + + "username=" + loginUsername + "&" + + "serverId=" + serverId + // TODO: Add ability to add ip query tag. See: https://wiki.vg/Protocol_Encryption#Authentication + ).openStream(); - MinecraftServer.LOGGER.info("UUID of player {} is {}", loginUsername, gameProfile.getId()); - CONNECTION_MANAGER.startPlayState(connection, gameProfile.getId(), gameProfile.getName(), true); + final JsonObject gameProfile = GSON.fromJson(new InputStreamReader(gameProfileStream), JsonObject.class); + nettyConnection.setEncryptionKey(getSecretKey()); + UUID profileUUID = UUID.fromString(gameProfile.get("id").getAsString().replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); + String profileName = gameProfile.get("name").getAsString(); + + MinecraftServer.LOGGER.info("UUID of player {} is {}", loginUsername, profileUUID); + CONNECTION_MANAGER.startPlayState(connection, profileUUID, profileName, true); } - } catch (AuthenticationUnavailableException e) { + } catch (IOException e) { MinecraftServer.getExceptionManager().handleException(e); } }); @@ -80,11 +95,11 @@ public class EncryptionResponsePacket implements ClientPreplayPacket { ByteArrayData.encodeByteArray(writer, verifyToken); } - public SecretKey getSecretKey() { + private SecretKey getSecretKey() { return MojangCrypt.decryptByteToSecretKey(MojangAuth.getKeyPair().getPrivate(), sharedSecret); } - public byte[] getNonce() { + private byte[] getNonce() { return MojangAuth.getKeyPair().getPrivate() == null ? this.verifyToken : MojangCrypt.decryptUsingKey(MojangAuth.getKeyPair().getPrivate(), this.verifyToken); } diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientSettingsPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientSettingsPacket.java index 968216cc9..8af3da577 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientSettingsPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientSettingsPacket.java @@ -1,6 +1,7 @@ package net.minestom.server.network.packet.client.play; import net.minestom.server.entity.Player; +import net.minestom.server.message.ChatMessageType; import net.minestom.server.network.packet.client.ClientPlayPacket; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; @@ -10,7 +11,7 @@ public class ClientSettingsPacket extends ClientPlayPacket { public String locale = ""; public byte viewDistance; - public Player.ChatMode chatMode = Player.ChatMode.ENABLED; + public ChatMessageType chatMessageType = ChatMessageType.FULL; public boolean chatColors; public byte displayedSkinParts; public Player.MainHand mainHand = Player.MainHand.RIGHT; @@ -19,7 +20,7 @@ public class ClientSettingsPacket extends ClientPlayPacket { public void read(@NotNull BinaryReader reader) { this.locale = reader.readSizedString(128); this.viewDistance = reader.readByte(); - this.chatMode = Player.ChatMode.values()[reader.readVarInt()]; + this.chatMessageType = ChatMessageType.fromPacketID(reader.readVarInt()); this.chatColors = reader.readBoolean(); this.displayedSkinParts = reader.readByte(); this.mainHand = Player.MainHand.values()[reader.readVarInt()]; @@ -31,7 +32,7 @@ public class ClientSettingsPacket extends ClientPlayPacket { throw new IllegalArgumentException("Locale cannot be longer than 128 characters."); writer.writeSizedString(locale); writer.writeByte(viewDistance); - writer.writeVarInt(chatMode.ordinal()); + writer.writeVarInt(chatMessageType.getPacketID()); writer.writeBoolean(chatColors); writer.writeByte(displayedSkinParts); writer.writeVarInt(mainHand.ordinal()); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java index cc9f57195..246edb55d 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java @@ -1,7 +1,7 @@ package net.minestom.server.network.packet.server.play; -import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.text.Component; +import net.minestom.server.message.ChatPosition; import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; @@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; +import java.util.Objects; import java.util.UUID; import java.util.function.UnaryOperator; @@ -22,21 +23,19 @@ public class ChatMessagePacket implements ComponentHoldingServerPacket { private static final UUID NULL_UUID = new UUID(0, 0); public Component message; - public Position position; + public ChatPosition position; public UUID uuid; public ChatMessagePacket() { - this(Component.empty(), Position.CHAT); + this.message = Component.empty(); + this.position = ChatPosition.SYSTEM_MESSAGE; + this.uuid = NULL_UUID; } - public ChatMessagePacket(Component message, Position position, UUID uuid) { + public ChatMessagePacket(@NotNull Component message, @NotNull ChatPosition position, @Nullable UUID uuid) { this.message = message; this.position = position; - this.uuid = uuid; - } - - public ChatMessagePacket(Component message, Position position) { - this(message, position, NULL_UUID); + this.uuid = Objects.requireNonNullElse(uuid, NULL_UUID); } @Override @@ -49,7 +48,7 @@ public class ChatMessagePacket implements ComponentHoldingServerPacket { @Override public void read(@NotNull BinaryReader reader) { message = reader.readComponent(Integer.MAX_VALUE); - position = Position.values()[reader.readByte()]; + position = ChatPosition.fromPacketID(reader.readByte()); uuid = reader.readUuid(); } @@ -67,41 +66,4 @@ public class ChatMessagePacket implements ComponentHoldingServerPacket { public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { return new ChatMessagePacket(operator.apply(message), position, uuid); } - - public enum Position { - CHAT(MessageType.CHAT), - SYSTEM_MESSAGE(MessageType.SYSTEM), - GAME_INFO(null); - - private final MessageType messageType; - - Position(MessageType messageType) { - this.messageType = messageType; - } - - /** - * Gets the Adventure message type from this position. Note that there is no - * message type for {@link #GAME_INFO}, as Adventure uses the title methods for this. - * - * @return the message type, if any - */ - public @Nullable MessageType getMessageType() { - return this.messageType; - } - - /** - * Gets a position from an Adventure message type. - * - * @param messageType the message type - * - * @return the position - */ - public static @NotNull Position fromMessageType(@NotNull MessageType messageType) { - switch (messageType) { - case CHAT: return CHAT; - case SYSTEM: return SYSTEM_MESSAGE; - } - throw new IllegalArgumentException("Cannot get position from message type!"); - } - } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java index a2e92abdd..5707fba40 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java @@ -1,6 +1,6 @@ package net.minestom.server.network.packet.server.play; -import net.minestom.server.event.item.ArmorEquipEvent; +import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; @@ -14,7 +14,7 @@ import java.util.List; public class EntityEquipmentPacket implements ServerPacket { public int entityId; - public Slot[] slots; + public EquipmentSlot[] slots; public ItemStack[] itemStacks; public EntityEquipmentPacket() { @@ -33,7 +33,7 @@ public class EntityEquipmentPacket implements ServerPacket { } for (int i = 0; i < slots.length; i++) { - final Slot slot = slots[i]; + final EquipmentSlot slot = slots[i]; final ItemStack itemStack = itemStacks[i]; final boolean last = i == slots.length - 1; @@ -52,17 +52,17 @@ public class EntityEquipmentPacket implements ServerPacket { entityId = reader.readVarInt(); boolean hasRemaining = true; - List slots = new LinkedList<>(); + List slots = new LinkedList<>(); List stacks = new LinkedList<>(); while (hasRemaining) { byte slotEnum = reader.readByte(); hasRemaining = (slotEnum & 0x80) == 0x80; - slots.add(Slot.values()[slotEnum & 0x7F]); + slots.add(EquipmentSlot.values()[slotEnum & 0x7F]); stacks.add(reader.readItemStack()); } - this.slots = slots.toArray(new Slot[0]); + this.slots = slots.toArray(new EquipmentSlot[0]); this.itemStacks = stacks.toArray(new ItemStack[0]); } @@ -71,29 +71,4 @@ public class EntityEquipmentPacket implements ServerPacket { return ServerPacketIdentifier.ENTITY_EQUIPMENT; } - public enum Slot { - MAIN_HAND, - OFF_HAND, - BOOTS, - LEGGINGS, - CHESTPLATE, - HELMET; - - @NotNull - public static Slot fromArmorSlot(ArmorEquipEvent.ArmorSlot armorSlot) { - switch (armorSlot) { - case HELMET: - return HELMET; - case CHESTPLATE: - return CHESTPLATE; - case LEGGINGS: - return LEGGINGS; - case BOOTS: - return BOOTS; - } - throw new IllegalStateException("Something weird happened"); - } - - } - } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java index b7942cb5e..1b21cb80e 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java @@ -8,7 +8,7 @@ import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.TickUtils; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; -import org.apache.commons.lang3.Validate; +import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -32,13 +32,13 @@ public class TitlePacket implements ComponentHoldingServerPacket { /** * Constructs a new title packet from an action that can take a component argument. * - * @param action the action + * @param action the action * @param payload the payload * @throws IllegalArgumentException if the action is not {@link Action#SET_TITLE}, - * {@link Action#SET_SUBTITLE} or {@link Action#SET_ACTION_BAR} + * {@link Action#SET_SUBTITLE} or {@link Action#SET_ACTION_BAR} */ public TitlePacket(@NotNull Action action, @NotNull Component payload) { - Validate.isTrue(action == SET_TITLE || action == SET_SUBTITLE || action == SET_ACTION_BAR, "Invalid action type"); + Check.argCondition(action != SET_TITLE && action != SET_SUBTITLE && action != SET_ACTION_BAR, "Invalid action type"); this.action = action; this.payload = payload; } @@ -48,7 +48,7 @@ public class TitlePacket implements ComponentHoldingServerPacket { * * @param action the action * @throws IllegalArgumentException if the action is not {@link Action#RESET}, - * or {@link Action#HIDE} + * or {@link Action#HIDE} */ public TitlePacket(@NotNull Action action) { this.action = action; @@ -57,8 +57,8 @@ public class TitlePacket implements ComponentHoldingServerPacket { /** * Constructs a new title packet for {@link Action#SET_TIMES_AND_DISPLAY}. * - * @param fadeIn the fade in time - * @param stay the stay time + * @param fadeIn the fade in time + * @param stay the stay time * @param fadeOut the fade out time */ public TitlePacket(int fadeIn, int stay, int fadeOut) { diff --git a/src/main/java/net/minestom/server/network/player/PlayerConnection.java b/src/main/java/net/minestom/server/network/player/PlayerConnection.java index 730836dbd..0d890c232 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java @@ -95,7 +95,7 @@ public abstract class PlayerConnection { } /** - * Serializes the packet and send it to the client, skipping the translation phase. + * Serializes the packet and send it to the client, optionally skipping the translation phase. *

* Also responsible for executing {@link ConnectionManager#onPacketSend(ServerPacketConsumer)} consumers. * diff --git a/src/main/java/net/minestom/server/registry/ResourceGatherer.java b/src/main/java/net/minestom/server/registry/ResourceGatherer.java index 7af935d5b..bda94d91c 100644 --- a/src/main/java/net/minestom/server/registry/ResourceGatherer.java +++ b/src/main/java/net/minestom/server/registry/ResourceGatherer.java @@ -5,16 +5,27 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.io.FastBufferedInputStream; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang3.StringUtils; +import net.minestom.server.utils.StringUtils; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.math.BigInteger; import java.net.URL; -import java.nio.file.*; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; /** * Responsible for making sure Minestom has the necessary files to run (notably registry files) @@ -177,13 +188,19 @@ public class ResourceGatherer { } // Verify checksum try (FileInputStream fis = new FileInputStream(target)) { - String sha1Target = DigestUtils.sha1Hex(fis); + MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); + messageDigest.reset(); + // This just converts the sha1 back into a readable string. + String sha1Target = new BigInteger(1, messageDigest.digest(fis.readAllBytes())).toString(16); if (!sha1Target.equals(sha1Source)) { - LOGGER.debug("The checksum test failed after downloading the Minecraft server jar."); - LOGGER.debug("The expected checksum was: {}.", sha1Source); - LOGGER.debug("The calculated checksum was: {}.", sha1Target); + LOGGER.error("The checksum test failed after downloading the Minecraft server jar."); + LOGGER.error("The expected checksum was: {}.", sha1Source); + LOGGER.error("The calculated checksum was: {}.", sha1Target); throw new IOException("Failed to download Minecraft server jar."); } + } catch (NoSuchAlgorithmException e) { + LOGGER.error("Failed to find SHA-1 hashing algorithm in Java Environment."); + throw new IOException("Failed to download Minecraft server jar."); } return target; } diff --git a/src/main/java/net/minestom/server/tag/Tag.java b/src/main/java/net/minestom/server/tag/Tag.java new file mode 100644 index 000000000..63e054446 --- /dev/null +++ b/src/main/java/net/minestom/server/tag/Tag.java @@ -0,0 +1,197 @@ +package net.minestom.server.tag; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.NBT; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Represents a key to retrieve or change a value. + *

+ * All tags are serializable. + * + * @param the tag type + */ +@ApiStatus.NonExtendable +public class Tag { + + private final String key; + private final Function readFunction; + private final BiConsumer writeConsumer; + + private final Supplier defaultValue; + + protected Tag(@NotNull String key, + @NotNull Function readFunction, + @NotNull BiConsumer writeConsumer, + @Nullable Supplier defaultValue) { + this.key = key; + this.readFunction = readFunction; + this.writeConsumer = writeConsumer; + this.defaultValue = defaultValue; + } + + protected Tag(@NotNull String key, + @NotNull Function readFunction, + @NotNull BiConsumer writeConsumer) { + this(key, readFunction, writeConsumer, null); + } + + public @NotNull String getKey() { + return key; + } + + @Contract(value = "_ -> new", pure = true) + public Tag defaultValue(@NotNull Supplier defaultValue) { + return new Tag<>(key, readFunction, writeConsumer, defaultValue); + } + + @Contract(value = "_ -> new", pure = true) + public Tag defaultValue(@NotNull T defaultValue) { + return defaultValue(() -> defaultValue); + } + + @Contract(value = "_, _ -> new", pure = true) + public Tag map(@NotNull Function readMap, + @NotNull Function writeMap) { + return new Tag(key, + // Read + nbtCompound -> { + final var old = readFunction.apply(nbtCompound); + if (old == null) { + return null; + } + return readMap.apply(old); + }, + // Write + (nbtCompound, r) -> { + var n = writeMap.apply(r); + writeConsumer.accept(nbtCompound, n); + }, + // Default value + () -> { + if (defaultValue == null) { + return null; + } + var old = defaultValue.get(); + return readMap.apply(old); + }); + } + + public @Nullable T read(@NotNull NBTCompound nbtCompound) { + if (nbtCompound.containsKey(key)) { + return readFunction.apply(nbtCompound); + } else { + final var supplier = defaultValue; + return supplier != null ? supplier.get() : null; + } + } + + public void write(@NotNull NBTCompound nbtCompound, @Nullable T value) { + if (value != null) { + this.writeConsumer.accept(nbtCompound, value); + } else { + nbtCompound.removeTag(key); + } + } + + public static @NotNull Tag Byte(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getByte(key), + (nbtCompound, value) -> nbtCompound.setByte(key, value)); + } + + public static @NotNull Tag Short(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getShort(key), + (nbtCompound, value) -> nbtCompound.setShort(key, value)); + } + + public static @NotNull Tag Integer(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getInt(key), + (nbtCompound, integer) -> nbtCompound.setInt(key, integer)); + } + + public static @NotNull Tag Long(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getLong(key), + (nbtCompound, value) -> nbtCompound.setLong(key, value)); + } + + public static @NotNull Tag Float(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getFloat(key), + (nbtCompound, value) -> nbtCompound.setFloat(key, value)); + } + + public static @NotNull Tag Double(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getDouble(key), + (nbtCompound, value) -> nbtCompound.setDouble(key, value)); + } + + public static @NotNull Tag ByteArray(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getByteArray(key), + (nbtCompound, value) -> nbtCompound.setByteArray(key, value)); + } + + public static @NotNull Tag String(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getString(key), + (nbtCompound, value) -> nbtCompound.setString(key, value)); + } + + public static @NotNull Tag NBT(@NotNull String key) { + return new Tag<>(key, + nbt -> { + var currentNBT = nbt.get(key); + + // Avoid a NPE when cloning a null variable. + if (currentNBT == null) { + return null; + } + + return currentNBT.deepClone(); + }, + ((nbt, value) -> nbt.set(key, value.deepClone()))); + } + + public static @NotNull Tag IntArray(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getIntArray(key), + (nbtCompound, value) -> nbtCompound.setIntArray(key, value)); + } + + public static @NotNull Tag LongArray(@NotNull String key) { + return new Tag<>(key, + nbtCompound -> nbtCompound.getLongArray(key), + (nbtCompound, value) -> nbtCompound.setLongArray(key, value)); + } + + public static @NotNull Tag Custom(@NotNull String key, @NotNull TagSerializer serializer) { + return new Tag<>(key, + nbtCompound -> { + final var compound = nbtCompound.getCompound(key); + if (compound == null) { + return null; + } + return serializer.read(TagReadable.fromCompound(compound)); + }, + (nbtCompound, value) -> { + var compound = nbtCompound.getCompound(key); + if (compound == null) { + compound = new NBTCompound(); + nbtCompound.set(key, compound); + } + serializer.write(TagWritable.fromCompound(compound), value); + }); + } +} diff --git a/src/main/java/net/minestom/server/tag/TagHandler.java b/src/main/java/net/minestom/server/tag/TagHandler.java new file mode 100644 index 000000000..b53db14a1 --- /dev/null +++ b/src/main/java/net/minestom/server/tag/TagHandler.java @@ -0,0 +1,10 @@ +package net.minestom.server.tag; + +import com.google.common.annotations.Beta; + +/** + * Represents an element which can read and write {@link Tag tags}. + */ +@Beta +public interface TagHandler extends TagReadable, TagWritable { +} diff --git a/src/main/java/net/minestom/server/tag/TagReadable.java b/src/main/java/net/minestom/server/tag/TagReadable.java new file mode 100644 index 000000000..18deb2f02 --- /dev/null +++ b/src/main/java/net/minestom/server/tag/TagReadable.java @@ -0,0 +1,48 @@ +package net.minestom.server.tag; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +/** + * Represents an element which can read {@link Tag tags}. + */ +public interface TagReadable { + + /** + * Reads the specified tag. + * + * @param tag the tag to read + * @param the tag type + * @return the read tag, null if not present + */ + @Nullable T getTag(@NotNull Tag tag); + + /** + * Returns if a tag is present. + * + * @param tag the tag to check + * @return true if the tag is present, false otherwise + */ + boolean hasTag(@NotNull Tag tag); + + /** + * Converts an nbt compound to a tag reader. + * + * @param compound the compound to convert + * @return a {@link TagReadable} capable of reading {@code compound} + */ + static @NotNull TagReadable fromCompound(@NotNull NBTCompound compound) { + return new TagReadable() { + @Override + public @Nullable T getTag(@NotNull Tag tag) { + return tag.read(compound); + } + + @Override + public boolean hasTag(@NotNull Tag tag) { + return compound.containsKey(tag.getKey()); + } + }; + } +} diff --git a/src/main/java/net/minestom/server/tag/TagSerializer.java b/src/main/java/net/minestom/server/tag/TagSerializer.java new file mode 100644 index 000000000..03318bfdf --- /dev/null +++ b/src/main/java/net/minestom/server/tag/TagSerializer.java @@ -0,0 +1,28 @@ +package net.minestom.server.tag; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface used to create custom types compatible with {@link Tag#Custom(String, TagSerializer)}. + * + * @param the type to serialize + */ +public interface TagSerializer { + + /** + * Reads the custom tag from a {@link TagReadable}. + * + * @param reader the reader + * @return the deserialized value + */ + @Nullable T read(@NotNull TagReadable reader); + + /** + * Writes the custom tag to a {@link TagWritable}. + * + * @param writer the writer + * @param value the value to serialize + */ + void write(@NotNull TagWritable writer, @NotNull T value); +} diff --git a/src/main/java/net/minestom/server/tag/TagWritable.java b/src/main/java/net/minestom/server/tag/TagWritable.java new file mode 100644 index 000000000..fe0c2962b --- /dev/null +++ b/src/main/java/net/minestom/server/tag/TagWritable.java @@ -0,0 +1,35 @@ +package net.minestom.server.tag; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +/** + * Represents an element which can write {@link Tag tags}. + */ +public interface TagWritable { + + /** + * Writes the specified type. + * + * @param tag the tag to write + * @param value the tag value, null to remove + * @param the tag type + */ + void setTag(@NotNull Tag tag, @Nullable T value); + + /** + * Converts an nbt compound to a tag writer. + * + * @param compound the compound to convert + * @return a {@link TagWritable} capable of writing {@code compound} + */ + static @NotNull TagWritable fromCompound(@NotNull NBTCompound compound) { + return new TagWritable() { + @Override + public void setTag(@NotNull Tag tag, @Nullable T value) { + tag.write(compound, value); + } + }; + } +} diff --git a/src/main/java/net/minestom/server/utils/StringUtils.java b/src/main/java/net/minestom/server/utils/StringUtils.java new file mode 100644 index 000000000..870380cba --- /dev/null +++ b/src/main/java/net/minestom/server/utils/StringUtils.java @@ -0,0 +1,161 @@ +package net.minestom.server.utils; + +import org.jetbrains.annotations.NotNull; + +public class StringUtils { + + public static final String SPACE = " "; + public static final char SPACE_CHAR = ' '; + + public static int countMatches(@NotNull final CharSequence str, final char ch) { + if (str.length() == 0) { + return 0; + } + int count = 0; + // We could also call str.toCharArray() for faster look ups but that would generate more garbage. + for (int i = 0; i < str.length(); i++) { + if (ch == str.charAt(i)) { + count++; + } + } + return count; + } + + /** + * Applies the Jaro-Winkler distance algorithm to the given strings, providing information about the + * similarity of them. + * + * @param s1 The first string that gets compared. May be null or empty. + * @param s2 The second string that gets compared. May be null or empty. + * @return The Jaro-Winkler score (between 0.0 and 1.0), with a higher value indicating larger similarity. + * @author Thomas Trojer thomas@trojer.net + */ + public static double jaroWinklerScore(final String s1, final String s2) { + // lowest score on empty strings + if (s1 == null || s2 == null || s1.isEmpty() || s2.isEmpty()) { + return 0; + } + // highest score on equal strings + if (s1.equals(s2)) { + return 1; + } + // some score on different strings + int prefixMatch = 0; // exact prefix matches + int matches = 0; // matches (including prefix and ones requiring transpostion) + int transpositions = 0; // matching characters that are not aligned but close together + int maxLength = Math.max(s1.length(), s2.length()); + int maxMatchDistance = Math.max((int) Math.floor(maxLength / 2.0) - 1, 0); // look-ahead/-behind to limit transposed matches + // comparison + final String shorter = s1.length() < s2.length() ? s1 : s2; + final String longer = s1.length() >= s2.length() ? s1 : s2; + for (int i = 0; i < shorter.length(); i++) { + // check for exact matches + boolean match = shorter.charAt(i) == longer.charAt(i); + if (match) { + if (i < 4) { + // prefix match (of at most 4 characters, as described by the algorithm) + prefixMatch++; + } + matches++; + continue; + } + // check fro transposed matches + for (int j = Math.max(i - maxMatchDistance, 0); j < Math.min(i + maxMatchDistance, longer.length()); j++) { + if (i == j) { + // case already covered + continue; + } + // transposition required to match? + match = shorter.charAt(i) == longer.charAt(j); + if (match) { + transpositions++; + break; + } + } + } + // any matching characters? + if (matches == 0) { + return 0; + } + // modify transpositions (according to the algorithm) + transpositions = (int) (transpositions / 2.0); + // non prefix-boosted score + double score = 0.3334 * (matches / (double) longer.length() + matches / (double) shorter.length() + (matches - transpositions) + / (double) matches); + if (score < 0.7) { + return score; + } + // we already have a good match, hence we boost the score proportional to the common prefix + return score + prefixMatch * 0.1 * (1.0 - score); + } + + public static String unescapeJavaString(String st) { + StringBuilder sb = new StringBuilder(st.length()); + + for (int i = 0; i < st.length(); i++) { + char ch = st.charAt(i); + if (ch == '\\') { + char nextChar = (i == st.length() - 1) ? '\\' : st + .charAt(i + 1); + // Octal escape? + if (nextChar >= '0' && nextChar <= '7') { + String code = "" + nextChar; + i++; + if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' + && st.charAt(i + 1) <= '7') { + code += st.charAt(i + 1); + i++; + if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' + && st.charAt(i + 1) <= '7') { + code += st.charAt(i + 1); + i++; + } + } + sb.append((char) Integer.parseInt(code, 8)); + continue; + } + switch (nextChar) { + case '\\': + ch = '\\'; + break; + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + case '\"': + ch = '\"'; + break; + case '\'': + ch = '\''; + break; + // Hex Unicode: u???? + case 'u': + if (i >= st.length() - 5) { + ch = 'u'; + break; + } + int code = Integer.parseInt( + "" + st.charAt(i + 2) + st.charAt(i + 3) + + st.charAt(i + 4) + st.charAt(i + 5), 16); + sb.append(Character.toChars(code)); + i += 5; + continue; + } + i++; + } + sb.append(ch); + } + return sb.toString(); + } +} diff --git a/src/main/java/net/minestom/server/utils/TickUtils.java b/src/main/java/net/minestom/server/utils/TickUtils.java index aa5c98500..7b68618a3 100644 --- a/src/main/java/net/minestom/server/utils/TickUtils.java +++ b/src/main/java/net/minestom/server/utils/TickUtils.java @@ -1,7 +1,7 @@ package net.minestom.server.utils; import net.minestom.server.MinecraftServer; -import org.apache.commons.lang3.Validate; +import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import java.time.Duration; @@ -22,6 +22,7 @@ public class TickUtils { /** * Creates a number of ticks from a given duration, based on {@link MinecraftServer#TICK_MS}. + * * @param duration the duration * @return the number of ticks * @throws IllegalArgumentException if duration is negative @@ -32,14 +33,14 @@ public class TickUtils { /** * Creates a number of ticks from a given duration. - * @param duration the duration + * + * @param duration the duration * @param msPerTick the number of milliseconds per tick * @return the number of ticks * @throws IllegalArgumentException if duration is negative */ public static int fromDuration(@NotNull Duration duration, int msPerTick) { - Validate.isTrue(!duration.isNegative(), "Duration cannot be negative"); - + Check.argCondition(duration.isNegative(), "Duration cannot be negative"); return (int) (duration.toMillis() / msPerTick); } } diff --git a/src/main/java/net/minestom/server/utils/Utils.java b/src/main/java/net/minestom/server/utils/Utils.java index a32c1470d..ac694e669 100644 --- a/src/main/java/net/minestom/server/utils/Utils.java +++ b/src/main/java/net/minestom/server/utils/Utils.java @@ -118,16 +118,11 @@ public final class Utils { } public static void writeSectionBlocks(ByteBuf buffer, Section section) { - /*short count = 0; - for (short id : blocksId) - if (id != 0) - count++;*/ + final short blockCount = section.getBlockCount(); final int bitsPerEntry = section.getBitsPerEntry(); - //buffer.writeShort(count); - // TODO count blocks - buffer.writeShort(200); + buffer.writeShort(blockCount); buffer.writeByte((byte) bitsPerEntry); // Palette diff --git a/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java b/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java index e14a34d84..da2e64a23 100644 --- a/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java +++ b/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java @@ -26,7 +26,6 @@ public class TemporaryCache { public TemporaryCache(long duration, TimeUnit timeUnit, RemovalListener removalListener) { this.cache = CacheBuilder.newBuilder() .expireAfterWrite(duration, timeUnit) - .softValues() .removalListener(removalListener) .build(); } diff --git a/src/main/java/net/minestom/server/utils/mojang/MojangUtils.java b/src/main/java/net/minestom/server/utils/mojang/MojangUtils.java index b42b98bbf..04e0df2d7 100644 --- a/src/main/java/net/minestom/server/utils/mojang/MojangUtils.java +++ b/src/main/java/net/minestom/server/utils/mojang/MojangUtils.java @@ -27,7 +27,6 @@ public final class MojangUtils { .softValues() .build(); - @Nullable public static JsonObject fromUuid(@NotNull String uuid) { diff --git a/src/test/java/demo/commands/EchoCommand.java b/src/test/java/demo/commands/EchoCommand.java index e04800fc6..67932cbee 100644 --- a/src/test/java/demo/commands/EchoCommand.java +++ b/src/test/java/demo/commands/EchoCommand.java @@ -1,24 +1,32 @@ package demo.commands; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.arguments.ArgumentType; import net.minestom.server.command.builder.arguments.minecraft.ArgumentComponent; +import net.minestom.server.command.builder.arguments.minecraft.ArgumentUUID; public class EchoCommand extends Command { public EchoCommand() { super("echo"); this.setDefaultExecutor((sender, context) -> sender.sendMessage( - Component.text("Usage: /echo ") + Component.text("Usage: /echo [uuid]") .hoverEvent(Component.text("Click to get this command.") .clickEvent(ClickEvent.suggestCommand("/echo "))))); ArgumentComponent json = ArgumentType.Component("json"); + ArgumentUUID uuid = ArgumentType.UUID("uuid"); this.addSyntax((sender, context) -> { sender.sendMessage(context.get(json)); }, json); + + this.addSyntax((sender, context) -> { + sender.sendMessage(Identity.identity(context.get(uuid)), context.get(json), MessageType.CHAT); + }, uuid, json); } } diff --git a/src/test/java/readwritepackets/ReadWritePackets.java b/src/test/java/readwritepackets/ReadWritePackets.java index 5f28d8152..30d6ace81 100644 --- a/src/test/java/readwritepackets/ReadWritePackets.java +++ b/src/test/java/readwritepackets/ReadWritePackets.java @@ -2,6 +2,7 @@ package readwritepackets; import com.google.common.reflect.ClassPath; import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.ClientPacket; import net.minestom.server.network.packet.server.ServerPacket; @@ -67,7 +68,7 @@ public class ReadWritePackets { // requires at least one slot and one item EntityEquipmentPacket p = new EntityEquipmentPacket(); p.itemStacks = new ItemStack[]{ItemStack.AIR}; - p.slots = new EntityEquipmentPacket.Slot[]{EntityEquipmentPacket.Slot.MAIN_HAND}; + p.slots = new EquipmentSlot[]{EquipmentSlot.MAIN_HAND}; packet = (T) p; } else { packet = (T) constructor.newInstance();