mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-28 12:07:42 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
399c857dc4
3
.gitignore
vendored
3
.gitignore
vendored
@ -53,3 +53,6 @@ gradle-app.setting
|
||||
|
||||
# When running the demo we generate the extensions folder
|
||||
/extensions/
|
||||
|
||||
# When compiling we get a docs folder
|
||||
/docs
|
28
build.gradle
28
build.gradle
@ -5,7 +5,7 @@ plugins {
|
||||
id 'java'
|
||||
id 'maven-publish'
|
||||
id 'net.ltgt.apt' version '0.10'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.4.21'
|
||||
id 'checkstyle'
|
||||
}
|
||||
|
||||
@ -106,11 +106,14 @@ dependencies {
|
||||
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.6.2')
|
||||
|
||||
// Netty
|
||||
api 'io.netty:netty-handler:4.1.55.Final'
|
||||
api 'io.netty:netty-codec:4.1.55.Final'
|
||||
api 'io.netty:netty-transport-native-epoll:4.1.55.Final:linux-x86_64'
|
||||
api 'io.netty:netty-transport-native-kqueue:4.1.55.Final:osx-x86_64'
|
||||
api 'io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.2.Final:linux-x86_64'
|
||||
api 'io.netty:netty-handler:4.1.58.Final'
|
||||
api 'io.netty:netty-codec:4.1.58.Final'
|
||||
api 'io.netty:netty-transport-native-epoll:4.1.58.Final:linux-x86_64'
|
||||
api 'io.netty:netty-transport-native-kqueue:4.1.58.Final:osx-x86_64'
|
||||
api 'io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.3.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.4.4'
|
||||
@ -123,7 +126,7 @@ dependencies {
|
||||
api 'com.github.Articdive:Jnoise:1.0.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.rocksdb/rocksdbjni
|
||||
api 'org.rocksdb:rocksdbjni:6.13.3'
|
||||
api 'org.rocksdb:rocksdbjni:6.15.2'
|
||||
|
||||
// Logging
|
||||
api 'org.apache.logging.log4j:log4j-core:2.14.0'
|
||||
@ -143,14 +146,15 @@ dependencies {
|
||||
api "org.spongepowered:mixin:${mixinVersion}"
|
||||
|
||||
// Path finding
|
||||
api 'com.github.MadMartian:hydrazine-path-finding:1.5.1'
|
||||
api 'com.github.MadMartian:hydrazine-path-finding:1.5.2'
|
||||
|
||||
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${project.kotlinVersion}"
|
||||
api "org.jetbrains.kotlin:kotlin-reflect:${project.kotlinVersion}"
|
||||
|
||||
// NBT parsing/manipulation/saving
|
||||
api("com.github.jglrxavpok:Hephaistos:${project.hephaistos_version}")
|
||||
api("com.github.jglrxavpok:Hephaistos:${project.hephaistos_version}:gson")
|
||||
api("com.github.jglrxavpok:Hephaistos:${project.hephaistos_version}") {
|
||||
api("com.github.jglrxavpok:Hephaistos:${project.hephaistosVersion}")
|
||||
api("com.github.jglrxavpok:Hephaistos:${project.hephaistosVersion}:gson")
|
||||
api("com.github.jglrxavpok:Hephaistos:${project.hephaistosVersion}") {
|
||||
capabilities {
|
||||
requireCapability("org.jglrxavpok.nbt:Hephaistos-gson")
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
asmVersion=9.0
|
||||
mixinVersion=0.8.1
|
||||
hephaistos_version=v1.1.5
|
||||
hephaistosVersion=v1.1.7
|
||||
kotlinVersion=1.4.21
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,7 +1,6 @@
|
||||
#Thu Aug 01 13:49:10 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=fd591a34af7385730970399f473afabdb8b28d57fd97d6625c388d090039d6fd
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||
distributionSha256Sum=0f316a67b971b7b571dac7215dcf2591a30994b3450e0629925ffcfe2c68cc5c
|
||||
|
53
gradlew
vendored
53
gradlew
vendored
@ -1,5 +1,21 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
@ -66,6 +82,7 @@ esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
@ -109,10 +126,11 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
@ -138,19 +156,19 @@ if $cygwin ; then
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@ -159,14 +177,9 @@ save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
43
gradlew.bat
vendored
43
gradlew.bat
vendored
@ -1,3 +1,19 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@ -35,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@ -45,28 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
@ -1,7 +1,6 @@
|
||||
// AUTOGENERATED by net.minestom.codegen.RegistriesGenerator
|
||||
package net.minestom.server.registry;
|
||||
|
||||
import java.util.HashMap;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.fluids.Fluid;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
@ -16,6 +15,8 @@ import net.minestom.server.utils.NamespaceID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* AUTOGENERATED
|
||||
*/
|
||||
@ -129,19 +130,19 @@ public final class Registries {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corresponding EntityType matching the given id. Returns 'PIG' if none match.
|
||||
* Returns the corresponding EntityType matching the given id. Returns null if none match.
|
||||
*/
|
||||
@NotNull
|
||||
@Nullable
|
||||
public static EntityType getEntityType(String id) {
|
||||
return getEntityType(NamespaceID.from(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corresponding EntityType matching the given id. Returns 'PIG' if none match.
|
||||
* Returns the corresponding EntityType matching the given id. Returns null if none match.
|
||||
*/
|
||||
@NotNull
|
||||
@Nullable
|
||||
public static EntityType getEntityType(NamespaceID id) {
|
||||
return entityTypes.getOrDefault(id, EntityType.PIG);
|
||||
return entityTypes.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,7 +35,7 @@ public class BlockEnumGenerator extends MinestomEnumGenerator<BlockContainer> {
|
||||
private final String targetVersion;
|
||||
private final File targetFolder;
|
||||
|
||||
private CodeBlock.Builder staticBlock = CodeBlock.builder();
|
||||
private final CodeBlock.Builder staticBlock = CodeBlock.builder();
|
||||
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
@ -161,8 +161,6 @@ public class BlockEnumGenerator extends MinestomEnumGenerator<BlockContainer> {
|
||||
}
|
||||
|
||||
return blocks;
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,8 +176,6 @@ public class BlockEnumGenerator extends MinestomEnumGenerator<BlockContainer> {
|
||||
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(blockFile))) {
|
||||
PrismarineJSBlock[] blocks = gson.fromJson(bufferedReader, PrismarineJSBlock[].class);
|
||||
return Arrays.asList(blocks);
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,8 +85,6 @@ public class ItemEnumGenerator extends MinestomEnumGenerator<ItemContainer> {
|
||||
items.add(item);
|
||||
}
|
||||
return items;
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.minestom.server.map;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
@ -10,7 +13,7 @@ import java.util.Map;
|
||||
public class PaletteGenerator {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Map<Integer, Integer> colors = new HashMap<>();
|
||||
Int2IntMap colors = new Int2IntOpenHashMap();
|
||||
int highestIndex = 0;
|
||||
for(MapColors c : MapColors.values()) {
|
||||
if (c == MapColors.NONE)
|
||||
|
@ -6,10 +6,10 @@ import net.minestom.server.command.CommandManager;
|
||||
import net.minestom.server.data.DataManager;
|
||||
import net.minestom.server.data.DataType;
|
||||
import net.minestom.server.data.SerializableData;
|
||||
import net.minestom.server.entity.EntityManager;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.GlobalEventHandler;
|
||||
import net.minestom.server.exception.ExceptionManager;
|
||||
import net.minestom.server.extensions.Extension;
|
||||
import net.minestom.server.extensions.ExtensionManager;
|
||||
import net.minestom.server.fluids.Fluid;
|
||||
@ -56,7 +56,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* The main server class used to start the server and retrieve all the managers.
|
||||
@ -68,7 +67,7 @@ public final class MinecraftServer {
|
||||
|
||||
public final static Logger LOGGER = LoggerFactory.getLogger(MinecraftServer.class);
|
||||
|
||||
public static final String VERSION_NAME = "1.16.4";
|
||||
public static final String VERSION_NAME = "1.16.5";
|
||||
public static final int PROTOCOL_VERSION = 754;
|
||||
|
||||
// Threads
|
||||
@ -101,11 +100,12 @@ public final class MinecraftServer {
|
||||
private static int nettyThreadCount = Runtime.getRuntime().availableProcessors();
|
||||
private static boolean processNettyErrors = false;
|
||||
|
||||
private static ExceptionManager exceptionManager;
|
||||
|
||||
// In-Game Manager
|
||||
private static ConnectionManager connectionManager;
|
||||
private static InstanceManager instanceManager;
|
||||
private static BlockManager blockManager;
|
||||
private static EntityManager entityManager;
|
||||
private static CommandManager commandManager;
|
||||
private static RecipeManager recipeManager;
|
||||
private static StorageManager storageManager;
|
||||
@ -133,6 +133,7 @@ public final class MinecraftServer {
|
||||
private static int entityViewDistance = 5;
|
||||
private static int compressionThreshold = 256;
|
||||
private static boolean packetCaching = true;
|
||||
private static boolean groupedPacket = true;
|
||||
private static ResponseDataConsumer responseDataConsumer;
|
||||
private static String brandName = "Minestom";
|
||||
private static Difficulty difficulty = Difficulty.NORMAL;
|
||||
@ -142,8 +143,11 @@ public final class MinecraftServer {
|
||||
public static MinecraftServer init() {
|
||||
if (minecraftServer != null) // don't init twice
|
||||
return minecraftServer;
|
||||
|
||||
// Initialize the ExceptionManager at first
|
||||
exceptionManager = new ExceptionManager();
|
||||
|
||||
extensionManager = new ExtensionManager();
|
||||
extensionManager.loadExtensions();
|
||||
|
||||
// warmup/force-init registries
|
||||
// without this line, registry types that are not loaded explicitly will have an internal empty registry in Registries
|
||||
@ -167,7 +171,6 @@ public final class MinecraftServer {
|
||||
|
||||
instanceManager = new InstanceManager();
|
||||
blockManager = new BlockManager();
|
||||
entityManager = new EntityManager();
|
||||
commandManager = new CommandManager();
|
||||
recipeManager = new RecipeManager();
|
||||
storageManager = new StorageManager();
|
||||
@ -217,7 +220,6 @@ public final class MinecraftServer {
|
||||
* @throws NullPointerException if {@code brandName} is null
|
||||
*/
|
||||
public static void setBrandName(@NotNull String brandName) {
|
||||
Check.notNull(brandName, "The brand name cannot be null");
|
||||
MinecraftServer.brandName = brandName;
|
||||
|
||||
PacketUtils.sendGroupedPacket(connectionManager.getOnlinePlayers(), PluginMessagePacket.getBrandPacket());
|
||||
@ -275,7 +277,6 @@ public final class MinecraftServer {
|
||||
* @param difficulty the new server difficulty
|
||||
*/
|
||||
public static void setDifficulty(@NotNull Difficulty difficulty) {
|
||||
Check.notNull(difficulty, "The server difficulty cannot be null.");
|
||||
MinecraftServer.difficulty = difficulty;
|
||||
|
||||
// Send the packet to all online players
|
||||
@ -337,16 +338,6 @@ public final class MinecraftServer {
|
||||
return blockManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the manager handling waiting players.
|
||||
*
|
||||
* @return the entity manager
|
||||
*/
|
||||
public static EntityManager getEntityManager() {
|
||||
checkInitStatus(entityManager);
|
||||
return entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the manager handling commands.
|
||||
*
|
||||
@ -417,6 +408,16 @@ public final class MinecraftServer {
|
||||
return benchmarkManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exception manager for exception handling.
|
||||
*
|
||||
* @return the exception manager
|
||||
*/
|
||||
public static ExceptionManager getExceptionManager() {
|
||||
checkInitStatus(exceptionManager);
|
||||
return exceptionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the manager handling server connections.
|
||||
*
|
||||
@ -478,9 +479,7 @@ public final class MinecraftServer {
|
||||
MinecraftServer.chunkViewDistance = chunkViewDistance;
|
||||
if (started) {
|
||||
|
||||
final Collection<Player> players = connectionManager.getOnlinePlayers();
|
||||
|
||||
players.forEach(player -> {
|
||||
for (final Player player : connectionManager.getOnlinePlayers()) {
|
||||
final Chunk playerChunk = player.getChunk();
|
||||
if (playerChunk != null) {
|
||||
|
||||
@ -490,7 +489,7 @@ public final class MinecraftServer {
|
||||
|
||||
player.refreshVisibleChunks(playerChunk);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -514,12 +513,12 @@ public final class MinecraftServer {
|
||||
"The entity view distance must be between 0 and 32");
|
||||
MinecraftServer.entityViewDistance = entityViewDistance;
|
||||
if (started) {
|
||||
connectionManager.getOnlinePlayers().forEach(player -> {
|
||||
for (final Player player : connectionManager.getOnlinePlayers()) {
|
||||
final Chunk playerChunk = player.getChunk();
|
||||
if (playerChunk != null) {
|
||||
player.refreshVisibleEntities(playerChunk);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -551,7 +550,8 @@ public final class MinecraftServer {
|
||||
* This feature allows some packets (implementing the {@link net.minestom.server.utils.cache.CacheablePacket} to be cached
|
||||
* in order to do not have to be written and compressed over and over again), this is especially useful for chunk and light packets.
|
||||
* <p>
|
||||
* It is enabled by default and it is our recommendation, you should only disable it if you want to focus on low memory usage
|
||||
* It is enabled by default and it is our recommendation,
|
||||
* you should only disable it if you want to focus on low memory usage
|
||||
* at the cost of many packet writing and compression.
|
||||
*
|
||||
* @return true if the packet caching feature is enabled, false otherwise
|
||||
@ -572,6 +572,34 @@ public final class MinecraftServer {
|
||||
MinecraftServer.packetCaching = packetCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the packet caching feature is enabled.
|
||||
* <p>
|
||||
* This features allow sending the exact same packet/buffer to multiple connections.
|
||||
* It does provide a great performance benefit by allocating and writing/compressing only once.
|
||||
* <p>
|
||||
* It is enabled by default and it is our recommendation,
|
||||
* you should only disable it if you want to modify packet per-players instead of sharing it.
|
||||
* Disabling the feature would result in performance decrease.
|
||||
*
|
||||
* @return true if the grouped packet feature is enabled, false otherwise
|
||||
*/
|
||||
public static boolean hasGroupedPacket() {
|
||||
return groupedPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disable grouped packet.
|
||||
*
|
||||
* @param groupedPacket true to enable grouped packet
|
||||
* @throws IllegalStateException if this is called after the server started
|
||||
* @see #hasGroupedPacket()
|
||||
*/
|
||||
public static void setGroupedPacket(boolean groupedPacket) {
|
||||
Check.stateCondition(started, "You cannot change the grouped packet value after the server has been started.");
|
||||
MinecraftServer.groupedPacket = groupedPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the consumer executed to show server-list data.
|
||||
*
|
||||
@ -675,7 +703,7 @@ public final class MinecraftServer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the server should process netty errors and other unnecessary netty events
|
||||
* Gets if the server should process netty errors and other unnecessary netty events.
|
||||
*
|
||||
* @return should process netty errors
|
||||
*/
|
||||
@ -684,7 +712,7 @@ public final class MinecraftServer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the server should process netty errors and other unnecessary netty events
|
||||
* Sets if the server should process netty errors and other unnecessary netty events.
|
||||
* false is faster
|
||||
*
|
||||
* @param processNettyErrors should process netty errors
|
||||
@ -718,17 +746,25 @@ public final class MinecraftServer {
|
||||
nettyServer.init();
|
||||
nettyServer.start(address, port);
|
||||
|
||||
final long t1 = -System.nanoTime();
|
||||
// Init extensions
|
||||
// TODO: Extensions should handle depending on each other and have a load-order.
|
||||
extensionManager.getExtensions().forEach(Extension::preInitialize);
|
||||
extensionManager.getExtensions().forEach(Extension::initialize);
|
||||
extensionManager.getExtensions().forEach(Extension::postInitialize);
|
||||
if (extensionManager.shouldLoadOnStartup()) {
|
||||
final long loadStartTime = System.nanoTime();
|
||||
// Load extensions
|
||||
extensionManager.loadExtensions();
|
||||
// Init extensions
|
||||
// TODO: Extensions should handle depending on each other and have a load-order.
|
||||
extensionManager.getExtensions().forEach(Extension::preInitialize);
|
||||
extensionManager.getExtensions().forEach(Extension::initialize);
|
||||
extensionManager.getExtensions().forEach(Extension::postInitialize);
|
||||
|
||||
final double loadTime = MathUtils.round((t1 + System.nanoTime()) / 1_000_000D, 2);
|
||||
LOGGER.info("Extensions loaded in {}ms", loadTime);
|
||||
final double loadTime = MathUtils.round((System.nanoTime() - loadStartTime) / 1_000_000D, 2);
|
||||
LOGGER.info("Extensions loaded in {}ms", loadTime);
|
||||
} else {
|
||||
LOGGER.warn("Extension loadOnStartup option is set to false, extensions are therefore neither loaded or initialized.");
|
||||
}
|
||||
|
||||
LOGGER.info("Minestom server started successfully.");
|
||||
|
||||
commandManager.startConsoleThread();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,12 +1,11 @@
|
||||
package net.minestom.server;
|
||||
|
||||
import com.google.common.collect.Queues;
|
||||
import net.minestom.server.entity.EntityManager;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.thread.PerInstanceThreadProvider;
|
||||
import net.minestom.server.thread.ThreadProvider;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
@ -50,35 +49,39 @@ public final class UpdateManager {
|
||||
* Starts the server loop in the update thread.
|
||||
*/
|
||||
protected void start() {
|
||||
final EntityManager entityManager = MinecraftServer.getEntityManager();
|
||||
final ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
|
||||
|
||||
updateExecutionService.scheduleAtFixedRate(() -> {
|
||||
if (stopRequested) {
|
||||
updateExecutionService.shutdown();
|
||||
return;
|
||||
try {
|
||||
if (stopRequested) {
|
||||
updateExecutionService.shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
long currentTime = System.nanoTime();
|
||||
final long tickStart = System.currentTimeMillis();
|
||||
|
||||
// Tick start callbacks
|
||||
doTickCallback(tickStartCallbacks, tickStart);
|
||||
|
||||
// Waiting players update (newly connected clients waiting to get into the server)
|
||||
connectionManager.updateWaitingPlayers();
|
||||
|
||||
// Keep Alive Handling
|
||||
connectionManager.handleKeepAlive(tickStart);
|
||||
|
||||
// Server tick (chunks/entities)
|
||||
serverTick(tickStart);
|
||||
|
||||
// the time that the tick took in nanoseconds
|
||||
final long tickTime = System.nanoTime() - currentTime;
|
||||
|
||||
// Tick end callbacks
|
||||
doTickCallback(tickEndCallbacks, tickTime / 1000000L);
|
||||
|
||||
} catch (Exception e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
|
||||
long currentTime = System.nanoTime();
|
||||
final long tickStart = System.currentTimeMillis();
|
||||
|
||||
// Tick start callbacks
|
||||
doTickCallback(tickStartCallbacks, tickStart);
|
||||
|
||||
// Waiting players update (newly connected clients waiting to get into the server)
|
||||
entityManager.updateWaitingPlayers();
|
||||
|
||||
// Keep Alive Handling
|
||||
entityManager.handleKeepAlive(tickStart);
|
||||
|
||||
// Server tick (chunks/entities)
|
||||
serverTick(tickStart);
|
||||
|
||||
// the time that the tick took in nanoseconds
|
||||
final long tickTime = System.nanoTime() - currentTime;
|
||||
|
||||
// Tick end callbacks
|
||||
doTickCallback(tickEndCallbacks, tickTime / 1000000L);
|
||||
|
||||
}, 0, MinecraftServer.TICK_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@ -100,7 +103,7 @@ public final class UpdateManager {
|
||||
try {
|
||||
future.get();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,7 +139,6 @@ public final class UpdateManager {
|
||||
* @throws NullPointerException if <code>threadProvider</code> is null
|
||||
*/
|
||||
public synchronized void setThreadProvider(ThreadProvider threadProvider) {
|
||||
Check.notNull(threadProvider, "The thread provider cannot be null");
|
||||
this.threadProvider = threadProvider;
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ public interface Viewable {
|
||||
/**
|
||||
* Sends a packet to all viewers and the viewable element if it is a player.
|
||||
* <p>
|
||||
* If 'this' isn't a player, then {only @link #sendPacketToViewers(ServerPacket)} is called.
|
||||
* If 'this' isn't a player, then only {@link #sendPacketToViewers(ServerPacket)} is called.
|
||||
*
|
||||
* @param packet the packet to send
|
||||
*/
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.minestom.server.advancements;
|
||||
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
@ -23,8 +23,8 @@ public class Advancement {
|
||||
|
||||
private boolean achieved;
|
||||
|
||||
private ColoredText title;
|
||||
private ColoredText description;
|
||||
private JsonMessage title;
|
||||
private JsonMessage description;
|
||||
|
||||
private ItemStack icon;
|
||||
|
||||
@ -42,7 +42,7 @@ public class Advancement {
|
||||
// Packet
|
||||
private AdvancementsPacket.Criteria criteria;
|
||||
|
||||
public Advancement(@NotNull ColoredText title, ColoredText description,
|
||||
public Advancement(@NotNull JsonMessage title, JsonMessage description,
|
||||
@NotNull ItemStack icon, @NotNull FrameType frameType,
|
||||
float x, float y) {
|
||||
this.title = title;
|
||||
@ -53,7 +53,7 @@ public class Advancement {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Advancement(@NotNull ColoredText title, @NotNull ColoredText description,
|
||||
public Advancement(@NotNull JsonMessage title, @NotNull JsonMessage description,
|
||||
@NotNull Material icon, @NotNull FrameType frameType,
|
||||
float x, float y) {
|
||||
this(title, description, new ItemStack(icon, (byte) 1), frameType, x, y);
|
||||
@ -100,7 +100,7 @@ public class Advancement {
|
||||
* @return the advancement title
|
||||
*/
|
||||
@NotNull
|
||||
public ColoredText getTitle() {
|
||||
public JsonMessage getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ public class Advancement {
|
||||
*
|
||||
* @param title the new title
|
||||
*/
|
||||
public void setTitle(@NotNull ColoredText title) {
|
||||
public void setTitle(@NotNull JsonMessage title) {
|
||||
this.title = title;
|
||||
update();
|
||||
}
|
||||
@ -120,7 +120,7 @@ public class Advancement {
|
||||
* @return the description title
|
||||
*/
|
||||
@NotNull
|
||||
public ColoredText getDescription() {
|
||||
public JsonMessage getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ public class Advancement {
|
||||
*
|
||||
* @param description the new description
|
||||
*/
|
||||
public void setDescription(@NotNull ColoredText description) {
|
||||
public void setDescription(@NotNull JsonMessage description) {
|
||||
this.description = description;
|
||||
update();
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.advancements;
|
||||
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -14,7 +15,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public class AdvancementRoot extends Advancement {
|
||||
|
||||
public AdvancementRoot(@NotNull ColoredText title, @NotNull ColoredText description,
|
||||
public AdvancementRoot(@NotNull JsonMessage title, @NotNull JsonMessage description,
|
||||
@NotNull ItemStack icon, @NotNull FrameType frameType,
|
||||
float x, float y,
|
||||
@Nullable String background) {
|
||||
@ -22,7 +23,7 @@ public class AdvancementRoot extends Advancement {
|
||||
setBackground(background);
|
||||
}
|
||||
|
||||
public AdvancementRoot(@NotNull ColoredText title, @NotNull ColoredText description,
|
||||
public AdvancementRoot(@NotNull JsonMessage title, @NotNull JsonMessage description,
|
||||
@NotNull Material icon, FrameType frameType,
|
||||
float x, float y,
|
||||
@Nullable String background) {
|
||||
|
@ -73,7 +73,6 @@ public class AdvancementTab implements Viewable {
|
||||
* @param parent the parent of this advancement, it cannot be null
|
||||
*/
|
||||
public void createAdvancement(@NotNull String identifier, @NotNull Advancement advancement, @NotNull Advancement parent) {
|
||||
Check.argCondition(identifier == null, "the advancement identifier cannot be null");
|
||||
Check.stateCondition(!advancementMap.containsKey(parent),
|
||||
"You tried to set a parent which doesn't exist or isn't registered");
|
||||
cacheAdvancement(identifier, advancement, parent);
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.minestom.server.advancements.notifications;
|
||||
|
||||
import net.minestom.server.advancements.FrameType;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -11,17 +11,17 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public class Notification {
|
||||
|
||||
private final ColoredText title;
|
||||
private final JsonMessage title;
|
||||
private final FrameType frameType;
|
||||
private final ItemStack icon;
|
||||
|
||||
public Notification(@NotNull ColoredText title, @NotNull FrameType frameType, @NotNull ItemStack icon) {
|
||||
public Notification(@NotNull JsonMessage title, @NotNull FrameType frameType, @NotNull ItemStack icon) {
|
||||
this.title = title;
|
||||
this.frameType = frameType;
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public Notification(@NotNull ColoredText title, @NotNull FrameType frameType, @NotNull Material icon) {
|
||||
public Notification(@NotNull JsonMessage title, @NotNull FrameType frameType, @NotNull Material icon) {
|
||||
this.title = title;
|
||||
this.frameType = frameType;
|
||||
this.icon = new ItemStack(icon, (byte) 1);
|
||||
@ -33,7 +33,7 @@ public class Notification {
|
||||
* @return the notification title
|
||||
*/
|
||||
@NotNull
|
||||
public ColoredText getTitle() {
|
||||
public JsonMessage getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,10 @@ public class Attribute {
|
||||
|
||||
private static final Map<String, Attribute> ATTRIBUTES = new HashMap<>();
|
||||
|
||||
static {
|
||||
Attributes.init();
|
||||
}
|
||||
|
||||
private final String key;
|
||||
private final float defaultValue;
|
||||
private final float maxValue;
|
||||
|
@ -18,13 +18,13 @@ public class AttributeInstance {
|
||||
private final Map<UUID, AttributeModifier> modifiers = new HashMap<>();
|
||||
private final Consumer<AttributeInstance> propertyChangeListener;
|
||||
private float baseValue;
|
||||
private boolean dirty = true;
|
||||
private float cachedValue = 0.0f;
|
||||
|
||||
public AttributeInstance(@NotNull Attribute attribute, @Nullable Consumer<AttributeInstance> listener) {
|
||||
this.attribute = attribute;
|
||||
this.propertyChangeListener = listener;
|
||||
this.baseValue = attribute.getDefaultValue();
|
||||
refreshCachedValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,19 +47,6 @@ public class AttributeInstance {
|
||||
return baseValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this instance dirty to trigger calculation of the new value.
|
||||
* Triggers the {@link #propertyChangeListener}.
|
||||
*/
|
||||
private void setDirty() {
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
if (propertyChangeListener != null) {
|
||||
propertyChangeListener.accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base value of this instance.
|
||||
*
|
||||
@ -69,7 +56,11 @@ public class AttributeInstance {
|
||||
public void setBaseValue(float baseValue) {
|
||||
if (this.baseValue != baseValue) {
|
||||
this.baseValue = baseValue;
|
||||
setDirty();
|
||||
refreshCachedValue();
|
||||
|
||||
if (propertyChangeListener != null) {
|
||||
propertyChangeListener.accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +71,7 @@ public class AttributeInstance {
|
||||
*/
|
||||
public void addModifier(@NotNull AttributeModifier modifier) {
|
||||
if (modifiers.putIfAbsent(modifier.getId(), modifier) == null) {
|
||||
setDirty();
|
||||
refreshCachedValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +82,7 @@ public class AttributeInstance {
|
||||
*/
|
||||
public void removeModifier(@NotNull AttributeModifier modifier) {
|
||||
if (modifiers.remove(modifier.getId()) != null) {
|
||||
setDirty();
|
||||
refreshCachedValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,6 +91,7 @@ public class AttributeInstance {
|
||||
*
|
||||
* @return the modifiers.
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<AttributeModifier> getModifiers() {
|
||||
return modifiers.values();
|
||||
}
|
||||
@ -110,34 +102,29 @@ public class AttributeInstance {
|
||||
* @return the attribute value
|
||||
*/
|
||||
public float getValue() {
|
||||
if (dirty) {
|
||||
cachedValue = processModifiers();
|
||||
dirty = false;
|
||||
}
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate the value of this attribute instance using the modifiers.
|
||||
*
|
||||
* @return the attribute value
|
||||
*/
|
||||
protected float processModifiers() {
|
||||
protected void refreshCachedValue() {
|
||||
final Collection<AttributeModifier> modifiers = getModifiers();
|
||||
float base = getBaseValue();
|
||||
|
||||
for (var modifier : modifiers.values().stream().filter(mod -> mod.getOperation() == AttributeOperation.ADDITION).toArray(AttributeModifier[]::new)) {
|
||||
for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.ADDITION).toArray(AttributeModifier[]::new)) {
|
||||
base += modifier.getAmount();
|
||||
}
|
||||
|
||||
float result = base;
|
||||
|
||||
for (var modifier : modifiers.values().stream().filter(mod -> mod.getOperation() == AttributeOperation.MULTIPLY_BASE).toArray(AttributeModifier[]::new)) {
|
||||
for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.MULTIPLY_BASE).toArray(AttributeModifier[]::new)) {
|
||||
result += (base * modifier.getAmount());
|
||||
}
|
||||
for (var modifier : modifiers.values().stream().filter(mod -> mod.getOperation() == AttributeOperation.MULTIPLY_TOTAL).toArray(AttributeModifier[]::new)) {
|
||||
for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.MULTIPLY_TOTAL).toArray(AttributeModifier[]::new)) {
|
||||
result *= (1.0f + modifier.getAmount());
|
||||
}
|
||||
|
||||
return Math.min(result, getAttribute().getMaxValue());
|
||||
this.cachedValue = Math.min(result, getAttribute().getMaxValue());
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ package net.minestom.server.attribute;
|
||||
* The Minecraft, vanilla, standards attributes.
|
||||
*/
|
||||
public final class Attributes {
|
||||
|
||||
public static final Attribute MAX_HEALTH = (new Attribute("generic.max_health", true, 20, 1024)).register();
|
||||
public static final Attribute FOLLOW_RANGE = (new Attribute("generic.follow_range", true, 32, 2048)).register();
|
||||
public static final Attribute KNOCKBACK_RESISTANCE = (new Attribute("generic.knockback_resistance", true, 0, 1)).register();
|
||||
@ -21,4 +22,8 @@ public final class Attributes {
|
||||
private Attributes() throws IllegalAccessException {
|
||||
throw new IllegalAccessException("Cannot instantiate a static class");
|
||||
}
|
||||
|
||||
protected static void init() {
|
||||
// Empty, here to register all the vanilla attributes
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ public final class BenchmarkManager {
|
||||
try {
|
||||
Thread.sleep(time);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.minestom.server.bossbar;
|
||||
|
||||
import net.minestom.server.Viewable;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.packet.server.play.BossBarPacket;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
@ -29,7 +29,7 @@ public class BossBar implements Viewable {
|
||||
private final Set<Player> viewers = new CopyOnWriteArraySet<>();
|
||||
private final Set<Player> unmodifiableViewers = Collections.unmodifiableSet(viewers);
|
||||
|
||||
private ColoredText title;
|
||||
private JsonMessage title;
|
||||
private float progress;
|
||||
private BarColor color;
|
||||
private BarDivision division;
|
||||
@ -42,7 +42,7 @@ public class BossBar implements Viewable {
|
||||
* @param color the boss bar color
|
||||
* @param division the boss bar division
|
||||
*/
|
||||
public BossBar(@NotNull ColoredText title, @NotNull BarColor color, @NotNull BarDivision division) {
|
||||
public BossBar(@NotNull JsonMessage title, @NotNull BarColor color, @NotNull BarDivision division) {
|
||||
this.title = title;
|
||||
this.color = color;
|
||||
this.division = division;
|
||||
@ -99,7 +99,7 @@ public class BossBar implements Viewable {
|
||||
* @return the current title of the bossbar
|
||||
*/
|
||||
@NotNull
|
||||
public ColoredText getTitle() {
|
||||
public JsonMessage getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ public class BossBar implements Viewable {
|
||||
*
|
||||
* @param title the new title of the bossbar
|
||||
*/
|
||||
public void setTitle(@NotNull ColoredText title) {
|
||||
public void setTitle(@NotNull JsonMessage title) {
|
||||
this.title = title;
|
||||
updateTitle();
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -199,6 +200,15 @@ public final class ChatColor {
|
||||
return legacyColorCodesMap.getOrDefault(colorCode, NO_COLOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a collection of all chat colors
|
||||
* @return a collection of all chat colors
|
||||
*/
|
||||
@NotNull
|
||||
public static Collection<ChatColor> values() {
|
||||
return colorCode.values();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return empty;
|
||||
}
|
||||
|
@ -51,13 +51,13 @@ public class ChatHoverEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a {@link ColoredText} when hovered.
|
||||
* Shows a {@link JsonMessage} when hovered.
|
||||
*
|
||||
* @param text the text to show
|
||||
* @return the chat hover event
|
||||
*/
|
||||
@NotNull
|
||||
public static ChatHoverEvent showText(@NotNull ColoredText text) {
|
||||
public static ChatHoverEvent showText(@NotNull JsonMessage text) {
|
||||
return new ChatHoverEvent("show_text", text.getJsonObject());
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,11 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class ColoredText extends JsonMessage {
|
||||
|
||||
// the raw text
|
||||
private String message;
|
||||
/**
|
||||
* The raw text StringBuilder
|
||||
* Its a single StringBuilder instance for easier and faster concenation
|
||||
*/
|
||||
private final StringBuilder message = new StringBuilder();
|
||||
|
||||
/**
|
||||
* Creates a colored text.
|
||||
@ -31,7 +34,7 @@ public class ColoredText extends JsonMessage {
|
||||
* @see #of(String) to create a colored text
|
||||
*/
|
||||
private ColoredText(@NotNull String message) {
|
||||
this.message = message;
|
||||
this.message.append(message);
|
||||
refreshUpdate();
|
||||
}
|
||||
|
||||
@ -79,7 +82,7 @@ public class ColoredText extends JsonMessage {
|
||||
*/
|
||||
@NotNull
|
||||
public ColoredText append(@NotNull ChatColor color, @NotNull String message) {
|
||||
this.message += color + message;
|
||||
this.message.append(color).append(message);
|
||||
refreshUpdate();
|
||||
return this;
|
||||
}
|
||||
@ -116,7 +119,7 @@ public class ColoredText extends JsonMessage {
|
||||
*/
|
||||
@NotNull
|
||||
public String getMessage() {
|
||||
return message;
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,7 +163,7 @@ public class ColoredText extends JsonMessage {
|
||||
protected List<JsonObject> getComponents() {
|
||||
final List<JsonObject> objects = new ArrayList<>();
|
||||
// No message, return empty list
|
||||
if (message.isEmpty())
|
||||
if (getMessage().isEmpty())
|
||||
return objects;
|
||||
|
||||
boolean inFormat = false;
|
||||
|
@ -1,8 +1,11 @@
|
||||
package net.minestom.server.chat;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a json message which can be send to a player.
|
||||
* <p>
|
||||
@ -35,6 +38,16 @@ public abstract class JsonMessage {
|
||||
this.updated = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content of the message without any formatting or effects.
|
||||
*
|
||||
* @return The message without formatting or effects
|
||||
*/
|
||||
@NotNull
|
||||
public String getRawMessage() {
|
||||
return getTextMessage(getJsonObject()).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Json representation.
|
||||
* <p>
|
||||
@ -56,6 +69,38 @@ public abstract class JsonMessage {
|
||||
return compiledJson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
JsonMessage message = (JsonMessage) o;
|
||||
return Objects.equals(toString(), message.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively collects the 'text' field from the provided object and it's 'extra's.
|
||||
*
|
||||
* @param obj The object to parse
|
||||
* @return The text content of the object and its 'extra's
|
||||
*/
|
||||
@NotNull
|
||||
private static StringBuilder getTextMessage(@NotNull JsonObject obj) {
|
||||
StringBuilder message = new StringBuilder(obj.get("text").getAsString());
|
||||
JsonElement extra = obj.get("extra");
|
||||
if (extra != null && extra.isJsonArray()) {
|
||||
for (JsonElement child : extra.getAsJsonArray()) {
|
||||
if (!child.isJsonObject()) continue;
|
||||
message.append(getTextMessage(child.getAsJsonObject()));
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public static class RawJsonMessage extends JsonMessage {
|
||||
|
||||
private final JsonObject jsonObject;
|
||||
|
@ -37,7 +37,6 @@ public class RichMessage extends JsonMessage {
|
||||
* @return the created rich message object
|
||||
*/
|
||||
public static RichMessage of(@NotNull ColoredText coloredText) {
|
||||
Check.notNull(coloredText, "ColoredText cannot be null");
|
||||
|
||||
RichMessage richMessage = new RichMessage();
|
||||
appendText(richMessage, coloredText);
|
||||
@ -58,7 +57,6 @@ public class RichMessage extends JsonMessage {
|
||||
* @return the rich message
|
||||
*/
|
||||
public RichMessage append(@NotNull ColoredText coloredText) {
|
||||
Check.notNull(coloredText, "ColoredText cannot be null");
|
||||
|
||||
appendText(this, coloredText);
|
||||
return this;
|
||||
|
@ -26,7 +26,10 @@ public class CollisionUtils {
|
||||
* @param velocityOut the Vector object in which the new velocity will be saved
|
||||
* @return whether this entity is on the ground
|
||||
*/
|
||||
public static boolean handlePhysics(Entity entity, Vector deltaPosition, Position positionOut, Vector velocityOut) {
|
||||
public static boolean handlePhysics(@NotNull Entity entity,
|
||||
@NotNull Vector deltaPosition,
|
||||
@NotNull Position positionOut,
|
||||
@NotNull Vector velocityOut) {
|
||||
// TODO handle collisions with nearby entities (should it be done here?)
|
||||
final Instance instance = entity.getInstance();
|
||||
final Position currentPosition = entity.getPosition();
|
||||
@ -75,7 +78,7 @@ public class CollisionUtils {
|
||||
* @param stepAmount how much to step in the direction (in blocks)
|
||||
* @param positionOut the vector in which to store the new position
|
||||
* @param corners the corners to check against
|
||||
* @return true iif a collision has been found
|
||||
* @return true if a collision has been found
|
||||
*/
|
||||
private static boolean stepAxis(Instance instance, Vector startPosition, Vector axis, float stepAmount, Vector positionOut, Vector... corners) {
|
||||
positionOut.copy(startPosition);
|
||||
@ -182,6 +185,7 @@ public class CollisionUtils {
|
||||
* @param newPosition the future target position
|
||||
* @return the position with the world border collision applied (can be {@code newPosition} if not changed)
|
||||
*/
|
||||
@NotNull
|
||||
public static Position applyWorldBorder(@NotNull Instance instance,
|
||||
@NotNull Position currentPosition, @NotNull Position newPosition) {
|
||||
final WorldBorder worldBorder = instance.getWorldBorder();
|
||||
|
@ -2,6 +2,7 @@ package net.minestom.server.command;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandDispatcher;
|
||||
import net.minestom.server.command.builder.CommandSyntax;
|
||||
@ -22,6 +23,7 @@ import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
import net.minestom.server.utils.callback.CommandCallback;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -41,7 +43,7 @@ public final class CommandManager {
|
||||
|
||||
public static final String COMMAND_PREFIX = "/";
|
||||
|
||||
private volatile boolean running;
|
||||
private volatile boolean running = true;
|
||||
|
||||
private final ConsoleSender consoleSender = new ConsoleSender();
|
||||
|
||||
@ -51,38 +53,6 @@ public final class CommandManager {
|
||||
private CommandCallback unknownCommandCallback;
|
||||
|
||||
public CommandManager() {
|
||||
running = true;
|
||||
// Setup console thread
|
||||
Thread consoleThread = new Thread(() -> {
|
||||
BufferedReader bi = new BufferedReader(new InputStreamReader(System.in));
|
||||
while (running) {
|
||||
|
||||
try {
|
||||
if (bi.ready()) {
|
||||
final String command = bi.readLine();
|
||||
execute(consoleSender, command);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prevent permanent looping
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
try {
|
||||
bi.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, "ConsoleCommand-Thread");
|
||||
consoleThread.setDaemon(true);
|
||||
consoleThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -185,8 +155,6 @@ public final class CommandManager {
|
||||
* @return true if the command hadn't been cancelled and has been successful
|
||||
*/
|
||||
public boolean execute(@NotNull CommandSender sender, @NotNull String command) {
|
||||
Check.notNull(sender, "Source cannot be null");
|
||||
Check.notNull(command, "Command string cannot be null");
|
||||
|
||||
// Command event
|
||||
if (sender instanceof Player) {
|
||||
@ -210,7 +178,7 @@ public final class CommandManager {
|
||||
return true;
|
||||
} else {
|
||||
// Check for legacy-command
|
||||
final String[] splitCommand = command.split(" ");
|
||||
final String[] splitCommand = command.split(StringUtils.SPACE);
|
||||
final String commandName = splitCommand[0];
|
||||
final CommandProcessor commandProcessor = commandProcessorMap.get(commandName.toLowerCase());
|
||||
if (commandProcessor == null) {
|
||||
@ -221,7 +189,7 @@ public final class CommandManager {
|
||||
}
|
||||
|
||||
// Execute the legacy-command
|
||||
final String[] args = command.substring(command.indexOf(" ") + 1).split(" ");
|
||||
final String[] args = command.substring(command.indexOf(StringUtils.SPACE) + 1).split(StringUtils.SPACE);
|
||||
|
||||
return commandProcessor.process(sender, commandName, args);
|
||||
}
|
||||
@ -258,6 +226,43 @@ public final class CommandManager {
|
||||
return consoleSender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the thread responsible for executing commands from the console.
|
||||
*/
|
||||
public void startConsoleThread() {
|
||||
Thread consoleThread = new Thread(() -> {
|
||||
BufferedReader bi = new BufferedReader(new InputStreamReader(System.in));
|
||||
while (running) {
|
||||
|
||||
try {
|
||||
|
||||
if (bi.ready()) {
|
||||
final String command = bi.readLine();
|
||||
execute(consoleSender, command);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prevent permanent looping
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
|
||||
}
|
||||
try {
|
||||
bi.close();
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}, "ConsoleCommand-Thread");
|
||||
consoleThread.setDaemon(true);
|
||||
consoleThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link DeclareCommandsPacket} for a specific player.
|
||||
* <p>
|
||||
@ -388,9 +393,9 @@ public final class CommandManager {
|
||||
nodes.add(literalNode);
|
||||
|
||||
// Contains the arguments of the already-parsed syntaxes
|
||||
List<Argument[]> syntaxesArguments = new ArrayList<>();
|
||||
List<Argument<?>[]> syntaxesArguments = new ArrayList<>();
|
||||
// Contains the nodes of an argument
|
||||
Map<Argument, List<DeclareCommandsPacket.Node>> storedArgumentsNodes = new HashMap<>();
|
||||
Map<Argument<?>, List<DeclareCommandsPacket.Node>> storedArgumentsNodes = new HashMap<>();
|
||||
|
||||
for (CommandSyntax syntax : syntaxes) {
|
||||
final CommandCondition commandCondition = syntax.getCommandCondition();
|
||||
@ -406,16 +411,17 @@ public final class CommandManager {
|
||||
// Represent the children of the last node
|
||||
IntList argChildren = null;
|
||||
|
||||
final Argument[] arguments = syntax.getArguments();
|
||||
final Argument<?>[] arguments = syntax.getArguments();
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
final Argument argument = arguments[i];
|
||||
final Argument<?> argument = arguments[i];
|
||||
final boolean isFirst = i == 0;
|
||||
final boolean isLast = i == arguments.length - 1;
|
||||
|
||||
// Find shared part
|
||||
boolean foundSharedPart = false;
|
||||
for (Argument[] parsedArguments : syntaxesArguments) {
|
||||
for (Argument<?>[] parsedArguments : syntaxesArguments) {
|
||||
if (ArrayUtils.sameStart(arguments, parsedArguments, i + 1)) {
|
||||
final Argument sharedArgument = parsedArguments[i];
|
||||
final Argument<?> sharedArgument = parsedArguments[i];
|
||||
|
||||
argChildren = new IntArrayList();
|
||||
lastNodes = storedArgumentsNodes.get(sharedArgument);
|
||||
@ -508,7 +514,6 @@ public final class CommandManager {
|
||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||
|
||||
argumentNode.parser = "brigadier:bool";
|
||||
argumentNode.properties = packetWriter -> packetWriter.writeByte((byte) 0);
|
||||
} else if (argument instanceof ArgumentDouble) {
|
||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||
|
||||
@ -633,16 +638,16 @@ public final class CommandManager {
|
||||
} else if (argument instanceof ArgumentFloatRange) {
|
||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||
argumentNode.parser = "minecraft:float_range";
|
||||
} else if (argument instanceof ArgumentEntities) {
|
||||
ArgumentEntities argumentEntities = (ArgumentEntities) argument;
|
||||
} else if (argument instanceof ArgumentEntity) {
|
||||
ArgumentEntity argumentEntity = (ArgumentEntity) argument;
|
||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||
argumentNode.parser = "minecraft:entity";
|
||||
argumentNode.properties = packetWriter -> {
|
||||
byte mask = 0;
|
||||
if (argumentEntities.isOnlySingleEntity()) {
|
||||
if (argumentEntity.isOnlySingleEntity()) {
|
||||
mask += 1;
|
||||
}
|
||||
if (argumentEntities.isOnlyPlayers()) {
|
||||
if (argumentEntity.isOnlyPlayers()) {
|
||||
mask += 2;
|
||||
}
|
||||
packetWriter.writeByte(mask);
|
||||
|
@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* start by {@link #getCommandName()} or any of the aliases in {@link #getAliases()}.
|
||||
* <p>
|
||||
* Tab-completion can be activated by overriding {@link #enableWritingTracking()} and return true, you should then listen to
|
||||
* {@link #onWrite(String)} and return the possible completions to suggest.
|
||||
* {@link #onWrite(CommandSender, String)} and return the possible completions to suggest.
|
||||
* <p>
|
||||
* Please be sure to check {@link net.minestom.server.command.builder.Command} as it is likely to be better for your use case.
|
||||
*/
|
||||
@ -36,7 +36,7 @@ public interface CommandProcessor {
|
||||
String[] getAliases();
|
||||
|
||||
/**
|
||||
* Called when the command is executed by a {@link CommandSender}
|
||||
* Called when the command is executed by a {@link CommandSender}.
|
||||
*
|
||||
* @param sender the sender which executed the command
|
||||
* @param command the command name used
|
||||
@ -48,7 +48,7 @@ public interface CommandProcessor {
|
||||
/**
|
||||
* Called to know if a player has access to the command.
|
||||
* <p>
|
||||
* Right now it is only used to know if the player should see the command in auto-completion
|
||||
* Right now it is only used to know if the player should see the command in auto-completion.
|
||||
* Conditions still need to be checked in {@link #process(CommandSender, String, String[])}.
|
||||
*
|
||||
* @param player the player to check the access
|
||||
@ -57,12 +57,12 @@ public interface CommandProcessor {
|
||||
boolean hasAccess(@NotNull Player player);
|
||||
|
||||
/**
|
||||
* Needed to enable {@link #onWrite(String)} callback.
|
||||
* Needed to enable {@link #onWrite(CommandSender, String)} callback.
|
||||
* <p>
|
||||
* Be aware that enabling it can cost some performance because of how often it will be called.
|
||||
*
|
||||
* @return true to enable writing tracking (and server auto completion)
|
||||
* @see #onWrite(String)
|
||||
* @see #onWrite(CommandSender, String)
|
||||
*/
|
||||
default boolean enableWritingTracking() {
|
||||
return false;
|
||||
@ -73,12 +73,13 @@ public interface CommandProcessor {
|
||||
* <p>
|
||||
* WARNING: {@link #enableWritingTracking()} needs to return true, you need to override it by default.
|
||||
*
|
||||
* @param text the whole player text
|
||||
* @return the array containing all the suggestion for the current arg (split " "), can be null
|
||||
* @param sender the command sender
|
||||
* @param text the whole player text
|
||||
* @return the array containing all the suggestions for the current arg (split SPACE), can be null
|
||||
* @see #enableWritingTracking()
|
||||
*/
|
||||
@Nullable
|
||||
default String[] onWrite(@NotNull String text) {
|
||||
default String[] onWrite(@NotNull CommandSender sender, String text) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -32,16 +32,16 @@ public interface CommandSender extends PermissionHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a {@link ColoredText} message.
|
||||
* Sends a {@link JsonMessage} message.
|
||||
* If this is not a {@link Player}, only the content of the message will be sent as a string.
|
||||
*
|
||||
* @param text The {@link ColoredText} to send.
|
||||
* @param text The {@link JsonMessage} to send.
|
||||
* */
|
||||
default void sendMessage(@NotNull ColoredText text) {
|
||||
default void sendMessage(@NotNull JsonMessage text) {
|
||||
if (this instanceof Player) {
|
||||
((Player) this).sendMessage((JsonMessage) text);
|
||||
this.sendMessage(text);
|
||||
} else {
|
||||
sendMessage(text.getMessage());
|
||||
sendMessage(text.getRawMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package net.minestom.server.command.builder;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -13,9 +14,8 @@ public interface ArgumentCallback {
|
||||
/**
|
||||
* Executed when an error is found.
|
||||
*
|
||||
* @param source the sender which executed the command
|
||||
* @param value the raw string argument which is responsible for the error
|
||||
* @param error the error id (you can check its meaning in the specific argument class or ask the developer about it)
|
||||
* @param sender the sender which executed the command
|
||||
* @param exception the exception containing the message, input and error code related to the issue
|
||||
*/
|
||||
void apply(@NotNull CommandSender source, @NotNull String value, int error);
|
||||
void apply(@NotNull CommandSender sender, @NotNull ArgumentSyntaxException exception);
|
||||
}
|
||||
|
@ -1,24 +1,25 @@
|
||||
package net.minestom.server.command.builder;
|
||||
|
||||
import net.minestom.server.chat.ChatColor;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.item.Enchantment;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.particle.Particle;
|
||||
import net.minestom.server.potion.PotionEffect;
|
||||
import net.minestom.server.utils.entity.EntityFinder;
|
||||
import net.minestom.server.utils.location.RelativeBlockPosition;
|
||||
import net.minestom.server.utils.location.RelativeVec;
|
||||
import net.minestom.server.utils.math.FloatRange;
|
||||
import net.minestom.server.utils.math.IntRange;
|
||||
import net.minestom.server.utils.time.UpdateOption;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -34,6 +35,11 @@ public final class Arguments {
|
||||
|
||||
private Map<String, Object> args = new HashMap<>();
|
||||
|
||||
@NotNull
|
||||
public <T> T get(@NotNull Argument<T> argument) {
|
||||
return (T) getObject(argument.getId());
|
||||
}
|
||||
|
||||
public boolean getBoolean(@NotNull String id) {
|
||||
return (boolean) getObject(id);
|
||||
}
|
||||
@ -115,8 +121,8 @@ public final class Arguments {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Entity> getEntities(@NotNull String id) {
|
||||
return (List<Entity>) getObject(id);
|
||||
public EntityFinder getEntities(@NotNull String id) {
|
||||
return (EntityFinder) getObject(id);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -164,4 +170,15 @@ public final class Arguments {
|
||||
this.args.clear();
|
||||
}
|
||||
|
||||
protected void retrieveDefaultValues(@Nullable Map<String, Object> defaultValuesMap) {
|
||||
if (defaultValuesMap == null)
|
||||
return;
|
||||
|
||||
for (Map.Entry<String, Object> entry : defaultValuesMap.entrySet()) {
|
||||
final String key = entry.getKey();
|
||||
if (!args.containsKey(key))
|
||||
this.args.put(key, entry.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ import net.minestom.server.command.builder.condition.CommandCondition;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Represents a command which has suggestion/auto-completion.
|
||||
@ -37,6 +37,8 @@ import java.util.List;
|
||||
*/
|
||||
public class Command {
|
||||
|
||||
public final static Logger LOGGER = LoggerFactory.getLogger(Command.class);
|
||||
|
||||
private final String name;
|
||||
private final String[] aliases;
|
||||
|
||||
@ -114,27 +116,83 @@ public class Command {
|
||||
* @param commandCondition the condition to use the syntax
|
||||
* @param executor the executor to call when the syntax is successfully received
|
||||
* @param args all the arguments of the syntax, the length needs to be higher than 0
|
||||
* @return the created {@link CommandSyntax}
|
||||
* @return the created {@link CommandSyntax syntaxes},
|
||||
* there can be multiple of them when optional arguments are used
|
||||
*/
|
||||
public CommandSyntax addSyntax(@Nullable CommandCondition commandCondition,
|
||||
@NotNull CommandExecutor executor,
|
||||
@NotNull Argument<?>... args) {
|
||||
@NotNull
|
||||
public Collection<CommandSyntax> addSyntax(@Nullable CommandCondition commandCondition,
|
||||
@NotNull CommandExecutor executor,
|
||||
@NotNull Argument<?>... args) {
|
||||
Check.argCondition(args.length == 0,
|
||||
"The syntax argument cannot be empty, consider using Command#setDefaultExecutor");
|
||||
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, args);
|
||||
this.syntaxes.add(syntax);
|
||||
return syntax;
|
||||
|
||||
// Check optional argument(s)
|
||||
boolean hasOptional = false;
|
||||
{
|
||||
for (Argument<?> argument : args) {
|
||||
if (argument.isOptional()) {
|
||||
hasOptional = true;
|
||||
}
|
||||
if (hasOptional && !argument.isOptional()) {
|
||||
LOGGER.warn("Optional arguments are followed by a non-optional one, the default values will be ignored.");
|
||||
hasOptional = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasOptional) {
|
||||
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, args);
|
||||
this.syntaxes.add(syntax);
|
||||
return Collections.singleton(syntax);
|
||||
} else {
|
||||
List<CommandSyntax> optionalSyntaxes = new ArrayList<>();
|
||||
|
||||
// the 'args' array starts by all the required arguments, followed by the optional ones
|
||||
List<Argument<?>> requiredArguments = new ArrayList<>();
|
||||
Map<String, Object> defaultValuesMap = new HashMap<>();
|
||||
boolean optionalBranch = false;
|
||||
int i = 0;
|
||||
for (Argument<?> argument : args) {
|
||||
final boolean isLast = ++i == args.length;
|
||||
if (argument.isOptional()) {
|
||||
// Set default value
|
||||
defaultValuesMap.put(argument.getId(), argument.getDefaultValue());
|
||||
|
||||
if (!optionalBranch && !requiredArguments.isEmpty()) {
|
||||
// First optional argument, create a syntax with current cached arguments
|
||||
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, defaultValuesMap,
|
||||
requiredArguments.toArray(new Argument[0]));
|
||||
optionalSyntaxes.add(syntax);
|
||||
optionalBranch = true;
|
||||
} else {
|
||||
// New optional argument, save syntax with current cached arguments and save default value
|
||||
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, defaultValuesMap,
|
||||
requiredArguments.toArray(new Argument[0]));
|
||||
optionalSyntaxes.add(syntax);
|
||||
}
|
||||
}
|
||||
requiredArguments.add(argument);
|
||||
if (isLast) {
|
||||
// Create the last syntax
|
||||
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, defaultValuesMap,
|
||||
requiredArguments.toArray(new Argument[0]));
|
||||
optionalSyntaxes.add(syntax);
|
||||
}
|
||||
}
|
||||
|
||||
this.syntaxes.addAll(optionalSyntaxes);
|
||||
return optionalSyntaxes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new syntax in the command without any condition.
|
||||
* Adds a new syntax without condition.
|
||||
*
|
||||
* @param executor the executor to call when the syntax is successfully received
|
||||
* @param args all the arguments of the syntax, the length needs to be higher than 0
|
||||
* @return the created {@link CommandSyntax}
|
||||
* @see #addSyntax(CommandCondition, CommandExecutor, Argument[])
|
||||
*/
|
||||
public CommandSyntax addSyntax(@NotNull CommandExecutor executor, @NotNull Argument<?>... args) {
|
||||
@NotNull
|
||||
public Collection<CommandSyntax> addSyntax(@NotNull CommandExecutor executor, @NotNull Argument<?>... args) {
|
||||
return addSyntax(null, executor, args);
|
||||
}
|
||||
|
||||
@ -196,11 +254,12 @@ public class Command {
|
||||
* when in a dynamic argument ({@link ArgumentDynamicWord} (when {@link SuggestionType#ASK_SERVER} is used)
|
||||
* and {@link ArgumentDynamicStringArray}).
|
||||
*
|
||||
* @param text the whole player's text
|
||||
* @return the array containing all the suggestion for the current arg (split " "), can be null
|
||||
* @param sender the command sender
|
||||
* @param text the whole player's text
|
||||
* @return the array containing all the suggestion for the current arg (split SPACE), can be null
|
||||
*/
|
||||
@Nullable
|
||||
public String[] onDynamicWrite(@NotNull String text) {
|
||||
public String[] onDynamicWrite(@NotNull CommandSender sender, @NotNull String text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,11 @@
|
||||
package net.minestom.server.command.builder;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.condition.CommandCondition;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -56,15 +59,15 @@ public class CommandDispatcher {
|
||||
* @param commandString the command (containing the command name and the args if any)
|
||||
* @return the result of the parsing, null if the command doesn't exist
|
||||
*/
|
||||
@Nullable
|
||||
public CommandResult parse(@NotNull String commandString) {
|
||||
commandString = commandString.trim();
|
||||
|
||||
// Split space
|
||||
final String spaceRegex = " ";
|
||||
final String[] parts = commandString.split(spaceRegex);
|
||||
final String[] parts = commandString.split(StringUtils.SPACE);
|
||||
final String commandName = parts[0];
|
||||
|
||||
final String[] args = commandString.replaceFirst(Pattern.quote(commandName), "").trim().split(spaceRegex);
|
||||
final String[] args = commandString.replaceFirst(Pattern.quote(commandName), "").trim().split(StringUtils.SPACE);
|
||||
|
||||
final Command command = findCommand(commandName);
|
||||
// Check if the command exists
|
||||
@ -107,6 +110,7 @@ public class CommandDispatcher {
|
||||
return commandMap.getOrDefault(commandName, null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CommandResult findCommandResult(@NotNull Command command, @NotNull String[] args) {
|
||||
CommandResult result = new CommandResult();
|
||||
result.command = command;
|
||||
@ -125,75 +129,89 @@ public class CommandDispatcher {
|
||||
// All the registered syntaxes of the command
|
||||
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
|
||||
// Contains all the fully validated syntaxes (we later find the one with the most amount of arguments)
|
||||
List<CommandSyntax> validSyntaxes = new ArrayList<>();
|
||||
// Contains the raw string value of each argument that has been validated
|
||||
// CommandSyntax - (Argument index/Raw string value)
|
||||
Map<CommandSyntax, String[]> syntaxesValues = new HashMap<>();
|
||||
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>();
|
||||
|
||||
// Contains all the syntaxes that are not fully correct, used to later, retrieve the "most correct syntax"
|
||||
// Number of correct argument - The data about the failing argument
|
||||
TreeMap<Integer, CommandSuggestionHolder> syntaxesSuggestions = new TreeMap<>(Collections.reverseOrder());
|
||||
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
|
||||
|
||||
for (CommandSyntax syntax : syntaxes) {
|
||||
final Argument<?>[] arguments = syntax.getArguments();
|
||||
final String[] argsValues = new String[Byte.MAX_VALUE];
|
||||
final List<Object> argsValues = new ArrayList<>(arguments.length);
|
||||
|
||||
boolean syntaxCorrect = true;
|
||||
// The current index in the raw command string arguments
|
||||
int argIndex = 0;
|
||||
int splitIndex = 0;
|
||||
|
||||
boolean useRemaining = false;
|
||||
// Check the validity of the arguments...
|
||||
for (int argCount = 0; argCount < syntax.getArguments().length; argCount++) {
|
||||
final Argument<?> argument = syntax.getArguments()[argCount];
|
||||
for (int argCount = 0; argCount < arguments.length; argCount++) {
|
||||
final boolean lastArgumentIteration = argCount + 1 == arguments.length;
|
||||
final Argument<?> argument = arguments[argCount];
|
||||
useRemaining = argument.useRemaining();
|
||||
|
||||
// the correction result of the argument
|
||||
int correctionResult = Argument.SUCCESS;
|
||||
// the parsed argument value, null if incorrect
|
||||
Object parsedValue;
|
||||
// the argument exception, null if the input is correct
|
||||
ArgumentSyntaxException argumentSyntaxException = null;
|
||||
// true if the arg is valid, false otherwise
|
||||
boolean correct = false;
|
||||
// the raw string representing the correct argument syntax
|
||||
StringBuilder argValue = new StringBuilder();
|
||||
|
||||
if (useRemaining) {
|
||||
final boolean hasArgs = args.length > argIndex;
|
||||
final boolean hasArgs = args.length > splitIndex;
|
||||
// Verify if there is any string part available
|
||||
if (hasArgs) {
|
||||
// Argument is supposed to take the rest of the command input
|
||||
for (int i = argIndex; i < args.length; i++) {
|
||||
for (int i = splitIndex; i < args.length; i++) {
|
||||
final String arg = args[i];
|
||||
if (argValue.length() > 0)
|
||||
argValue.append(" ");
|
||||
argValue.append(StringUtils.SPACE);
|
||||
argValue.append(arg);
|
||||
}
|
||||
|
||||
final String argValueString = argValue.toString();
|
||||
|
||||
correctionResult = argument.getCorrectionResult(argValueString);
|
||||
if (correctionResult == Argument.SUCCESS) {
|
||||
try {
|
||||
parsedValue = argument.parse(argValueString);
|
||||
correct = true;
|
||||
argsValues[argCount] = argValueString;
|
||||
argsValues.add(parsedValue);
|
||||
} catch (ArgumentSyntaxException exception) {
|
||||
argumentSyntaxException = exception;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Argument is either single-word or can accept optional delimited space(s)
|
||||
for (int i = argIndex; i < args.length; i++) {
|
||||
for (int i = splitIndex; i < args.length; i++) {
|
||||
final String rawArg = args[i];
|
||||
|
||||
argValue.append(rawArg);
|
||||
|
||||
final String argValueString = argValue.toString();
|
||||
|
||||
correctionResult = argument.getCorrectionResult(argValueString);
|
||||
if (correctionResult == Argument.SUCCESS) {
|
||||
try {
|
||||
parsedValue = argument.parse(argValueString);
|
||||
|
||||
// Prevent quitting the parsing too soon if the argument
|
||||
// does not allow space
|
||||
if (lastArgumentIteration && i + 1 < args.length) {
|
||||
if (!argument.allowSpace())
|
||||
break;
|
||||
argValue.append(StringUtils.SPACE);
|
||||
continue;
|
||||
}
|
||||
|
||||
correct = true;
|
||||
argsValues[argCount] = argValueString;
|
||||
argIndex = i + 1;
|
||||
argsValues.add(parsedValue);
|
||||
splitIndex = i + 1;
|
||||
break;
|
||||
} else {
|
||||
} catch (ArgumentSyntaxException exception) {
|
||||
argumentSyntaxException = exception;
|
||||
|
||||
if (!argument.allowSpace())
|
||||
break;
|
||||
argValue.append(" ");
|
||||
argValue.append(StringUtils.SPACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -204,8 +222,7 @@ public class CommandDispatcher {
|
||||
syntaxCorrect = false;
|
||||
CommandSuggestionHolder suggestionHolder = new CommandSuggestionHolder();
|
||||
suggestionHolder.syntax = syntax;
|
||||
suggestionHolder.argValue = argValue.toString();
|
||||
suggestionHolder.correctionResult = correctionResult;
|
||||
suggestionHolder.argumentSyntaxException = argumentSyntaxException;
|
||||
suggestionHolder.argIndex = argCount;
|
||||
syntaxesSuggestions.put(argCount, suggestionHolder);
|
||||
break;
|
||||
@ -214,9 +231,12 @@ public class CommandDispatcher {
|
||||
|
||||
// Add the syntax to the list of valid syntaxes if correct
|
||||
if (syntaxCorrect) {
|
||||
if (args.length == argIndex || useRemaining) {
|
||||
validSyntaxes.add(syntax);
|
||||
syntaxesValues.put(syntax, argsValues);
|
||||
if (arguments.length == argsValues.size() || useRemaining) {
|
||||
ValidSyntaxHolder validSyntaxHolder = new ValidSyntaxHolder();
|
||||
validSyntaxHolder.syntax = syntax;
|
||||
validSyntaxHolder.argumentsValue = argsValues;
|
||||
|
||||
validSyntaxes.add(validSyntaxHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -224,7 +244,7 @@ public class CommandDispatcher {
|
||||
// Check if there is at least one correct syntax
|
||||
if (!validSyntaxes.isEmpty()) {
|
||||
// Search the syntax with all perfect args
|
||||
final CommandSyntax finalSyntax = findMostCorrectSyntax(validSyntaxes, syntaxesValues, executorArgs);
|
||||
final CommandSyntax finalSyntax = findMostCorrectSyntax(validSyntaxes, executorArgs);
|
||||
if (finalSyntax != null) {
|
||||
// A fully correct syntax has been found, use it
|
||||
result.syntax = finalSyntax;
|
||||
@ -233,54 +253,25 @@ public class CommandDispatcher {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Otherwise, search for the first syntax with an incorrect argument
|
||||
for (CommandSyntax syntax : validSyntaxes) {
|
||||
final Argument[] arguments = syntax.getArguments();
|
||||
final String[] argsValues = syntaxesValues.get(syntax);
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
final Argument argument = arguments[i];
|
||||
final String argValue = argsValues[i];
|
||||
// Finally parse it
|
||||
final Object parsedValue = argument.parse(argValue);
|
||||
final int conditionResult = argument.getConditionResult(parsedValue);
|
||||
if (conditionResult != Argument.SUCCESS) {
|
||||
// Condition of an argument not correct, use the argument callback if any
|
||||
if (argument.hasErrorCallback()) {
|
||||
result.callback = argument.getCallback();
|
||||
result.value = argValue;
|
||||
result.error = conditionResult;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No all-correct syntax, find the closest one to use the argument callback
|
||||
{
|
||||
// Get closest valid syntax
|
||||
if (!syntaxesSuggestions.isEmpty()) {
|
||||
final int max = syntaxesSuggestions.firstKey(); // number of correct arguments
|
||||
// Check if at least 1 argument of the syntax is correct
|
||||
if (max > 0) {
|
||||
// Get the data of the closest syntax
|
||||
final CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max);
|
||||
final CommandSyntax syntax = suggestionHolder.syntax;
|
||||
final String argValue = suggestionHolder.argValue;
|
||||
final int correctionResult = suggestionHolder.correctionResult;
|
||||
final int argIndex = suggestionHolder.argIndex;
|
||||
final int max = syntaxesSuggestions.firstIntKey(); // number of correct arguments in the most correct syntax
|
||||
final CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max);
|
||||
final CommandSyntax syntax = suggestionHolder.syntax;
|
||||
final ArgumentSyntaxException argumentSyntaxException = suggestionHolder.argumentSyntaxException;
|
||||
final int argIndex = suggestionHolder.argIndex;
|
||||
|
||||
// Found the closest syntax with at least 1 correct argument
|
||||
final Argument<?> argument = syntax.getArguments()[argIndex];
|
||||
if (argument.hasErrorCallback()) {
|
||||
result.callback = argument.getCallback();
|
||||
result.value = argValue;
|
||||
result.error = correctionResult;
|
||||
// Found the closest syntax with at least 1 correct argument
|
||||
final Argument<?> argument = syntax.getArguments()[argIndex];
|
||||
if (argument.hasErrorCallback()) {
|
||||
result.callback = argument.getCallback();
|
||||
result.argumentSyntaxException = argumentSyntaxException;
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,60 +287,67 @@ public class CommandDispatcher {
|
||||
* Retrieves from the valid syntax map the arguments condition result and get the one with the most
|
||||
* valid arguments.
|
||||
*
|
||||
* @param validSyntaxes the list containing all the valid syntaxes
|
||||
* @param syntaxesValues the map containing the argument raw string values
|
||||
* @param executorArgs the recipient of the argument parsed values
|
||||
* @param validSyntaxes the list containing all the valid syntaxes
|
||||
* @param executorArgs the recipient of the argument parsed values
|
||||
* @return the command syntax with all of its arguments correct and with the most arguments count, null if not any
|
||||
*/
|
||||
@Nullable
|
||||
private CommandSyntax findMostCorrectSyntax(@NotNull List<CommandSyntax> validSyntaxes,
|
||||
@NotNull Map<CommandSyntax, String[]> syntaxesValues,
|
||||
private CommandSyntax findMostCorrectSyntax(@NotNull List<ValidSyntaxHolder> validSyntaxes,
|
||||
@NotNull Arguments executorArgs) {
|
||||
Map<CommandSyntax, Arguments> argumentsValueMap = new HashMap<>();
|
||||
|
||||
CommandSyntax finalSyntax = null;
|
||||
int maxArguments = 0;
|
||||
for (CommandSyntax syntax : validSyntaxes) {
|
||||
Arguments syntaxValues = new Arguments();
|
||||
boolean fullyCorrect = true;
|
||||
Arguments finalArguments = null;
|
||||
|
||||
for (ValidSyntaxHolder validSyntaxHolder : validSyntaxes) {
|
||||
final CommandSyntax syntax = validSyntaxHolder.syntax;
|
||||
|
||||
final Argument<?>[] arguments = syntax.getArguments();
|
||||
final String[] argsValues = syntaxesValues.get(syntax);
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
final Argument argument = arguments[i];
|
||||
final String argValue = argsValues[i];
|
||||
// Finally parse it
|
||||
final Object parsedValue = argument.parse(argValue);
|
||||
final int conditionResult = argument.getConditionResult(parsedValue);
|
||||
if (conditionResult == Argument.SUCCESS) {
|
||||
syntaxValues.setArg(argument.getId(), parsedValue);
|
||||
} else {
|
||||
// One argument is incorrect, stop the whole syntax check
|
||||
fullyCorrect = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final int argumentsCount = arguments.length;
|
||||
final List<Object> argsValues = validSyntaxHolder.argumentsValue;
|
||||
|
||||
final int argumentLength = arguments.length;
|
||||
if (fullyCorrect && argumentLength > maxArguments) {
|
||||
final int argsSize = argsValues.size();
|
||||
|
||||
if (argsSize > maxArguments) {
|
||||
finalSyntax = syntax;
|
||||
maxArguments = argumentLength;
|
||||
argumentsValueMap.put(syntax, syntaxValues);
|
||||
maxArguments = argsSize;
|
||||
|
||||
// Fill arguments map
|
||||
Arguments syntaxValues = new Arguments();
|
||||
for (int i = 0; i < argumentsCount; i++) {
|
||||
final Argument<?> argument = arguments[i];
|
||||
final Object argumentValue = argsValues.get(i);
|
||||
|
||||
syntaxValues.setArg(argument.getId(), argumentValue);
|
||||
}
|
||||
finalArguments = syntaxValues;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the arguments values
|
||||
if (finalSyntax != null) {
|
||||
executorArgs.copy(argumentsValueMap.get(finalSyntax));
|
||||
executorArgs.copy(finalArguments);
|
||||
}
|
||||
|
||||
return finalSyntax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the data of a validated syntax.
|
||||
*/
|
||||
private static class ValidSyntaxHolder {
|
||||
private CommandSyntax syntax;
|
||||
/**
|
||||
* (Argument index/Argument parsed object)
|
||||
*/
|
||||
private List<Object> argumentsValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the data of an invalidated syntax.
|
||||
*/
|
||||
private static class CommandSuggestionHolder {
|
||||
private CommandSyntax syntax;
|
||||
private String argValue;
|
||||
private int correctionResult;
|
||||
private ArgumentSyntaxException argumentSyntaxException;
|
||||
private int argIndex;
|
||||
}
|
||||
|
||||
@ -369,8 +367,7 @@ public class CommandDispatcher {
|
||||
|
||||
// Argument Callback
|
||||
private ArgumentCallback callback;
|
||||
private String value;
|
||||
private int error;
|
||||
private ArgumentSyntaxException argumentSyntaxException;
|
||||
|
||||
/**
|
||||
* Executes the command for the given source.
|
||||
@ -399,16 +396,17 @@ public class CommandDispatcher {
|
||||
// The executor is from a syntax
|
||||
final CommandCondition commandCondition = syntax.getCommandCondition();
|
||||
if (commandCondition == null || commandCondition.canUse(source, commandString)) {
|
||||
arguments.retrieveDefaultValues(syntax.getDefaultValuesMap());
|
||||
executor.apply(source, arguments);
|
||||
}
|
||||
} else {
|
||||
// The executor is probably the default one
|
||||
executor.apply(source, arguments);
|
||||
}
|
||||
} else if (callback != null) {
|
||||
} else if (callback != null && argumentSyntaxException != null) {
|
||||
// No syntax has been validated but the faulty argument with a callback has been found
|
||||
// Execute the faulty argument callback
|
||||
callback.apply(source, value, error);
|
||||
callback.apply(source, argumentSyntaxException);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@ import net.minestom.server.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a syntax in {@link Command}
|
||||
* which is initialized with {@link Command#addSyntax(CommandExecutor, Argument[])}.
|
||||
@ -14,16 +16,27 @@ public class CommandSyntax {
|
||||
|
||||
private CommandCondition commandCondition;
|
||||
private CommandExecutor executor;
|
||||
|
||||
private final Map<String, Object> defaultValuesMap;
|
||||
private final Argument<?>[] args;
|
||||
|
||||
protected CommandSyntax(@Nullable CommandCondition commandCondition,
|
||||
@NotNull CommandExecutor commandExecutor,
|
||||
@Nullable Map<String, Object> defaultValuesMap,
|
||||
@NotNull Argument<?>... args) {
|
||||
this.commandCondition = commandCondition;
|
||||
this.executor = commandExecutor;
|
||||
|
||||
this.defaultValuesMap = defaultValuesMap;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
protected CommandSyntax(@Nullable CommandCondition commandCondition,
|
||||
@NotNull CommandExecutor commandExecutor,
|
||||
@NotNull Argument<?>... args) {
|
||||
this(commandCondition, commandExecutor, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the condition to use this syntax.
|
||||
*
|
||||
@ -66,6 +79,11 @@ public class CommandSyntax {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Map<String, Object> getDefaultValuesMap() {
|
||||
return defaultValuesMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the required {@link Argument} for this syntax.
|
||||
*
|
||||
|
@ -3,6 +3,7 @@ package net.minestom.server.command.builder.arguments;
|
||||
import net.minestom.server.command.builder.ArgumentCallback;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandExecutor;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -11,24 +12,20 @@ import org.jetbrains.annotations.Nullable;
|
||||
* <p>
|
||||
* You can create your own with your own special conditions.
|
||||
* <p>
|
||||
* Here in order, how is parsed an argument: {@link #getCorrectionResult(String)} to check
|
||||
* if the syntax is correct, {@link #parse(String)} to convert the correct argument
|
||||
* and {@link #getConditionResult(Object)} to verify that the parsed object validate the additional
|
||||
* conditions.
|
||||
* Arguments are parsed using {@link #parse(String)}.
|
||||
*
|
||||
* @param <T> the type of this parsed argument
|
||||
*/
|
||||
public abstract class Argument<T> {
|
||||
|
||||
public static final int SUCCESS = 0;
|
||||
public static final int UNDEFINED_ERROR = -1;
|
||||
|
||||
private final String id;
|
||||
private final boolean allowSpace;
|
||||
private final boolean useRemaining;
|
||||
|
||||
private ArgumentCallback callback;
|
||||
|
||||
private T defaultValue;
|
||||
|
||||
/**
|
||||
* Creates a new argument.
|
||||
*
|
||||
@ -62,38 +59,15 @@ public abstract class Argument<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* First method called to check the validity of an input.
|
||||
* <p>
|
||||
* If {@link #allowSpace()} is enabled, the value will be incremented by the next word until it returns {@link #SUCCESS},
|
||||
* meaning that you need to be sure to check the inexpensive operations first (eg the number of brackets, the first and last char, etc...).
|
||||
* Parses the given input, and throw an {@link ArgumentSyntaxException}
|
||||
* if the input cannot be convert to {@code T}
|
||||
*
|
||||
* @param value The received argument
|
||||
* @return the error code or {@link #SUCCESS}
|
||||
*/
|
||||
public abstract int getCorrectionResult(@NotNull String value);
|
||||
|
||||
/**
|
||||
* Called after {@link #getCorrectionResult(String)} returned {@link #SUCCESS}.
|
||||
* <p>
|
||||
* The correction being correct means that {@code value} shouldn't be verified again, you can assume that no exception will occur
|
||||
* when converting it to the correct type.
|
||||
*
|
||||
* @param value The correct argument which does not need to be verified again
|
||||
* @return The parsed argument
|
||||
* @param input the argument to parse
|
||||
* @return the parsed argument
|
||||
* @throws ArgumentSyntaxException if {@code value} is not valid
|
||||
*/
|
||||
@NotNull
|
||||
public abstract T parse(@NotNull String value);
|
||||
|
||||
/**
|
||||
* Called after {@link #parse(String)} meaning that {@code value} should already represent a valid representation of the input.
|
||||
* <p>
|
||||
* The condition result has for goal to check the optional conditions that are user configurable (eg min/max values for a number, a specific material
|
||||
* for an item, etc...).
|
||||
*
|
||||
* @param value The parsed argument
|
||||
* @return the error code or {@link #SUCCESS}
|
||||
*/
|
||||
public abstract int getConditionResult(@NotNull T value);
|
||||
public abstract T parse(@NotNull String input) throws ArgumentSyntaxException;
|
||||
|
||||
/**
|
||||
* Gets the ID of the argument, showed in-game above the chat bar
|
||||
@ -146,6 +120,43 @@ public abstract class Argument<T> {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this argument is 'optional'.
|
||||
* <p>
|
||||
* Optional means that this argument can be put at the end of a syntax
|
||||
* and obtains a default value ({@link #getDefaultValue()}).
|
||||
*
|
||||
* @return true if this argument is considered optional
|
||||
*/
|
||||
public boolean isOptional() {
|
||||
return defaultValue != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value of this argument.
|
||||
*
|
||||
* @return the argument default value, null if the argument is not optional
|
||||
*/
|
||||
@Nullable
|
||||
public T getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value of the argument.
|
||||
* <p>
|
||||
* A non-null value means that the argument can be put at the end of a syntax
|
||||
* to act as an optional one.
|
||||
*
|
||||
* @param defaultValue the default argument value, null to make the argument non-optional
|
||||
* @return 'this' for chaining
|
||||
*/
|
||||
@NotNull
|
||||
public Argument<T> setDefaultValue(@Nullable T defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the argument has any error callback.
|
||||
*
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -15,21 +16,15 @@ public class ArgumentBoolean extends Argument<Boolean> {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
return (value.equalsIgnoreCase("true")
|
||||
|| value.equalsIgnoreCase("false")) ? SUCCESS : NOT_BOOLEAN_ERROR;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Boolean parse(@NotNull String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
public Boolean parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
if (input.equalsIgnoreCase("true"))
|
||||
return true;
|
||||
if (input.equalsIgnoreCase("false"))
|
||||
return false;
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull Boolean value) {
|
||||
return SUCCESS;
|
||||
throw new ArgumentSyntaxException("Not a boolean", input, NOT_BOOLEAN_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.utils.callback.validator.StringArrayValidator;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Same as {@link ArgumentStringArray} with the exception
|
||||
* that this argument can trigger {@link net.minestom.server.command.builder.Command#onDynamicWrite(String)}.
|
||||
* that this argument can trigger {@link net.minestom.server.command.builder.Command#onDynamicWrite(CommandSender, String)}.
|
||||
*/
|
||||
public class ArgumentDynamicStringArray extends Argument<String[]> {
|
||||
|
||||
@ -20,28 +21,19 @@ public class ArgumentDynamicStringArray extends Argument<String[]> {
|
||||
super(id, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String[] parse(@NotNull String value) {
|
||||
return value.split(Pattern.quote(" "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull String[] value) {
|
||||
public String[] parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
final String[] value = input.split(StringUtils.SPACE);
|
||||
|
||||
// true if 'value' is valid based on the dynamic restriction
|
||||
final boolean restrictionCheck = dynamicRestriction == null || dynamicRestriction.isValid(value);
|
||||
|
||||
if (!restrictionCheck) {
|
||||
return RESTRICTION_ERROR;
|
||||
throw new ArgumentSyntaxException("Argument does not respect the dynamic restriction", input, RESTRICTION_ERROR);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,13 +1,16 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.arguments.minecraft.SuggestionType;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.utils.callback.validator.StringValidator;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Same as {@link ArgumentWord} with the exception
|
||||
* that this argument can trigger {@link net.minestom.server.command.builder.Command#onDynamicWrite(String)}
|
||||
* that this argument can trigger {@link net.minestom.server.command.builder.Command#onDynamicWrite(CommandSender, String)}
|
||||
* when the suggestion type is {@link SuggestionType#ASK_SERVER}, or any other suggestions available in the enum.
|
||||
*/
|
||||
public class ArgumentDynamicWord extends Argument<String> {
|
||||
@ -24,31 +27,20 @@ public class ArgumentDynamicWord extends Argument<String> {
|
||||
this.suggestionType = suggestionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
if (value.contains(" "))
|
||||
return SPACE_ERROR;
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String parse(@NotNull String value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull String value) {
|
||||
public String parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
if (input.contains(StringUtils.SPACE))
|
||||
throw new ArgumentSyntaxException("Word cannot contain space characters", input, SPACE_ERROR);
|
||||
|
||||
// true if 'value' is valid based on the dynamic restriction
|
||||
final boolean restrictionCheck = dynamicRestriction == null || dynamicRestriction.isValid(value);
|
||||
final boolean restrictionCheck = dynamicRestriction == null || dynamicRestriction.isValid(input);
|
||||
|
||||
if (!restrictionCheck) {
|
||||
return RESTRICTION_ERROR;
|
||||
throw new ArgumentSyntaxException("Word does not respect the dynamic restriction", input, RESTRICTION_ERROR);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -15,45 +17,35 @@ public class ArgumentString extends Argument<String> {
|
||||
super(id, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
public String parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
return staticParse(input);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String staticParse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
// Check if value start and end with quote
|
||||
final char first = value.charAt(0);
|
||||
final char last = value.charAt(value.length() - 1);
|
||||
final char first = input.charAt(0);
|
||||
final char last = input.charAt(input.length() - 1);
|
||||
final boolean quote = first == '\"' && last == '\"';
|
||||
if (!quote)
|
||||
return QUOTE_ERROR;
|
||||
throw new ArgumentSyntaxException("String argument needs to start and end with quotes", input, QUOTE_ERROR);
|
||||
|
||||
for (int i = 1; i < value.length(); i++) {
|
||||
final char c = value.charAt(i);
|
||||
// Remove first and last characters (quotes)
|
||||
input = input.substring(1, input.length() - 1);
|
||||
|
||||
// Verify backslashes
|
||||
for (int i = 1; i < input.length(); i++) {
|
||||
final char c = input.charAt(i);
|
||||
if (c == '\"') {
|
||||
final char lastChar = value.charAt(i - 1);
|
||||
if (lastChar == '\\') {
|
||||
continue;
|
||||
} else if (i == value.length() - 1) {
|
||||
return SUCCESS;
|
||||
final char lastChar = input.charAt(i - 1);
|
||||
if (lastChar != '\\') {
|
||||
throw new ArgumentSyntaxException("Non-escaped quote", input, QUOTE_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last quote is written like \"
|
||||
return QUOTE_ERROR;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String parse(@NotNull String value) {
|
||||
// Remove first and last characters (quote)
|
||||
value = value.substring(1, value.length() - 1);
|
||||
|
||||
// Remove all backslashes
|
||||
value = value.replace("\\", "");
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull String value) {
|
||||
return SUCCESS;
|
||||
return StringEscapeUtils.unescapeJava(input);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@ -15,19 +17,9 @@ public class ArgumentStringArray extends Argument<String[]> {
|
||||
super(id, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String[] parse(@NotNull String value) {
|
||||
return value.split(Pattern.quote(" "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull String[] value) {
|
||||
return SUCCESS;
|
||||
public String[] parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
return input.split(Pattern.quote(StringUtils.SPACE));
|
||||
}
|
||||
}
|
||||
|
@ -100,9 +100,8 @@ public class ArgumentType {
|
||||
return new ArgumentFloatRange(id);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static ArgumentEntities Entities(@NotNull String id) {
|
||||
return new ArgumentEntities(id);
|
||||
public static ArgumentEntity Entities(@NotNull String id) {
|
||||
return new ArgumentEntity(id);
|
||||
}
|
||||
|
||||
public static ArgumentItemStack ItemStack(@NotNull String id) {
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -28,41 +31,41 @@ public class ArgumentWord extends Argument<String> {
|
||||
* <p>
|
||||
* WARNING: having an array too long would result in a packet too big or the client being stuck during login.
|
||||
*
|
||||
* @param restrictions the accepted words
|
||||
* @return 'this'
|
||||
* @param restrictions the accepted words,
|
||||
* can be null but if an array is passed
|
||||
* you need to ensure that it is filled with non-null values
|
||||
* @return 'this' for chaining
|
||||
* @throws NullPointerException if {@code restrictions} is not null but contains null value(s)
|
||||
*/
|
||||
@NotNull
|
||||
public ArgumentWord from(@Nullable String... restrictions) {
|
||||
if (restrictions != null) {
|
||||
for (String restriction : restrictions) {
|
||||
Check.notNull(restriction,
|
||||
"ArgumentWord restriction cannot be null, you can pass 'null' instead of an empty array");
|
||||
}
|
||||
}
|
||||
|
||||
this.restrictions = restrictions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
if (value.contains(" "))
|
||||
return SPACE_ERROR;
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String parse(@NotNull String value) {
|
||||
return value;
|
||||
}
|
||||
public String parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
if (input.contains(StringUtils.SPACE))
|
||||
throw new ArgumentSyntaxException("Word cannot contain space character", input, SPACE_ERROR);
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull String value) {
|
||||
// Check restrictions
|
||||
// Check restrictions (acting as literal)
|
||||
if (hasRestrictions()) {
|
||||
for (String r : restrictions) {
|
||||
if (value.equalsIgnoreCase(r))
|
||||
return SUCCESS;
|
||||
if (input.equalsIgnoreCase(r))
|
||||
return input;
|
||||
}
|
||||
return RESTRICTION_ERROR;
|
||||
throw new ArgumentSyntaxException("Word needs to be in the restriction list", input, RESTRICTION_ERROR);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@ package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.minestom.server.chat.ChatColor;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -17,20 +18,13 @@ public class ArgumentColor extends Argument<ChatColor> {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
final ChatColor color = ChatColor.fromName(value);
|
||||
return color == ChatColor.NO_COLOR ? UNDEFINED_COLOR : SUCCESS;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ChatColor parse(@NotNull String value) {
|
||||
return ChatColor.fromName(value);
|
||||
}
|
||||
public ChatColor parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
final ChatColor color = ChatColor.fromName(input);
|
||||
if (color == ChatColor.NO_COLOR)
|
||||
throw new ArgumentSyntaxException("Undefined color", input, UNDEFINED_COLOR);
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull ChatColor value) {
|
||||
return SUCCESS;
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
@ -1,134 +0,0 @@
|
||||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
// TODO
|
||||
|
||||
/**
|
||||
* Represents the target selector argument.
|
||||
* https://minecraft.gamepedia.com/Commands#Target_selectors
|
||||
*/
|
||||
public class ArgumentEntities extends Argument<ArrayList<Entity>> {
|
||||
|
||||
public static final int INVALID_SYNTAX = -2;
|
||||
public static final int ONLY_SINGLE_ENTITY_ERROR = -3;
|
||||
public static final int ONLY_PLAYERS_ERROR = -4;
|
||||
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||
private static final List<String> selectorVariables = Arrays.asList("@p", "@r", "@a", "@e", "@s");
|
||||
private static final List<String> playersOnlySelector = Arrays.asList("@p", "@r", "@a", "@s");
|
||||
private static final List<String> singleOnlySelector = Arrays.asList("@p", "@r", "@s");
|
||||
private static final List<String> validArguments = Arrays.asList("x", "y", "z",
|
||||
"distance", "dx", "dy", "dz",
|
||||
"scores", "tag", "team", "limit", "sort", "level", "gamemode", "name",
|
||||
"x_rotation", "y_rotation", "type", "nbt", "advancements", "predicate");
|
||||
private boolean onlySingleEntity;
|
||||
private boolean onlyPlayers;
|
||||
|
||||
public ArgumentEntities(String id) {
|
||||
super(id, true);
|
||||
}
|
||||
|
||||
public ArgumentEntities singleEntity(boolean singleEntity) {
|
||||
this.onlySingleEntity = singleEntity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArgumentEntities onlyPlayers(boolean onlyPlayers) {
|
||||
this.onlyPlayers = onlyPlayers;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
System.out.println("check: " + value);
|
||||
|
||||
// Check for raw player name
|
||||
if (value.length() <= 16) {
|
||||
if (CONNECTION_MANAGER.getPlayer(value) != null)
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// The minimum size is always 0 (for the selector variable, ex: @p)
|
||||
if (value.length() < 2)
|
||||
return INVALID_SYNTAX;
|
||||
|
||||
// The target selector variable always start by '@'
|
||||
if (!value.startsWith("@"))
|
||||
return INVALID_SYNTAX;
|
||||
|
||||
final String selectorVariable = value.substring(0, 2);
|
||||
|
||||
// Check if the selector variable used exists
|
||||
if (!selectorVariables.contains(selectorVariable))
|
||||
return INVALID_SYNTAX;
|
||||
|
||||
// Check if it should only select single entity and if the selector variable valid the condition
|
||||
if (onlySingleEntity && !singleOnlySelector.contains(selectorVariable))
|
||||
return ONLY_SINGLE_ENTITY_ERROR;
|
||||
|
||||
// Check if it should only select players and if the selector variable valid the condition
|
||||
if (onlyPlayers && !playersOnlySelector.contains(selectorVariable))
|
||||
return ONLY_PLAYERS_ERROR;
|
||||
|
||||
// The selector is a single selector variable which verify all the conditions
|
||||
if (value.length() == 2)
|
||||
return SUCCESS;
|
||||
|
||||
// START PARSING THE STRUCTURE
|
||||
final String structure = value.substring(2);
|
||||
|
||||
// The structure isn't opened or closed properly
|
||||
if (!structure.startsWith("[") || !structure.endsWith("]"))
|
||||
return INVALID_SYNTAX;
|
||||
final String structureData = structure.substring(1, structure.length() - 1);
|
||||
|
||||
String currentArgument = "";
|
||||
for (int i = 0; i < structureData.length(); i++) {
|
||||
final char c = structureData.charAt(i);
|
||||
if (c == '=') {
|
||||
i = retrieveArgument(structureData, currentArgument, i);
|
||||
} else {
|
||||
currentArgument += c;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int retrieveArgument(String structureData, String argument, int index) {
|
||||
int finalIndex = index;
|
||||
for (int i = index + 1; i < structureData.length(); i++) {
|
||||
System.out.println("char: " + structureData.charAt(i));
|
||||
System.out.println("retrieve: " + argument);
|
||||
}
|
||||
|
||||
return finalIndex;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ArrayList<Entity> parse(@NotNull String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull ArrayList<Entity> value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
public boolean isOnlySingleEntity() {
|
||||
return onlySingleEntity;
|
||||
}
|
||||
|
||||
public boolean isOnlyPlayers() {
|
||||
return onlyPlayers;
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.entity.GameMode;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.registry.Registries;
|
||||
import net.minestom.server.utils.entity.EntityFinder;
|
||||
import net.minestom.server.utils.math.IntRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
// TODO
|
||||
|
||||
/**
|
||||
* Represents the target selector argument.
|
||||
* https://minecraft.gamepedia.com/Commands#Target_selectors
|
||||
*/
|
||||
public class ArgumentEntity extends Argument<EntityFinder> {
|
||||
|
||||
public static final int INVALID_SYNTAX = -2;
|
||||
public static final int ONLY_SINGLE_ENTITY_ERROR = -3;
|
||||
public static final int ONLY_PLAYERS_ERROR = -4;
|
||||
public static final int INVALID_ARGUMENT_NAME = -5;
|
||||
public static final int INVALID_ARGUMENT_VALUE = -6;
|
||||
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||
private static final List<String> selectorVariables = Arrays.asList("@p", "@r", "@a", "@e", "@s");
|
||||
private static final List<String> playersOnlySelector = Arrays.asList("@p", "@r", "@a", "@s");
|
||||
private static final List<String> singleOnlySelector = Arrays.asList("@p", "@r", "@s");
|
||||
// List with all the valid arguments
|
||||
private static final List<String> validArguments = Arrays.asList(
|
||||
"x", "y", "z",
|
||||
"distance", "dx", "dy", "dz",
|
||||
"scores", "tag", "team", "limit", "sort", "level", "gamemode", "name",
|
||||
"x_rotation", "y_rotation", "type", "nbt", "advancements", "predicate");
|
||||
|
||||
// List with all the easily parsable arguments which only require reading until a specific character (comma)
|
||||
private static final List<String> simpleArguments = Arrays.asList(
|
||||
"x", "y", "z",
|
||||
"distance", "dx", "dy", "dz",
|
||||
"scores", "tag", "team", "limit", "sort", "level", "gamemode",
|
||||
"x_rotation", "y_rotation", "type", "advancements", "predicate");
|
||||
private boolean onlySingleEntity;
|
||||
private boolean onlyPlayers;
|
||||
|
||||
public ArgumentEntity(String id) {
|
||||
super(id, true);
|
||||
}
|
||||
|
||||
public ArgumentEntity singleEntity(boolean singleEntity) {
|
||||
this.onlySingleEntity = singleEntity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArgumentEntity onlyPlayers(boolean onlyPlayers) {
|
||||
this.onlyPlayers = onlyPlayers;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public EntityFinder parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
return staticParse(input, onlySingleEntity, onlyPlayers);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static EntityFinder staticParse(@NotNull String input,
|
||||
boolean onlySingleEntity, boolean onlyPlayers) throws ArgumentSyntaxException {
|
||||
// Check for raw player name
|
||||
if (input.length() <= 16) {
|
||||
if (CONNECTION_MANAGER.getPlayer(input) != null) {
|
||||
return new EntityFinder()
|
||||
.setTargetSelector(EntityFinder.TargetSelector.ALL_PLAYERS)
|
||||
.setName(input, EntityFinder.ToggleableType.INCLUDE);
|
||||
}
|
||||
}
|
||||
|
||||
// The minimum size is always 0 (for the selector variable, ex: @p)
|
||||
if (input.length() < 2)
|
||||
throw new ArgumentSyntaxException("Length needs to be > 1", input, INVALID_SYNTAX);
|
||||
|
||||
// The target selector variable always start by '@'
|
||||
if (!input.startsWith("@"))
|
||||
throw new ArgumentSyntaxException("Target selector needs to start with @", input, INVALID_SYNTAX);
|
||||
|
||||
final String selectorVariable = input.substring(0, 2);
|
||||
|
||||
// Check if the selector variable used exists
|
||||
if (!selectorVariables.contains(selectorVariable))
|
||||
throw new ArgumentSyntaxException("Invalid selector variable", input, INVALID_SYNTAX);
|
||||
|
||||
// Check if it should only select single entity and if the selector variable valid the condition
|
||||
if (onlySingleEntity && !singleOnlySelector.contains(selectorVariable))
|
||||
throw new ArgumentSyntaxException("Argument requires only a single entity", input, ONLY_SINGLE_ENTITY_ERROR);
|
||||
|
||||
// Check if it should only select players and if the selector variable valid the condition
|
||||
if (onlyPlayers && !playersOnlySelector.contains(selectorVariable))
|
||||
throw new ArgumentSyntaxException("Argument requires only players", input, ONLY_PLAYERS_ERROR);
|
||||
|
||||
// Create the EntityFinder which will be used for the rest of the parsing
|
||||
final EntityFinder entityFinder = new EntityFinder()
|
||||
.setTargetSelector(toTargetSelector(selectorVariable));
|
||||
|
||||
// The selector is a single selector variable which verify all the conditions
|
||||
if (input.length() == 2)
|
||||
return entityFinder;
|
||||
|
||||
// START PARSING THE STRUCTURE
|
||||
final String structure = input.substring(2);
|
||||
return parseStructure(input, entityFinder, structure);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static EntityFinder parseStructure(@NotNull String input,
|
||||
@NotNull EntityFinder entityFinder,
|
||||
@NotNull String structure) throws ArgumentSyntaxException {
|
||||
// The structure isn't opened or closed properly
|
||||
if (!structure.startsWith("[") || !structure.endsWith("]"))
|
||||
throw new ArgumentSyntaxException("Target selector needs to start and end with brackets", input, INVALID_SYNTAX);
|
||||
|
||||
// Remove brackets
|
||||
final String structureData = structure.substring(1, structure.length() - 1);
|
||||
//System.out.println("structure data: " + structureData);
|
||||
|
||||
String currentArgument = "";
|
||||
for (int i = 0; i < structureData.length(); i++) {
|
||||
final char c = structureData.charAt(i);
|
||||
if (c == '=') {
|
||||
|
||||
// Replace all unnecessary spaces
|
||||
currentArgument = currentArgument.trim();
|
||||
|
||||
if (!validArguments.contains(currentArgument))
|
||||
throw new ArgumentSyntaxException("Argument name '" + currentArgument + "' does not exist", input, INVALID_ARGUMENT_NAME);
|
||||
|
||||
i = parseArgument(entityFinder, currentArgument, input, structureData, i);
|
||||
currentArgument = ""; // Reset current argument
|
||||
} else {
|
||||
currentArgument += c;
|
||||
}
|
||||
}
|
||||
|
||||
return entityFinder;
|
||||
}
|
||||
|
||||
private static int parseArgument(@NotNull EntityFinder entityFinder,
|
||||
@NotNull String argumentName,
|
||||
@NotNull String input,
|
||||
@NotNull String structureData, int beginIndex) throws ArgumentSyntaxException {
|
||||
final char comma = ',';
|
||||
final boolean isSimple = simpleArguments.contains(argumentName);
|
||||
|
||||
int finalIndex = beginIndex + 1;
|
||||
StringBuilder valueBuilder = new StringBuilder();
|
||||
for (; finalIndex < structureData.length(); finalIndex++) {
|
||||
final char c = structureData.charAt(finalIndex);
|
||||
|
||||
// Command is parsed
|
||||
if (isSimple && c == comma)
|
||||
break;
|
||||
|
||||
valueBuilder.append(c);
|
||||
}
|
||||
|
||||
final String value = valueBuilder.toString().trim();
|
||||
|
||||
//System.out.println("value: " + value);
|
||||
switch (argumentName) {
|
||||
case "type": {
|
||||
final boolean include = !value.startsWith("!");
|
||||
final String entityName = include ? value : value.substring(1);
|
||||
final EntityType entityType = Registries.getEntityType(entityName);
|
||||
if (entityType == null)
|
||||
throw new ArgumentSyntaxException("Invalid entity name", input, INVALID_ARGUMENT_VALUE);
|
||||
entityFinder.setEntity(entityType, include ? EntityFinder.ToggleableType.INCLUDE : EntityFinder.ToggleableType.EXCLUDE);
|
||||
break;
|
||||
}
|
||||
case "gamemode": {
|
||||
final boolean include = !value.startsWith("!");
|
||||
final String gameModeName = include ? value : value.substring(1);
|
||||
try {
|
||||
final GameMode gameMode = GameMode.valueOf(gameModeName);
|
||||
entityFinder.setGameMode(gameMode, include ? EntityFinder.ToggleableType.INCLUDE : EntityFinder.ToggleableType.EXCLUDE);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ArgumentSyntaxException("Invalid entity game mode", input, INVALID_ARGUMENT_VALUE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "limit":
|
||||
try {
|
||||
final int limit = Integer.parseInt(value);
|
||||
entityFinder.setLimit(limit);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ArgumentSyntaxException("Invalid limit number", input, INVALID_ARGUMENT_VALUE);
|
||||
}
|
||||
break;
|
||||
case "sort":
|
||||
try {
|
||||
EntityFinder.EntitySort entitySort = EntityFinder.EntitySort.valueOf(value.toUpperCase());
|
||||
entityFinder.setEntitySort(entitySort);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ArgumentSyntaxException("Invalid entity sort", input, INVALID_ARGUMENT_VALUE);
|
||||
}
|
||||
break;
|
||||
case "level":
|
||||
try {
|
||||
final IntRange level = ArgumentIntRange.staticParse(value);
|
||||
entityFinder.setLevel(level);
|
||||
} catch (ArgumentSyntaxException e) {
|
||||
throw new ArgumentSyntaxException("Invalid level number", input, INVALID_ARGUMENT_VALUE);
|
||||
}
|
||||
break;
|
||||
case "distance":
|
||||
try {
|
||||
final IntRange distance = ArgumentIntRange.staticParse(value);
|
||||
entityFinder.setDistance(distance);
|
||||
} catch (ArgumentSyntaxException e) {
|
||||
throw new ArgumentSyntaxException("Invalid level number", input, INVALID_ARGUMENT_VALUE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return finalIndex;
|
||||
}
|
||||
|
||||
public boolean isOnlySingleEntity() {
|
||||
return onlySingleEntity;
|
||||
}
|
||||
|
||||
public boolean isOnlyPlayers() {
|
||||
return onlyPlayers;
|
||||
}
|
||||
|
||||
private static EntityFinder.TargetSelector toTargetSelector(@NotNull String selectorVariable) {
|
||||
if (selectorVariable.equals("@p"))
|
||||
return EntityFinder.TargetSelector.NEAREST_PLAYER;
|
||||
if (selectorVariable.equals("@r"))
|
||||
return EntityFinder.TargetSelector.RANDOM_PLAYER;
|
||||
if (selectorVariable.equals("@a"))
|
||||
return EntityFinder.TargetSelector.ALL_PLAYERS;
|
||||
if (selectorVariable.equals("@e"))
|
||||
return EntityFinder.TargetSelector.ALL_ENTITIES;
|
||||
if (selectorVariable.equals("@s"))
|
||||
return EntityFinder.TargetSelector.SELF;
|
||||
throw new IllegalStateException("Weird selector variable: " + selectorVariable);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.utils.math.FloatRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -16,63 +17,39 @@ public class ArgumentFloatRange extends ArgumentRange<FloatRange> {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
try {
|
||||
Float.valueOf(value);
|
||||
return SUCCESS; // Is a single number
|
||||
} catch (NumberFormatException e) {
|
||||
String[] split = value.split(Pattern.quote(".."));
|
||||
if (split.length == 1) {
|
||||
try {
|
||||
Float.valueOf(split[0]);
|
||||
return SUCCESS;
|
||||
} catch (NumberFormatException e2) {
|
||||
return FORMAT_ERROR;
|
||||
}
|
||||
} else if (split.length == 2) {
|
||||
try {
|
||||
Float.valueOf(split[0]); // min
|
||||
Float.valueOf(split[1]); // max
|
||||
return SUCCESS;
|
||||
} catch (NumberFormatException e2) {
|
||||
return FORMAT_ERROR;
|
||||
}
|
||||
} else {
|
||||
return FORMAT_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public FloatRange parse(@NotNull String value) {
|
||||
if (value.contains("..")) {
|
||||
final int index = value.indexOf('.');
|
||||
final String[] split = value.split(Pattern.quote(".."));
|
||||
public FloatRange parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
if (input.contains("..")) {
|
||||
final int index = input.indexOf('.');
|
||||
final String[] split = input.split(Pattern.quote(".."));
|
||||
|
||||
final float min;
|
||||
final float max;
|
||||
if (index == 0) {
|
||||
// Format ..NUMBER
|
||||
min = Float.MIN_VALUE;
|
||||
max = Float.parseFloat(split[0]);
|
||||
} else {
|
||||
if (split.length == 2) {
|
||||
// Format NUMBER..NUMBER
|
||||
min = Float.parseFloat(split[0]);
|
||||
max = Float.parseFloat(split[1]);
|
||||
final float min;
|
||||
final float max;
|
||||
if (index == 0) {
|
||||
// Format ..NUMBER
|
||||
min = Float.MIN_VALUE;
|
||||
max = Float.parseFloat(split[0]);
|
||||
} else {
|
||||
// Format NUMBER..
|
||||
min = Float.parseFloat(split[0]);
|
||||
max = Float.MAX_VALUE;
|
||||
if (split.length == 2) {
|
||||
// Format NUMBER..NUMBER
|
||||
min = Float.parseFloat(split[0]);
|
||||
max = Float.parseFloat(split[1]);
|
||||
} else {
|
||||
// Format NUMBER..
|
||||
min = Float.parseFloat(split[0]);
|
||||
max = Float.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new FloatRange(min, max);
|
||||
} else {
|
||||
final float number = Float.parseFloat(value);
|
||||
return new FloatRange(number);
|
||||
return new FloatRange(min, max);
|
||||
} else {
|
||||
final float number = Float.parseFloat(input);
|
||||
return new FloatRange(number);
|
||||
}
|
||||
} catch (NumberFormatException e2) {
|
||||
throw new ArgumentSyntaxException("Invalid number", input, FORMAT_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.utils.math.IntRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -16,63 +17,44 @@ public class ArgumentIntRange extends ArgumentRange<IntRange> {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
try {
|
||||
Integer.valueOf(value);
|
||||
return SUCCESS; // Is a single number
|
||||
} catch (NumberFormatException e) {
|
||||
String[] split = value.split(Pattern.quote(".."));
|
||||
if (split.length == 1) {
|
||||
try {
|
||||
Integer.valueOf(split[0]);
|
||||
return SUCCESS;
|
||||
} catch (NumberFormatException e2) {
|
||||
return FORMAT_ERROR;
|
||||
}
|
||||
} else if (split.length == 2) {
|
||||
try {
|
||||
Integer.valueOf(split[0]); // min
|
||||
Integer.valueOf(split[1]); // max
|
||||
return SUCCESS;
|
||||
} catch (NumberFormatException e2) {
|
||||
return FORMAT_ERROR;
|
||||
}
|
||||
} else {
|
||||
return FORMAT_ERROR;
|
||||
}
|
||||
}
|
||||
public IntRange parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
return staticParse(input);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public IntRange parse(@NotNull String value) {
|
||||
if (value.contains("..")) {
|
||||
final int index = value.indexOf('.');
|
||||
final String[] split = value.split(Pattern.quote(".."));
|
||||
public static IntRange staticParse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
if (input.contains("..")) {
|
||||
final int index = input.indexOf('.');
|
||||
final String[] split = input.split(Pattern.quote(".."));
|
||||
|
||||
final int min;
|
||||
final int max;
|
||||
if (index == 0) {
|
||||
// Format ..NUMBER
|
||||
min = Integer.MIN_VALUE;
|
||||
max = Integer.parseInt(split[0]);
|
||||
} else {
|
||||
if (split.length == 2) {
|
||||
// Format NUMBER..NUMBER
|
||||
min = Integer.parseInt(split[0]);
|
||||
max = Integer.parseInt(split[1]);
|
||||
final int min;
|
||||
final int max;
|
||||
if (index == 0) {
|
||||
// Format ..NUMBER
|
||||
min = Integer.MIN_VALUE;
|
||||
max = Integer.parseInt(split[0]);
|
||||
} else {
|
||||
// Format NUMBER..
|
||||
min = Integer.parseInt(split[0]);
|
||||
max = Integer.MAX_VALUE;
|
||||
if (split.length == 2) {
|
||||
// Format NUMBER..NUMBER
|
||||
min = Integer.parseInt(split[0]);
|
||||
max = Integer.parseInt(split[1]);
|
||||
} else {
|
||||
// Format NUMBER..
|
||||
min = Integer.parseInt(split[0]);
|
||||
max = Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new IntRange(min, max);
|
||||
} else {
|
||||
final int number = Integer.parseInt(value);
|
||||
return new IntRange(number);
|
||||
return new IntRange(min, max);
|
||||
} else {
|
||||
final int number = Integer.parseInt(input);
|
||||
return new IntRange(number);
|
||||
}
|
||||
} catch (NumberFormatException e2) {
|
||||
throw new ArgumentSyntaxException("Invalid number", input, FORMAT_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.registry.Registries;
|
||||
import net.minestom.server.utils.NBTUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||
import org.jglrxavpok.hephaistos.nbt.SNBTParser;
|
||||
@ -29,63 +29,36 @@ public class ArgumentItemStack extends Argument<ItemStack> {
|
||||
super(id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
if (value.startsWith("{")) {
|
||||
return NO_MATERIAL;
|
||||
}
|
||||
|
||||
final int nbtIndex = value.indexOf("{");
|
||||
|
||||
if (nbtIndex == -1 && !value.contains(" ")) {
|
||||
// Only item name
|
||||
return SUCCESS;
|
||||
} else {
|
||||
// has nbt
|
||||
final String sNBT = value.substring(nbtIndex);
|
||||
try {
|
||||
NBT nbt = new SNBTParser(new StringReader(sNBT)).parse();
|
||||
return nbt instanceof NBTCompound ? SUCCESS : INVALID_NBT;
|
||||
} catch (NBTException e) {
|
||||
return INVALID_NBT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ItemStack parse(@NotNull String value) {
|
||||
final int nbtIndex = value.indexOf("{");
|
||||
public ItemStack parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
final int nbtIndex = input.indexOf("{");
|
||||
|
||||
if (nbtIndex == 0)
|
||||
throw new ArgumentSyntaxException("The item needs a material", input, NO_MATERIAL);
|
||||
|
||||
if (nbtIndex == -1) {
|
||||
// Only item name
|
||||
final Material material = Registries.getMaterial(value);
|
||||
final Material material = Registries.getMaterial(input);
|
||||
return new ItemStack(material, (byte) 1);
|
||||
} else {
|
||||
final String materialName = value.substring(0, nbtIndex);
|
||||
final String materialName = input.substring(0, nbtIndex);
|
||||
final Material material = Registries.getMaterial(materialName);
|
||||
|
||||
ItemStack itemStack = new ItemStack(material, (byte) 1);
|
||||
|
||||
final String sNBT = value.substring(nbtIndex).replace("\\\"", "\"");
|
||||
final String sNBT = input.substring(nbtIndex).replace("\\\"", "\"");
|
||||
|
||||
NBTCompound compound = null;
|
||||
NBTCompound compound;
|
||||
try {
|
||||
compound = (NBTCompound) new SNBTParser(new StringReader(sNBT)).parse();
|
||||
} catch (NBTException e) {
|
||||
e.printStackTrace();
|
||||
throw new ArgumentSyntaxException("Item NBT is invalid", input, INVALID_NBT);
|
||||
}
|
||||
|
||||
assert compound != null;
|
||||
|
||||
NBTUtils.loadDataIntoItem(itemStack, compound);
|
||||
|
||||
return itemStack;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull ItemStack value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
@ -22,29 +23,18 @@ public class ArgumentNbtCompoundTag extends Argument<NBTCompound> {
|
||||
super(id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
try {
|
||||
NBT nbt = new SNBTParser(new StringReader(value)).parse();
|
||||
return nbt instanceof NBTCompound ? SUCCESS : INVALID_NBT;
|
||||
} catch (NBTException e) {
|
||||
return INVALID_NBT;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public NBTCompound parse(@NotNull String value) {
|
||||
public NBTCompound parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
NBT nbt = new SNBTParser(new StringReader(value)).parse();
|
||||
NBT nbt = new SNBTParser(new StringReader(input)).parse();
|
||||
|
||||
if (!(nbt instanceof NBTCompound))
|
||||
throw new ArgumentSyntaxException("NBTCompound is invalid", input, INVALID_NBT);
|
||||
|
||||
return (NBTCompound) nbt;
|
||||
} catch (NBTException e) {
|
||||
return null;
|
||||
throw new ArgumentSyntaxException("NBTCompound is invalid", input, INVALID_NBT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull NBTCompound value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||
@ -23,28 +24,13 @@ public class ArgumentNbtTag extends Argument<NBT> {
|
||||
super(id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
try {
|
||||
NBT nbt = new SNBTParser(new StringReader(value)).parse();
|
||||
return nbt != null ? SUCCESS : INVALID_NBT;
|
||||
} catch (NBTException e) {
|
||||
return INVALID_NBT;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public NBT parse(@NotNull String value) {
|
||||
public NBT parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
return new SNBTParser(new StringReader(value)).parse();
|
||||
return new SNBTParser(new StringReader(input)).parse();
|
||||
} catch (NBTException e) {
|
||||
return null;
|
||||
throw new ArgumentSyntaxException("Invalid NBT", input, INVALID_NBT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull NBT value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Abstract class used by {@link ArgumentIntRange} and {@link ArgumentFloatRange}.
|
||||
@ -15,9 +14,4 @@ public abstract class ArgumentRange<T> extends Argument<T> {
|
||||
public ArgumentRange(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull T value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package net.minestom.server.command.builder.arguments.minecraft;
|
||||
import it.unimi.dsi.fastutil.chars.CharArrayList;
|
||||
import it.unimi.dsi.fastutil.chars.CharList;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.utils.time.UpdateOption;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -23,43 +24,31 @@ public class ArgumentTime extends Argument<UpdateOption> {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
final char lastChar = value.charAt(value.length() - 1);
|
||||
if (!SUFFIXES.contains(lastChar))
|
||||
return INVALID_TIME_FORMAT;
|
||||
|
||||
value = value.substring(0, value.length() - 1);
|
||||
try {
|
||||
// Check if value is a number
|
||||
Integer.parseInt(value);
|
||||
|
||||
return SUCCESS;
|
||||
} catch (NumberFormatException e) {
|
||||
return NO_NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public UpdateOption parse(@NotNull String value) {
|
||||
final char lastChar = value.charAt(value.length() - 1);
|
||||
TimeUnit timeUnit = null;
|
||||
if (lastChar == 'd') {
|
||||
timeUnit = TimeUnit.DAY;
|
||||
} else if (lastChar == 's') {
|
||||
timeUnit = TimeUnit.SECOND;
|
||||
} else if (lastChar == 't') {
|
||||
timeUnit = TimeUnit.TICK;
|
||||
public UpdateOption parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
final char lastChar = input.charAt(input.length() - 1);
|
||||
if (!SUFFIXES.contains(lastChar))
|
||||
throw new ArgumentSyntaxException("Time format is invalid", input, INVALID_TIME_FORMAT);
|
||||
|
||||
// Remove last char
|
||||
input = input.substring(0, input.length() - 1);
|
||||
try {
|
||||
// Check if value is a number
|
||||
final int time = Integer.parseInt(input);
|
||||
|
||||
TimeUnit timeUnit = null;
|
||||
if (lastChar == 'd') {
|
||||
timeUnit = TimeUnit.DAY;
|
||||
} else if (lastChar == 's') {
|
||||
timeUnit = TimeUnit.SECOND;
|
||||
} else if (lastChar == 't') {
|
||||
timeUnit = TimeUnit.TICK;
|
||||
}
|
||||
|
||||
return new UpdateOption(time, timeUnit);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ArgumentSyntaxException("Time needs to be a number", input, NO_NUMBER);
|
||||
}
|
||||
value = value.substring(0, value.length() - 1);
|
||||
final int time = Integer.parseInt(value);
|
||||
|
||||
return new UpdateOption(time, timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull UpdateOption value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.command.builder.arguments.minecraft.registry;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class ArgumentRegistry<T> extends Argument<T> {
|
||||
@ -13,19 +14,13 @@ public abstract class ArgumentRegistry<T> extends Argument<T> {
|
||||
|
||||
public abstract T getRegistry(@NotNull String value);
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
return getRegistry(value) == null ? INVALID_NAME : SUCCESS;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public T parse(@NotNull String value) {
|
||||
return getRegistry(value);
|
||||
}
|
||||
public T parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
final T registryValue = getRegistry(input);
|
||||
if (registryValue == null)
|
||||
throw new ArgumentSyntaxException("Registry value is invalid", input, INVALID_NAME);
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull T value) {
|
||||
return SUCCESS;
|
||||
return registryValue;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.command.builder.arguments.number;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ArgumentDouble extends ArgumentNumber<Double> {
|
||||
@ -10,44 +11,33 @@ public class ArgumentDouble extends ArgumentNumber<Double> {
|
||||
this.max = Double.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
try {
|
||||
String parsed = parseValue(value);
|
||||
int radix = getRadix(value);
|
||||
if (radix != 10) {
|
||||
Long.parseLong(parsed, radix);
|
||||
} else {
|
||||
Double.parseDouble(parsed);
|
||||
}
|
||||
return SUCCESS;
|
||||
} catch (NumberFormatException | NullPointerException e) {
|
||||
return NOT_NUMBER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Double parse(@NotNull String value) {
|
||||
String parsed = parseValue(value);
|
||||
int radix = getRadix(value);
|
||||
if (radix != 10) {
|
||||
return (double) Long.parseLong(parsed, radix);
|
||||
}
|
||||
return Double.parseDouble(parsed);
|
||||
}
|
||||
public Double parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
final double value;
|
||||
{
|
||||
String parsed = parseValue(input);
|
||||
int radix = getRadix(input);
|
||||
if (radix != 10) {
|
||||
value = (double) Long.parseLong(parsed, radix);
|
||||
} else {
|
||||
value = Double.parseDouble(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull Double value) {
|
||||
// Check range
|
||||
if (hasMin && value < min) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
if (hasMax && value > max) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
// Check range
|
||||
if (hasMin && value < min) {
|
||||
throw new ArgumentSyntaxException("Input is lower than the minimum required value", input, RANGE_ERROR);
|
||||
}
|
||||
if (hasMax && value > max) {
|
||||
throw new ArgumentSyntaxException("Input is higher than the minimum required value", input, RANGE_ERROR);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
return value;
|
||||
} catch (NumberFormatException | NullPointerException e) {
|
||||
throw new ArgumentSyntaxException("Input is not a number/long", input, NOT_NUMBER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.command.builder.arguments.number;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ArgumentFloat extends ArgumentNumber<Float> {
|
||||
@ -10,44 +11,33 @@ public class ArgumentFloat extends ArgumentNumber<Float> {
|
||||
this.max = Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
try {
|
||||
String parsed = parseValue(value);
|
||||
int radix = getRadix(value);
|
||||
if (radix != 10) {
|
||||
Integer.parseInt(parsed, radix);
|
||||
} else {
|
||||
Float.parseFloat(parsed);
|
||||
}
|
||||
return SUCCESS;
|
||||
} catch (NumberFormatException | NullPointerException e) {
|
||||
return NOT_NUMBER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Float parse(@NotNull String value) {
|
||||
String parsed = parseValue(value);
|
||||
int radix = getRadix(value);
|
||||
if (radix != 10) {
|
||||
return (float) Integer.parseInt(parsed, radix);
|
||||
}
|
||||
return Float.parseFloat(parsed);
|
||||
}
|
||||
public Float parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
final float value;
|
||||
{
|
||||
String parsed = parseValue(input);
|
||||
int radix = getRadix(input);
|
||||
if (radix != 10) {
|
||||
value = (float) Integer.parseInt(parsed, radix);
|
||||
} else {
|
||||
value = Float.parseFloat(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull Float value) {
|
||||
// Check range
|
||||
if (hasMin && value < min) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
if (hasMax && value > max) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
// Check range
|
||||
if (hasMin && value < min) {
|
||||
throw new ArgumentSyntaxException("Input is lower than the minimum required value", input, RANGE_ERROR);
|
||||
}
|
||||
if (hasMax && value > max) {
|
||||
throw new ArgumentSyntaxException("Input is higher than the minimum required value", input, RANGE_ERROR);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
return value;
|
||||
} catch (NumberFormatException | NullPointerException e) {
|
||||
throw new ArgumentSyntaxException("Input is not a number/long", input, NOT_NUMBER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.command.builder.arguments.number;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ArgumentInteger extends ArgumentNumber<Integer> {
|
||||
@ -10,33 +11,24 @@ public class ArgumentInteger extends ArgumentNumber<Integer> {
|
||||
this.max = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
try {
|
||||
Integer.parseInt(parseValue(value), getRadix(value));
|
||||
return SUCCESS;
|
||||
} catch (NumberFormatException | NullPointerException e) {
|
||||
return NOT_NUMBER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Integer parse(@NotNull String value) {
|
||||
return Integer.parseInt(parseValue(value), getRadix(value));
|
||||
}
|
||||
public Integer parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
final int value = Integer.parseInt(parseValue(input), getRadix(input));
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull Integer value) {
|
||||
// Check range
|
||||
if (hasMin && value < min) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
if (hasMax && value > max) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
// Check range
|
||||
if (hasMin && value < min) {
|
||||
throw new ArgumentSyntaxException("Input is lower than the minimum required value", input, RANGE_ERROR);
|
||||
}
|
||||
if (hasMax && value > max) {
|
||||
throw new ArgumentSyntaxException("Input is higher than the minimum required value", input, RANGE_ERROR);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
return value;
|
||||
} catch (NumberFormatException | NullPointerException e) {
|
||||
throw new ArgumentSyntaxException("Input is not a number/long", input, NOT_NUMBER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.command.builder.arguments.number;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ArgumentLong extends ArgumentNumber<Long> {
|
||||
@ -10,33 +11,24 @@ public class ArgumentLong extends ArgumentNumber<Long> {
|
||||
this.max = Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
try {
|
||||
Long.parseLong(parseValue(value), getRadix(value));
|
||||
return SUCCESS;
|
||||
} catch (NumberFormatException | NullPointerException e) {
|
||||
return NOT_NUMBER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Long parse(@NotNull String value) {
|
||||
return Long.parseLong(parseValue(value), getRadix(value));
|
||||
}
|
||||
public Long parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
final long value = Long.parseLong(parseValue(input), getRadix(input));
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull Long value) {
|
||||
// Check range
|
||||
if (hasMin && value < min) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
if (hasMax && value > max) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
// Check range
|
||||
if (hasMin && value < min) {
|
||||
throw new ArgumentSyntaxException("Input is lower than the minimum required value", input, RANGE_ERROR);
|
||||
}
|
||||
if (hasMax && value > max) {
|
||||
throw new ArgumentSyntaxException("Input is higher than the minimum required value", input, RANGE_ERROR);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
return value;
|
||||
} catch (NumberFormatException | NullPointerException e) {
|
||||
throw new ArgumentSyntaxException("Input is not a number/long", input, NOT_NUMBER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ public abstract class ArgumentNumber<T extends Number> extends Argument<T> {
|
||||
return max;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected String parseValue(@NotNull String value) {
|
||||
if (value.startsWith("0b")) {
|
||||
value = value.replaceFirst(Pattern.quote("0b"), "");
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.minestom.server.command.builder.arguments.relative;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.location.RelativeBlockPosition;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -15,45 +17,16 @@ public class ArgumentRelativeBlockPosition extends ArgumentRelative<RelativeBloc
|
||||
super(id, 3);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
final String[] split = value.split(" ");
|
||||
public RelativeBlockPosition parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
final String[] split = input.split(StringUtils.SPACE);
|
||||
|
||||
// Check if the value has enough element to be correct
|
||||
if (split.length != getNumberCount()) {
|
||||
return INVALID_NUMBER_COUNT_ERROR;
|
||||
throw new ArgumentSyntaxException("Invalid number of values", input, INVALID_NUMBER_COUNT_ERROR);
|
||||
}
|
||||
|
||||
// Check if each element is correct
|
||||
for (String element : split) {
|
||||
if (!element.startsWith(RELATIVE_CHAR)) {
|
||||
try {
|
||||
// Will throw the exception if not an integer
|
||||
Integer.parseInt(element);
|
||||
} catch (NumberFormatException e) {
|
||||
return INVALID_NUMBER_ERROR;
|
||||
}
|
||||
} else {
|
||||
if (element.length() > RELATIVE_CHAR.length()) {
|
||||
try {
|
||||
final String potentialNumber = element.substring(1);
|
||||
// Will throw the exception if not an integer
|
||||
Integer.parseInt(potentialNumber);
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||
return INVALID_NUMBER_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RelativeBlockPosition parse(@NotNull String value) {
|
||||
final String[] split = value.split(" ");
|
||||
|
||||
BlockPosition blockPosition = new BlockPosition(0, 0, 0);
|
||||
boolean relativeX = false;
|
||||
boolean relativeY = false;
|
||||
@ -72,8 +45,24 @@ public class ArgumentRelativeBlockPosition extends ArgumentRelative<RelativeBloc
|
||||
}
|
||||
|
||||
if (element.length() != RELATIVE_CHAR.length()) {
|
||||
final String potentialNumber = element.substring(1);
|
||||
final int number = Integer.parseInt(potentialNumber);
|
||||
try {
|
||||
final String potentialNumber = element.substring(1);
|
||||
final int number = Integer.parseInt(potentialNumber);
|
||||
if (i == 0) {
|
||||
blockPosition.setX(number);
|
||||
} else if (i == 1) {
|
||||
blockPosition.setY(number);
|
||||
} else if (i == 2) {
|
||||
blockPosition.setZ(number);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ArgumentSyntaxException("Invalid number", input, INVALID_NUMBER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
try {
|
||||
final int number = Integer.parseInt(element);
|
||||
if (i == 0) {
|
||||
blockPosition.setX(number);
|
||||
} else if (i == 1) {
|
||||
@ -81,25 +70,12 @@ public class ArgumentRelativeBlockPosition extends ArgumentRelative<RelativeBloc
|
||||
} else if (i == 2) {
|
||||
blockPosition.setZ(number);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
final int number = Integer.parseInt(element);
|
||||
if (i == 0) {
|
||||
blockPosition.setX(number);
|
||||
} else if (i == 1) {
|
||||
blockPosition.setY(number);
|
||||
} else if (i == 2) {
|
||||
blockPosition.setZ(number);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ArgumentSyntaxException("Invalid number", input, INVALID_NUMBER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RelativeBlockPosition(blockPosition, relativeX, relativeY, relativeZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull RelativeBlockPosition value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +0,0 @@
|
||||
package net.minestom.server.command.builder.arguments.relative;
|
||||
|
||||
import net.minestom.server.utils.location.RelativeVec;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Common super class for {@link ArgumentRelativeVec2} and {@link ArgumentRelativeVec3}.
|
||||
*/
|
||||
public abstract class ArgumentRelativeVec extends ArgumentRelative<RelativeVec> {
|
||||
|
||||
public ArgumentRelativeVec(@NotNull String id, int numberCount) {
|
||||
super(id, numberCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
final String[] split = value.split(" ");
|
||||
|
||||
// Check if the value has enough element to be correct
|
||||
if (split.length != getNumberCount()) {
|
||||
return INVALID_NUMBER_COUNT_ERROR;
|
||||
}
|
||||
|
||||
// Check if each element is correct
|
||||
for (String element : split) {
|
||||
if (!element.startsWith(RELATIVE_CHAR)) {
|
||||
try {
|
||||
// Will throw the exception if not a float
|
||||
Float.parseFloat(element);
|
||||
} catch (NumberFormatException e) {
|
||||
return INVALID_NUMBER_ERROR;
|
||||
}
|
||||
} else {
|
||||
if (element.length() > RELATIVE_CHAR.length()) {
|
||||
try {
|
||||
final String potentialNumber = element.substring(1);
|
||||
// Will throw the exception if not a float
|
||||
Float.parseFloat(potentialNumber);
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||
return INVALID_NUMBER_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull RelativeVec value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package net.minestom.server.command.builder.arguments.relative;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.utils.Vector;
|
||||
import net.minestom.server.utils.location.RelativeVec;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -9,7 +11,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
* <p>
|
||||
* Example: -1.2 ~
|
||||
*/
|
||||
public class ArgumentRelativeVec2 extends ArgumentRelativeVec {
|
||||
public class ArgumentRelativeVec2 extends ArgumentRelative<RelativeVec> {
|
||||
|
||||
public ArgumentRelativeVec2(@NotNull String id) {
|
||||
super(id, 2);
|
||||
@ -17,8 +19,13 @@ public class ArgumentRelativeVec2 extends ArgumentRelativeVec {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RelativeVec parse(@NotNull String value) {
|
||||
final String[] split = value.split(" ");
|
||||
public RelativeVec parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
final String[] split = input.split(StringUtils.SPACE);
|
||||
|
||||
// Check if the value has enough element to be correct
|
||||
if (split.length != getNumberCount()) {
|
||||
throw new ArgumentSyntaxException("Invalid number of values", input, INVALID_NUMBER_COUNT_ERROR);
|
||||
}
|
||||
|
||||
Vector vector = new Vector();
|
||||
boolean relativeX = false;
|
||||
@ -26,30 +33,34 @@ public class ArgumentRelativeVec2 extends ArgumentRelativeVec {
|
||||
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
final String element = split[i];
|
||||
if (element.startsWith(RELATIVE_CHAR)) {
|
||||
if (i == 0) {
|
||||
relativeX = true;
|
||||
} else if (i == 1) {
|
||||
relativeZ = true;
|
||||
}
|
||||
try {
|
||||
if (element.startsWith(RELATIVE_CHAR)) {
|
||||
if (i == 0) {
|
||||
relativeX = true;
|
||||
} else if (i == 1) {
|
||||
relativeZ = true;
|
||||
}
|
||||
|
||||
if (element.length() != RELATIVE_CHAR.length()) {
|
||||
final String potentialNumber = element.substring(1);
|
||||
final float number = Float.parseFloat(potentialNumber);
|
||||
if (element.length() != RELATIVE_CHAR.length()) {
|
||||
final String potentialNumber = element.substring(1);
|
||||
final float number = Float.parseFloat(potentialNumber);
|
||||
if (i == 0) {
|
||||
vector.setX(number);
|
||||
} else if (i == 1) {
|
||||
vector.setZ(number);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
final float number = Float.parseFloat(element);
|
||||
if (i == 0) {
|
||||
vector.setX(number);
|
||||
} else if (i == 1) {
|
||||
vector.setZ(number);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
final float number = Float.parseFloat(element);
|
||||
if (i == 0) {
|
||||
vector.setX(number);
|
||||
} else if (i == 1) {
|
||||
vector.setZ(number);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ArgumentSyntaxException("Invalid number", input, INVALID_NUMBER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.minestom.server.command.builder.arguments.relative;
|
||||
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.utils.Vector;
|
||||
import net.minestom.server.utils.location.RelativeVec;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -9,7 +11,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
* <p>
|
||||
* Example: -1.2 ~ 5
|
||||
*/
|
||||
public class ArgumentRelativeVec3 extends ArgumentRelativeVec {
|
||||
public class ArgumentRelativeVec3 extends ArgumentRelative<RelativeVec> {
|
||||
|
||||
public ArgumentRelativeVec3(@NotNull String id) {
|
||||
super(id, 3);
|
||||
@ -17,8 +19,13 @@ public class ArgumentRelativeVec3 extends ArgumentRelativeVec {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RelativeVec parse(@NotNull String value) {
|
||||
final String[] split = value.split(" ");
|
||||
public RelativeVec parse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
final String[] split = input.split(StringUtils.SPACE);
|
||||
|
||||
// Check if the value has enough element to be correct
|
||||
if (split.length != getNumberCount()) {
|
||||
throw new ArgumentSyntaxException("Invalid number of values", input, INVALID_NUMBER_COUNT_ERROR);
|
||||
}
|
||||
|
||||
Vector vector = new Vector();
|
||||
boolean relativeX = false;
|
||||
@ -27,19 +34,30 @@ public class ArgumentRelativeVec3 extends ArgumentRelativeVec {
|
||||
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
final String element = split[i];
|
||||
if (element.startsWith(RELATIVE_CHAR)) {
|
||||
try {
|
||||
if (element.startsWith(RELATIVE_CHAR)) {
|
||||
if (i == 0) {
|
||||
relativeX = true;
|
||||
} else if (i == 1) {
|
||||
relativeY = true;
|
||||
} else if (i == 2) {
|
||||
relativeZ = true;
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
relativeX = true;
|
||||
} else if (i == 1) {
|
||||
relativeY = true;
|
||||
} else if (i == 2) {
|
||||
relativeZ = true;
|
||||
}
|
||||
if (element.length() != RELATIVE_CHAR.length()) {
|
||||
final String potentialNumber = element.substring(1);
|
||||
final float number = Float.parseFloat(potentialNumber);
|
||||
if (i == 0) {
|
||||
vector.setX(number);
|
||||
} else if (i == 1) {
|
||||
vector.setY(number);
|
||||
} else if (i == 2) {
|
||||
vector.setZ(number);
|
||||
}
|
||||
}
|
||||
|
||||
if (element.length() != RELATIVE_CHAR.length()) {
|
||||
final String potentialNumber = element.substring(1);
|
||||
final float number = Float.parseFloat(potentialNumber);
|
||||
} else {
|
||||
final float number = Float.parseFloat(element);
|
||||
if (i == 0) {
|
||||
vector.setX(number);
|
||||
} else if (i == 1) {
|
||||
@ -48,16 +66,8 @@ public class ArgumentRelativeVec3 extends ArgumentRelativeVec {
|
||||
vector.setZ(number);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
final float number = Float.parseFloat(element);
|
||||
if (i == 0) {
|
||||
vector.setX(number);
|
||||
} else if (i == 1) {
|
||||
vector.setY(number);
|
||||
} else if (i == 2) {
|
||||
vector.setZ(number);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ArgumentSyntaxException("Invalid number", input, INVALID_NUMBER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,48 @@
|
||||
package net.minestom.server.command.builder.exception;
|
||||
|
||||
import net.minestom.server.command.builder.ArgumentCallback;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Exception triggered when an {@link Argument} is wrongly parsed.
|
||||
* <p>
|
||||
* Retrieved in {@link ArgumentCallback} defined in {@link Command#setArgumentCallback(ArgumentCallback, Argument)}.
|
||||
* <p>
|
||||
* Be aware that the message returned by {@link #getMessage()} is only here for debugging purpose,
|
||||
* you should refer to {@link #getErrorCode()} to identify the exceptions.
|
||||
*/
|
||||
public class ArgumentSyntaxException extends Exception {
|
||||
|
||||
private final String input;
|
||||
private final int errorCode;
|
||||
|
||||
public ArgumentSyntaxException(@NotNull String message, @NotNull String input, int errorCode) {
|
||||
super(message);
|
||||
this.input = input;
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the problematic command input.
|
||||
*
|
||||
* @return the command input which triggered the exception
|
||||
*/
|
||||
@NotNull
|
||||
public String getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error code of the exception.
|
||||
* <p>
|
||||
* The code is decided arbitrary by the argument,
|
||||
* check the argument class to know the meaning of each one.
|
||||
*
|
||||
* @return the argument error code
|
||||
*/
|
||||
public int getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.data;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.utils.clone.PublicCloneable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -126,7 +127,7 @@ public abstract class Data implements PublicCloneable<Data> {
|
||||
try {
|
||||
return (Data) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
throw new IllegalStateException("Weird thing happened");
|
||||
}
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ public class SerializableDataImpl extends SerializableData {
|
||||
try {
|
||||
return Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
@ -3,17 +3,14 @@ package net.minestom.server.entity;
|
||||
import com.google.common.collect.Queues;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.Viewable;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.collision.BoundingBox;
|
||||
import net.minestom.server.collision.CollisionUtils;
|
||||
import net.minestom.server.data.Data;
|
||||
import net.minestom.server.data.DataContainer;
|
||||
import net.minestom.server.event.Event;
|
||||
import net.minestom.server.event.EventCallback;
|
||||
import net.minestom.server.event.entity.EntityDeathEvent;
|
||||
import net.minestom.server.event.entity.EntitySpawnEvent;
|
||||
import net.minestom.server.event.entity.EntityTickEvent;
|
||||
import net.minestom.server.event.entity.EntityVelocityEvent;
|
||||
import net.minestom.server.event.entity.*;
|
||||
import net.minestom.server.event.handler.EventHandler;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
@ -22,6 +19,9 @@ import net.minestom.server.instance.block.CustomBlock;
|
||||
import net.minestom.server.network.packet.server.play.*;
|
||||
import net.minestom.server.permission.Permission;
|
||||
import net.minestom.server.permission.PermissionHandler;
|
||||
import net.minestom.server.potion.Potion;
|
||||
import net.minestom.server.potion.PotionEffect;
|
||||
import net.minestom.server.potion.TimedPotion;
|
||||
import net.minestom.server.thread.ThreadProvider;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.Position;
|
||||
@ -41,6 +41,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
@ -53,6 +54,7 @@ import java.util.function.Consumer;
|
||||
public abstract class Entity implements Viewable, EventHandler, DataContainer, PermissionHandler {
|
||||
|
||||
private static final Map<Integer, Entity> entityById = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, Entity> entityByUuid = new ConcurrentHashMap<>();
|
||||
private static final AtomicInteger lastEntityId = new AtomicInteger();
|
||||
|
||||
// Metadata
|
||||
@ -66,7 +68,14 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
protected static final byte METADATA_BOOLEAN = 7;
|
||||
protected static final byte METADATA_ROTATION = 8;
|
||||
protected static final byte METADATA_POSITION = 9;
|
||||
protected static final byte METADATA_OPTPOSITION = 10;
|
||||
protected static final byte METADATA_DIRECTION = 11;
|
||||
protected static final byte METADATA_OPTUUID = 12;
|
||||
protected static final byte METADATA_OPTBLOCKID = 13;
|
||||
protected static final byte METADATA_NBT = 14;
|
||||
protected static final byte METADATA_PARTICLE = 15;
|
||||
protected static final byte METADATA_VILLAGERDATA = 16;
|
||||
protected static final byte METADATA_OPTVARINT = 17;
|
||||
protected static final byte METADATA_POSE = 18;
|
||||
|
||||
protected Instance instance;
|
||||
@ -88,7 +97,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
protected float gravityAcceleration;
|
||||
protected float gravityTerminalVelocity;
|
||||
protected int gravityTickCount; // Number of tick where gravity tick was applied
|
||||
protected float eyeHeight;
|
||||
|
||||
private boolean autoViewable;
|
||||
private final int id;
|
||||
@ -123,12 +131,14 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
protected boolean glowing;
|
||||
protected boolean usingElytra;
|
||||
protected int air = 300;
|
||||
protected ColoredText customName;
|
||||
protected JsonMessage customName;
|
||||
protected boolean customNameVisible;
|
||||
protected boolean silent;
|
||||
protected boolean noGravity;
|
||||
protected Pose pose = Pose.STANDING;
|
||||
|
||||
private final List<TimedPotion> effects = new CopyOnWriteArrayList<>();
|
||||
|
||||
// list of scheduled tasks to be executed during the next entity tick
|
||||
protected final Queue<Consumer<Entity>> nextTick = Queues.newConcurrentLinkedQueue();
|
||||
|
||||
@ -136,17 +146,25 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
private long ticks;
|
||||
private final EntityTickEvent tickEvent = new EntityTickEvent(this);
|
||||
|
||||
public Entity(@NotNull EntityType entityType, @NotNull Position spawnPosition) {
|
||||
public Entity(@NotNull EntityType entityType, @NotNull UUID uuid, @NotNull Position spawnPosition) {
|
||||
this.id = generateId();
|
||||
this.entityType = entityType;
|
||||
this.uuid = UUID.randomUUID();
|
||||
this.uuid = uuid;
|
||||
this.position = spawnPosition.clone();
|
||||
this.lastX = spawnPosition.getX();
|
||||
this.lastY = spawnPosition.getY();
|
||||
this.lastZ = spawnPosition.getZ();
|
||||
|
||||
setBoundingBox(0, 0, 0);
|
||||
|
||||
setAutoViewable(true);
|
||||
|
||||
entityById.put(id, this);
|
||||
Entity.entityById.put(id, this);
|
||||
Entity.entityByUuid.put(uuid, this);
|
||||
}
|
||||
|
||||
public Entity(@NotNull EntityType entityType, @NotNull Position spawnPosition) {
|
||||
this(entityType, UUID.randomUUID(), spawnPosition);
|
||||
}
|
||||
|
||||
public Entity(@NotNull EntityType entityType) {
|
||||
@ -173,9 +191,21 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
*/
|
||||
@Nullable
|
||||
public static Entity getEntity(int id) {
|
||||
return entityById.getOrDefault(id, null);
|
||||
return Entity.entityById.getOrDefault(id, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an entity based on its UUID (from {@link #getUuid()}).
|
||||
*
|
||||
* @param uuid the entity UUID
|
||||
* @return the entity having the specified uuid, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public static Entity getEntity(@NotNull UUID uuid) {
|
||||
return Entity.entityByUuid.getOrDefault(uuid, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate and return a new unique entity id.
|
||||
* <p>
|
||||
@ -215,12 +245,13 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
* @throws IllegalStateException if you try to teleport an entity before settings its instance
|
||||
*/
|
||||
public void teleport(@NotNull Position position, @Nullable long[] chunks, @Nullable Runnable callback) {
|
||||
Check.notNull(position, "Teleport position cannot be null");
|
||||
Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!");
|
||||
|
||||
final Position teleportPosition = position.clone(); // Prevent synchronization issue
|
||||
|
||||
final ChunkCallback endCallback = (chunk) -> {
|
||||
refreshPosition(position.getX(), position.getY(), position.getZ());
|
||||
refreshView(position.getYaw(), position.getPitch());
|
||||
refreshPosition(teleportPosition);
|
||||
refreshView(teleportPosition.getYaw(), teleportPosition.getPitch());
|
||||
|
||||
sendSynchronization();
|
||||
|
||||
@ -228,7 +259,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
};
|
||||
|
||||
if (chunks == null || chunks.length == 0) {
|
||||
instance.loadOptionalChunk(position, endCallback);
|
||||
instance.loadOptionalChunk(teleportPosition, endCallback);
|
||||
} else {
|
||||
ChunkUtils.optionalLoadAll(instance, chunks, null, endCallback);
|
||||
}
|
||||
@ -302,7 +333,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
|
||||
@Override
|
||||
public boolean addViewer(@NotNull Player player) {
|
||||
Check.notNull(player, "Viewer cannot be null");
|
||||
boolean result = this.viewers.add(player);
|
||||
if (!result)
|
||||
return false;
|
||||
@ -312,7 +342,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
|
||||
@Override
|
||||
public boolean removeViewer(@NotNull Player player) {
|
||||
Check.notNull(player, "Viewer cannot be null");
|
||||
if (!viewers.remove(player))
|
||||
return false;
|
||||
|
||||
@ -432,6 +461,13 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
// Entity tick
|
||||
{
|
||||
|
||||
// Cache the number of "gravity tick"
|
||||
if (!onGround) {
|
||||
gravityTickCount++;
|
||||
} else {
|
||||
gravityTickCount = 0;
|
||||
}
|
||||
|
||||
// Velocity
|
||||
boolean applyVelocity;
|
||||
// Non-player entities with either velocity or gravity enabled
|
||||
@ -446,45 +482,32 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
final float newZ = position.getZ() + velocity.getZ() / tps;
|
||||
Position newPosition = new Position(newX, newY, newZ);
|
||||
|
||||
// Gravity
|
||||
{
|
||||
// Cache the number of "gravity tick"
|
||||
if (!onGround) {
|
||||
gravityTickCount++;
|
||||
} else {
|
||||
gravityTickCount = 0;
|
||||
}
|
||||
|
||||
// Compute the gravity change (drag per tick + acceleration)
|
||||
final float gravityY = Math.min(
|
||||
gravityDragPerTick + (gravityAcceleration * (float) gravityTickCount),
|
||||
gravityTerminalVelocity);
|
||||
|
||||
// Change velocity to apply gravity
|
||||
if (!noGravity) {
|
||||
velocity.setY(velocity.getY() - gravityY);
|
||||
}
|
||||
}
|
||||
|
||||
Vector newVelocityOut = new Vector();
|
||||
|
||||
// Gravity force
|
||||
final float gravityY = !noGravity ? Math.min(
|
||||
gravityDragPerTick + (gravityAcceleration * (float) gravityTickCount),
|
||||
gravityTerminalVelocity) : 0f;
|
||||
|
||||
final Vector deltaPos = new Vector(
|
||||
getVelocity().getX() / tps,
|
||||
getVelocity().getY() / tps,
|
||||
(getVelocity().getY() - gravityY) / tps,
|
||||
getVelocity().getZ() / tps
|
||||
);
|
||||
|
||||
this.onGround = CollisionUtils.handlePhysics(this, deltaPos, newPosition, newVelocityOut);
|
||||
|
||||
// Stop here if the position is the same
|
||||
final boolean updatePosition = !newPosition.isSimilar(position);
|
||||
// World border collision
|
||||
final Position finalVelocityPosition = CollisionUtils.applyWorldBorder(instance, position, newPosition);
|
||||
final Chunk finalChunk = instance.getChunkAt(finalVelocityPosition);
|
||||
|
||||
// Check chunk
|
||||
if (!ChunkUtils.isLoaded(instance, newPosition.getX(), newPosition.getZ())) {
|
||||
// Entity shouldn't be updated when moving in an unloaded chunk
|
||||
if (!ChunkUtils.isLoaded(finalChunk)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// World border and apply the position
|
||||
final Position finalVelocityPosition = CollisionUtils.applyWorldBorder(instance, position, newPosition);
|
||||
if (finalVelocityPosition != null && updatePosition) {
|
||||
// Apply the position if changed
|
||||
if (!newPosition.isSimilar(position)) {
|
||||
refreshPosition(finalVelocityPosition);
|
||||
}
|
||||
|
||||
@ -497,7 +520,10 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
float drag;
|
||||
if (onGround) {
|
||||
final BlockPosition blockPosition = position.toBlockPosition();
|
||||
final CustomBlock customBlock = instance.getCustomBlock(blockPosition);
|
||||
final CustomBlock customBlock = finalChunk.getCustomBlock(
|
||||
blockPosition.getX(),
|
||||
blockPosition.getY(),
|
||||
blockPosition.getZ());
|
||||
if (customBlock != null) {
|
||||
// Custom drag
|
||||
drag = customBlock.getDrag(instance, blockPosition);
|
||||
@ -508,7 +534,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
|
||||
// Stop player velocity
|
||||
if (PlayerUtils.isNettyClient(this)) {
|
||||
velocity.zero();
|
||||
this.velocity.zero();
|
||||
}
|
||||
} else {
|
||||
drag = 0.98f; // air drag
|
||||
@ -522,7 +548,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
sendSynchronization();
|
||||
// Verify if velocity packet has to be sent
|
||||
if (hasVelocity() || gravityTickCount > 0) {
|
||||
sendVelocityPacket();
|
||||
sendPacketsToViewers(getVelocityPacket());
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,6 +590,24 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
|
||||
ticks++;
|
||||
callEvent(EntityTickEvent.class, tickEvent); // reuse tickEvent to avoid recreating it each tick
|
||||
|
||||
// remove expired effects
|
||||
{
|
||||
this.effects.removeIf(timedPotion -> {
|
||||
final long potionTime = (long) timedPotion.getPotion().getDuration() * MinecraftServer.TICK_MS;
|
||||
// Remove if the potion should be expired
|
||||
if (time >= timedPotion.getStartingTime() + potionTime) {
|
||||
// Send the packet that the potion should no longer be applied
|
||||
timedPotion.getPotion().sendRemovePacket(this);
|
||||
callEvent(EntityPotionRemoveEvent.class, new EntityPotionRemoveEvent(
|
||||
this,
|
||||
timedPotion.getPotion()
|
||||
));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Scheduled synchronization
|
||||
@ -577,13 +621,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to <code>sendPacketsToViewers(getVelocityPacket());</code>.
|
||||
*/
|
||||
public void sendVelocityPacket() {
|
||||
sendPacketsToViewers(getVelocityPacket());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of ticks this entity has been active for.
|
||||
*
|
||||
@ -643,7 +680,11 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
*
|
||||
* @param uuid the new entity uuid
|
||||
*/
|
||||
protected void setUuid(@NotNull UUID uuid) {
|
||||
public void setUuid(@NotNull UUID uuid) {
|
||||
// Refresh internal map
|
||||
Entity.entityByUuid.remove(this.uuid);
|
||||
Entity.entityByUuid.put(uuid, this);
|
||||
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@ -679,6 +720,17 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
this.boundingBox = new BoundingBox(this, x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the internal entity bounding box.
|
||||
* <p>
|
||||
* WARNING: this does not change the entity hit-box which is client-side.
|
||||
*
|
||||
* @param boundingBox the new bounding box
|
||||
*/
|
||||
public void setBoundingBox(BoundingBox boundingBox) {
|
||||
this.boundingBox = boundingBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to get the entity current chunk.
|
||||
*
|
||||
@ -707,7 +759,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
* @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager}
|
||||
*/
|
||||
public void setInstance(@NotNull Instance instance) {
|
||||
Check.notNull(instance, "instance cannot be null!");
|
||||
Check.stateCondition(!instance.isRegistered(),
|
||||
"Instances need to be registered, please use InstanceManager#registerInstance or InstanceManager#registerSharedInstance");
|
||||
|
||||
@ -816,7 +867,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
* @return the distance between this and {@code entity}
|
||||
*/
|
||||
public float getDistance(@NotNull Entity entity) {
|
||||
Check.notNull(entity, "Entity cannot be null");
|
||||
return getPosition().getDistance(entity.getPosition());
|
||||
}
|
||||
|
||||
@ -838,7 +888,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
* @throws IllegalStateException if {@link #getInstance()} returns null
|
||||
*/
|
||||
public void addPassenger(@NotNull Entity entity) {
|
||||
Check.notNull(entity, "Passenger cannot be null");
|
||||
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
|
||||
|
||||
if (entity.getVehicle() != null) {
|
||||
@ -859,7 +908,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
* @throws IllegalStateException if {@link #getInstance()} returns null
|
||||
*/
|
||||
public void removePassenger(@NotNull Entity entity) {
|
||||
Check.notNull(entity, "Passenger cannot be null");
|
||||
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
|
||||
|
||||
if (!passengers.remove(entity))
|
||||
@ -980,7 +1028,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
*
|
||||
* @return the custom name of the entity, null if there is not
|
||||
*/
|
||||
public ColoredText getCustomName() {
|
||||
public JsonMessage getCustomName() {
|
||||
return customName;
|
||||
}
|
||||
|
||||
@ -989,7 +1037,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
*
|
||||
* @param customName the custom name of the entity, null to remove it
|
||||
*/
|
||||
public void setCustomName(ColoredText customName) {
|
||||
public void setCustomName(JsonMessage customName) {
|
||||
this.customName = customName;
|
||||
sendMetadataIndex(2);
|
||||
}
|
||||
@ -1055,9 +1103,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
* @param z new position Z
|
||||
*/
|
||||
public void refreshPosition(float x, float y, float z) {
|
||||
this.lastX = position.getX();
|
||||
this.lastY = position.getY();
|
||||
this.lastZ = position.getZ();
|
||||
position.setX(x);
|
||||
position.setY(y);
|
||||
position.setZ(z);
|
||||
@ -1094,6 +1139,10 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.lastX = position.getX();
|
||||
this.lastY = position.getY();
|
||||
this.lastZ = position.getZ();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1129,9 +1178,33 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
* @param sneaking true to make the entity sneak
|
||||
*/
|
||||
public void setSneaking(boolean sneaking) {
|
||||
this.crouched = sneaking;
|
||||
this.pose = sneaking ? Pose.SNEAKING : Pose.STANDING;
|
||||
sendMetadataIndex(0);
|
||||
setPose(sneaking ? Pose.SNEAKING : Pose.STANDING);
|
||||
sendMetadataIndex(0); // update the crouched metadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current entity pose.
|
||||
*
|
||||
* @return the entity pose
|
||||
*/
|
||||
@NotNull
|
||||
public Pose getPose() {
|
||||
return pose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the entity pose.
|
||||
* <p>
|
||||
* The internal {@code crouched} and {@code swimming} field will be
|
||||
* updated accordingly.
|
||||
*
|
||||
* @param pose the new entity pose
|
||||
*/
|
||||
@NotNull
|
||||
public void setPose(@NotNull Pose pose) {
|
||||
this.crouched = pose == Pose.SNEAKING;
|
||||
this.swimming = pose == Pose.SWIMMING;
|
||||
this.pose = pose;
|
||||
sendMetadataIndex(6);
|
||||
}
|
||||
|
||||
@ -1152,37 +1225,73 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
*
|
||||
* @return the current position of the entity
|
||||
*/
|
||||
@NotNull
|
||||
public Position getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity eye height.
|
||||
* <p>
|
||||
* Default to {@link BoundingBox#getHeight()}x0.85
|
||||
*
|
||||
* @return the entity eye height
|
||||
*/
|
||||
public float getEyeHeight() {
|
||||
return eyeHeight;
|
||||
return boundingBox.getHeight() * 0.85f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the entity eye height.
|
||||
* Gets all the potion effect of this entity.
|
||||
*
|
||||
* @param eyeHeight the entity eye height
|
||||
* @return an unmodifiable list of all this entity effects
|
||||
*/
|
||||
public void setEyeHeight(float eyeHeight) {
|
||||
this.eyeHeight = eyeHeight;
|
||||
@NotNull
|
||||
public List<TimedPotion> getActiveEffects() {
|
||||
return Collections.unmodifiableList(effects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes effect from entity, if it has it.
|
||||
*
|
||||
* @param effect The effect to remove
|
||||
*/
|
||||
public void removeEffect(@NotNull PotionEffect effect) {
|
||||
this.effects.removeIf(timedPotion -> {
|
||||
if (timedPotion.getPotion().getEffect() == effect) {
|
||||
timedPotion.getPotion().sendRemovePacket(this);
|
||||
callEvent(EntityPotionRemoveEvent.class, new EntityPotionRemoveEvent(
|
||||
this,
|
||||
timedPotion.getPotion()
|
||||
));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an effect to an entity.
|
||||
*
|
||||
* @param potion The potion to add
|
||||
*/
|
||||
public void addEffect(@NotNull Potion potion) {
|
||||
removeEffect(potion.getEffect());
|
||||
this.effects.add(new TimedPotion(potion, System.currentTimeMillis()));
|
||||
potion.sendAddPacket(this);
|
||||
callEvent(EntityPotionAddEvent.class, new EntityPotionAddEvent(this, potion));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the entity from the server immediately.
|
||||
* <p>
|
||||
* WARNING: this do not trigger the {@link EntityDeathEvent} event.
|
||||
* WARNING: this does not trigger {@link EntityDeathEvent}.
|
||||
*/
|
||||
public void remove() {
|
||||
this.removed = true;
|
||||
this.shouldRemove = true;
|
||||
entityById.remove(id);
|
||||
Entity.entityById.remove(id);
|
||||
Entity.entityByUuid.remove(uuid);
|
||||
if (instance != null)
|
||||
instance.UNSAFE_removeEntity(this);
|
||||
}
|
||||
@ -1377,7 +1486,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
protected void sendSynchronization() {
|
||||
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
|
||||
entityTeleportPacket.entityId = getEntityId();
|
||||
entityTeleportPacket.position = getPosition();
|
||||
entityTeleportPacket.position = getPosition().clone();
|
||||
entityTeleportPacket.onGround = isOnGround();
|
||||
sendPacketToViewers(entityTeleportPacket);
|
||||
}
|
||||
@ -1389,7 +1498,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
this.lastAbsoluteSynchronizationTime = 0;
|
||||
}
|
||||
|
||||
private enum Pose {
|
||||
public enum Pose {
|
||||
STANDING,
|
||||
FALL_FLYING,
|
||||
SLEEPING,
|
||||
|
@ -13,7 +13,6 @@ import net.minestom.server.event.item.ArmorEquipEvent;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.network.packet.server.play.EntityEquipmentPacket;
|
||||
import net.minestom.server.network.packet.server.play.EntityMovementPacket;
|
||||
import net.minestom.server.network.packet.server.play.SpawnLivingEntityPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.Position;
|
||||
@ -123,17 +122,13 @@ public abstract class EntityCreature extends LivingEntity implements NavigableEn
|
||||
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
|
||||
EntityMovementPacket entityMovementPacket = new EntityMovementPacket();
|
||||
entityMovementPacket.entityId = getEntityId();
|
||||
|
||||
SpawnLivingEntityPacket spawnLivingEntityPacket = new SpawnLivingEntityPacket();
|
||||
spawnLivingEntityPacket.entityId = getEntityId();
|
||||
spawnLivingEntityPacket.entityUuid = getUuid();
|
||||
spawnLivingEntityPacket.entityType = getEntityType().getId();
|
||||
spawnLivingEntityPacket.position = getPosition();
|
||||
spawnLivingEntityPacket.headPitch = 0;
|
||||
spawnLivingEntityPacket.headPitch = getPosition().getYaw();
|
||||
|
||||
playerConnection.sendPacket(entityMovementPacket);
|
||||
playerConnection.sendPacket(spawnLivingEntityPacket);
|
||||
playerConnection.sendPacket(getVelocityPacket());
|
||||
playerConnection.sendPacket(getMetadataPacket());
|
||||
@ -224,7 +219,7 @@ public abstract class EntityCreature extends LivingEntity implements NavigableEn
|
||||
/**
|
||||
* Gets the entity target.
|
||||
*
|
||||
* @return the entity target
|
||||
* @return the entity target, can be null if not any
|
||||
*/
|
||||
@Nullable
|
||||
public Entity getTarget() {
|
||||
@ -234,9 +229,9 @@ public abstract class EntityCreature extends LivingEntity implements NavigableEn
|
||||
/**
|
||||
* Changes the entity target.
|
||||
*
|
||||
* @param target the new entity target
|
||||
* @param target the new entity target, null to remove
|
||||
*/
|
||||
public void setTarget(@NotNull Entity target) {
|
||||
public void setTarget(@Nullable Entity target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
|
@ -1,110 +0,0 @@
|
||||
package net.minestom.server.entity;
|
||||
|
||||
import com.google.common.collect.Queues;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.chat.ChatColor;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.event.player.PlayerLoginEvent;
|
||||
import net.minestom.server.event.player.PlayerPreLoginEvent;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.packet.server.play.KeepAlivePacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class EntityManager {
|
||||
|
||||
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||
|
||||
private static final long KEEP_ALIVE_DELAY = 10_000;
|
||||
private static final long KEEP_ALIVE_KICK = 30_000;
|
||||
private static final ColoredText TIMEOUT_TEXT = ColoredText.of(ChatColor.RED + "Timeout");
|
||||
|
||||
private final Queue<Player> waitingPlayers = Queues.newConcurrentLinkedQueue();
|
||||
|
||||
/**
|
||||
* Connects waiting players.
|
||||
*/
|
||||
public void updateWaitingPlayers() {
|
||||
// Connect waiting players
|
||||
waitingPlayersTick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates keep alive by checking the last keep alive packet and send a new one if needed.
|
||||
*
|
||||
* @param tickStart the time of the update in milliseconds, forwarded to the packet
|
||||
*/
|
||||
public void handleKeepAlive(long tickStart) {
|
||||
final KeepAlivePacket keepAlivePacket = new KeepAlivePacket(tickStart);
|
||||
for (Player player : CONNECTION_MANAGER.getOnlinePlayers()) {
|
||||
final long lastKeepAlive = tickStart - player.getLastKeepAlive();
|
||||
if (lastKeepAlive > KEEP_ALIVE_DELAY && player.didAnswerKeepAlive()) {
|
||||
player.refreshKeepAlive(tickStart);
|
||||
player.getPlayerConnection().sendPacket(keepAlivePacket);
|
||||
} else if (lastKeepAlive >= KEEP_ALIVE_KICK) {
|
||||
player.kick(TIMEOUT_TEXT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds connected clients after the handshake (used to free the networking threads).
|
||||
*/
|
||||
private void waitingPlayersTick() {
|
||||
Player waitingPlayer;
|
||||
while ((waitingPlayer = waitingPlayers.poll()) != null) {
|
||||
|
||||
PlayerLoginEvent loginEvent = new PlayerLoginEvent(waitingPlayer);
|
||||
waitingPlayer.callEvent(PlayerLoginEvent.class, loginEvent);
|
||||
final Instance spawningInstance = loginEvent.getSpawningInstance();
|
||||
|
||||
Check.notNull(spawningInstance, "You need to specify a spawning instance in the PlayerLoginEvent");
|
||||
|
||||
waitingPlayer.init();
|
||||
|
||||
// Spawn the player at Player#getRespawnPoint during the next instance tick
|
||||
spawningInstance.scheduleNextTick(waitingPlayer::setInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the player initialization callbacks and the event {@link PlayerPreLoginEvent}.
|
||||
* If the {@link Player} hasn't been kicked, add him to the waiting list.
|
||||
* <p>
|
||||
* Can be considered as a pre-init thing,
|
||||
* currently executed in {@link Player#Player(UUID, String, PlayerConnection)}.
|
||||
*
|
||||
* @param player the {@link Player} to add
|
||||
*/
|
||||
public void addWaitingPlayer(@NotNull Player player) {
|
||||
|
||||
// Init player (register events)
|
||||
for (Consumer<Player> playerInitialization : MinecraftServer.getConnectionManager().getPlayerInitializations()) {
|
||||
playerInitialization.accept(player);
|
||||
}
|
||||
|
||||
// Call pre login event
|
||||
PlayerPreLoginEvent playerPreLoginEvent = new PlayerPreLoginEvent(player, player.getUsername(), player.getUuid());
|
||||
player.callEvent(PlayerPreLoginEvent.class, playerPreLoginEvent);
|
||||
|
||||
// Ignore the player if he has been disconnected (kick)
|
||||
final boolean online = player.isOnline();
|
||||
if (!online)
|
||||
return;
|
||||
|
||||
// Add him to the list and change his username/uuid if changed
|
||||
this.waitingPlayers.add(player);
|
||||
|
||||
final String username = playerPreLoginEvent.getUsername();
|
||||
final UUID uuid = playerPreLoginEvent.getPlayerUuid();
|
||||
|
||||
player.setUsername(username);
|
||||
player.setUuid(uuid);
|
||||
}
|
||||
}
|
@ -3,18 +3,24 @@ package net.minestom.server.entity;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.network.packet.server.play.SpawnExperienceOrbPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ExperienceOrb extends Entity {
|
||||
|
||||
private short experienceCount;
|
||||
private Player target;
|
||||
private long lastTargetUpdateTick;
|
||||
|
||||
|
||||
public ExperienceOrb(short experienceCount, @NotNull Position spawnPosition) {
|
||||
super(EntityType.EXPERIENCE_ORB, spawnPosition);
|
||||
setGravity(0.02f, 0.04f, 1.96f);
|
||||
setBoundingBox(0.5f, 0.5f, 0.5f);
|
||||
//todo vanilla sets random velocity here?
|
||||
this.experienceCount = experienceCount;
|
||||
}
|
||||
|
||||
@ -28,7 +34,51 @@ public class ExperienceOrb extends Entity {
|
||||
|
||||
@Override
|
||||
public void update(long time) {
|
||||
|
||||
// TODO slide toward nearest player
|
||||
|
||||
//todo water movement
|
||||
if (hasNoGravity()) {
|
||||
setVelocity(getVelocity().add(0, -0.3f, 0));
|
||||
}
|
||||
|
||||
//todo lava
|
||||
|
||||
double d = 8.0;
|
||||
if (lastTargetUpdateTick < time - 20 + getEntityId() % 100) {
|
||||
if (target == null || target.getPosition().getDistanceSquared(getPosition()) > 64) {
|
||||
this.target = getClosestPlayer(this, 8);
|
||||
}
|
||||
|
||||
lastTargetUpdateTick = time;
|
||||
}
|
||||
|
||||
if (target != null && target.getGameMode() == GameMode.SPECTATOR) {
|
||||
target = null;
|
||||
}
|
||||
|
||||
if (target != null) {
|
||||
Position pos = getPosition();
|
||||
Position targetPos = target.getPosition();
|
||||
Vector toTarget = new Vector(targetPos.getX() - pos.getX(), targetPos.getY() + (target.getEyeHeight() / 2) - pos.getY(), targetPos.getZ() - pos.getZ());
|
||||
double e = toTarget.length(); //could really be lengthSquared
|
||||
if (e < 8) {
|
||||
double f = 1 - (e / 8);
|
||||
setVelocity(getVelocity().add(toTarget.normalize().multiply(f * f * 0.1)));
|
||||
}
|
||||
}
|
||||
|
||||
// Move should be called here
|
||||
float g = 0.98f;
|
||||
if (this.onGround) {
|
||||
// g = 2f;
|
||||
g = 0.6f * 0.98f;
|
||||
}
|
||||
// apply slipperiness
|
||||
|
||||
setVelocity(getVelocity().multiply(new Vector(g, 0.98f, g)));
|
||||
if (isOnGround())
|
||||
setVelocity(getVelocity().multiply(new Vector(1, -0.9f, 1)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -77,4 +127,15 @@ public class ExperienceOrb extends Entity {
|
||||
|
||||
getViewers().forEach(this::addViewer);
|
||||
}
|
||||
|
||||
private Player getClosestPlayer(Entity entity, float maxDistance) {
|
||||
Player closest = entity.getInstance()
|
||||
.getPlayers()
|
||||
.stream()
|
||||
.min((a, b) -> Float.compare(a.getDistance(entity), b.getDistance(entity)))
|
||||
.orElse(null);
|
||||
if (closest == null) return null;
|
||||
if (closest.getDistance(entity) > maxDistance) return null;
|
||||
return closest;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import net.minestom.server.event.entity.EntityDeathEvent;
|
||||
import net.minestom.server.event.entity.EntityFireEvent;
|
||||
import net.minestom.server.event.item.PickupItemEvent;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.inventory.EquipmentHandler;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.network.packet.server.play.CollectItemPacket;
|
||||
@ -19,8 +20,10 @@ import net.minestom.server.network.packet.server.play.SoundEffectPacket;
|
||||
import net.minestom.server.scoreboard.Team;
|
||||
import net.minestom.server.sound.Sound;
|
||||
import net.minestom.server.sound.SoundCategory;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.block.BlockIterator;
|
||||
import net.minestom.server.utils.time.CooldownUtils;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.utils.time.UpdateOption;
|
||||
@ -28,8 +31,7 @@ import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -270,7 +272,6 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler {
|
||||
* @return true if damage has been applied, false if it didn't
|
||||
*/
|
||||
public boolean damage(@NotNull DamageType type, float value) {
|
||||
Check.notNull(type, "The damage type cannot be null!");
|
||||
if (isDead())
|
||||
return false;
|
||||
if (isInvulnerable() || isImmune(type)) {
|
||||
@ -501,12 +502,15 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler {
|
||||
*/
|
||||
@NotNull
|
||||
protected EntityPropertiesPacket getPropertiesPacket() {
|
||||
// Get all the attributes which should be sent to the client
|
||||
final AttributeInstance[] instances = attributeModifiers.values().stream()
|
||||
.filter(i -> i.getAttribute().isShared())
|
||||
.toArray(AttributeInstance[]::new);
|
||||
|
||||
|
||||
EntityPropertiesPacket propertiesPacket = new EntityPropertiesPacket();
|
||||
propertiesPacket.entityId = getEntityId();
|
||||
|
||||
AttributeInstance[] instances = attributeModifiers.values().stream()
|
||||
.filter(i -> i.getAttribute().isShared())
|
||||
.toArray(AttributeInstance[]::new);
|
||||
EntityPropertiesPacket.Property[] properties = new EntityPropertiesPacket.Property[instances.length];
|
||||
for (int i = 0; i < properties.length; ++i) {
|
||||
EntityPropertiesPacket.Property property = new EntityPropertiesPacket.Property();
|
||||
@ -529,7 +533,8 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler {
|
||||
*/
|
||||
private void setupAttributes() {
|
||||
for (Attribute attribute : Attribute.values()) {
|
||||
attributeModifiers.put(attribute.getKey(), new AttributeInstance(attribute, this::onAttributeChanged));
|
||||
final AttributeInstance attributeInstance = new AttributeInstance(attribute, this::onAttributeChanged);
|
||||
this.attributeModifiers.put(attribute.getKey(), attributeInstance);
|
||||
}
|
||||
}
|
||||
|
||||
@ -597,4 +602,36 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler {
|
||||
public Team getTeam() {
|
||||
return team;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line of sight in {@link BlockPosition} of the entity.
|
||||
*
|
||||
* @param maxDistance The max distance to scan
|
||||
* @return A list of {@link BlockPosition} in this entities line of sight
|
||||
*/
|
||||
public List<BlockPosition> getLineOfSight(int maxDistance) {
|
||||
List<BlockPosition> blocks = new ArrayList<>();
|
||||
Iterator<BlockPosition> it = new BlockIterator(this, maxDistance);
|
||||
while (it.hasNext()) {
|
||||
BlockPosition position = it.next();
|
||||
if (Block.fromStateId(getInstance().getBlockStateId(position)) != Block.AIR) blocks.add(position);
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the target (not-air) {@link BlockPosition} of the entity.
|
||||
*
|
||||
* @param maxDistance The max distance to scan before returning null
|
||||
* @return The {@link BlockPosition} targeted by this entity, null if non are found
|
||||
*/
|
||||
public BlockPosition getTargetBlockPosition(int maxDistance) {
|
||||
Iterator<BlockPosition> it = new BlockIterator(this, maxDistance);
|
||||
while (it.hasNext()) {
|
||||
BlockPosition position = it.next();
|
||||
if (Block.fromStateId(getInstance().getBlockStateId(position)) != Block.AIR) return position;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,12 +29,15 @@ 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.network.ConnectionManager;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.PlayerProvider;
|
||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.packet.server.play.*;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
@ -51,6 +54,7 @@ import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.callback.OptionalCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.entity.EntityUtils;
|
||||
import net.minestom.server.utils.instance.InstanceUtils;
|
||||
import net.minestom.server.utils.time.CooldownUtils;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
@ -60,6 +64,7 @@ import net.minestom.server.world.DimensionType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -120,7 +125,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
protected final Set<Entity> viewableEntities = new CopyOnWriteArraySet<>();
|
||||
|
||||
private int latency;
|
||||
private ColoredText displayName;
|
||||
private JsonMessage displayName;
|
||||
private PlayerSkin skin;
|
||||
|
||||
private DimensionType dimensionType;
|
||||
@ -128,14 +133,13 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
protected final Set<Chunk> viewableChunks = new CopyOnWriteArraySet<>();
|
||||
private final AtomicInteger teleportId = new AtomicInteger();
|
||||
|
||||
protected boolean onGround;
|
||||
private final Queue<ClientPlayPacket> packets = Queues.newConcurrentLinkedQueue();
|
||||
private final boolean levelFlat;
|
||||
private final PlayerSettings settings;
|
||||
private float exp;
|
||||
private int level;
|
||||
|
||||
private final PlayerInventory inventory;
|
||||
protected PlayerInventory inventory;
|
||||
private Inventory openInventory;
|
||||
// Used internally to allow the closing of inventory within the inventory listener
|
||||
private boolean didCloseInventory;
|
||||
@ -199,7 +203,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
this.username = username;
|
||||
this.playerConnection = playerConnection;
|
||||
|
||||
setBoundingBox(0.69f, 1.8f, 0.69f);
|
||||
setBoundingBox(0.6f, 1.8f, 0.6f);
|
||||
|
||||
setRespawnPoint(new Position(0, 0, 0));
|
||||
|
||||
@ -212,14 +216,12 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
refreshAnswerKeepAlive(true);
|
||||
|
||||
this.gameMode = GameMode.SURVIVAL;
|
||||
this.dimensionType = DimensionType.OVERWORLD;
|
||||
this.dimensionType = DimensionType.OVERWORLD; // Default dimension
|
||||
this.levelFlat = true;
|
||||
getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.1f);
|
||||
|
||||
// FakePlayer init its connection there
|
||||
playerConnectionInit();
|
||||
|
||||
MinecraftServer.getEntityManager().addWaitingPlayer(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,8 +229,13 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* Init the player and spawn him.
|
||||
* <p>
|
||||
* WARNING: executed in the main update thread
|
||||
* UNSAFE: Only meant to be used when a netty player connects through the server.
|
||||
*
|
||||
* @param spawnInstance the player spawn instance (defined in {@link PlayerLoginEvent})
|
||||
*/
|
||||
protected void init() {
|
||||
public void UNSAFE_init(@NotNull Instance spawnInstance) {
|
||||
this.dimensionType = spawnInstance.getDimensionType();
|
||||
|
||||
JoinGamePacket joinGamePacket = new JoinGamePacket();
|
||||
joinGamePacket.entityId = getEntityId();
|
||||
joinGamePacket.gameMode = gameMode;
|
||||
@ -456,13 +463,8 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
position, new Position(lastPlayerSyncX, lastPlayerSyncY, lastPlayerSyncZ), onGround);
|
||||
} else {
|
||||
// View changed
|
||||
EntityRotationPacket entityRotationPacket = new EntityRotationPacket();
|
||||
entityRotationPacket.entityId = getEntityId();
|
||||
entityRotationPacket.yaw = position.getYaw();
|
||||
entityRotationPacket.pitch = position.getPitch();
|
||||
entityRotationPacket.onGround = onGround;
|
||||
|
||||
updatePacket = entityRotationPacket;
|
||||
updatePacket = EntityRotationPacket.getPacket(getEntityId(),
|
||||
position.getYaw(), position.getPitch(), onGround);
|
||||
}
|
||||
|
||||
if (viewChanged) {
|
||||
@ -500,43 +502,52 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
public void kill() {
|
||||
if (!isDead()) {
|
||||
|
||||
// send death screen text to the killed player
|
||||
JsonMessage deathText;
|
||||
JsonMessage chatMessage;
|
||||
|
||||
// get death screen text to the killed player
|
||||
{
|
||||
ColoredText deathText;
|
||||
if (lastDamageSource != null) {
|
||||
deathText = lastDamageSource.buildDeathScreenText(this);
|
||||
} else { // may happen if killed by the server without applying damage
|
||||
deathText = ColoredText.of("Killed by poor programming.");
|
||||
}
|
||||
|
||||
// #buildDeathScreenText can return null, check here
|
||||
if (deathText != null) {
|
||||
CombatEventPacket deathPacket = CombatEventPacket.death(this, Optional.empty(), deathText);
|
||||
playerConnection.sendPacket(deathPacket);
|
||||
}
|
||||
}
|
||||
|
||||
// send death message to chat
|
||||
// get death message to chat
|
||||
{
|
||||
JsonMessage chatMessage;
|
||||
if (lastDamageSource != null) {
|
||||
chatMessage = lastDamageSource.buildDeathMessage(this);
|
||||
} else { // may happen if killed by the server without applying damage
|
||||
chatMessage = ColoredText.of(getUsername() + " was killed by poor programming.");
|
||||
}
|
||||
|
||||
// #buildDeathMessage can return null, check here
|
||||
if (chatMessage != null) {
|
||||
MinecraftServer.getConnectionManager().broadcastMessage(chatMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Call player death event
|
||||
PlayerDeathEvent playerDeathEvent = new PlayerDeathEvent(this, deathText, chatMessage);
|
||||
callEvent(PlayerDeathEvent.class, playerDeathEvent);
|
||||
|
||||
deathText = playerDeathEvent.getDeathText();
|
||||
chatMessage = playerDeathEvent.getChatMessage();
|
||||
|
||||
// #buildDeathScreenText can return null, check here
|
||||
if (deathText != null) {
|
||||
CombatEventPacket deathPacket = CombatEventPacket.death(this, null, deathText);
|
||||
playerConnection.sendPacket(deathPacket);
|
||||
}
|
||||
|
||||
// #buildDeathMessage can return null, check here
|
||||
if (chatMessage != null) {
|
||||
MinecraftServer.getConnectionManager().broadcastMessage(chatMessage);
|
||||
}
|
||||
|
||||
}
|
||||
super.kill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawns the player by sending a {@link RespawnPacket} to the player and teleporting him
|
||||
* to {@link #getRespawnPoint()}. It also resets fire and his health
|
||||
* to {@link #getRespawnPoint()}. It also resets fire and health.
|
||||
*/
|
||||
public void respawn() {
|
||||
if (!isDead())
|
||||
@ -652,29 +663,24 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* it is possible for this method to be non-blocking when retrieving chunks is required.
|
||||
*
|
||||
* @param instance the new player instance
|
||||
* @param spawnPosition the new position of the player,
|
||||
* can be null or {@link #getPosition()} if you do not want to teleport the player
|
||||
* @param spawnPosition the new position of the player
|
||||
*/
|
||||
public void setInstance(@NotNull Instance instance, @Nullable Position spawnPosition) {
|
||||
Check.notNull(instance, "instance cannot be null!");
|
||||
public void setInstance(@NotNull Instance instance, @NotNull Position spawnPosition) {
|
||||
Check.argCondition(this.instance == instance, "Instance should be different than the current one");
|
||||
|
||||
// true if the chunks need to be sent to the client, can be false if the instances share the same chunks (eg SharedInstance)
|
||||
final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance);
|
||||
final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance) ||
|
||||
!spawnPosition.inSameChunk(this.position);
|
||||
|
||||
if (needWorldRefresh) {
|
||||
final boolean firstSpawn = this.instance == null; // TODO: Handle player reconnections, must be false in that case too
|
||||
|
||||
// Remove all previous viewable chunks (from the previous instance)
|
||||
for (Chunk viewableChunk : viewableChunks) {
|
||||
viewableChunk.removeViewer(this);
|
||||
}
|
||||
|
||||
// Send the new dimension
|
||||
if (this.instance != null) {
|
||||
// Send the new dimension if player isn't in any instance or if the dimension is different
|
||||
{
|
||||
final DimensionType instanceDimensionType = instance.getDimensionType();
|
||||
if (dimensionType != instanceDimensionType)
|
||||
if (dimensionType != instanceDimensionType) {
|
||||
sendDimension(instanceDimensionType);
|
||||
}
|
||||
}
|
||||
|
||||
// Load all the required chunks
|
||||
@ -693,7 +699,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
|
||||
final ChunkCallback endCallback = chunk -> {
|
||||
// This is the last chunk to be loaded , spawn player
|
||||
spawnPlayer(instance, spawnPosition, firstSpawn);
|
||||
spawnPlayer(instance, spawnPosition, firstSpawn, true);
|
||||
};
|
||||
|
||||
// Chunk 0;0 always needs to be loaded
|
||||
@ -704,13 +710,13 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
} else {
|
||||
// The player already has the good version of all the chunks.
|
||||
// We just need to refresh his entity viewing list and add him to the instance
|
||||
spawnPlayer(instance, spawnPosition, false);
|
||||
spawnPlayer(instance, spawnPosition, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the player instance without changing its position (defaulted to {@link #getRespawnPoint()}
|
||||
* if the player is not in any instance.
|
||||
* if the player is not in any instance).
|
||||
*
|
||||
* @param instance the new player instance
|
||||
* @see #setInstance(Instance, Position)
|
||||
@ -730,16 +736,29 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param spawnPosition the position to teleport the player
|
||||
* @param firstSpawn true if this is the player first spawn
|
||||
*/
|
||||
private void spawnPlayer(@NotNull Instance instance, @Nullable Position spawnPosition, boolean firstSpawn) {
|
||||
private void spawnPlayer(@NotNull Instance instance, @Nullable Position spawnPosition,
|
||||
boolean firstSpawn, boolean updateChunks) {
|
||||
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
||||
|
||||
super.setInstance(instance);
|
||||
|
||||
// Runnable used to send newly visible chunks to player once spawned in the instance
|
||||
final Runnable refreshRunnable = () -> {
|
||||
if (!updateChunks)
|
||||
return;
|
||||
|
||||
// Remove all previous viewable chunks (from the previous instance)
|
||||
this.viewableChunks.forEach(chunk -> chunk.removeViewer(this));
|
||||
final Chunk chunk = getChunk();
|
||||
if (chunk != null) {
|
||||
refreshVisibleChunks(chunk);
|
||||
}
|
||||
};
|
||||
|
||||
if (spawnPosition != null && !position.isSimilar(spawnPosition)) {
|
||||
teleport(spawnPosition,
|
||||
position.inSameChunk(spawnPosition) ? () -> refreshVisibleChunks(getChunk()) : null);
|
||||
teleport(spawnPosition, refreshRunnable);
|
||||
} else {
|
||||
refreshVisibleChunks(getChunk());
|
||||
refreshRunnable.run();
|
||||
}
|
||||
|
||||
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
|
||||
@ -785,18 +804,15 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
|
||||
/**
|
||||
* Sends a plugin message to the player.
|
||||
* <p>
|
||||
* Message encoded to UTF-8.
|
||||
*
|
||||
* @param channel the message channel
|
||||
* @param message the message
|
||||
*/
|
||||
public void sendPluginMessage(@NotNull String channel, @NotNull String message) {
|
||||
// Write the data
|
||||
BinaryWriter writer = new BinaryWriter();
|
||||
writer.writeSizedString(message);
|
||||
// Retrieve the data
|
||||
final byte[] data = writer.toByteArray();
|
||||
|
||||
sendPluginMessage(channel, data);
|
||||
final byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
|
||||
sendPluginMessage(channel, bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -950,7 +966,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param header the header text, null to set empty
|
||||
* @param footer the footer text, null to set empty
|
||||
*/
|
||||
public void sendHeaderFooter(@Nullable ColoredText header, @Nullable ColoredText footer) {
|
||||
public void sendHeaderFooter(@Nullable JsonMessage header, @Nullable JsonMessage footer) {
|
||||
PlayerListHeaderAndFooterPacket playerListHeaderAndFooterPacket = new PlayerListHeaderAndFooterPacket();
|
||||
playerListHeaderAndFooterPacket.emptyHeader = header == null;
|
||||
playerListHeaderAndFooterPacket.emptyFooter = footer == null;
|
||||
@ -967,7 +983,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param action the action of the title (where to show it)
|
||||
* @see #sendTitleTime(int, int, int) to specify the display time
|
||||
*/
|
||||
private void sendTitle(@NotNull ColoredText text, @NotNull TitlePacket.Action action) {
|
||||
private void sendTitle(@NotNull JsonMessage text, @NotNull TitlePacket.Action action) {
|
||||
TitlePacket titlePacket = new TitlePacket();
|
||||
titlePacket.action = action;
|
||||
|
||||
@ -995,7 +1011,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param subtitle the subtitle message
|
||||
* @see #sendTitleTime(int, int, int) to specify the display time
|
||||
*/
|
||||
public void sendTitleSubtitleMessage(@NotNull ColoredText title, @NotNull ColoredText subtitle) {
|
||||
public void sendTitleSubtitleMessage(@NotNull JsonMessage title, @NotNull JsonMessage subtitle) {
|
||||
sendTitle(title, TitlePacket.Action.SET_TITLE);
|
||||
sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE);
|
||||
}
|
||||
@ -1006,7 +1022,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param title the title message
|
||||
* @see #sendTitleTime(int, int, int) to specify the display time
|
||||
*/
|
||||
public void sendTitleMessage(@NotNull ColoredText title) {
|
||||
public void sendTitleMessage(@NotNull JsonMessage title) {
|
||||
sendTitle(title, TitlePacket.Action.SET_TITLE);
|
||||
}
|
||||
|
||||
@ -1016,7 +1032,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param subtitle the subtitle message
|
||||
* @see #sendTitleTime(int, int, int) to specify the display time
|
||||
*/
|
||||
public void sendSubtitleMessage(@NotNull ColoredText subtitle) {
|
||||
public void sendSubtitleMessage(@NotNull JsonMessage subtitle) {
|
||||
sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE);
|
||||
}
|
||||
|
||||
@ -1026,7 +1042,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param actionBar the action bar message
|
||||
* @see #sendTitleTime(int, int, int) to specify the display time
|
||||
*/
|
||||
public void sendActionBarMessage(@NotNull ColoredText actionBar) {
|
||||
public void sendActionBarMessage(@NotNull JsonMessage actionBar) {
|
||||
sendTitle(actionBar, TitlePacket.Action.SET_ACTION_BAR);
|
||||
}
|
||||
|
||||
@ -1064,6 +1080,30 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
playerConnection.sendPacket(titlePacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a book ui for the player with the given book metadata.
|
||||
*
|
||||
* @param bookMeta The metadata of the book to open
|
||||
*/
|
||||
public void openBook(@NotNull WrittenBookMeta bookMeta) {
|
||||
// Set book in offhand
|
||||
final ItemStack writtenBook = new ItemStack(Material.WRITTEN_BOOK, (byte) 1);
|
||||
writtenBook.setItemMeta(bookMeta);
|
||||
final SetSlotPacket setSlotPacket = new SetSlotPacket();
|
||||
setSlotPacket.windowId = 0;
|
||||
setSlotPacket.slot = 45;
|
||||
setSlotPacket.itemStack = writtenBook;
|
||||
this.playerConnection.sendPacket(setSlotPacket);
|
||||
|
||||
// Open the book
|
||||
final OpenBookPacket openBookPacket = new OpenBookPacket();
|
||||
openBookPacket.hand = Hand.OFF;
|
||||
this.playerConnection.sendPacket(openBookPacket);
|
||||
|
||||
// Update inventory to remove book (which the actual inventory does not have)
|
||||
this.inventory.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImmune(@NotNull DamageType type) {
|
||||
if (!getGameMode().canTakeDamage()) {
|
||||
@ -1073,9 +1113,12 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttributeChanged(@NotNull final AttributeInstance instance) {
|
||||
if (instance.getAttribute().isShared() && playerConnection != null)
|
||||
protected void onAttributeChanged(@NotNull final AttributeInstance attributeInstance) {
|
||||
if (attributeInstance.getAttribute().isShared() &&
|
||||
playerConnection != null &&
|
||||
playerConnection.getConnectionState() == ConnectionState.PLAY) {
|
||||
playerConnection.sendPacket(getPropertiesPacket());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1173,7 +1216,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @return the player display name, null means that {@link #getUsername()} is displayed
|
||||
*/
|
||||
@Nullable
|
||||
public ColoredText getDisplayName() {
|
||||
public JsonMessage getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
@ -1184,7 +1227,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
*
|
||||
* @param displayName the display name, null to display the username
|
||||
*/
|
||||
public void setDisplayName(@Nullable ColoredText displayName) {
|
||||
public void setDisplayName(@Nullable JsonMessage displayName) {
|
||||
this.displayName = displayName;
|
||||
|
||||
PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME);
|
||||
@ -1276,12 +1319,12 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the internal player name, used for the {@link PlayerPreLoginEvent}
|
||||
* Changes the internal player name, used for the {@link AsyncPlayerPreLoginEvent}
|
||||
* mostly unsafe outside of it.
|
||||
*
|
||||
* @param username the new player name
|
||||
*/
|
||||
protected void setUsername(@NotNull String username) {
|
||||
public void setUsernameField(@NotNull String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@ -1316,7 +1359,6 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param resourcePack the resource pack
|
||||
*/
|
||||
public void setResourcePack(@NotNull ResourcePack resourcePack) {
|
||||
Check.notNull(resourcePack, "The resource pack cannot be null");
|
||||
final String url = resourcePack.getUrl();
|
||||
final String hash = resourcePack.getHash();
|
||||
|
||||
@ -1551,7 +1593,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
|
||||
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
|
||||
|
||||
instance.loadOptionalChunk(chunkX, chunkZ, chunk -> {
|
||||
this.instance.loadOptionalChunk(chunkX, chunkZ, chunk -> {
|
||||
if (chunk == null) {
|
||||
// Cannot load chunk (auto load is not enabled)
|
||||
return;
|
||||
@ -1586,24 +1628,15 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
});
|
||||
|
||||
// Manage entities in unchecked chunks
|
||||
final long[] chunksInRange = ChunkUtils.getChunksInRange(newChunk.toPosition(), entityViewDistance);
|
||||
EntityUtils.forEachRange(instance, newChunk.toPosition(), entityViewDistance, entity -> {
|
||||
if (entity.isAutoViewable() && !entity.viewers.contains(this)) {
|
||||
entity.addViewer(this);
|
||||
}
|
||||
|
||||
for (long chunkIndex : chunksInRange) {
|
||||
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
|
||||
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
|
||||
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
|
||||
if (chunk == null)
|
||||
continue;
|
||||
instance.getChunkEntities(chunk).forEach(entity -> {
|
||||
if (isAutoViewable() && !entity.viewers.contains(this)) {
|
||||
entity.addViewer(this);
|
||||
}
|
||||
|
||||
if (entity instanceof Player && entity.isAutoViewable() && !viewers.contains(entity)) {
|
||||
addViewer((Player) entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (entity instanceof Player && isAutoViewable() && !viewers.contains(entity)) {
|
||||
addViewer((Player) entity);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -1698,13 +1731,16 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param gameMode the new player GameMode
|
||||
*/
|
||||
public void setGameMode(@NotNull GameMode gameMode) {
|
||||
Check.notNull(gameMode, "GameMode cannot be null");
|
||||
this.gameMode = gameMode;
|
||||
sendChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.getId());
|
||||
|
||||
PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE);
|
||||
infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateGamemode(getUuid(), gameMode));
|
||||
sendPacketToViewersAndSelf(infoPacket);
|
||||
// Condition to prevent sending the packets before spawning the player
|
||||
if (isActive()) {
|
||||
sendChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.getId());
|
||||
|
||||
PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE);
|
||||
infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateGamemode(getUuid(), gameMode));
|
||||
sendPacketToViewersAndSelf(infoPacket);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1723,7 +1759,6 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @param dimensionType the new player dimension
|
||||
*/
|
||||
protected void sendDimension(@NotNull DimensionType dimensionType) {
|
||||
Check.notNull(dimensionType, "Dimension cannot be null!");
|
||||
Check.argCondition(dimensionType.equals(getDimensionType()), "The dimension needs to be different than the current one!");
|
||||
|
||||
this.dimensionType = dimensionType;
|
||||
@ -1739,11 +1774,18 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
*
|
||||
* @param text the kick reason
|
||||
*/
|
||||
public void kick(@NotNull ColoredText text) {
|
||||
DisconnectPacket disconnectPacket = new DisconnectPacket();
|
||||
disconnectPacket.message = text;
|
||||
public void kick(@NotNull JsonMessage text) {
|
||||
final ConnectionState connectionState = playerConnection.getConnectionState();
|
||||
|
||||
// Packet type depends on the current player connection state
|
||||
final ServerPacket disconnectPacket;
|
||||
if (connectionState == ConnectionState.LOGIN) {
|
||||
disconnectPacket = new LoginDisconnectPacket(text);
|
||||
} else {
|
||||
disconnectPacket = new DisconnectPacket(text);
|
||||
}
|
||||
|
||||
playerConnection.sendPacket(disconnectPacket);
|
||||
playerConnection.disconnect();
|
||||
playerConnection.refreshOnline(false);
|
||||
}
|
||||
|
||||
@ -1850,7 +1892,6 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* @return true if the inventory has been opened/sent to the player, false otherwise (cancelled by event)
|
||||
*/
|
||||
public boolean openInventory(@NotNull Inventory inventory) {
|
||||
Check.notNull(inventory, "Inventory cannot be null, use Player#closeInventory() to close current");
|
||||
|
||||
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this);
|
||||
|
||||
|
@ -3,12 +3,11 @@ package net.minestom.server.entity;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import net.minestom.server.utils.url.URLUtils;
|
||||
import net.minestom.server.utils.mojang.MojangUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Contains all the data required to store a skin.
|
||||
@ -26,6 +25,43 @@ public class PlayerSkin {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a skin from a Mojang UUID.
|
||||
*
|
||||
* @param uuid Mojang UUID
|
||||
* @return a player skin based on the UUID, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public static PlayerSkin fromUuid(@NotNull String uuid) {
|
||||
final JsonObject jsonObject = MojangUtils.fromUuid(uuid);
|
||||
final JsonArray propertiesArray = jsonObject.get("properties").getAsJsonArray();
|
||||
|
||||
for (JsonElement jsonElement : propertiesArray) {
|
||||
final JsonObject propertyObject = jsonElement.getAsJsonObject();
|
||||
final String name = propertyObject.get("name").getAsString();
|
||||
if (!name.equals("textures"))
|
||||
continue;
|
||||
final String textureValue = propertyObject.get("value").getAsString();
|
||||
final String signatureValue = propertyObject.get("signature").getAsString();
|
||||
return new PlayerSkin(textureValue, signatureValue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a skin from a Minecraft username.
|
||||
*
|
||||
* @param username the Minecraft username
|
||||
* @return a skin based on a Minecraft username, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public static PlayerSkin fromUsername(@NotNull String username) {
|
||||
final JsonObject jsonObject = MojangUtils.fromUsername(username);
|
||||
final String uuid = jsonObject.get("id").getAsString();
|
||||
// Retrieve the skin data from the mojang uuid
|
||||
return fromUuid(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the skin textures value.
|
||||
*
|
||||
@ -44,58 +80,32 @@ public class PlayerSkin {
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a skin from a Mojang UUID.
|
||||
*
|
||||
* @param uuid Mojang UUID
|
||||
* @return a player skin based on the UUID, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public static PlayerSkin fromUuid(@NotNull String uuid) {
|
||||
final String url = "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false";
|
||||
|
||||
try {
|
||||
final String response = URLUtils.getText(url);
|
||||
final JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject();
|
||||
final JsonArray propertiesArray = jsonObject.get("properties").getAsJsonArray();
|
||||
|
||||
for (JsonElement jsonElement : propertiesArray) {
|
||||
final JsonObject propertyObject = jsonElement.getAsJsonObject();
|
||||
final String name = propertyObject.get("name").getAsString();
|
||||
if (!name.equals("textures"))
|
||||
continue;
|
||||
final String textureValue = propertyObject.get("value").getAsString();
|
||||
final String signatureValue = propertyObject.get("signature").getAsString();
|
||||
return new PlayerSkin(textureValue, signatureValue);
|
||||
}
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlayerSkin{" +
|
||||
"textures='" + textures + '\'' +
|
||||
", signature='" + signature + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a skin from a Minecraft username.
|
||||
*
|
||||
* @param username the Minecraft username
|
||||
* @return a skin based on a Minecraft username, null if not found
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
public static PlayerSkin fromUsername(@NotNull String username) {
|
||||
final String url = "https://api.mojang.com/users/profiles/minecraft/" + username;
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) return true;
|
||||
if (object == null || getClass() != object.getClass()) return false;
|
||||
PlayerSkin that = (PlayerSkin) object;
|
||||
return Objects.equals(textures, that.textures) &&
|
||||
Objects.equals(signature, that.signature);
|
||||
}
|
||||
|
||||
try {
|
||||
// Retrieve the mojang uuid from the name
|
||||
final String response = URLUtils.getText(url);
|
||||
final JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject();
|
||||
final String uuid = jsonObject.get("id").getAsString();
|
||||
// Retrieve the skin data from the mojang uuid
|
||||
return fromUuid(uuid);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(textures, signature);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
package net.minestom.server.entity.ai.goal;
|
||||
|
||||
import it.unimi.dsi.fastutil.shorts.Short2ShortArrayMap;
|
||||
import net.minestom.server.entity.EntityCreature;
|
||||
import net.minestom.server.entity.ai.GoalSelector;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class EatBlockGoal extends GoalSelector {
|
||||
private static final Random RANDOM = new Random();
|
||||
private final Map<Short, Short> eatBelowMap;
|
||||
private final Map<Short, Short> eatInMap;
|
||||
private final Short2ShortArrayMap eatBelowMap;
|
||||
private final Short2ShortArrayMap eatInMap;
|
||||
private final int chancePerTick;
|
||||
private int eatAnimationTick;
|
||||
|
||||
@ -24,8 +24,8 @@ public class EatBlockGoal extends GoalSelector {
|
||||
*/
|
||||
public EatBlockGoal(
|
||||
@NotNull EntityCreature entityCreature,
|
||||
@NotNull Map<Short, Short> eatInMap,
|
||||
@NotNull Map<Short, Short> eatBelowMap,
|
||||
@NotNull Short2ShortArrayMap eatInMap,
|
||||
@NotNull Short2ShortArrayMap eatBelowMap,
|
||||
int chancePerTick) {
|
||||
super(entityCreature);
|
||||
this.eatInMap = eatInMap;
|
||||
@ -39,7 +39,14 @@ public class EatBlockGoal extends GoalSelector {
|
||||
if (RANDOM.nextInt(chancePerTick) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Instance instance = entityCreature.getInstance();
|
||||
|
||||
// An entity shouldn't be eating blocks on null instances.
|
||||
if (instance == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final BlockPosition blockPosition = entityCreature.getPosition().toBlockPosition();
|
||||
final short blockStateIdIn = instance.getBlockStateId(blockPosition.clone().subtract(0, 1, 0));
|
||||
final short blockStateIdBelow = instance.getBlockStateId(blockPosition.clone().subtract(0, 2, 0));
|
||||
|
@ -20,17 +20,20 @@ public class MeleeAttackGoal extends GoalSelector {
|
||||
private long lastHit;
|
||||
private final int delay;
|
||||
private final TimeUnit timeUnit;
|
||||
private final int range;
|
||||
|
||||
private boolean stop;
|
||||
|
||||
/**
|
||||
* @param entityCreature the entity to add the goal to
|
||||
* @param delay the delay between each attacks
|
||||
* @param range the allowed range the entity can attack others.
|
||||
* @param timeUnit the unit of the delay
|
||||
*/
|
||||
public MeleeAttackGoal(@NotNull EntityCreature entityCreature, int delay, @NotNull TimeUnit timeUnit) {
|
||||
public MeleeAttackGoal(@NotNull EntityCreature entityCreature, int delay, int range, @NotNull TimeUnit timeUnit) {
|
||||
super(entityCreature);
|
||||
this.delay = delay;
|
||||
this.range = range;
|
||||
this.timeUnit = timeUnit;
|
||||
}
|
||||
|
||||
@ -56,7 +59,7 @@ public class MeleeAttackGoal extends GoalSelector {
|
||||
if (!stop) {
|
||||
|
||||
// Attack the target entity
|
||||
if (entityCreature.getBoundingBox().intersect(target)) {
|
||||
if (entityCreature.getDistance(target) <= range) {
|
||||
if (!CooldownUtils.hasCooldown(time, lastHit, timeUnit, delay)) {
|
||||
entityCreature.attack(target, true);
|
||||
this.lastHit = time;
|
||||
|
@ -56,6 +56,11 @@ public class ClosestEntityTarget extends TargetSelector {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ent.isRemoved()) {
|
||||
// Entity not valid
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the entity type can be targeted
|
||||
final Class<? extends Entity> clazz = ent.getClass();
|
||||
boolean correct = false;
|
||||
|
@ -110,7 +110,7 @@ public class DamageType implements DataContainer {
|
||||
* @return the death screen text, null to do not send anything
|
||||
*/
|
||||
@Nullable
|
||||
public ColoredText buildDeathScreenText(@NotNull Player killed) {
|
||||
public JsonMessage buildDeathScreenText(@NotNull Player killed) {
|
||||
return ColoredText.of("{@death." + identifier + "}");
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package net.minestom.server.entity.fakeplayer;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.player.PlayerSpawnEvent;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.player.FakePlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
@ -22,23 +23,41 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public class FakePlayer extends Player {
|
||||
|
||||
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||
|
||||
private final FakePlayerOption option;
|
||||
private final FakePlayerController fakePlayerController;
|
||||
|
||||
private FakePlayer(@NotNull UUID uuid, @NotNull String username, @NotNull FakePlayerOption option) {
|
||||
/**
|
||||
* Initializes a new {@link FakePlayer} with the given {@code uuid}, {@code username} and {@code option}'s.
|
||||
*
|
||||
* @param uuid The unique identifier for the fake player.
|
||||
* @param username The username for the fake player.
|
||||
* @param option Any option for the fake player.
|
||||
*/
|
||||
private FakePlayer(@NotNull UUID uuid, @NotNull String username,
|
||||
@NotNull FakePlayerOption option,
|
||||
@Nullable Consumer<FakePlayer> spawnCallback) {
|
||||
super(uuid, username, new FakePlayerConnection());
|
||||
|
||||
this.option = option;
|
||||
|
||||
this.fakePlayerController = new FakePlayerController(this);
|
||||
|
||||
if (option.isRegistered()) {
|
||||
MinecraftServer.getConnectionManager().createPlayer(this);
|
||||
if (spawnCallback != null) {
|
||||
addEventCallback(PlayerSpawnEvent.class,
|
||||
event -> {
|
||||
if (event.isFirstSpawn()) {
|
||||
spawnCallback.accept(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CONNECTION_MANAGER.startPlayState(this, option.isRegistered());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits a new {@link FakePlayer}.
|
||||
* Initializes a new {@link FakePlayer}.
|
||||
*
|
||||
* @param uuid the FakePlayer uuid
|
||||
* @param username the FakePlayer username
|
||||
@ -46,20 +65,11 @@ public class FakePlayer extends Player {
|
||||
*/
|
||||
public static void initPlayer(@NotNull UUID uuid, @NotNull String username,
|
||||
@NotNull FakePlayerOption option, @Nullable Consumer<FakePlayer> spawnCallback) {
|
||||
final FakePlayer fakePlayer = new FakePlayer(uuid, username, option);
|
||||
|
||||
if (spawnCallback != null) {
|
||||
fakePlayer.addEventCallback(PlayerSpawnEvent.class,
|
||||
event -> {
|
||||
if (event.isFirstSpawn()) {
|
||||
spawnCallback.accept(fakePlayer);
|
||||
}
|
||||
});
|
||||
}
|
||||
new FakePlayer(uuid, username, option, spawnCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits a new {@link FakePlayer} without adding it in cache.
|
||||
* Initializes a new {@link FakePlayer} without adding it in cache.
|
||||
* <p>
|
||||
* If you want the fake player to be obtainable with the {@link net.minestom.server.network.ConnectionManager}
|
||||
* you need to specify it in a {@link FakePlayerOption} and use {@link #initPlayer(UUID, String, FakePlayerOption, Consumer)}.
|
||||
@ -82,11 +92,19 @@ public class FakePlayer extends Player {
|
||||
return option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the controller for the fake player.
|
||||
*
|
||||
* @return The fake player's controller.
|
||||
*/
|
||||
@NotNull
|
||||
public FakePlayerController getController() {
|
||||
return fakePlayerController;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void showPlayer(@NotNull PlayerConnection connection) {
|
||||
super.showPlayer(connection);
|
||||
|
@ -12,8 +12,11 @@ import net.minestom.server.network.packet.client.play.*;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.play.KeepAlivePacket;
|
||||
import net.minestom.server.network.packet.server.play.PlayerPositionAndLookPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -25,10 +28,24 @@ public class FakePlayerController {
|
||||
|
||||
private final FakePlayer fakePlayer;
|
||||
|
||||
/**
|
||||
* Initializes a new {@link FakePlayerController} with the given {@link FakePlayer}.
|
||||
*
|
||||
* @param fakePlayer The fake player that should used the controller.
|
||||
*/
|
||||
public FakePlayerController(@NotNull FakePlayer fakePlayer) {
|
||||
this.fakePlayer = fakePlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a click in a window.
|
||||
*
|
||||
* @param playerInventory {@code true} if the window a {@link PlayerInventory}, otherwise {@code false}.
|
||||
* @param slot The slot where the fake player should click on.
|
||||
* @param button The mouse button that the fake player should used.
|
||||
* @param action The action that the fake player should perform.
|
||||
* @param mode The inventory operation mode that the fake player should used.
|
||||
*/
|
||||
public void clickWindow(boolean playerInventory, short slot, byte button, short action, int mode) {
|
||||
Inventory inventory = playerInventory ? null : fakePlayer.getOpenInventory();
|
||||
InventoryModifier inventoryModifier = inventory == null ? fakePlayer.getInventory() : inventory;
|
||||
@ -48,6 +65,9 @@ public class FakePlayerController {
|
||||
addToQueue(clickWindowPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current opened inventory if there is any.
|
||||
*/
|
||||
public void closeWindow() {
|
||||
Inventory openInventory = fakePlayer.getOpenInventory();
|
||||
|
||||
@ -56,6 +76,12 @@ public class FakePlayerController {
|
||||
addToQueue(closeWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a plugin message to the player.
|
||||
*
|
||||
* @param channel The channel of the message.
|
||||
* @param message The message data.
|
||||
*/
|
||||
public void sendPluginMessage(String channel, byte[] message) {
|
||||
ClientPluginMessagePacket pluginMessagePacket = new ClientPluginMessagePacket();
|
||||
pluginMessagePacket.channel = channel;
|
||||
@ -63,10 +89,21 @@ public class FakePlayerController {
|
||||
addToQueue(pluginMessagePacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a plugin message to the player.
|
||||
*
|
||||
* @param channel The channel of the message.
|
||||
* @param message The message data.
|
||||
*/
|
||||
public void sendPluginMessage(String channel, String message) {
|
||||
sendPluginMessage(channel, message.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Attacks the given {@code entity}.
|
||||
*
|
||||
* @param entity The entity that is to be attacked.
|
||||
*/
|
||||
public void attackEntity(Entity entity) {
|
||||
ClientInteractEntityPacket interactEntityPacket = new ClientInteractEntityPacket();
|
||||
interactEntityPacket.targetId = entity.getEntityId();
|
||||
@ -74,6 +111,11 @@ public class FakePlayerController {
|
||||
addToQueue(interactEntityPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawns the player.
|
||||
*
|
||||
* @see Player#respawn()
|
||||
*/
|
||||
public void respawn() {
|
||||
// Sending the respawn packet for some reason
|
||||
// Is related to FakePlayer#showPlayer and the tablist option (probably because of the scheduler)
|
||||
@ -83,24 +125,48 @@ public class FakePlayerController {
|
||||
fakePlayer.respawn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the current held slot for the player.
|
||||
*
|
||||
* @param slot The slot that the player has to held.
|
||||
* @throws IllegalArgumentException If {@code slot} is not between {@code 0} and {@code 8}.
|
||||
*/
|
||||
public void setHeldItem(short slot) {
|
||||
Check.argCondition(!MathUtils.isBetween(slot, 0, 8), "Slot has to be between 0 and 8!");
|
||||
|
||||
ClientHeldItemChangePacket heldItemChangePacket = new ClientHeldItemChangePacket();
|
||||
heldItemChangePacket.slot = slot;
|
||||
addToQueue(heldItemChangePacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an animation packet that animates the specified arm.
|
||||
*
|
||||
* @param hand The hand of the arm to be animated.
|
||||
*/
|
||||
public void sendArmAnimation(Player.Hand hand) {
|
||||
ClientAnimationPacket animationPacket = new ClientAnimationPacket();
|
||||
animationPacket.hand = hand;
|
||||
addToQueue(animationPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the item in the given {@code hand}.
|
||||
*
|
||||
* @param hand The hand in which an ite mshould be.
|
||||
*/
|
||||
public void useItem(Player.Hand hand) {
|
||||
ClientUseItemPacket useItemPacket = new ClientUseItemPacket();
|
||||
useItemPacket.hand = hand;
|
||||
addToQueue(useItemPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the fake player.
|
||||
*
|
||||
* @param yaw The new yaw for the fake player.
|
||||
* @param pitch The new pitch for the fake player.
|
||||
*/
|
||||
public void rotate(float yaw, float pitch) {
|
||||
ClientPlayerRotationPacket playerRotationPacket = new ClientPlayerRotationPacket();
|
||||
playerRotationPacket.yaw = yaw;
|
||||
@ -109,34 +175,52 @@ public class FakePlayerController {
|
||||
addToQueue(playerRotationPacket);
|
||||
}
|
||||
|
||||
public void startDigging(BlockPosition blockPosition) {
|
||||
/**
|
||||
* Starts the digging process of the fake player.
|
||||
*
|
||||
* @param blockPosition The position of the block to be excavated.
|
||||
* @param blockFace From where the block is struck.
|
||||
*/
|
||||
public void startDigging(BlockPosition blockPosition, BlockFace blockFace) {
|
||||
ClientPlayerDiggingPacket playerDiggingPacket = new ClientPlayerDiggingPacket();
|
||||
playerDiggingPacket.status = ClientPlayerDiggingPacket.Status.STARTED_DIGGING;
|
||||
playerDiggingPacket.blockPosition = blockPosition;
|
||||
playerDiggingPacket.blockFace = BlockFace.BOTTOM; // TODO not hardcode
|
||||
playerDiggingPacket.blockFace = blockFace;
|
||||
addToQueue(playerDiggingPacket);
|
||||
}
|
||||
|
||||
public void stopDigging(BlockPosition blockPosition) {
|
||||
/**
|
||||
* Stops the digging process of the fake player.
|
||||
*
|
||||
* @param blockPosition The position of the block to be excavated.
|
||||
* @param blockFace From where the block is struck.
|
||||
*/
|
||||
public void stopDigging(BlockPosition blockPosition, BlockFace blockFace) {
|
||||
ClientPlayerDiggingPacket playerDiggingPacket = new ClientPlayerDiggingPacket();
|
||||
playerDiggingPacket.status = ClientPlayerDiggingPacket.Status.CANCELLED_DIGGING;
|
||||
playerDiggingPacket.blockPosition = blockPosition;
|
||||
playerDiggingPacket.blockFace = BlockFace.BOTTOM; // TODO not hardcode
|
||||
playerDiggingPacket.blockFace = blockFace;
|
||||
addToQueue(playerDiggingPacket);
|
||||
}
|
||||
|
||||
public void finishDigging(BlockPosition blockPosition) {
|
||||
/**
|
||||
* Finishes the digging process of the fake player.
|
||||
*
|
||||
* @param blockPosition The position of the block to be excavated.
|
||||
* @param blockFace From where the block is struck.
|
||||
*/
|
||||
public void finishDigging(BlockPosition blockPosition, BlockFace blockFace) {
|
||||
ClientPlayerDiggingPacket playerDiggingPacket = new ClientPlayerDiggingPacket();
|
||||
playerDiggingPacket.status = ClientPlayerDiggingPacket.Status.FINISHED_DIGGING;
|
||||
playerDiggingPacket.blockPosition = blockPosition;
|
||||
playerDiggingPacket.blockFace = BlockFace.BOTTOM; // TODO not hardcode
|
||||
playerDiggingPacket.blockFace = blockFace;
|
||||
addToQueue(playerDiggingPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the player receives a packet
|
||||
* WARNING: pretty much unsafe, used internally to redirect packets here,
|
||||
* you should instead use {@link net.minestom.server.network.player.PlayerConnection#sendPacket(ServerPacket)}
|
||||
* you should instead use {@link PlayerConnection#sendPacket(ServerPacket)}
|
||||
*
|
||||
* @param serverPacket the packet to consume
|
||||
*/
|
||||
@ -152,6 +236,12 @@ public class FakePlayerController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All packets in the queue are executed in the {@link Player#update(long)} method. It is used internally to add all
|
||||
* received packet from the client. Could be used to "simulate" a received packet, but to use at your own risk!
|
||||
*
|
||||
* @param clientPlayPacket The packet to add in the queue.
|
||||
*/
|
||||
private void addToQueue(ClientPlayPacket clientPlayPacket) {
|
||||
this.fakePlayer.addPacketToQueue(clientPlayPacket);
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ package net.minestom.server.entity.fakeplayer;
|
||||
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
|
||||
/**
|
||||
* Represents any options for a {@link FakePlayer}.
|
||||
*/
|
||||
public class FakePlayerOption {
|
||||
|
||||
private boolean registered = false;
|
||||
|
@ -2,6 +2,7 @@ package net.minestom.server.entity.hologram;
|
||||
|
||||
import net.minestom.server.Viewable;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.entity.type.decoration.EntityArmorStand;
|
||||
import net.minestom.server.instance.Instance;
|
||||
@ -12,7 +13,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an invisible armor stand showing a {@link ColoredText}.
|
||||
* Represents an invisible armor stand showing a {@link JsonMessage}.
|
||||
*/
|
||||
public class Hologram implements Viewable {
|
||||
|
||||
@ -21,11 +22,19 @@ public class Hologram implements Viewable {
|
||||
private final HologramEntity entity;
|
||||
|
||||
private Position position;
|
||||
private ColoredText text;
|
||||
private JsonMessage text;
|
||||
|
||||
private boolean removed;
|
||||
|
||||
public Hologram(Instance instance, Position spawnPosition, ColoredText text, boolean autoViewable) {
|
||||
/**
|
||||
* Constructs a new {@link Hologram} with the given parameters.
|
||||
*
|
||||
* @param instance The instance where the hologram should be spawned.
|
||||
* @param spawnPosition The spawn position of this hologram.
|
||||
* @param text The text of this hologram.
|
||||
* @param autoViewable {@code true}if the hologram should be visible automatically, otherwise {@code false}.
|
||||
*/
|
||||
public Hologram(Instance instance, Position spawnPosition, JsonMessage text, boolean autoViewable) {
|
||||
this.entity = new HologramEntity(spawnPosition.clone().add(0, OFFSET_Y, 0));
|
||||
this.entity.setInstance(instance);
|
||||
this.entity.setAutoViewable(autoViewable);
|
||||
@ -34,7 +43,14 @@ public class Hologram implements Viewable {
|
||||
setText(text);
|
||||
}
|
||||
|
||||
public Hologram(Instance instance, Position spawnPosition, ColoredText text) {
|
||||
/**
|
||||
* Constructs a new {@link Hologram} with the given parameters.
|
||||
*
|
||||
* @param instance The instance where the hologram should be spawned.
|
||||
* @param spawnPosition The spawn position of this hologram.
|
||||
* @param text The text of this hologram.
|
||||
*/
|
||||
public Hologram(Instance instance, Position spawnPosition, JsonMessage text) {
|
||||
this(instance, spawnPosition, text, true);
|
||||
}
|
||||
|
||||
@ -64,7 +80,7 @@ public class Hologram implements Viewable {
|
||||
*
|
||||
* @return the hologram text
|
||||
*/
|
||||
public ColoredText getText() {
|
||||
public JsonMessage getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@ -73,7 +89,7 @@ public class Hologram implements Viewable {
|
||||
*
|
||||
* @param text the new hologram text
|
||||
*/
|
||||
public void setText(ColoredText text) {
|
||||
public void setText(JsonMessage text) {
|
||||
checkRemoved();
|
||||
this.text = text;
|
||||
this.entity.setCustomName(text);
|
||||
@ -105,29 +121,46 @@ public class Hologram implements Viewable {
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean addViewer(@NotNull Player player) {
|
||||
return entity.addViewer(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean removeViewer(@NotNull Player player) {
|
||||
return entity.removeViewer(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<Player> getViewers() {
|
||||
return entity.getViewers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #isRemoved()
|
||||
*/
|
||||
private void checkRemoved() {
|
||||
Check.stateCondition(isRemoved(), "You cannot interact with a removed Hologram");
|
||||
}
|
||||
|
||||
|
||||
private static class HologramEntity extends EntityArmorStand {
|
||||
public static class HologramEntity extends EntityArmorStand {
|
||||
|
||||
/**
|
||||
* Constructs a new {@link HologramEntity} with the given {@code spawnPosition}.
|
||||
*
|
||||
* @param spawnPosition The spawn position of this hologram entity.
|
||||
*/
|
||||
public HologramEntity(Position spawnPosition) {
|
||||
super(spawnPosition);
|
||||
setSmall(true);
|
||||
|
@ -30,7 +30,6 @@ public interface NavigableEntity {
|
||||
* @param speed define how far the entity will move
|
||||
*/
|
||||
default void moveTowards(@NotNull Position direction, float speed) {
|
||||
Check.notNull(direction, "The direction cannot be null");
|
||||
|
||||
final Position position = getNavigableEntity().getPosition();
|
||||
|
||||
@ -86,10 +85,12 @@ public interface NavigableEntity {
|
||||
* The position is cloned, if you want the entity to continually follow this position object
|
||||
* you need to call this when you want the path to update.
|
||||
*
|
||||
* @param position the position to find the path to, null to reset the pathfinder
|
||||
* @param position the position to find the path to, null to reset the pathfinder
|
||||
* @param bestEffort whether to use the best-effort algorithm to the destination,
|
||||
* if false then this method is more likely to return immediately
|
||||
* @return true if a path has been found
|
||||
*/
|
||||
default boolean setPathTo(@Nullable Position position) {
|
||||
default boolean setPathTo(@Nullable Position position, boolean bestEffort) {
|
||||
if (position != null && getPathPosition() != null && position.isSimilar(getPathPosition())) {
|
||||
// Tried to set path to the same target position
|
||||
return false;
|
||||
@ -108,6 +109,11 @@ public interface NavigableEntity {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can't path with a null instance.
|
||||
if (instance == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can't path outside of the world border
|
||||
final WorldBorder worldBorder = instance.getWorldBorder();
|
||||
if (!worldBorder.isInside(position)) {
|
||||
@ -122,7 +128,11 @@ public interface NavigableEntity {
|
||||
|
||||
final Position targetPosition = position.clone();
|
||||
|
||||
final IPath path = pathFinder.initiatePathTo(targetPosition.getX(), targetPosition.getY(), targetPosition.getZ());
|
||||
final IPath path = pathFinder.initiatePathTo(
|
||||
targetPosition.getX(),
|
||||
targetPosition.getY(),
|
||||
targetPosition.getZ(),
|
||||
bestEffort);
|
||||
setPath(path);
|
||||
|
||||
final boolean success = path != null;
|
||||
@ -131,6 +141,13 @@ public interface NavigableEntity {
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #setPathTo(Position, boolean) with {@code bestEffort} sets to {@code true}.
|
||||
*/
|
||||
default boolean setPathTo(@Nullable Position position) {
|
||||
return setPathTo(position, true);
|
||||
}
|
||||
|
||||
default void pathFindingTick(float speed) {
|
||||
final Position pathPosition = getPathPosition();
|
||||
if (pathPosition != null) {
|
||||
|
@ -9,6 +9,5 @@ public class EntityCaveSpider extends EntityCreature implements Monster {
|
||||
public EntityCaveSpider(Position spawnPosition) {
|
||||
super(EntityType.CAVE_SPIDER, spawnPosition);
|
||||
setBoundingBox(0.7f, 0.5f, 0.7f);
|
||||
setEyeHeight(0.45f);
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ public class EntityCreeper extends EntityCreature implements Monster {
|
||||
return packet -> {
|
||||
super.getMetadataConsumer().accept(packet);
|
||||
fillMetadataIndex(packet, 15);
|
||||
fillMetadataIndex(packet, 16);
|
||||
fillMetadataIndex(packet, 17);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,5 @@ public class EntityEndermite extends EntityCreature implements Monster {
|
||||
public EntityEndermite(Position spawnPosition) {
|
||||
super(EntityType.ENDERMITE, spawnPosition);
|
||||
setBoundingBox(0.4f, 0.3f, 0.4f);
|
||||
setEyeHeight(0.13f);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ public class EntityGhast extends EntityCreature implements Monster {
|
||||
public EntityGhast(Position spawnPosition) {
|
||||
super(EntityType.GHAST, spawnPosition);
|
||||
setBoundingBox(4, 4, 4);
|
||||
setEyeHeight(2.6f);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -9,6 +9,5 @@ public class EntitySilverfish extends EntityCreature implements Monster {
|
||||
public EntitySilverfish(Position spawnPosition) {
|
||||
super(EntityType.SILVERFISH, spawnPosition);
|
||||
setBoundingBox(0.4f, 0.3f, 0.4f);
|
||||
setEyeHeight(0.13f);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ public class EntitySpider extends EntityCreature implements Monster {
|
||||
public EntitySpider(Position spawnPosition) {
|
||||
super(EntityType.SPIDER, spawnPosition);
|
||||
setBoundingBox(1.4f, 0.9f, 1.4f);
|
||||
setEyeHeight(0.65f);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -16,7 +16,6 @@ public class EntityWitch extends EntityCreature implements Monster {
|
||||
public EntityWitch(Position spawnPosition) {
|
||||
super(EntityType.WITCH, spawnPosition);
|
||||
setBoundingBox(0.6f, 1.95f, 0.6f);
|
||||
setEyeHeight(1.62f);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -0,0 +1,76 @@
|
||||
package net.minestom.server.entity.type.other;
|
||||
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.entity.ObjectEntity;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class EntityEndCrystal extends ObjectEntity {
|
||||
|
||||
private BlockPosition beamTarget;
|
||||
private boolean showBottom;
|
||||
|
||||
public EntityEndCrystal(@NotNull Position spawnPosition) {
|
||||
super(EntityType.END_CRYSTAL, spawnPosition);
|
||||
|
||||
setBoundingBox(2f, 2f, 2f);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Consumer<BinaryWriter> getMetadataConsumer() {
|
||||
return packet -> {
|
||||
super.getMetadataConsumer().accept(packet);
|
||||
fillMetadataIndex(packet, 7);
|
||||
fillMetadataIndex(packet, 8);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillMetadataIndex(@NotNull BinaryWriter packet, int index) {
|
||||
super.fillMetadataIndex(packet, index);
|
||||
if (index == 7) {
|
||||
final boolean hasTarget = beamTarget != null;
|
||||
|
||||
packet.writeByte((byte) 7);
|
||||
packet.writeByte(METADATA_OPTPOSITION);
|
||||
packet.writeBoolean(hasTarget);
|
||||
if (hasTarget) {
|
||||
packet.writeBlockPosition(beamTarget);
|
||||
}
|
||||
} else if (index == 8) {
|
||||
packet.writeByte((byte) 8);
|
||||
packet.writeByte(METADATA_BOOLEAN);
|
||||
packet.writeBoolean(showBottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockPosition getBeamTarget() {
|
||||
return beamTarget;
|
||||
}
|
||||
|
||||
public void setBeamTarget(@Nullable BlockPosition beamTarget) {
|
||||
this.beamTarget = beamTarget;
|
||||
sendMetadataIndex(7);
|
||||
}
|
||||
|
||||
public boolean showBottom() {
|
||||
return showBottom;
|
||||
}
|
||||
|
||||
public void setShowBottom(boolean showBottom) {
|
||||
this.showBottom = showBottom;
|
||||
sendMetadataIndex(8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getObjectData() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -63,6 +63,6 @@ public class EntityDamageEvent extends EntityEvent implements CancellableEvent {
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancelled;
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
package net.minestom.server.event.entity;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.event.EntityEvent;
|
||||
import net.minestom.server.potion.Potion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class EntityPotionAddEvent extends EntityEvent {
|
||||
|
||||
private final Potion potion;
|
||||
|
||||
public EntityPotionAddEvent(@NotNull Entity entity, @NotNull Potion potion) {
|
||||
super(entity);
|
||||
this.potion = potion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the potion that was added.
|
||||
*
|
||||
* @return the added potion.
|
||||
*/
|
||||
@NotNull
|
||||
public Potion getPotion() {
|
||||
return potion;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package net.minestom.server.event.entity;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.event.EntityEvent;
|
||||
import net.minestom.server.potion.Potion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class EntityPotionRemoveEvent extends EntityEvent {
|
||||
|
||||
private final Potion potion;
|
||||
|
||||
public EntityPotionRemoveEvent(@NotNull Entity entity, @NotNull Potion potion) {
|
||||
super(entity);
|
||||
this.potion = potion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the potion that was removed.
|
||||
*
|
||||
* @return the removed potion.
|
||||
*/
|
||||
@NotNull
|
||||
public Potion getPotion() {
|
||||
return potion;
|
||||
}
|
||||
}
|
@ -37,8 +37,6 @@ public interface EventHandler {
|
||||
* @return true if the callback collection changed as a result of the call
|
||||
*/
|
||||
default <E extends Event> boolean addEventCallback(@NotNull Class<E> eventClass, @NotNull EventCallback<E> eventCallback) {
|
||||
Check.notNull(eventClass, "Event class cannot be null");
|
||||
Check.notNull(eventCallback, "Event callback cannot be null");
|
||||
Collection<EventCallback> callbacks = getEventCallbacks(eventClass);
|
||||
return callbacks.add(eventCallback);
|
||||
}
|
||||
@ -52,8 +50,6 @@ public interface EventHandler {
|
||||
* @return true if the callback was removed as a result of this call
|
||||
*/
|
||||
default <E extends Event> boolean removeEventCallback(@NotNull Class<E> eventClass, @NotNull EventCallback<E> eventCallback) {
|
||||
Check.notNull(eventClass, "Event class cannot be null");
|
||||
Check.notNull(eventCallback, "Event callback cannot be null");
|
||||
Collection<EventCallback> callbacks = getEventCallbacks(eventClass);
|
||||
return callbacks.remove(eventCallback);
|
||||
}
|
||||
@ -67,7 +63,6 @@ public interface EventHandler {
|
||||
*/
|
||||
@NotNull
|
||||
default <E extends Event> Collection<EventCallback> getEventCallbacks(@NotNull Class<E> eventClass) {
|
||||
Check.notNull(eventClass, "Event class cannot be null");
|
||||
return getEventCallbacksMap().computeIfAbsent(eventClass, clazz -> new CopyOnWriteArraySet<>());
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package net.minestom.server.event.player;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.PlayerEvent;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
@ -11,12 +10,12 @@ import java.util.UUID;
|
||||
* Called before the player initialization, it can be used to kick the player before any connection
|
||||
* or to change his final username/uuid.
|
||||
*/
|
||||
public class PlayerPreLoginEvent extends PlayerEvent {
|
||||
public class AsyncPlayerPreLoginEvent extends PlayerEvent {
|
||||
|
||||
private String username;
|
||||
private UUID playerUuid;
|
||||
|
||||
public PlayerPreLoginEvent(@NotNull Player player, @NotNull String username, @NotNull UUID playerUuid) {
|
||||
public AsyncPlayerPreLoginEvent(@NotNull Player player, @NotNull String username, @NotNull UUID playerUuid) {
|
||||
super(player);
|
||||
this.username = username;
|
||||
this.playerUuid = playerUuid;
|
||||
@ -38,7 +37,6 @@ public class PlayerPreLoginEvent extends PlayerEvent {
|
||||
* @param username the new player username
|
||||
*/
|
||||
public void setUsername(@NotNull String username) {
|
||||
Check.notNull(username, "The player username cannot be null");
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@ -58,7 +56,6 @@ public class PlayerPreLoginEvent extends PlayerEvent {
|
||||
* @param playerUuid the new player uuid
|
||||
*/
|
||||
public void setPlayerUuid(@NotNull UUID playerUuid) {
|
||||
Check.notNull(playerUuid, "The player uuid cannot be null");
|
||||
this.playerUuid = playerUuid;
|
||||
}
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
package net.minestom.server.event.player;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.data.Data;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.CancellableEvent;
|
||||
import net.minestom.server.event.PlayerEvent;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockManager;
|
||||
import net.minestom.server.instance.block.CustomBlock;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Called when a player tries placing a block.
|
||||
@ -18,6 +22,7 @@ public class PlayerBlockPlaceEvent extends PlayerEvent implements CancellableEve
|
||||
|
||||
private short blockStateId;
|
||||
private short customBlockId;
|
||||
private Data blockData;
|
||||
private final BlockPosition blockPosition;
|
||||
private final Player.Hand hand;
|
||||
|
||||
@ -25,11 +30,10 @@ public class PlayerBlockPlaceEvent extends PlayerEvent implements CancellableEve
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public PlayerBlockPlaceEvent(@NotNull Player player, short blockStateId, short customBlockId,
|
||||
public PlayerBlockPlaceEvent(@NotNull Player player, @NotNull Block block,
|
||||
@NotNull BlockPosition blockPosition, @NotNull Player.Hand hand) {
|
||||
super(player);
|
||||
this.blockStateId = blockStateId;
|
||||
this.customBlockId = customBlockId;
|
||||
this.blockStateId = block.getBlockId();
|
||||
this.blockPosition = blockPosition;
|
||||
this.hand = hand;
|
||||
this.consumeBlock = true;
|
||||
@ -52,6 +56,7 @@ public class PlayerBlockPlaceEvent extends PlayerEvent implements CancellableEve
|
||||
*/
|
||||
public void setCustomBlock(short customBlockId) {
|
||||
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
||||
Check.notNull(customBlock, "The custom block with the id '" + customBlockId + "' does not exist");
|
||||
setCustomBlock(customBlock);
|
||||
}
|
||||
|
||||
@ -62,6 +67,7 @@ public class PlayerBlockPlaceEvent extends PlayerEvent implements CancellableEve
|
||||
*/
|
||||
public void setCustomBlock(@NotNull String customBlockId) {
|
||||
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
||||
Check.notNull(customBlock, "The custom block with the identifier '" + customBlockId + "' does not exist");
|
||||
setCustomBlock(customBlock);
|
||||
}
|
||||
|
||||
@ -104,6 +110,25 @@ public class PlayerBlockPlaceEvent extends PlayerEvent implements CancellableEve
|
||||
this.blockStateId = blockStateId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data that the (not placed yet) block should have
|
||||
*
|
||||
* @return the block data, null if not any
|
||||
*/
|
||||
@Nullable
|
||||
public Data getBlockData() {
|
||||
return blockData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data of the block to place.
|
||||
*
|
||||
* @param blockData the block data, null if not any
|
||||
*/
|
||||
public void setBlockData(@Nullable Data blockData) {
|
||||
this.blockData = blockData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block position.
|
||||
*
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.minestom.server.event.player;
|
||||
|
||||
import net.minestom.server.chat.RichMessage;
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.CancellableEvent;
|
||||
import net.minestom.server.event.PlayerEvent;
|
||||
@ -19,7 +19,7 @@ public class PlayerChatEvent extends PlayerEvent implements CancellableEvent {
|
||||
|
||||
private final Collection<Player> recipients;
|
||||
private String message;
|
||||
private Function<PlayerChatEvent, RichMessage> chatFormat;
|
||||
private Function<PlayerChatEvent, JsonMessage> chatFormat;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
@ -34,7 +34,7 @@ public class PlayerChatEvent extends PlayerEvent implements CancellableEvent {
|
||||
*
|
||||
* @param chatFormat the custom chat format, null to use the default one
|
||||
*/
|
||||
public void setChatFormat(@Nullable Function<PlayerChatEvent, RichMessage> chatFormat) {
|
||||
public void setChatFormat(@Nullable Function<PlayerChatEvent, JsonMessage> chatFormat) {
|
||||
this.chatFormat = chatFormat;
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ public class PlayerChatEvent extends PlayerEvent implements CancellableEvent {
|
||||
* @return the chat format which will be used, null if this is the default one
|
||||
*/
|
||||
@Nullable
|
||||
public Function<PlayerChatEvent, RichMessage> getChatFormatFunction() {
|
||||
public Function<PlayerChatEvent, JsonMessage> getChatFormatFunction() {
|
||||
return chatFormat;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,61 @@
|
||||
package net.minestom.server.event.player;
|
||||
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Called when a player die in {@link Player#kill()}.
|
||||
*/
|
||||
public class PlayerDeathEvent extends PlayerEvent {
|
||||
|
||||
private JsonMessage deathText;
|
||||
private JsonMessage chatMessage;
|
||||
|
||||
public PlayerDeathEvent(@NotNull Player player, JsonMessage deathText, JsonMessage chatMessage) {
|
||||
super(player);
|
||||
this.deathText = deathText;
|
||||
this.chatMessage = chatMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text displayed in the death screen.
|
||||
*
|
||||
* @return the death text, can be null
|
||||
*/
|
||||
@Nullable
|
||||
public JsonMessage getDeathText() {
|
||||
return deathText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the text displayed in the death screen.
|
||||
*
|
||||
* @param deathText the death text to display, null to remove
|
||||
*/
|
||||
public void setDeathText(@Nullable JsonMessage deathText) {
|
||||
this.deathText = deathText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message sent to chat.
|
||||
*
|
||||
* @return the death chat message
|
||||
*/
|
||||
@Nullable
|
||||
public JsonMessage getChatMessage() {
|
||||
return chatMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the text sent in chat
|
||||
*
|
||||
* @param chatMessage the death message to send, null to remove
|
||||
*/
|
||||
public void setChatMessage(@Nullable JsonMessage chatMessage) {
|
||||
this.chatMessage = chatMessage;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user