Merge branch 'master' into event-api

This commit is contained in:
TheMode 2021-06-02 06:41:42 +02:00
commit 047d4a92ac
84 changed files with 1539 additions and 515 deletions

3
.github/javadoc-publish-clear vendored Normal file
View File

@ -0,0 +1,3 @@
**/*
!.git
!CNAME

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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}"

View File

@ -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

View File

@ -277,7 +277,7 @@
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="scope" value="public"/>
<property name="accessModifiers" value="public"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowedAnnotations" value="Override, Test"/>

View File

@ -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));
}
}

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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> 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> T getOrDefault(@NotNull Argument<T> argument, T defaultValue) {
return getOrDefault(argument.getId(), defaultValue);
}
public <T> T getOrDefault(@NotNull String identifier, T defaultValue) {
T value;
return (value = get(identifier)) != null ? value : defaultValue;
}
public boolean has(@NotNull Argument<?> argument) {

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -195,23 +195,48 @@ public abstract class Argument<T> {
return this;
}
/**
* Sets the default value supplier of the argument.
*
* @param defaultValue the default argument value
* @return 'this' for chaining
*/
@NotNull
public Argument<T> setDefaultValue(@Nullable T defaultValue) {
public Argument<T> 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.
* <p>
* Note: This will not automatically filter arguments by user input.
*
* @param suggestionCallback The suggestion callback to set.
* @return 'this' for chaining
*/
@Beta
public Argument<T> 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;
}

View File

@ -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<CommandResult> {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<String> {
}
}
return StringEscapeUtils.unescapeJava(input);
return StringUtils.unescapeJavaString(input);
}
@Override

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<String> {

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;

View File

@ -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;

View File

@ -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!
* <p>
* The following packets are sent to viewers (check are performed in this order):
* <ol>
* <li>{@link EntityTeleportPacket} if {@code distanceX > 8 || distanceY > 8 || distanceZ > 8}
* <i>(performed using {@link #synchronizePosition()})</i></li>
* <li>{@link EntityPositionAndRotationPacket} if {@code positionChange && viewChange}</li>
* <li>{@link EntityPositionPacket} if {@code positionChange}</li>
* <li>{@link EntityRotationPacket} and {@link EntityHeadLookPacket} if {@code viewChange}</li>
* </ol>
* 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):
* <ol>
* <li>{@link EntityTeleportPacket} if {@code distanceX > 8 || distanceY > 8 || distanceZ > 8}
* <i>(performed using {@link #synchronizePosition(boolean)})</i></li>
* <li>{@link EntityPositionAndRotationPacket} if {@code positionChange && viewChange}</li>
* <li>{@link EntityPositionPacket} if {@code positionChange}</li>
* <li>{@link EntityRotationPacket} and {@link EntityHeadLookPacket} if {@code viewChange}</li>
* </ol>
* 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();

View File

@ -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");
}
}

View File

@ -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.
*

View File

@ -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;

View File

@ -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();
}
}
}

View File

@ -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 {

View File

@ -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();
}
}

View File

@ -108,22 +108,27 @@ public interface EventHandler extends IExtensionObserver {
*/
default <E extends Event> void callEvent(@NotNull Class<E> 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<EventCallback> 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<EventCallback> 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);
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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.
* <p>
* If it does not exist in the extension directory, it will be copied from inside the jar.
* <p>
* 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.
* <p>
* If it does not exist in the extension directory, it will be copied from inside the jar.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.

View File

@ -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<Extension> getExtensions() {
return immutableExtensions.values();

View File

@ -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;
}
}

View File

@ -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<Shape, Facing> 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;

View File

@ -53,6 +53,8 @@ public class Section implements PublicCloneable<Section> {
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<Section> {
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<Section> {
{
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<Section> {
this.hasPalette = section.hasPalette;
this.blocks = section.blocks;
this.blockCount = section.blockCount;
}
/**
@ -171,12 +187,22 @@ public class Section implements PublicCloneable<Section> {
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> {
section.blocks = blocks.clone();
section.paletteBlockMap = paletteBlockMap.clone();
section.blockPaletteMap = blockPaletteMap.clone();
section.blockCount = blockCount;
return section;
} catch (CloneNotSupportedException e) {
MinecraftServer.getExceptionManager().handleException(e);

View File

@ -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<ItemStack> itemStacks = new ArrayList<>(slots.length);
// Fill items
for (EntityEquipmentPacket.Slot slot : slots) {
for (EquipmentSlot slot : slots) {
final ItemStack equipment = getEquipment(slot);
itemStacks.add(equipment);
}

View File

@ -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;

View File

@ -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();

View File

@ -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> T getOrDefault(@NotNull ItemTag<T> tag, @Nullable T defaultValue) {
var key = tag.getKey();
if (nbt.containsKey(key)) {
return tag.read(toNBT());
} else {
return defaultValue;
}
@Override
public <T> @Nullable T getTag(@NotNull Tag<T> tag) {
return tag.read(nbt);
}
public <T> @Nullable T get(@NotNull ItemTag<T> 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> T getOrDefault(@NotNull Tag<T> 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 <T> @Nullable T get(@NotNull Tag<T> tag) {
return getTag(tag);
}
}

View File

@ -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 <T> @NotNull ItemMetaBuilder set(@NotNull ItemTag<T> tag, @Nullable T value) {
if (value != null) {
tag.write(nbt, value);
} else {
this.nbt.removeTag(tag.getKey());
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(nbt, value);
}
public <T> @NotNull ItemMetaBuilder set(@NotNull Tag<T> tag, @Nullable T value) {
setTag(tag, value);
return this;
}

View File

@ -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;
* <p>
* An item stack cannot be null, {@link ItemStack#AIR} should be used instead.
*/
public final class ItemStack implements HoverEventSource<HoverEvent.ShowItem> {
public final class ItemStack implements TagReadable, HoverEventSource<HoverEvent.ShowItem> {
/**
* Constant AIR item. Should be used instead of 'null'.
@ -191,6 +193,21 @@ public final class ItemStack implements HoverEventSource<HoverEvent.ShowItem> {
.stackingRule(stackingRule);
}
@Contract(value = "_, _ -> new", pure = true)
public <T> @NotNull ItemStack withTag(@NotNull Tag<T> tag, @Nullable T value) {
return builder().meta(metaBuilder -> metaBuilder.set(tag, value)).build();
}
@Override
public <T> @Nullable T getTag(@NotNull Tag<T> tag) {
return meta.getTag(tag);
}
@Override
public boolean hasTag(@NotNull Tag<?> tag) {
return meta.hasTag(tag);
}
@Override
public @NotNull HoverEvent<HoverEvent.ShowItem> asHoverEvent(@NotNull UnaryOperator<HoverEvent.ShowItem> op) {
return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(this.material,

View File

@ -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<T> {
/**
* @deprecated use {@link Tag}.
*/
@Deprecated
public class ItemTag<T> extends Tag<T> {
private final String key;
private final Function<NBTCompound, T> readFunction;
private final BiConsumer<NBTCompound, T> writeConsumer;
private ItemTag(@NotNull String key,
@NotNull Function<NBTCompound, T> readFunction,
@NotNull BiConsumer<NBTCompound, T> writeConsumer) {
this.key = key;
this.readFunction = readFunction;
this.writeConsumer = writeConsumer;
protected ItemTag(@NotNull String key, @NotNull Function<NBTCompound, T> readFunction, @NotNull BiConsumer<NBTCompound, T> writeConsumer) {
super(key, readFunction, writeConsumer);
}
public @NotNull String getKey() {
return key;
public static @NotNull Tag<Byte> Byte(@NotNull String key) {
return Tag.Byte(key);
}
protected T read(@NotNull NBTCompound nbtCompound) {
return readFunction.apply(nbtCompound);
public static @NotNull Tag<Short> 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> Integer(@NotNull String key) {
return Tag.Integer(key);
}
public static @NotNull ItemTag<Byte> Byte(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getByte(key),
(nbtCompound, value) -> nbtCompound.setByte(key, value));
public static @NotNull Tag<Long> Long(@NotNull String key) {
return Tag.Long(key);
}
public static @NotNull ItemTag<Short> Short(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getShort(key),
(nbtCompound, value) -> nbtCompound.setShort(key, value));
public static @NotNull Tag<Float> Float(@NotNull String key) {
return Tag.Float(key);
}
public static @NotNull ItemTag<Integer> Integer(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getInt(key),
(nbtCompound, integer) -> nbtCompound.setInt(key, integer));
public static @NotNull Tag<Double> Double(@NotNull String key) {
return Tag.Double(key);
}
public static @NotNull ItemTag<Long> Long(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getLong(key),
(nbtCompound, value) -> nbtCompound.setLong(key, value));
public static @NotNull Tag<byte[]> ByteArray(@NotNull String key) {
return Tag.ByteArray(key);
}
public static @NotNull ItemTag<Float> Float(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getFloat(key),
(nbtCompound, value) -> nbtCompound.setFloat(key, value));
public static @NotNull Tag<String> String(@NotNull String key) {
return Tag.String(key);
}
public static @NotNull ItemTag<Double> Double(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getDouble(key),
(nbtCompound, value) -> nbtCompound.setDouble(key, value));
public static @NotNull Tag<NBT> NBT(@NotNull String key) {
return Tag.NBT(key);
}
public static @NotNull ItemTag<byte[]> ByteArray(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getByteArray(key),
(nbtCompound, value) -> nbtCompound.setByteArray(key, value));
public static @NotNull Tag<int[]> IntArray(@NotNull String key) {
return Tag.IntArray(key);
}
public static @NotNull ItemTag<String> String(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getString(key),
(nbtCompound, value) -> nbtCompound.setString(key, value));
}
public static @NotNull ItemTag<NBT> 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<int[]> IntArray(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getIntArray(key),
(nbtCompound, value) -> nbtCompound.setIntArray(key, value));
}
public static @NotNull ItemTag<long[]> LongArray(@NotNull String key) {
return new ItemTag<>(key,
nbtCompound -> nbtCompound.getLongArray(key),
(nbtCompound, value) -> nbtCompound.setLongArray(key, value));
public static @NotNull Tag<long[]> LongArray(@NotNull String key) {
return Tag.LongArray(key);
}
}

View File

@ -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<Player> 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<PlayerChatEvent, Component> formatFunction = playerChatEvent.getChatFormatFunction();
Component textObject;
@ -55,15 +64,10 @@ public class ChatMessageListener {
final Collection<Player> 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) {

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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<ChatPosition> acceptedPositions;
ChatMessageType(@NotNull EnumSet<ChatPosition> 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)");
}
}
}

View File

@ -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)");
}
}
}

View File

@ -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<Player> 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);
}
}

View File

@ -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.
* <p>
*
* @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<Player, Double> 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<ClientPacketConsumer> 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<ServerPacketConsumer> 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);
}

View File

@ -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);
}

View File

@ -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());

View File

@ -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<Component> 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!");
}
}
}

View File

@ -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<Slot> slots = new LinkedList<>();
List<EquipmentSlot> slots = new LinkedList<>();
List<ItemStack> 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");
}
}
}

View File

@ -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) {

View File

@ -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.
* <p>
* Also responsible for executing {@link ConnectionManager#onPacketSend(ServerPacketConsumer)} consumers.
*

View File

@ -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;
}

View File

@ -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.
* <p>
* All tags are serializable.
*
* @param <T> the tag type
*/
@ApiStatus.NonExtendable
public class Tag<T> {
private final String key;
private final Function<NBTCompound, T> readFunction;
private final BiConsumer<NBTCompound, T> writeConsumer;
private final Supplier<T> defaultValue;
protected Tag(@NotNull String key,
@NotNull Function<NBTCompound, T> readFunction,
@NotNull BiConsumer<NBTCompound, T> writeConsumer,
@Nullable Supplier<T> defaultValue) {
this.key = key;
this.readFunction = readFunction;
this.writeConsumer = writeConsumer;
this.defaultValue = defaultValue;
}
protected Tag(@NotNull String key,
@NotNull Function<NBTCompound, T> readFunction,
@NotNull BiConsumer<NBTCompound, T> writeConsumer) {
this(key, readFunction, writeConsumer, null);
}
public @NotNull String getKey() {
return key;
}
@Contract(value = "_ -> new", pure = true)
public Tag<T> defaultValue(@NotNull Supplier<T> defaultValue) {
return new Tag<>(key, readFunction, writeConsumer, defaultValue);
}
@Contract(value = "_ -> new", pure = true)
public Tag<T> defaultValue(@NotNull T defaultValue) {
return defaultValue(() -> defaultValue);
}
@Contract(value = "_, _ -> new", pure = true)
public <R> Tag<R> map(@NotNull Function<T, R> readMap,
@NotNull Function<R, T> writeMap) {
return new Tag<R>(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> Byte(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getByte(key),
(nbtCompound, value) -> nbtCompound.setByte(key, value));
}
public static @NotNull Tag<Short> Short(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getShort(key),
(nbtCompound, value) -> nbtCompound.setShort(key, value));
}
public static @NotNull Tag<Integer> Integer(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getInt(key),
(nbtCompound, integer) -> nbtCompound.setInt(key, integer));
}
public static @NotNull Tag<Long> Long(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getLong(key),
(nbtCompound, value) -> nbtCompound.setLong(key, value));
}
public static @NotNull Tag<Float> Float(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getFloat(key),
(nbtCompound, value) -> nbtCompound.setFloat(key, value));
}
public static @NotNull Tag<Double> Double(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getDouble(key),
(nbtCompound, value) -> nbtCompound.setDouble(key, value));
}
public static @NotNull Tag<byte[]> ByteArray(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getByteArray(key),
(nbtCompound, value) -> nbtCompound.setByteArray(key, value));
}
public static @NotNull Tag<String> String(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getString(key),
(nbtCompound, value) -> nbtCompound.setString(key, value));
}
public static @NotNull Tag<NBT> 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<int[]> IntArray(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getIntArray(key),
(nbtCompound, value) -> nbtCompound.setIntArray(key, value));
}
public static @NotNull Tag<long[]> LongArray(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getLongArray(key),
(nbtCompound, value) -> nbtCompound.setLongArray(key, value));
}
public static <T> @NotNull Tag<T> Custom(@NotNull String key, @NotNull TagSerializer<T> 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);
});
}
}

View File

@ -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 {
}

View File

@ -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 <T> the tag type
* @return the read tag, null if not present
*/
<T> @Nullable T getTag(@NotNull Tag<T> 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 <T> @Nullable T getTag(@NotNull Tag<T> tag) {
return tag.read(compound);
}
@Override
public boolean hasTag(@NotNull Tag<?> tag) {
return compound.containsKey(tag.getKey());
}
};
}
}

View File

@ -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 <T> the type to serialize
*/
public interface TagSerializer<T> {
/**
* 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);
}

View File

@ -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 <T> the tag type
*/
<T> void setTag(@NotNull Tag<T> 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 <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(compound, value);
}
};
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -26,7 +26,6 @@ public class TemporaryCache<T> {
public TemporaryCache(long duration, TimeUnit timeUnit, RemovalListener<UUID, T> removalListener) {
this.cache = CacheBuilder.newBuilder()
.expireAfterWrite(duration, timeUnit)
.softValues()
.removalListener(removalListener)
.build();
}

View File

@ -27,7 +27,6 @@ public final class MojangUtils {
.softValues()
.build();
@Nullable
public static JsonObject fromUuid(@NotNull String uuid) {

View File

@ -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 <json>")
Component.text("Usage: /echo <json> [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);
}
}

View File

@ -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();