Merge branch 'master' into area

This commit is contained in:
themode 2021-11-09 02:37:40 +01:00
commit 9a390aadf1
298 changed files with 5843 additions and 4894 deletions

4
.github/README.md vendored
View File

@ -4,7 +4,7 @@
[![license](https://img.shields.io/github/license/Minestom/Minestom?style=for-the-badge&color=b2204c)](../LICENSE)
[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=for-the-badge)](https://github.com/RichardLitt/standard-readme)
[![javadocs](https://img.shields.io/badge/documentation-javadocs-4d7a97?style=for-the-badge)](https://minestom.github.io/Minestom/)
[![wiki](https://img.shields.io/badge/documentation-wiki-74aad6?style=for-the-badge)](https://wiki.minestom.com/)
[![wiki](https://img.shields.io/badge/documentation-wiki-74aad6?style=for-the-badge)](https://wiki.minestom.net/)
[![discord-banner](https://img.shields.io/discord/706185253441634317?label=discord&style=for-the-badge&color=7289da)](https://discord.gg/pkFRvqB)
Minestom is a complete rewrite of Minecraft server software, open-source and without any code from Mojang.
@ -33,7 +33,7 @@ This means you need to add Minestom as a dependency, add your code and compile b
# Usage
An example of how to use the Minestom library is available [here](/src/test/java/demo).
Alternatively you can check the official [wiki](https://wiki.minestom.com/) or the [javadocs](https://minestom.github.io/Minestom/).
Alternatively you can check the official [wiki](https://wiki.minestom.net/) or the [javadocs](https://minestom.github.io/Minestom/).
# Why Minestom?
Minecraft has evolved a lot since its release, most of the servers today do not take advantage of vanilla features and even have to struggle because of them. Our target audience is those who want to make a completely different server compared to vanilla Minecraft such as survival or creative building.

View File

@ -11,10 +11,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 11
java-version: 17
- name: Run java checkstyle
uses: nikitasavinov/checkstyle-action@0.3.1
with:

View File

@ -11,10 +11,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 11
java-version: 17
- name: Build javadoc
run: gradle javadoc

View File

@ -13,11 +13,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 16
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 16
distribution: 'zulu'
java-version: 17
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Setup gradle cache

View File

@ -0,0 +1,12 @@
name: Trigger Jitpack Build
on:
push:
branches: [ master ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Trigger Jitpack Build
run: curl "https://jitpack.io/com/github/Minestom/Minestom/${GITHUB_SHA:0:10}/build.log"

View File

@ -3,14 +3,14 @@ import org.gradle.internal.os.OperatingSystem
plugins {
id 'java-library'
id 'maven-publish'
id 'org.jetbrains.kotlin.jvm' version '1.5.0'
id 'checkstyle'
id 'org.jetbrains.kotlin.jvm' version '1.5.31'
//id 'checkstyle'
}
group 'net.minestom.server'
version '1.0'
sourceCompatibility = 11
sourceCompatibility = 17
project.ext.lwjglVersion = "3.2.3"
switch (OperatingSystem.current()) {
@ -55,10 +55,10 @@ allprojects {
}
}
checkstyle {
toolVersion "8.42"
configFile file("${projectDir}/minestom_checks.xml")
}
//checkstyle {
// toolVersion "8.42"
// configFile file("${projectDir}/minestom_checks.xml")
//}
}
sourceSets {
@ -100,17 +100,17 @@ tasks.withType(Zip).configureEach {
dependencies {
// Junit Testing Framework
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.7.2')
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.1')
// Only here to ensure J9 module support for extensions and our classloaders
testCompileOnly 'org.mockito:mockito-core:3.11.1'
testCompileOnly 'org.mockito:mockito-core:4.0.0'
// https://mvnrepository.com/artifact/it.unimi.dsi/fastutil
api 'it.unimi.dsi:fastutil:8.5.4'
api 'it.unimi.dsi:fastutil:8.5.6'
// https://mvnrepository.com/artifact/com.google.code.gson/gson
api 'com.google.code.gson:gson:2.8.7'
api 'com.google.code.gson:gson:2.8.9'
// Noise library for terrain generation
// https://jitpack.io/#Articdive/Jnoise
@ -126,10 +126,13 @@ dependencies {
// https://mvnrepository.com/artifact/org.jline/jline-terminal-jansi
implementation group: 'org.jline', name: 'jline-terminal-jansi', version: '3.20.0'
implementation 'com.github.ben-manes.caffeine:caffeine:3.0.3'
implementation 'com.github.ben-manes.caffeine:caffeine:3.0.4'
// https://mvnrepository.com/artifact/com.zaxxer/SparseBitSet
implementation group: 'com.zaxxer', name: 'SparseBitSet', version: '1.2'
// Guava 21.0+ required for Mixin
api 'com.google.guava:guava:30.1.1-jre'
api 'com.google.guava:guava:31.0.1-jre'
// Code modification
api "org.ow2.asm:asm:${asmVersion}"
@ -172,7 +175,7 @@ dependencies {
lwjglApi "org.lwjgl:lwjgl-opengles"
lwjglApi "org.lwjgl:lwjgl-glfw"
lwjglApi "org.lwjgl:lwjgl-glfw"
lwjglApi 'org.joml:joml:1.9.25'
lwjglApi 'org.joml:joml:1.10.2'
lwjglRuntimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengles::$lwjglNatives"

View File

@ -7,7 +7,7 @@ plugins {
group 'net.minestom.server'
version '1.0'
sourceCompatibility = 1.11
sourceCompatibility = 17
application {
mainClass.set("net.minestom.codegen.Generators")

View File

@ -17,15 +17,15 @@ public class Generators {
}
File outputFolder = new File(args[0]);
var generator = new CodeGenerator(outputFolder);
generator.generate(resource("blocks.json"), "net.minestom.server.instance.block", "Block", "BlockImpl", "BlockConstants");
generator.generate(resource("items.json"), "net.minestom.server.item", "Material", "MaterialImpl", "MaterialConstants");
generator.generate(resource("entities.json"), "net.minestom.server.entity", "EntityType", "EntityTypeImpl", "EntityTypeConstants");
generator.generate(resource("enchantments.json"), "net.minestom.server.item", "Enchantment", "EnchantmentImpl", "EnchantmentConstants");
generator.generate(resource("potion_effects.json"), "net.minestom.server.potion", "PotionEffect", "PotionEffectImpl", "PotionEffectConstants");
generator.generate(resource("potions.json"), "net.minestom.server.potion", "PotionType", "PotionTypeImpl", "PotionTypeConstants");
generator.generate(resource("particles.json"), "net.minestom.server.particle", "Particle", "ParticleImpl", "ParticleConstants");
generator.generate(resource("sounds.json"), "net.minestom.server.sound", "SoundEvent", "SoundEventImpl", "SoundEventConstants");
generator.generate(resource("custom_statistics.json"), "net.minestom.server.statistic", "StatisticType", "StatisticTypeImpl", "StatisticTypeConstants");
generator.generate(resource("blocks.json"), "net.minestom.server.instance.block", "Block", "BlockImpl", "Blocks");
generator.generate(resource("items.json"), "net.minestom.server.item", "Material", "MaterialImpl", "Materials");
generator.generate(resource("entities.json"), "net.minestom.server.entity", "EntityType", "EntityTypeImpl", "EntityTypes");
generator.generate(resource("enchantments.json"), "net.minestom.server.item", "Enchantment", "EnchantmentImpl", "Enchantments");
generator.generate(resource("potion_effects.json"), "net.minestom.server.potion", "PotionEffect", "PotionEffectImpl", "PotionEffects");
generator.generate(resource("potions.json"), "net.minestom.server.potion", "PotionType", "PotionTypeImpl", "PotionTypes");
generator.generate(resource("particles.json"), "net.minestom.server.particle", "Particle", "ParticleImpl", "Particles");
generator.generate(resource("sounds.json"), "net.minestom.server.sound", "SoundEvent", "SoundEventImpl", "SoundEvents");
generator.generate(resource("custom_statistics.json"), "net.minestom.server.statistic", "StatisticType", "StatisticTypeImpl", "StatisticTypes");
// Generate fluids
new FluidGenerator(resource("fluids.json"), outputFolder).generate();

View File

@ -1,8 +1,8 @@
# Update this version with every release. It is purely used for the code generator and data dependency.
mcVersion = 1.17
asmVersion=9.0
mixinVersion=0.8.1
asmVersion=9.2
mixinVersion=0.8.4
hephaistosVersion=v1.1.8
kotlinVersion=1.5.0
adventureVersion=4.8.1
kotlinVersion=1.5.31
adventureVersion=4.9.3

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-rc-2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

269
gradlew vendored Normal file → Executable file
View File

@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright ? 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,67 +17,101 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions <20>á$var<61>â, <20>á${var}<7D>â, <20>á${var:-default}<7D>â, <20>á${var+SET}<7D>â,
# <20>á${var#prefix}<7D>â, <20>á${var%suffix}<7D>â, and <20>á$( cmd )<29>â;
# * compound commands having a testable exit status, especially <20>ácase<73>â;
# * various built-in commands including <20>ácommand<6E>â, <20>áset<65>â, and <20>áulimit<69>â.
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${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='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -106,80 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# 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
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
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" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# 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"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@ -1,2 +1,5 @@
jdk:
- openjdk11
before_install:
- source "$HOME/.sdkman/bin/sdkman-init.sh"
- sdk update
- sdk install java 17-open
- sdk use java 17-open

View File

@ -4,7 +4,7 @@ package net.minestom.server.entity;
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface EntityTypeConstants {
interface EntityTypes {
EntityType AREA_EFFECT_CLOUD = EntityTypeImpl.get("minecraft:area_effect_cloud");
EntityType ARMOR_STAND = EntityTypeImpl.get("minecraft:armor_stand");

View File

@ -4,7 +4,7 @@ package net.minestom.server.instance.block;
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface BlockConstants {
interface Blocks {
Block AIR = BlockImpl.get("minecraft:air");
Block STONE = BlockImpl.get("minecraft:stone");

View File

@ -4,7 +4,7 @@ package net.minestom.server.item;
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface EnchantmentConstants {
interface Enchantments {
Enchantment PROTECTION = EnchantmentImpl.get("minecraft:protection");
Enchantment FIRE_PROTECTION = EnchantmentImpl.get("minecraft:fire_protection");

View File

@ -4,7 +4,7 @@ package net.minestom.server.item;
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface MaterialConstants {
interface Materials {
Material AIR = MaterialImpl.get("minecraft:air");
Material STONE = MaterialImpl.get("minecraft:stone");

View File

@ -4,7 +4,7 @@ package net.minestom.server.particle;
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface ParticleConstants {
interface Particles {
Particle AMBIENT_ENTITY_EFFECT = ParticleImpl.get("minecraft:ambient_entity_effect");
Particle ANGRY_VILLAGER = ParticleImpl.get("minecraft:angry_villager");

View File

@ -4,7 +4,7 @@ package net.minestom.server.potion;
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface PotionEffectConstants {
interface PotionEffects {
PotionEffect SPEED = PotionEffectImpl.get("minecraft:speed");
PotionEffect SLOWNESS = PotionEffectImpl.get("minecraft:slowness");

View File

@ -4,7 +4,7 @@ package net.minestom.server.potion;
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface PotionTypeConstants {
interface PotionTypes {
PotionType EMPTY = PotionTypeImpl.get("minecraft:empty");
PotionType WATER = PotionTypeImpl.get("minecraft:water");

View File

@ -4,7 +4,7 @@ package net.minestom.server.sound;
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface SoundEventConstants {
interface SoundEvents {
SoundEvent AMBIENT_CAVE = SoundEventImpl.get("minecraft:ambient.cave");
SoundEvent AMBIENT_BASALT_DELTAS_ADDITIONS = SoundEventImpl.get("minecraft:ambient.basalt_deltas.additions");

View File

@ -4,7 +4,7 @@ package net.minestom.server.statistic;
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface StatisticTypeConstants {
interface StatisticTypes {
StatisticType LEAVE_GAME = StatisticTypeImpl.get("minecraft:leave_game");
StatisticType PLAY_TIME = StatisticTypeImpl.get("minecraft:play_time");

View File

@ -4,6 +4,7 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.map.Framebuffer;
import net.minestom.server.map.MapColors;
import net.minestom.server.timer.Task;
import net.minestom.server.utils.thread.ThreadBindingExecutor;
import org.lwjgl.BufferUtils;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFWErrorCallback;
@ -27,6 +28,8 @@ public abstract class GLFWCapableBuffer {
private final ByteBuffer colorsBuffer;
private boolean onlyMapColors;
private static ThreadBindingExecutor threadBindingPool;
protected GLFWCapableBuffer(int width, int height) {
this(width, height, GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
}
@ -60,6 +63,12 @@ public abstract class GLFWCapableBuffer {
throw new RuntimeException("("+errcode+") Failed to create GLFW Window.");
}
}
synchronized(GLFWCapableBuffer.class) {
if(threadBindingPool == null) {
threadBindingPool = new ThreadBindingExecutor(MinecraftServer.THREAD_COUNT_SCHEDULER, MinecraftServer.THREAD_NAME_SCHEDULER);
}
}
}
public GLFWCapableBuffer unbindContextFromThread() {
@ -80,14 +89,17 @@ public abstract class GLFWCapableBuffer {
return MinecraftServer.getSchedulerManager()
.buildTask(new Runnable() {
private boolean first = true;
@Override
public void run() {
private final Runnable subAction = () -> {
if(first) {
changeRenderingThreadToCurrent();
first = false;
}
render(rendering);
};
@Override
public void run() {
threadBindingPool.execute(subAction);
}
})
.repeat(period)

View File

@ -6,14 +6,12 @@ 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.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.fluid.Fluid;
import net.minestom.server.gamedata.tags.TagManager;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.instance.block.BlockManager;
import net.minestom.server.instance.block.rule.BlockPlacementRule;
@ -23,7 +21,6 @@ import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.PacketProcessor;
import net.minestom.server.network.packet.server.play.PluginMessagePacket;
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
import net.minestom.server.network.packet.server.play.UpdateViewDistancePacket;
import net.minestom.server.network.socket.Server;
import net.minestom.server.ping.ResponseDataConsumer;
import net.minestom.server.recipe.RecipeManager;
@ -31,10 +28,10 @@ import net.minestom.server.scoreboard.TeamManager;
import net.minestom.server.storage.StorageLocation;
import net.minestom.server.storage.StorageManager;
import net.minestom.server.terminal.MinestomTerminal;
import net.minestom.server.thread.MinestomThreadPool;
import net.minestom.server.timer.SchedulerManager;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.thread.MinestomThread;
import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.Difficulty;
import net.minestom.server.world.DimensionTypeManager;
@ -118,10 +115,10 @@ public final class MinecraftServer {
// Data
private static boolean initialized;
private static boolean started;
private static boolean stopping;
private static volatile boolean stopping;
private static int chunkViewDistance = 8;
private static int entityViewDistance = 5;
private static int chunkViewDistance = Integer.getInteger("minestom.chunk-view-distance", 8);
private static int entityViewDistance = Integer.getInteger("minestom.entity-view-distance", 5);
private static int compressionThreshold = 256;
private static boolean groupedPacket = true;
private static boolean terminalEnabled = System.getProperty("minestom.terminal.disabled") == null;
@ -337,6 +334,7 @@ public final class MinecraftServer {
*
* @return the data manager
*/
@Deprecated
public static DataManager getDataManager() {
checkInitStatus(dataManager);
return dataManager;
@ -446,20 +444,14 @@ public final class MinecraftServer {
*
* @param chunkViewDistance the new chunk view distance
* @throws IllegalArgumentException if {@code chunkViewDistance} is not between 2 and 32
* @deprecated should instead be defined with a java property
*/
@Deprecated
public static void setChunkViewDistance(int chunkViewDistance) {
Check.stateCondition(started, "You cannot change the chunk view distance after the server has been started.");
Check.argCondition(!MathUtils.isBetween(chunkViewDistance, 2, 32),
"The chunk view distance must be between 2 and 32");
MinecraftServer.chunkViewDistance = chunkViewDistance;
if (started) {
for (final Player player : connectionManager.getOnlinePlayers()) {
final Chunk playerChunk = player.getChunk();
if (playerChunk != null) {
player.getPlayerConnection().sendPacket(new UpdateViewDistancePacket(player.getChunkRange()));
player.refreshVisibleChunks(playerChunk);
}
}
}
}
/**
@ -476,19 +468,14 @@ public final class MinecraftServer {
*
* @param entityViewDistance the new entity view distance
* @throws IllegalArgumentException if {@code entityViewDistance} is not between 0 and 32
* @deprecated should instead be defined with a java property
*/
@Deprecated
public static void setEntityViewDistance(int entityViewDistance) {
Check.stateCondition(started, "You cannot change the entity view distance after the server has been started.");
Check.argCondition(!MathUtils.isBetween(entityViewDistance, 0, 32),
"The entity view distance must be between 0 and 32");
MinecraftServer.entityViewDistance = entityViewDistance;
if (started) {
for (final Player player : connectionManager.getOnlinePlayers()) {
final Chunk playerChunk = player.getChunk();
if (playerChunk != null) {
player.refreshVisibleEntities(playerChunk);
}
}
}
}
/**
@ -673,9 +660,9 @@ public final class MinecraftServer {
updateManager.start();
// Init & start the TCP server
// Init server
try {
server.start(new InetSocketAddress(address, port));
server.init(new InetSocketAddress(address, port));
} catch (IOException e) {
e.printStackTrace();
}
@ -695,17 +682,24 @@ public final class MinecraftServer {
LOGGER.warn("Extension loadOnStartup option is set to false, extensions are therefore neither loaded or initialized.");
}
// Start server
server.start();
LOGGER.info("Minestom server started successfully.");
if (terminalEnabled) {
MinestomTerminal.start();
}
// Stop the server on SIGINT
Runtime.getRuntime().addShutdownHook(new Thread(MinecraftServer::stopCleanly));
}
/**
* Stops this server properly (saves if needed, kicking players, etc.)
*/
public static void stopCleanly() {
if (stopping) return;
stopping = true;
LOGGER.info("Stopping Minestom server.");
extensionManager.unloadAllExtensions();
@ -719,7 +713,7 @@ public final class MinecraftServer {
LOGGER.info("Shutting down all thread pools.");
benchmarkManager.disable();
MinestomTerminal.stop();
MinestomThread.shutdownAll();
MinestomThreadPool.shutdownAll();
LOGGER.info("Minestom server stopped successfully.");
}

View File

@ -1,14 +1,15 @@
package net.minestom.server;
import net.minestom.server.acquirable.Acquirable;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.monitoring.TickMonitor;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.thread.SingleThreadProvider;
import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.thread.MinestomThread;
import net.minestom.server.thread.ThreadDispatcher;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.async.AsyncUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -22,14 +23,13 @@ import java.util.function.LongConsumer;
/**
* Manager responsible for the server ticks.
* <p>
* The {@link ThreadProvider} manages the multi-thread aspect of chunk ticks.
* The {@link ThreadDispatcher} manages the multi-thread aspect of chunk ticks.
*/
public final class UpdateManager {
private volatile boolean stopRequested;
// TODO make configurable
private ThreadProvider threadProvider = new SingleThreadProvider();
private final ThreadDispatcher threadDispatcher = ThreadDispatcher.singleThread();
private final Queue<LongConsumer> tickStartCallbacks = new ConcurrentLinkedQueue<>();
private final Queue<LongConsumer> tickEndCallbacks = new ConcurrentLinkedQueue<>();
@ -45,144 +45,60 @@ public final class UpdateManager {
* Starts the server loop in the update thread.
*/
void start() {
final ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
new Thread(() -> {
while (!stopRequested) {
try {
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);
// Monitoring
if (!tickMonitors.isEmpty()) {
final double acquisitionTimeMs = Acquirable.getAcquiringTime() / 1e6D;
final double tickTimeMs = tickTime / 1e6D;
final TickMonitor tickMonitor = new TickMonitor(tickTimeMs, acquisitionTimeMs);
this.tickMonitors.forEach(consumer -> consumer.accept(tickMonitor));
Acquirable.resetAcquiringTime();
}
// Flush all waiting packets
for (Player player : connectionManager.getOnlinePlayers()) {
player.getPlayerConnection().flush();
}
// Disable thread until next tick
LockSupport.parkNanos((long) ((MinecraftServer.TICK_MS * 1e6) - tickTime));
} catch (Exception e) {
MinecraftServer.getExceptionManager().handleException(e);
}
}
this.threadProvider.shutdown();
}, MinecraftServer.THREAD_NAME_TICK_SCHEDULER).start();
new TickSchedulerThread().start();
}
/**
* Executes a server tick and returns only once all the futures are completed.
*
* @param tickStart the time of the tick in milliseconds
*/
private void serverTick(long tickStart) {
// Tick all instances
MinecraftServer.getInstanceManager().getInstances().forEach(instance -> {
try {
instance.tick(tickStart);
} catch (Exception e) {
MinecraftServer.getExceptionManager().handleException(e);
}
});
// Tick all chunks (and entities inside)
this.threadProvider.updateAndAwait(tickStart);
// Clear removed entities & update threads
long tickTime = System.currentTimeMillis() - tickStart;
this.threadProvider.refreshThreads(tickTime);
}
/**
* Used to execute tick-related callbacks.
*
* @param callbacks the callbacks to execute
* @param value the value to give to the consumers
*/
private void doTickCallback(Queue<LongConsumer> callbacks, long value) {
if (!callbacks.isEmpty()) {
LongConsumer callback;
while ((callback = callbacks.poll()) != null) {
callback.accept(value);
}
}
}
/**
* Gets the current {@link ThreadProvider}.
* Gets the current {@link ThreadDispatcher}.
*
* @return the current thread provider
*/
public @NotNull ThreadProvider getThreadProvider() {
return threadProvider;
public @NotNull ThreadDispatcher getThreadProvider() {
return threadDispatcher;
}
/**
* Signals the {@link ThreadProvider} that an instance has been created.
* Signals the {@link ThreadDispatcher} that an instance has been created.
* <p>
* WARNING: should be automatically done by the {@link InstanceManager}.
*
* @param instance the instance
*/
public void signalInstanceCreate(Instance instance) {
this.threadProvider.onInstanceCreate(instance);
this.threadDispatcher.onInstanceCreate(instance);
}
/**
* Signals the {@link ThreadProvider} that an instance has been deleted.
* Signals the {@link ThreadDispatcher} that an instance has been deleted.
* <p>
* WARNING: should be automatically done by the {@link InstanceManager}.
*
* @param instance the instance
*/
public void signalInstanceDelete(Instance instance) {
this.threadProvider.onInstanceDelete(instance);
this.threadDispatcher.onInstanceDelete(instance);
}
/**
* Signals the {@link ThreadProvider} that a chunk has been loaded.
* Signals the {@link ThreadDispatcher} that a chunk has been loaded.
* <p>
* WARNING: should be automatically done by the {@link Instance} implementation.
*
* @param chunk the loaded chunk
*/
public void signalChunkLoad(@NotNull Chunk chunk) {
this.threadProvider.onChunkLoad(chunk);
this.threadDispatcher.onChunkLoad(chunk);
}
/**
* Signals the {@link ThreadProvider} that a chunk has been unloaded.
* Signals the {@link ThreadDispatcher} that a chunk has been unloaded.
* <p>
* WARNING: should be automatically done by the {@link Instance} implementation.
*
* @param chunk the unloaded chunk
*/
public void signalChunkUnload(@NotNull Chunk chunk) {
this.threadProvider.onChunkUnload(chunk);
this.threadDispatcher.onChunkUnload(chunk);
}
/**
@ -239,4 +155,94 @@ public final class UpdateManager {
public void stop() {
this.stopRequested = true;
}
private final class TickSchedulerThread extends MinestomThread {
private final ThreadDispatcher threadDispatcher = UpdateManager.this.threadDispatcher;
TickSchedulerThread() {
super(MinecraftServer.THREAD_NAME_TICK_SCHEDULER);
}
@Override
public void run() {
final ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
while (!stopRequested) {
try {
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);
// Flush all waiting packets
PacketUtils.flush();
AsyncUtils.runAsync(() -> MinecraftServer.getConnectionManager()
.getOnlinePlayers()
.parallelStream()
.forEach(player -> player.getPlayerConnection().flush()));
// the time that the tick took in nanoseconds
final long tickTime = System.nanoTime() - currentTime;
// Tick end callbacks
doTickCallback(tickEndCallbacks, tickTime);
// Monitoring
if (!tickMonitors.isEmpty()) {
final double acquisitionTimeMs = Acquirable.getAcquiringTime() / 1e6D;
final double tickTimeMs = tickTime / 1e6D;
final TickMonitor tickMonitor = new TickMonitor(tickTimeMs, acquisitionTimeMs);
for (Consumer<TickMonitor> consumer : tickMonitors) {
consumer.accept(tickMonitor);
}
Acquirable.resetAcquiringTime();
}
// Disable thread until next tick
LockSupport.parkNanos((long) ((MinecraftServer.TICK_MS * 1e6) - tickTime));
} catch (Exception e) {
MinecraftServer.getExceptionManager().handleException(e);
}
}
this.threadDispatcher.shutdown();
}
/**
* Executes a server tick and returns only once all the futures are completed.
*
* @param tickStart the time of the tick in milliseconds
*/
private void serverTick(long tickStart) {
// Tick all instances
for (Instance instance : MinecraftServer.getInstanceManager().getInstances()) {
try {
instance.tick(tickStart);
} catch (Exception e) {
MinecraftServer.getExceptionManager().handleException(e);
}
}
// Tick all chunks (and entities inside)
this.threadDispatcher.updateAndAwait(tickStart);
// Clear removed entities & update threads
final long tickTime = System.currentTimeMillis() - tickStart;
this.threadDispatcher.refreshThreads(tickTime);
}
private void doTickCallback(Queue<LongConsumer> callbacks, long value) {
LongConsumer callback;
while ((callback = callbacks.poll()) != null) {
callback.accept(value);
}
}
}
}

View File

@ -3,8 +3,10 @@ package net.minestom.server;
import net.kyori.adventure.audience.Audience;
import net.minestom.server.adventure.audience.PacketGroupingAudience;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.FramedPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
@ -35,8 +37,7 @@ public interface Viewable {
*
* @return A Set containing all the element's viewers
*/
@NotNull
Set<Player> getViewers();
@NotNull Set<@NotNull Player> getViewers();
/**
* Gets if a player is seeing this viewable object.
@ -60,6 +61,13 @@ public interface Viewable {
PacketUtils.sendGroupedPacket(getViewers(), packet);
}
@ApiStatus.Experimental
default void sendPacketToViewers(@NotNull FramedPacket framedPacket) {
for (Player viewer : getViewers()) {
viewer.sendPacket(framedPacket);
}
}
/**
* Sends multiple packets to all viewers.
* <p>
@ -85,6 +93,11 @@ public interface Viewable {
sendPacketToViewers(packet);
}
@ApiStatus.Experimental
default void sendPacketToViewersAndSelf(@NotNull FramedPacket framedPacket) {
sendPacketToViewers(framedPacket);
}
/**
* Gets the result of {@link #getViewers()} as an Adventure Audience.
*

View File

@ -1,14 +1,12 @@
package net.minestom.server.acquirable;
import net.minestom.server.entity.Entity;
import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.thread.ThreadDispatcher;
import net.minestom.server.thread.TickThread;
import net.minestom.server.utils.async.AsyncUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;
@ -26,19 +24,12 @@ public interface Acquirable<T> {
* @return the entities ticked in the current thread
*/
static @NotNull Stream<@NotNull Entity> currentEntities() {
return AcquirableImpl.ENTRIES.get().stream()
.flatMap(chunkEntry -> chunkEntry.getEntities().stream());
}
/**
* Mostly for internal use, external calls are unrecommended as they could lead
* to unexpected behavior.
*
* @param entries the new chunk entries
*/
@ApiStatus.Internal
static void refreshEntries(@NotNull Collection<ThreadProvider.ChunkEntry> entries) {
AcquirableImpl.ENTRIES.set(entries);
final Thread currentThread = Thread.currentThread();
if (currentThread instanceof TickThread) {
return ((TickThread) currentThread).entries().stream()
.flatMap(chunkEntry -> chunkEntry.entities().stream());
}
return Stream.empty();
}
/**
@ -85,8 +76,7 @@ public interface Acquirable<T> {
* @see #sync(Consumer) for auto-closeable capability
*/
default @NotNull Acquired<T> lock() {
var optional = local();
return optional.map(Acquired::local).orElseGet(() -> Acquired.locked(this));
return new Acquired<>(unwrap(), getHandler().getTickThread());
}
/**
@ -100,14 +90,19 @@ public interface Acquirable<T> {
* {@link Optional#empty()} otherwise
*/
default @NotNull Optional<T> local() {
final Thread currentThread = Thread.currentThread();
final TickThread tickThread = getHandler().getTickThread();
if (Objects.equals(currentThread, tickThread)) {
return Optional.of(unwrap());
}
if (isLocal()) return Optional.of(unwrap());
return Optional.empty();
}
/**
* Gets if the acquirable element is local to this thread
*
* @return true if the element is linked to the current thread
*/
default boolean isLocal() {
return Thread.currentThread() == getHandler().getTickThread();
}
/**
* Locks the acquirable element, execute {@code consumer} synchronously and unlock the thread.
* <p>
@ -117,7 +112,7 @@ public interface Acquirable<T> {
* @see #async(Consumer)
*/
default void sync(@NotNull Consumer<T> consumer) {
var acquired = lock();
Acquired<T> acquired = lock();
consumer.accept(acquired.get());
acquired.unlock();
}
@ -153,22 +148,21 @@ public interface Acquirable<T> {
@ApiStatus.Internal
@NotNull Handler getHandler();
class Handler {
final class Handler {
private volatile ThreadDispatcher.ChunkEntry chunkEntry;
private volatile ThreadProvider.ChunkEntry chunkEntry;
public ThreadProvider.ChunkEntry getChunkEntry() {
public ThreadDispatcher.ChunkEntry getChunkEntry() {
return chunkEntry;
}
@ApiStatus.Internal
public void refreshChunkEntry(@NotNull ThreadProvider.ChunkEntry chunkEntry) {
public void refreshChunkEntry(@NotNull ThreadDispatcher.ChunkEntry chunkEntry) {
this.chunkEntry = chunkEntry;
}
public TickThread getTickThread() {
return chunkEntry != null ? chunkEntry.getThread() : null;
final ThreadDispatcher.ChunkEntry entry = this.chunkEntry;
return entry != null ? entry.thread() : null;
}
}
}

View File

@ -1,19 +1,14 @@
package net.minestom.server.acquirable;
import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.thread.TickThread;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
class AcquirableImpl<T> implements Acquirable<T> {
protected static final ThreadLocal<Collection<ThreadProvider.ChunkEntry>> ENTRIES = ThreadLocal.withInitial(Collections::emptySet);
protected static final AtomicLong WAIT_COUNTER_NANO = new AtomicLong();
final class AcquirableImpl<T> implements Acquirable<T> {
static final AtomicLong WAIT_COUNTER_NANO = new AtomicLong();
/**
* Global lock used for synchronization.
@ -38,41 +33,37 @@ class AcquirableImpl<T> implements Acquirable<T> {
return handler;
}
protected static @Nullable ReentrantLock enter(@Nullable Thread currentThread, @Nullable TickThread elementThread) {
// Monitoring
long time = System.nanoTime();
ReentrantLock currentLock;
{
final TickThread current = currentThread instanceof TickThread ?
(TickThread) currentThread : null;
currentLock = current != null && current.getLock().isHeldByCurrentThread() ?
current.getLock() : null;
}
if (currentLock != null)
currentLock.unlock();
GLOBAL_LOCK.lock();
if (currentLock != null)
currentLock.lock();
final var lock = elementThread != null ? elementThread.getLock() : null;
final boolean acquired = lock == null || lock.isHeldByCurrentThread();
if (!acquired) {
lock.lock();
}
static @Nullable ReentrantLock enter(@NotNull Thread currentThread, @Nullable TickThread elementThread) {
if (elementThread == null) return null;
if (currentThread == elementThread) return null;
final ReentrantLock currentLock = currentThread instanceof TickThread ? ((TickThread) currentThread).lock() : null;
final ReentrantLock targetLock = elementThread.lock();
if (targetLock.isHeldByCurrentThread()) return null;
// Monitoring
AcquirableImpl.WAIT_COUNTER_NANO.addAndGet(System.nanoTime() - time);
final long time = System.nanoTime();
return !acquired ? lock : null;
// Enter the target thread
// TODO reduce global lock scope
if (currentLock != null) {
while (!GLOBAL_LOCK.tryLock()) {
currentLock.unlock();
currentLock.lock();
}
} else {
GLOBAL_LOCK.lock();
}
targetLock.lock();
// Monitoring
WAIT_COUNTER_NANO.addAndGet(System.nanoTime() - time);
return targetLock;
}
protected static void leave(@Nullable ReentrantLock lock) {
static void leave(@Nullable ReentrantLock lock) {
if (lock != null) {
lock.unlock();
GLOBAL_LOCK.unlock();
}
GLOBAL_LOCK.unlock();
}
}

View File

@ -6,47 +6,39 @@ import org.jetbrains.annotations.NotNull;
import java.util.concurrent.locks.ReentrantLock;
public class Acquired<T> {
/**
* Represents an object that has been safely acquired and can be freed again.
* <p>
* This class should not be shared, and it is recommended to call {@link #unlock()}
* once the acquisition goal has been fulfilled to limit blocking time.
*
* @param <T> the type of the acquired object
*/
public final class Acquired<T> {
private final T value;
private final boolean locked;
private final Thread owner;
private final ReentrantLock lock;
private boolean unlocked;
protected static <T> Acquired<T> local(@NotNull T value) {
return new Acquired<>(value, false, null, null);
}
protected static <T> Acquired<T> locked(@NotNull Acquirable<T> acquirable) {
final Thread currentThread = Thread.currentThread();
final TickThread tickThread = acquirable.getHandler().getTickThread();
return new Acquired<>(acquirable.unwrap(), true, currentThread, tickThread);
}
private Acquired(@NotNull T value,
boolean locked, Thread currentThread, TickThread tickThread) {
Acquired(T value, TickThread tickThread) {
this.value = value;
this.locked = locked;
this.lock = locked ? AcquirableImpl.enter(currentThread, tickThread) : null;
this.owner = Thread.currentThread();
this.lock = AcquirableImpl.enter(owner, tickThread);
}
public @NotNull T get() {
checkLock();
safeCheck();
return value;
}
public void unlock() {
checkLock();
safeCheck();
this.unlocked = true;
if (!locked)
return;
AcquirableImpl.leave(lock);
}
private void checkLock() {
private void safeCheck() {
Check.stateCondition(Thread.currentThread() != owner, "Acquired object is owned by the thread {0}", owner);
Check.stateCondition(unlocked, "The acquired element has already been unlocked!");
}
}

View File

@ -6,14 +6,15 @@ import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.sound.SoundStop;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.title.Title;
import net.kyori.adventure.title.TitlePart;
import net.minestom.server.entity.Entity;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.play.EntitySoundEffectPacket;
import net.minestom.server.network.packet.server.play.NamedSoundEffectPacket;
import net.minestom.server.network.packet.server.play.SoundEffectPacket;
import net.minestom.server.network.packet.server.play.StopSoundPacket;
import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.utils.TickUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
@ -141,10 +142,9 @@ public class AdventurePacketConvertor {
public static @NotNull ServerPacket createSoundPacket(@NotNull Sound sound, Sound.@NotNull Emitter emitter) {
if (emitter == Sound.Emitter.self())
throw new IllegalArgumentException("you must replace instances of Emitter.self() before calling this method");
if (!(emitter instanceof Entity))
if (!(emitter instanceof Entity entity))
throw new IllegalArgumentException("you can only call this method with entities");
final Entity entity = (Entity) emitter;
final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString());
if (minestomSound != null) {
@ -206,4 +206,28 @@ public class AdventurePacketConvertor {
return packet;
}
/**
* Creates one of the three title packets from a title part and a value.
*
* @param part the part
* @param value the value
* @param <T> the type of the part
* @return the title packet
*/
public static <T> @NotNull ServerPacket createTitlePartPacket(@NotNull TitlePart<T> part, @NotNull T value) {
if (part == TitlePart.TITLE) {
return new SetTitleTextPacket((Component) value);
} else if (part == TitlePart.SUBTITLE) {
return new SetTitleSubTitlePacket((Component) value);
} else if (part == TitlePart.TIMES) {
Title.Times times = (Title.Times) value;
return new SetTitleTimePacket(
TickUtils.fromDuration(times.fadeIn(), TickUtils.CLIENT_TICK_MS),
TickUtils.fromDuration(times.stay(), TickUtils.CLIENT_TICK_MS),
TickUtils.fromDuration(times.fadeOut(), TickUtils.CLIENT_TICK_MS));
} else {
throw new IllegalArgumentException("Unknown TitlePart " + part);
}
}
}

View File

@ -1,203 +0,0 @@
package net.minestom.server.adventure;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.translation.Translator;
import net.minestom.server.utils.ComponentUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
/**
* Manager class for handling Adventure serialization. By default AdventureSerializer will simply
* serialize components to Strings using {@link GsonComponentSerializer}. However, AdventureSerializer
* class can be used to change the way text is serialized. For example, a pre-JSON
* implementation of Minestom could change AdventureSerializer to the plain component serializer.
* <br><br>
* This manager also performs translation on all messages and the {@code serialize}
* method should be used when converting {@link Component}s into strings. This allows for
* messages with {@link TranslatableComponent} to be automatically translated into the locale
* of specific players, or other elements which implement {@link Localizable}. To add your
* own translations, use {@link GlobalTranslator#addSource(Translator)} with a
* {@link TranslationRegistry} or your own implementation of {@link Translator}.
*
* @deprecated Use {@link MinestomAdventure}
*/
@Deprecated(forRemoval = true)
public class AdventureSerializer {
/**
* If components should be automatically translated in outgoing packets.
* @deprecated Use {@link MinestomAdventure#AUTOMATIC_COMPONENT_TRANSLATION}
*/
@Deprecated(forRemoval = true)
public static final boolean AUTOMATIC_COMPONENT_TRANSLATION = MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION;
private static Function<Component, String> serializer = component -> GsonComponentSerializer.gson().serialize(component);
private AdventureSerializer() {}
/**
* Gets the root serializer that is used to convert components into strings.
*
* @return the serializer
* @deprecated The serializer is no longer in use, use the adventure-provided serializers
*/
@Deprecated(forRemoval = true)
public static @NotNull Function<Component, String> getSerializer() {
return AdventureSerializer.serializer;
}
/**
* Sets the root serializer that is used to convert components into strings.
*
* @param serializer the serializer
* @deprecated The serializer is no longer in use
*/
@Deprecated
public static void setSerializer(@NotNull Function<Component, String> serializer) {
AdventureSerializer.serializer = serializer;
}
/**
* Gets the default locale used to translate {@link TranslatableComponent} if, when
* {@link #translate(Component, Localizable)} is called with a localizable that
* does not have a locale.
*
* @return the default locale
* @deprecated Use {@link MinestomAdventure#getDefaultLocale()}
*/
@Deprecated(forRemoval = true)
public static @NotNull Locale getDefaultLocale() {
return MinestomAdventure.getDefaultLocale();
}
/**
* Sets the default locale used to translate {@link TranslatableComponent} if, when
* {@link #translate(Component, Localizable)} is called with a localizable that
* does not have a locale.
*
* @param defaultLocale the new default locale, or {@code null} to return to the default
* @deprecated Use {@link MinestomAdventure#setDefaultLocale(Locale)}}
*/
@Deprecated(forRemoval = true)
public static void setDefaultLocale(@Nullable Locale defaultLocale) {
MinestomAdventure.setDefaultLocale(defaultLocale);
}
/**
* Gets the global translator object used by AdventureSerializer manager. This is just shorthand for
* {@link GlobalTranslator#get()}.
*
* @return the global translator
* @deprecated Use {@link GlobalTranslator#get()}
*/
@Deprecated(forRemoval = true)
public static @NotNull GlobalTranslator getTranslator() {
return GlobalTranslator.get();
}
/**
* Prepares a component for serialization. This runs the component through the
* translator for the localizable's locale.
*
* @param component the component
* @param localizable the localizable
*
* @return the prepared component
* @deprecated Use {@link GlobalTranslator#translate(String, Locale)}
*/
@Deprecated(forRemoval = true)
public static @NotNull Component translate(@NotNull Component component, @NotNull Localizable localizable) {
return GlobalTranslator.renderer().render(component, Objects.requireNonNullElse(localizable.getLocale(), AdventureSerializer.getDefaultLocale()));
}
/**
* Prepares a component for serialization. This runs the component through the
* translator for the locale.
*
* @param component the component
* @param locale the locale
*
* @return the prepared component
* @deprecated Use {@link GlobalTranslator#translate(String, Locale)}
*/
@Deprecated(forRemoval = true)
public static @NotNull Component translate(@NotNull Component component, @NotNull Locale locale) {
return GlobalTranslator.renderer().render(component, locale);
}
/**
* Serializes a component into a string using {@link #getSerializer()}.
*
* @param component the component
*
* @return the serialized string
* @deprecated Use the Adventure serializers directly
*/
@Deprecated(forRemoval = true)
public static @NotNull String serialize(@NotNull Component component) {
return AdventureSerializer.serializer.apply(component);
}
/**
* Prepares and then serializes a component.
*
* @param component the component
* @param localizable the localisable
*
* @return the string
* @deprecated Use {@link GlobalTranslator#translate(String, Locale)} and the Adventure serializers
*/
@Deprecated(forRemoval = true)
public static String translateAndSerialize(@NotNull Component component, @NotNull Localizable localizable) {
return AdventureSerializer.translateAndSerialize(component, Objects.requireNonNullElse(localizable.getLocale(), AdventureSerializer.getDefaultLocale()));
}
/**
* Prepares and then serializes a component.
*
* @param component the component
* @param locale the locale
*
* @return the string
* @deprecated Use {@link GlobalTranslator#translate(String, Locale)} and the Adventure serializers
*/
@Deprecated(forRemoval = true)
public static String translateAndSerialize(@NotNull Component component, @NotNull Locale locale) {
return AdventureSerializer.serialize(AdventureSerializer.translate(component, locale));
}
/**
* Checks if a component can be translated server-side. This is done by running the
* component through the translator and seeing if the translated component is equal
* to the non translated component.
* @param component the component
* @return {@code true} if the component can be translated server-side,
* {@code false} otherwise
* @deprecated Use {@link ComponentUtils#isTranslatable(Component)}
*/
@Deprecated(forRemoval = true)
public static boolean isTranslatable(@NotNull Component component) {
return ComponentUtils.isTranslatable(component);
}
/**
* Checks if any of a series of components are translatable server-side.
* @param components the components
* @return {@code true} if any of the components can be translated server-side,
* {@code false} otherwise
* @deprecated Use {@link ComponentUtils#areAnyTranslatable(Collection)}
*/
@Deprecated(forRemoval = true)
public static boolean areAnyTranslatable(@NotNull Collection<Component> components) {
return ComponentUtils.areAnyTranslatable(components);
}
}

View File

@ -12,7 +12,6 @@ import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Holder of custom audiences.
@ -97,7 +96,7 @@ public class AudienceRegistry {
if (this.isEmpty()) {
return Collections.emptyList();
} else {
return this.registry.values().stream().flatMap(Collection::stream).collect(Collectors.toUnmodifiableList());
return this.registry.values().stream().flatMap(Collection::stream).toList();
}
}
@ -128,6 +127,6 @@ public class AudienceRegistry {
* @return the matching audience members
*/
public @NotNull Iterable<? extends Audience> of(@NotNull Predicate<Audience> filter) {
return this.registry.values().stream().flatMap(Collection::stream).filter(filter).collect(Collectors.toUnmodifiableList());
return this.registry.values().stream().flatMap(Collection::stream).filter(filter).toList();
}
}

View File

@ -8,14 +8,16 @@ import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.sound.SoundStop;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import net.kyori.adventure.title.TitlePart;
import net.minestom.server.MinecraftServer;
import net.minestom.server.adventure.AdventurePacketConvertor;
import net.minestom.server.entity.Player;
import net.minestom.server.message.ChatPosition;
import net.minestom.server.message.Messenger;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.network.packet.server.play.ActionBarPacket;
import net.minestom.server.network.packet.server.play.ClearTitlesPacket;
import net.minestom.server.network.packet.server.play.PlayerListHeaderAndFooterPacket;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.NotNull;
@ -47,6 +49,7 @@ public interface PacketGroupingAudience extends ForwardingAudience {
/**
* Broadcast a ServerPacket to all players of this audience
*
* @param packet the packet to broadcast
*/
default void sendGroupedPacket(ServerPacket packet) {
@ -69,9 +72,8 @@ public interface PacketGroupingAudience extends ForwardingAudience {
}
@Override
default void showTitle(@NotNull Title title) {
sendGroupedPacket(new SetTitleTextPacket(title.title()));
sendGroupedPacket(new SetTitleSubTitlePacket(title.subtitle()));
default <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value) {
sendGroupedPacket(AdventurePacketConvertor.createTitlePartPacket(part, value));
}
@Override
@ -116,8 +118,6 @@ public interface PacketGroupingAudience extends ForwardingAudience {
sendGroupedPacket(AdventurePacketConvertor.createSoundStopPacket(stop));
}
@Override
default @NotNull Iterable<? extends Audience> audiences() {
return this.getPlayers();

View File

@ -147,7 +147,7 @@ public class BossBarManager {
if (holders == null) {
return Collections.emptyList();
} else {
return holders.stream().map(holder -> holder.bar).collect(Collectors.toUnmodifiableList());
return holders.stream().map(holder -> holder.bar).toList();
}
}

View File

@ -1,7 +1,5 @@
package net.minestom.server.adventure.provider;
import java.util.UUID;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.Component;
@ -17,6 +15,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTException;
import java.io.IOException;
import java.util.Objects;
import java.util.UUID;
final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer {
static final NBTLegacyHoverEventSerializer INSTANCE = new NBTLegacyHoverEventSerializer();
@ -33,8 +32,8 @@ final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer
try {
// attempt the parse
final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw);
if (!(nbt instanceof NBTCompound)) throw new IOException("contents were not a compound");
final NBTCompound contents = (NBTCompound) nbt, tag = contents.getCompound(ITEM_TAG);
if (!(nbt instanceof NBTCompound contents)) throw new IOException("contents were not a compound");
final NBTCompound tag = contents.getCompound(ITEM_TAG);
// create the event
return HoverEvent.ShowItem.of(
@ -53,9 +52,7 @@ final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer
try {
final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw);
if (!(nbt instanceof NBTCompound)) throw new IOException("contents were not a compound");
final NBTCompound contents = (NBTCompound) nbt;
if (!(nbt instanceof NBTCompound contents)) throw new IOException("contents were not a compound");
return HoverEvent.ShowEntity.of(
Key.key(Objects.requireNonNullElse(contents.getString(ENTITY_TYPE), "")),

View File

@ -1,10 +1,8 @@
package net.minestom.server.attribute;
import net.minestom.server.utils.UniqueIdUtils;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
/**
* Represent an attribute modifier.
@ -24,7 +22,7 @@ public class AttributeModifier {
* @param operation the operation to apply this modifier with
*/
public AttributeModifier(@NotNull String name, float amount, @NotNull AttributeOperation operation) {
this(UniqueIdUtils.createRandomUUID(ThreadLocalRandom.current()), name, amount, operation);
this(UUID.randomUUID(), name, amount, operation);
}
/**

View File

@ -8,6 +8,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
/**
@ -18,13 +19,42 @@ public class BoundingBox {
private final Entity entity;
private final double x, y, z;
private volatile Pos lastPosition;
private List<Vec> bottomFace;
private List<Vec> topFace;
private List<Vec> leftFace;
private List<Vec> rightFace;
private List<Vec> frontFace;
private List<Vec> backFace;
private final CachedFace bottomFace = new CachedFace(() -> List.of(
new Vec(getMinX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMinY(), getMaxZ()),
new Vec(getMinX(), getMinY(), getMaxZ())
));
private final CachedFace topFace = new CachedFace(() -> List.of(
new Vec(getMinX(), getMaxY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMaxZ()),
new Vec(getMinX(), getMaxY(), getMaxZ())
));
private final CachedFace leftFace = new CachedFace(() -> List.of(
new Vec(getMinX(), getMinY(), getMinZ()),
new Vec(getMinX(), getMaxY(), getMinZ()),
new Vec(getMinX(), getMaxY(), getMaxZ()),
new Vec(getMinX(), getMinY(), getMaxZ())
));
private final CachedFace rightFace = new CachedFace(() -> List.of(
new Vec(getMaxX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMaxZ()),
new Vec(getMaxX(), getMinY(), getMaxZ())
));
private final CachedFace frontFace = new CachedFace(() -> List.of(
new Vec(getMinX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMinZ()),
new Vec(getMinX(), getMaxY(), getMinZ())
));
private final CachedFace backFace = new CachedFace(() -> List.of(
new Vec(getMinX(), getMinY(), getMaxZ()),
new Vec(getMaxX(), getMinY(), getMaxZ()),
new Vec(getMaxX(), getMaxY(), getMaxZ()),
new Vec(getMinX(), getMaxY(), getMaxZ())
));
/**
* Creates a {@link BoundingBox} linked to an {@link Entity} and with a specific size.
@ -74,20 +104,15 @@ public class BoundingBox {
public boolean intersectWithBlock(int blockX, int blockY, int blockZ) {
final double offsetX = 1;
final double maxX = (double) blockX + offsetX;
final boolean checkX = getMinX() < maxX && getMaxX() > (double) blockX;
if (!checkX)
return false;
if (!checkX) return false;
final double maxY = (double) blockY + 0.99999;
final boolean checkY = getMinY() < maxY && getMaxY() > (double) blockY;
if (!checkY)
return false;
if (!checkY) return false;
final double offsetZ = 1;
final double maxZ = (double) blockZ + offsetZ;
// Z check
return getMinZ() < maxZ && getMaxZ() > (double) blockZ;
}
@ -102,16 +127,106 @@ public class BoundingBox {
return intersectWithBlock(blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ());
}
/**
* Used to know if the bounding box intersects (contains) a point.
*
* @param x x-coord of a point
* @param y y-coord of a point
* @param z z-coord of a point
* @return true if the bounding box intersects (contains) with the point, false otherwise
*/
public boolean intersect(double x, double y, double z) {
return (x >= getMinX() && x <= getMaxX()) &&
(y >= getMinY() && y <= getMaxY()) &&
(z >= getMinZ() && z <= getMaxZ());
}
/**
* Used to know if the bounding box intersects (contains) a point.
*
* @param point the point to check
* @return true if the bounding box intersects (contains) with the point, false otherwise
*/
public boolean intersect(@NotNull Point point) {
return intersect(point.x(), point.y(), point.z());
}
/**
* Used to know if the bounding box intersects a line segment.
*
* @param x1 x-coord of first line segment point
* @param y1 y-coord of first line segment point
* @param z1 z-coord of first line segment point
* @param x2 x-coord of second line segment point
* @param y2 y-coord of second line segment point
* @param z2 z-coord of second line segment point
* @return true if the bounding box intersects with the line segment, false otherwise.
*/
public boolean intersect(double x1, double y1, double z1, double x2, double y2, double z2) {
// originally from http://www.3dkingdoms.com/weekly/weekly.php?a=3
double x3 = getMinX();
double x4 = getMaxX();
double y3 = getMinY();
double y4 = getMaxY();
double z3 = getMinZ();
double z4 = getMaxZ();
if (x1 > x3 && x1 < x4 && y1 > y3 && y1 < y4 && z1 > z3 && z1 < z4) {
return true;
}
if (x1 < x3 && x2 < x3 || x1 > x4 && x2 > x4 ||
y1 < y3 && y2 < y3 || y1 > y4 && y2 > y4 ||
z1 < z3 && z2 < z3 || z1 > z4 && z2 > z4) {
return false;
}
return isInsideBoxWithAxis(Axis.X, getSegmentIntersection(x1 - x3, x2 - x3, x1, y1, z1, x2, y2, z2)) ||
isInsideBoxWithAxis(Axis.X, getSegmentIntersection(x1 - x4, x2 - x4, x1, y1, z1, x2, y2, z2)) ||
isInsideBoxWithAxis(Axis.Y, getSegmentIntersection(y1 - y3, y2 - y3, x1, y1, z1, x2, y2, z2)) ||
isInsideBoxWithAxis(Axis.Y, getSegmentIntersection(y1 - y4, y2 - y4, x1, y1, z1, x2, y2, z2)) ||
isInsideBoxWithAxis(Axis.Z, getSegmentIntersection(z1 - z3, z2 - z3, x1, y1, z1, x2, y2, z2)) ||
isInsideBoxWithAxis(Axis.Z, getSegmentIntersection(z1 - z4, z2 - z4, x1, y1, z1, x2, y2, z2));
}
/**
* Used to know if the bounding box intersects a line segment.
*
* @param start first line segment point
* @param end second line segment point
* @return true if the bounding box intersects with the line segment, false otherwise.
*/
public boolean intersect(@NotNull Point start, @NotNull Point end) {
return intersect(
Math.min(start.x(), end.x()),
Math.min(start.y(), end.y()),
Math.min(start.z(), end.z()),
Math.max(start.x(), end.x()),
Math.max(start.y(), end.y()),
Math.max(start.z(), end.z())
);
}
private @Nullable Vec getSegmentIntersection(double dst1, double dst2, double x1, double y1, double z1, double x2, double y2, double z2) {
if (dst1 == dst2 || dst1 * dst2 >= 0D) return null;
final double delta = dst1 / (dst1 - dst2);
return new Vec(
x1 + (x2 - x1) * delta,
y1 + (y2 - y1) * delta,
z1 + (z2 - z1) * delta
);
}
private boolean isInsideBoxWithAxis(Axis axis, @Nullable Vec intersection) {
if (intersection == null) return false;
double x1 = getMinX();
double x2 = getMaxX();
double y1 = getMinY();
double y2 = getMaxY();
double z1 = getMinZ();
double z2 = getMaxZ();
return axis == Axis.X && intersection.z() > z1 && intersection.z() < z2 && intersection.y() > y1 && intersection.y() < y2 ||
axis == Axis.Y && intersection.z() > z1 && intersection.z() < z2 && intersection.x() > x1 && intersection.x() < x2 ||
axis == Axis.Z && intersection.x() > x1 && intersection.x() < x2 && intersection.y() > y1 && intersection.y() < y2;
}
/**
* Creates a new {@link BoundingBox} linked to the same {@link Entity} with expanded size.
*
@ -120,8 +235,7 @@ public class BoundingBox {
* @param z the Z offset
* @return a new {@link BoundingBox} expanded
*/
@NotNull
public BoundingBox expand(double x, double y, double z) {
public @NotNull BoundingBox expand(double x, double y, double z) {
return new BoundingBox(entity, this.x + x, this.y + y, this.z + z);
}
@ -133,8 +247,7 @@ public class BoundingBox {
* @param z the Z offset
* @return a new bounding box contracted
*/
@NotNull
public BoundingBox contract(double x, double y, double z) {
public @NotNull BoundingBox contract(double x, double y, double z) {
return new BoundingBox(entity, this.x - x, this.y - y, this.z - z);
}
@ -225,12 +338,7 @@ public class BoundingBox {
* @return the points at the bottom of the {@link BoundingBox}
*/
public @NotNull List<Vec> getBottomFace() {
this.bottomFace = get(bottomFace, () ->
List.of(new Vec(getMinX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMinY(), getMaxZ()),
new Vec(getMinX(), getMinY(), getMaxZ())));
return bottomFace;
return bottomFace.get();
}
/**
@ -239,12 +347,7 @@ public class BoundingBox {
* @return the points at the top of the {@link BoundingBox}
*/
public @NotNull List<Vec> getTopFace() {
this.topFace = get(topFace, () ->
List.of(new Vec(getMinX(), getMaxY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMaxZ()),
new Vec(getMinX(), getMaxY(), getMaxZ())));
return topFace;
return topFace.get();
}
/**
@ -253,12 +356,7 @@ public class BoundingBox {
* @return the points on the left face of the {@link BoundingBox}
*/
public @NotNull List<Vec> getLeftFace() {
this.leftFace = get(leftFace, () ->
List.of(new Vec(getMinX(), getMinY(), getMinZ()),
new Vec(getMinX(), getMaxY(), getMinZ()),
new Vec(getMinX(), getMaxY(), getMaxZ()),
new Vec(getMinX(), getMinY(), getMaxZ())));
return leftFace;
return leftFace.get();
}
/**
@ -267,12 +365,7 @@ public class BoundingBox {
* @return the points on the right face of the {@link BoundingBox}
*/
public @NotNull List<Vec> getRightFace() {
this.rightFace = get(rightFace, () ->
List.of(new Vec(getMaxX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMaxZ()),
new Vec(getMaxX(), getMinY(), getMaxZ())));
return rightFace;
return rightFace.get();
}
/**
@ -281,12 +374,7 @@ public class BoundingBox {
* @return the points at the front of the {@link BoundingBox}
*/
public @NotNull List<Vec> getFrontFace() {
this.frontFace = get(frontFace, () ->
List.of(new Vec(getMinX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMinY(), getMinZ()),
new Vec(getMaxX(), getMaxY(), getMinZ()),
new Vec(getMinX(), getMaxY(), getMinZ())));
return frontFace;
return frontFace.get();
}
/**
@ -295,12 +383,7 @@ public class BoundingBox {
* @return the points at the back of the {@link BoundingBox}
*/
public @NotNull List<Vec> getBackFace() {
this.backFace = get(backFace, () -> List.of(
new Vec(getMinX(), getMinY(), getMaxZ()),
new Vec(getMaxX(), getMinY(), getMaxZ()),
new Vec(getMaxX(), getMaxY(), getMaxZ()),
new Vec(getMinX(), getMaxY(), getMaxZ())));
return backFace;
return backFace.get();
}
@Override
@ -315,13 +398,37 @@ public class BoundingBox {
return result;
}
private @NotNull List<Vec> get(@Nullable List<Vec> face, Supplier<? extends List<Vec>> vecSupplier) {
final var lastPos = this.lastPosition;
final var entityPos = entity.getPosition();
if (face != null && lastPos != null && lastPos.samePoint(entityPos)) {
return face;
private enum Axis {
X, Y, Z
}
private final class CachedFace {
private final AtomicReference<@Nullable PositionedPoints> reference = new AtomicReference<>(null);
private final Supplier<@NotNull List<Vec>> faceProducer;
private CachedFace(Supplier<@NotNull List<Vec>> faceProducer) {
this.faceProducer = faceProducer;
}
@NotNull List<Vec> get() {
//noinspection ConstantConditions
return reference.updateAndGet(value -> {
Pos entityPosition = entity.getPosition();
if (value == null || !value.lastPosition.samePoint(entityPosition)) {
return new PositionedPoints(entityPosition, faceProducer.get());
}
return value;
}).points;
}
}
private static final class PositionedPoints {
private final @NotNull Pos lastPosition;
private final @NotNull List<Vec> points;
private PositionedPoints(@NotNull Pos lastPosition, @NotNull List<Vec> points) {
this.lastPosition = lastPosition;
this.points = points;
}
this.lastPosition = entityPos;
return vecSupplier.get();
}
}

View File

@ -7,10 +7,10 @@ import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.WorldBorder;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockGetter;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class CollisionUtils {
@ -75,7 +75,7 @@ public class CollisionUtils {
* @return result of the step
*/
private static StepResult stepAxis(Instance instance, Chunk originChunk, Vec startPosition, Vec axis, double stepAmount, List<Vec> corners) {
final List<Vec> mutableCorners = new ArrayList<>(corners);
final Vec[] mutableCorners = corners.toArray(Vec[]::new);
final double sign = Math.signum(stepAmount);
final int blockLength = (int) stepAmount;
final double remainingLength = stepAmount - blockLength;
@ -94,7 +94,7 @@ public class CollisionUtils {
// find the corner which moved the least
double smallestDisplacement = Double.POSITIVE_INFINITY;
for (int i = 0; i < corners.size(); i++) {
final double displacement = corners.get(i).distance(mutableCorners.get(i));
final double displacement = corners.get(i).distance(mutableCorners[i]);
if (displacement < smallestDisplacement) {
smallestDisplacement = displacement;
}
@ -111,27 +111,27 @@ public class CollisionUtils {
* @param corners the corners of the bounding box to consider
* @return true if found collision
*/
private static boolean stepOnce(Instance instance, Chunk originChunk, Vec axis, double amount, List<Vec> corners) {
private static boolean stepOnce(Instance instance, Chunk originChunk, Vec axis, double amount, Vec[] corners) {
final double sign = Math.signum(amount);
for (int cornerIndex = 0; cornerIndex < corners.size(); cornerIndex++) {
final Vec originalCorner = corners.get(cornerIndex);
for (int cornerIndex = 0; cornerIndex < corners.length; cornerIndex++) {
final Vec originalCorner = corners[cornerIndex];
final Vec newCorner = originalCorner.add(axis.mul(amount));
final Chunk chunk = ChunkUtils.retrieve(instance, originChunk, newCorner);
if (!ChunkUtils.isLoaded(chunk)) {
// Collision at chunk border
return true;
}
final Block block = chunk.getBlock(newCorner);
final Block block = chunk.getBlock(newCorner, BlockGetter.Condition.TYPE);
// TODO: block collision boxes
// TODO: for the moment, always consider a full block
if (block.isSolid()) {
corners.set(cornerIndex, new Vec(
if (block != null && block.isSolid()) {
corners[cornerIndex] = new Vec(
Math.abs(axis.x()) > 10e-16 ? newCorner.blockX() - axis.x() * sign : originalCorner.x(),
Math.abs(axis.y()) > 10e-16 ? newCorner.blockY() - axis.y() * sign : originalCorner.y(),
Math.abs(axis.z()) > 10e-16 ? newCorner.blockZ() - axis.z() * sign : originalCorner.z()));
Math.abs(axis.z()) > 10e-16 ? newCorner.blockZ() - axis.z() * sign : originalCorner.z());
return true;
}
corners.set(cornerIndex, newCorner);
corners[cornerIndex] = newCorner;
}
return false;
}
@ -148,54 +148,28 @@ public class CollisionUtils {
@NotNull Pos currentPosition, @NotNull Pos newPosition) {
final WorldBorder worldBorder = instance.getWorldBorder();
final WorldBorder.CollisionAxis collisionAxis = worldBorder.getCollisionAxis(newPosition);
switch (collisionAxis) {
case NONE:
// Apply velocity + gravity
return newPosition;
case BOTH:
// Apply Y velocity/gravity
return new Pos(currentPosition.x(), newPosition.y(), currentPosition.z());
case X:
// Apply Y/Z velocity/gravity
return new Pos(currentPosition.x(), newPosition.y(), newPosition.z());
case Z:
// Apply X/Y velocity/gravity
return new Pos(newPosition.x(), newPosition.y(), currentPosition.z());
}
throw new IllegalStateException("Something weird happened...");
return switch (collisionAxis) {
case NONE ->
// Apply velocity + gravity
newPosition;
case BOTH ->
// Apply Y velocity/gravity
new Pos(currentPosition.x(), newPosition.y(), currentPosition.z());
case X ->
// Apply Y/Z velocity/gravity
new Pos(currentPosition.x(), newPosition.y(), newPosition.z());
case Z ->
// Apply X/Y velocity/gravity
new Pos(newPosition.x(), newPosition.y(), currentPosition.z());
};
}
public static class PhysicsResult {
private final Pos newPosition;
private final Vec newVelocity;
private final boolean isOnGround;
public PhysicsResult(Pos newPosition, Vec newVelocity, boolean isOnGround) {
this.newPosition = newPosition;
this.newVelocity = newVelocity;
this.isOnGround = isOnGround;
}
public Pos newPosition() {
return newPosition;
}
public Vec newVelocity() {
return newVelocity;
}
public boolean isOnGround() {
return isOnGround;
}
public record PhysicsResult(Pos newPosition,
Vec newVelocity,
boolean isOnGround) {
}
private static class StepResult {
private final Vec newPosition;
private final boolean foundCollision;
public StepResult(Vec newPosition, boolean foundCollision) {
this.newPosition = newPosition;
this.foundCollision = foundCollision;
}
private record StepResult(Vec newPosition,
boolean foundCollision) {
}
}

View File

@ -32,7 +32,7 @@ public class Color implements RGBLike {
* @param rgbLike the color
*/
public Color(RGBLike rgbLike) {
this(rgbLike.red(), rgbLike.blue(), rgbLike.green());
this(rgbLike.red(), rgbLike.green(), rgbLike.blue());
}
/**

View File

@ -81,7 +81,7 @@ public final class CommandManager {
}
/**
* Gets if a command with the name {@code commandName} already exists or name.
* Gets if a command with the name {@code commandName} already exists or not.
*
* @param commandName the command name to check
* @return true if the command does exist
@ -92,29 +92,23 @@ public final class CommandManager {
}
/**
* Executes a command for a {@link ConsoleSender}.
* Executes a command for a {@link CommandSender}.
*
* @param sender the sender of the command
* @param command the raw command string (without the command prefix)
* @return the execution result
*/
public @NotNull CommandResult execute(@NotNull CommandSender sender, @NotNull String command) {
// Command event
if (sender instanceof Player) {
Player player = (Player) sender;
if (sender instanceof Player player) {
PlayerCommandEvent playerCommandEvent = new PlayerCommandEvent(player, command);
EventDispatcher.call(playerCommandEvent);
if (playerCommandEvent.isCancelled())
return CommandResult.of(CommandResult.Type.CANCELLED, command);
command = playerCommandEvent.getCommand();
}
// Process the command
final var result = dispatcher.execute(sender, command);
final CommandResult result = dispatcher.execute(sender, command);
if (result.getType() == CommandResult.Type.UNKNOWN) {
if (unknownCommandCallback != null) {
this.unknownCommandCallback.apply(sender, command);
@ -124,8 +118,8 @@ public final class CommandManager {
}
/**
* Executes the command using a {@link ServerSender} to do not
* print the command messages, and rely instead on the command return data.
* Executes the command using a {@link ServerSender}. This can be used
* to run a silent command (nothing is printed to console).
*
* @see #execute(CommandSender, String)
*/
@ -440,8 +434,7 @@ public final class CommandManager {
return literalNode;
}
@NotNull
private DeclareCommandsPacket.Node createMainNode(@NotNull String name, boolean executable) {
private @NotNull DeclareCommandsPacket.Node createMainNode(@NotNull String name, boolean executable) {
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
literalNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL, executable, false, false);
literalNode.name = name;

View File

@ -367,10 +367,8 @@ public class Command {
node = findNode.apply(node, Collections.singleton(literal));
continue;
} else if (argument instanceof ArgumentWord) {
ArgumentWord argumentWord = (ArgumentWord) argument;
} else if (argument instanceof ArgumentWord argumentWord) {
if (argumentWord.hasRestrictions()) {
addArguments.accept(node, arguments);
arguments = new ArrayList<>();

View File

@ -65,8 +65,7 @@ public class CommandDispatcher {
this.cache.invalidateAll();
}
@NotNull
public Set<Command> getCommands() {
public @NotNull Set<Command> getCommands() {
return Collections.unmodifiableSet(commands);
}
@ -76,8 +75,7 @@ public class CommandDispatcher {
* @param commandName the command name
* @return the {@link Command} associated with the name, null if not any
*/
@Nullable
public Command findCommand(@NotNull String commandName) {
public @Nullable Command findCommand(@NotNull String commandName) {
commandName = commandName.toLowerCase();
return commandMap.getOrDefault(commandName, null);
}
@ -89,8 +87,7 @@ public class CommandDispatcher {
* @param commandString the command with the argument(s)
* @return the command result
*/
@NotNull
public CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) {
public @NotNull CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) {
CommandResult commandResult = parse(commandString);
ParsedCommand parsedCommand = commandResult.parsedCommand;
if (parsedCommand != null) {
@ -105,10 +102,8 @@ public class CommandDispatcher {
* @param commandString the command (containing the command name and the args if any)
* @return the parsing result
*/
@NotNull
public CommandResult parse(@NotNull String commandString) {
public @NotNull CommandResult parse(@NotNull String commandString) {
commandString = commandString.trim();
// Verify if the result is cached
{
final CommandResult cachedResult = cache.getIfPresent(commandString);
@ -134,18 +129,15 @@ public class CommandDispatcher {
findParsedCommand(command, commandName, commandQueryResult.args, commandString, result);
// Cache result
{
this.cache.put(commandString, result);
}
this.cache.put(commandString, result);
return result;
}
@Nullable
private ParsedCommand findParsedCommand(@NotNull Command command,
@NotNull String commandName, @NotNull String[] args,
@NotNull String commandString,
@NotNull CommandResult result) {
private @Nullable ParsedCommand findParsedCommand(@NotNull Command command,
@NotNull String commandName, @NotNull String[] args,
@NotNull String commandString,
@NotNull CommandResult result) {
final boolean hasArgument = args.length > 0;
// Search for subcommand
@ -162,41 +154,37 @@ public class CommandDispatcher {
final String input = commandName + StringUtils.SPACE + String.join(StringUtils.SPACE, args);
ParsedCommand parsedCommand = new ParsedCommand();
parsedCommand.command = command;
parsedCommand.commandString = commandString;
// The default executor should be used if no argument is provided
{
if (!hasArgument) {
Optional<CommandSyntax> optionalSyntax = command.getSyntaxes()
.stream()
.filter(syntax -> syntax.getArguments().length == 0)
.findFirst();
if (!hasArgument) {
Optional<CommandSyntax> optionalSyntax = command.getSyntaxes()
.stream()
.filter(syntax -> syntax.getArguments().length == 0)
.findFirst();
if (optionalSyntax.isPresent()) {
// Empty syntax found
final CommandSyntax syntax = optionalSyntax.get();
if (optionalSyntax.isPresent()) {
// Empty syntax found
final CommandSyntax syntax = optionalSyntax.get();
parsedCommand.syntax = syntax;
parsedCommand.executor = syntax.getExecutor();
parsedCommand.context = new CommandContext(input);
parsedCommand.syntax = syntax;
parsedCommand.executor = syntax.getExecutor();
result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand;
return parsedCommand;
} else {
// No empty syntax, use default executor if any
final CommandExecutor defaultExecutor = command.getDefaultExecutor();
if (defaultExecutor != null) {
parsedCommand.executor = defaultExecutor;
parsedCommand.context = new CommandContext(input);
result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand;
return parsedCommand;
} else {
// No empty syntax, use default executor if any
final CommandExecutor defaultExecutor = command.getDefaultExecutor();
if (defaultExecutor != null) {
parsedCommand.executor = defaultExecutor;
parsedCommand.context = new CommandContext(input);
result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand;
return parsedCommand;
}
}
}
}
@ -207,7 +195,6 @@ public class CommandDispatcher {
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
// Contains all the fully validated syntaxes (we later find the one with the most amount of arguments)
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>(syntaxes.size());
// 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
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
@ -233,29 +220,25 @@ public class CommandDispatcher {
result.parsedCommand = parsedCommand;
return parsedCommand;
}
}
// No all-correct syntax, find the closest one to use the argument callback
{
// Get closest valid syntax
if (!syntaxesSuggestions.isEmpty()) {
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;
if (!syntaxesSuggestions.isEmpty()) {
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()) {
parsedCommand.callback = argument.getCallback();
parsedCommand.argumentSyntaxException = argumentSyntaxException;
// Found the closest syntax with at least 1 correct argument
final Argument<?> argument = syntax.getArguments()[argIndex];
if (argument.hasErrorCallback() && argumentSyntaxException != null) {
parsedCommand.callback = argument.getCallback();
parsedCommand.argumentSyntaxException = argumentSyntaxException;
result.type = CommandResult.Type.INVALID_SYNTAX;
result.parsedCommand = parsedCommand;
return parsedCommand;
}
result.type = CommandResult.Type.INVALID_SYNTAX;
result.parsedCommand = parsedCommand;
return parsedCommand;
}
}

View File

@ -10,23 +10,19 @@ public class CommandResult {
protected ParsedCommand parsedCommand;
protected CommandData commandData;
@NotNull
public Type getType() {
public @NotNull Type getType() {
return type;
}
@NotNull
public String getInput() {
public @NotNull String getInput() {
return input;
}
@Nullable
public ParsedCommand getParsedCommand() {
public @Nullable ParsedCommand getParsedCommand() {
return parsedCommand;
}
@Nullable
public CommandData getCommandData() {
public @Nullable CommandData getCommandData() {
return commandData;
}
@ -35,30 +31,25 @@ public class CommandResult {
* Command and syntax successfully found.
*/
SUCCESS,
/**
* Command found, but the syntax is invalid.
* Executor sets to {@link Command#getDefaultExecutor()}.
*/
INVALID_SYNTAX,
/**
* Command cancelled by an event listener.
*/
CANCELLED,
/**
* Command is not registered, it is also the default result type.
*/
UNKNOWN
}
@NotNull
public static CommandResult of(@NotNull Type type, @NotNull String input) {
public static @NotNull CommandResult of(@NotNull Type type, @NotNull String input) {
CommandResult result = new CommandResult();
result.type = type;
result.input = input;
return result;
}
}

View File

@ -7,6 +7,8 @@ import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* Represents a {@link Command} ready to be executed (already parsed).
*/
@ -35,16 +37,14 @@ public class ParsedCommand {
* @param source the command source
* @return the command data, null if none
*/
@Nullable
public CommandData execute(@NotNull CommandSender source) {
public @Nullable CommandData execute(@NotNull CommandSender source) {
// Global listener
command.globalListener(source, context, commandString);
command.globalListener(source, Objects.requireNonNullElseGet(context, () -> new CommandContext(commandString)), commandString);
// Command condition check
final CommandCondition condition = command.getCondition();
if (condition != null) {
final boolean result = condition.canUse(source, commandString);
if (!result)
return null;
if (!result) return null;
}
// Condition is respected
if (executor != null) {
@ -57,16 +57,16 @@ public class ParsedCommand {
context.retrieveDefaultValues(syntax.getDefaultValuesMap());
try {
executor.apply(source, context);
} catch (Exception exception) {
MinecraftServer.getExceptionManager().handleException(exception);
} catch (Throwable throwable) {
MinecraftServer.getExceptionManager().handleException(throwable);
}
}
} else {
// The executor is probably the default one
try {
executor.apply(source, context);
} catch (Exception exception) {
MinecraftServer.getExceptionManager().handleException(exception);
} catch (Throwable throwable) {
MinecraftServer.getExceptionManager().handleException(throwable);
}
}
} else if (callback != null && argumentSyntaxException != null) {
@ -83,8 +83,7 @@ public class ParsedCommand {
return context.getReturnData();
}
@NotNull
public static ParsedCommand withDefaultExecutor(@NotNull Command command, @NotNull String input) {
public static @NotNull ParsedCommand withDefaultExecutor(@NotNull Command command, @NotNull String input) {
ParsedCommand parsedCommand = new ParsedCommand();
parsedCommand.command = command;
parsedCommand.commandString = input;
@ -92,5 +91,4 @@ public class ParsedCommand {
parsedCommand.context = new CommandContext(input);
return parsedCommand;
}
}

View File

@ -13,6 +13,7 @@ public class ArgumentBlockState extends Argument<Block> {
public static final int NO_BLOCK = 1;
public static final int INVALID_BLOCK = 2;
public static final int INVALID_PROPERTY = 3;
public static final int INVALID_PROPERTY_VALUE = 4;
public ArgumentBlockState(@NotNull String id) {
super(id, true, false);
@ -58,7 +59,11 @@ public class ArgumentBlockState extends Argument<Block> {
// Compute properties
final String query = input.substring(nbtIndex);
final var propertyMap = BlockUtils.parseProperties(query);
return block.withProperties(propertyMap);
try {
return block.withProperties(propertyMap);
} catch (IllegalArgumentException e) {
throw new ArgumentSyntaxException("Invalid property values", input, INVALID_PROPERTY_VALUE);
}
}
}

View File

@ -245,7 +245,7 @@ public class ArgumentEntity extends Argument<EntityFinder> {
break;
case "level":
try {
final IntRange level = ArgumentIntRange.staticParse(value);
final IntRange level = Argument.parse(new ArgumentIntRange(value));
entityFinder.setLevel(level);
} catch (ArgumentSyntaxException e) {
throw new ArgumentSyntaxException("Invalid level number", input, INVALID_ARGUMENT_VALUE);
@ -253,7 +253,7 @@ public class ArgumentEntity extends Argument<EntityFinder> {
break;
case "distance":
try {
final IntRange distance = ArgumentIntRange.staticParse(value);
final IntRange distance = Argument.parse(new ArgumentIntRange(value));
entityFinder.setDistance(distance);
} catch (ArgumentSyntaxException e) {
throw new ArgumentSyntaxException("Invalid level number", input, INVALID_ARGUMENT_VALUE);

View File

@ -1,66 +1,16 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.math.FloatRange;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Pattern;
/**
* Represents an argument which will give you an {@link FloatRange}.
* <p>
* Example: ..3, 3.., 5..10, 15
*/
public class ArgumentFloatRange extends ArgumentRange<FloatRange> {
public class ArgumentFloatRange extends ArgumentRange<FloatRange, Float> {
public ArgumentFloatRange(String id) {
super(id);
}
@NotNull
@Override
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]);
} else {
// Format NUMBER..
min = Float.parseFloat(split[0]);
max = Float.MAX_VALUE;
}
}
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);
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:float_range";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
super(id, "minecraft:float_range", Float.MIN_VALUE, Float.MAX_VALUE, Float::parseFloat, FloatRange::new);
}
@Override

View File

@ -1,76 +1,16 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.math.IntRange;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Pattern;
/**
* Represents an argument which will give you an {@link IntRange}.
* <p>
* Example: ..3, 3.., 5..10, 15
*/
public class ArgumentIntRange extends ArgumentRange<IntRange> {
public class ArgumentIntRange extends ArgumentRange<IntRange, Integer> {
public ArgumentIntRange(String id) {
super(id);
}
@NotNull
@Override
public IntRange parse(@NotNull String input) throws ArgumentSyntaxException {
return staticParse(input);
}
/**
* @deprecated use {@link Argument#parse(Argument)}
*/
@Deprecated
@NotNull
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]);
} else {
// Format NUMBER..
min = Integer.parseInt(split[0]);
max = Integer.MAX_VALUE;
}
}
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);
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:int_range";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
super(id, "minecraft:int_range", Integer.MIN_VALUE, Integer.MAX_VALUE, Integer::parseInt, IntRange::new);
}
@Override

View File

@ -1,17 +1,81 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.math.Range;
import org.jetbrains.annotations.NotNull;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Pattern;
/**
* Abstract class used by {@link ArgumentIntRange} and {@link ArgumentFloatRange}.
*
* @param <T> the type of the range
*/
public abstract class ArgumentRange<T> extends Argument<T> {
public abstract class ArgumentRange<T extends Range<N>, N extends Number> extends Argument<T> {
public static final int FORMAT_ERROR = -1;
private final N min;
private final N max;
private final Function<String, N> parser;
private final String parserName;
private final BiFunction<N, N, T> rangeConstructor;
public ArgumentRange(String id) {
public ArgumentRange(@NotNull String id, String parserName, N min, N max, Function<String, N> parser, BiFunction<N, N, T> rangeConstructor) {
super(id);
this.min = min;
this.max = max;
this.parser = parser;
this.parserName = parserName;
this.rangeConstructor = rangeConstructor;
}
@NotNull
@Override
public T parse(@NotNull String input) throws ArgumentSyntaxException {
try {
final String[] split = input.split(Pattern.quote(".."), -1);
if (split.length == 2) {
final N min;
final N max;
if (split[0].length() == 0 && split[1].length() > 0) {
// Format ..NUMBER
min = this.min;
max = parser.apply(split[1]);
} else if (split[0].length() > 0 && split[1].length() == 0) {
// Format NUMBER..
min = parser.apply(split[0]);
max = this.max;
} else if (split[0].length() > 0) {
// Format NUMBER..NUMBER
min = parser.apply(split[0]);
max = parser.apply(split[1]);
} else {
// Format ..
throw new ArgumentSyntaxException("Invalid range format", input, FORMAT_ERROR);
}
return rangeConstructor.apply(min, max);
} else if (split.length == 1) {
final N number = parser.apply(input);
return rangeConstructor.apply(number, number);
}
} catch (NumberFormatException e2) {
throw new ArgumentSyntaxException("Invalid number", input, FORMAT_ERROR);
}
throw new ArgumentSyntaxException("Invalid range format", input, FORMAT_ERROR);
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = parserName;
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -23,7 +23,7 @@ public class ArgumentEntityType extends ArgumentRegistry<EntityType> {
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, true);
argumentNode.parser = "minecraft:entity_summon";
argumentNode.parser = "minecraft:resource_location";
argumentNode.suggestionsType = SuggestionType.SUMMONABLE_ENTITIES.getIdentifier();
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});

View File

@ -18,7 +18,8 @@ import java.util.regex.Pattern;
public class ArgumentNumber<T extends Number> extends Argument<T> {
public static final int NOT_NUMBER_ERROR = 1;
public static final int RANGE_ERROR = 2;
public static final int TOO_LOW_ERROR = 2;
public static final int TOO_HIGH_ERROR = 3;
protected boolean hasMin, hasMax;
protected T min, max;
@ -53,10 +54,10 @@ public class ArgumentNumber<T extends Number> extends Argument<T> {
// Check range
if (hasMin && comparator.compare(value, min) < 0) {
throw new ArgumentSyntaxException("Input is lower than the minimum required value", input, RANGE_ERROR);
throw new ArgumentSyntaxException("Input is lower than the minimum allowed value", input, TOO_LOW_ERROR);
}
if (hasMax && comparator.compare(value, max) > 0) {
throw new ArgumentSyntaxException("Input is higher than the minimum required value", input, RANGE_ERROR);
throw new ArgumentSyntaxException("Input is higher than the maximum allowed value", input, TOO_HIGH_ERROR);
}
return value;

View File

@ -3,7 +3,6 @@ package net.minestom.server.coordinate;
import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@ -15,8 +14,7 @@ import java.util.function.DoubleUnaryOperator;
* Can either be a {@link Pos} or {@link Vec}.
* Interface will become {@code sealed} in the future.
*/
@ApiStatus.NonExtendable
public interface Point {
public sealed interface Point permits Vec, Pos {
/**
* Gets the X coordinate.
@ -72,6 +70,16 @@ public interface Point {
return (int) Math.floor(z());
}
@Contract(pure = true)
default int chunkX() {
return ChunkUtils.getChunkCoordinate(x());
}
@Contract(pure = true)
default int chunkZ() {
return ChunkUtils.getChunkCoordinate(z());
}
/**
* Creates a point with a modified X coordinate based on its value.
*
@ -164,22 +172,35 @@ public interface Point {
@Contract(pure = true)
default @NotNull Point relative(@NotNull BlockFace face) {
switch (face) {
case BOTTOM:
return sub(0, 1, 0);
case TOP:
return add(0, 1, 0);
case NORTH:
return sub(0, 0, 1);
case SOUTH:
return add(0, 0, 1);
case WEST:
return sub(1, 0, 0);
case EAST:
return add(1, 0, 0);
default: // should never be called
return this;
}
return switch (face) {
case BOTTOM -> sub(0, 1, 0);
case TOP -> add(0, 1, 0);
case NORTH -> sub(0, 0, 1);
case SOUTH -> add(0, 0, 1);
case WEST -> sub(1, 0, 0);
case EAST -> add(1, 0, 0);
};
}
@Contract(pure = true)
default double distanceSquared(double x, double y, double z) {
return MathUtils.square(x() - x) + MathUtils.square(y() - y) + MathUtils.square(z() - z);
}
/**
* Gets the squared distance between this point and another.
*
* @param point the other point
* @return the squared distance
*/
@Contract(pure = true)
default double distanceSquared(@NotNull Point point) {
return distanceSquared(point.x(), point.y(), point.z());
}
@Contract(pure = true)
default double distance(double x, double y, double z) {
return Math.sqrt(distanceSquared(x, y, z));
}
/**
@ -194,22 +215,11 @@ public interface Point {
*/
@Contract(pure = true)
default double distance(@NotNull Point point) {
return Math.sqrt(MathUtils.square(x() - point.x()) +
MathUtils.square(y() - point.y()) +
MathUtils.square(z() - point.z()));
return distance(point.x(), point.y(), point.z());
}
/**
* Gets the squared distance between this point and another.
*
* @param point the other point
* @return the squared distance
*/
@Contract(pure = true)
default double distanceSquared(@NotNull Point point) {
return MathUtils.square(x() - point.x()) +
MathUtils.square(y() - point.y()) +
MathUtils.square(z() - point.z());
default boolean samePoint(double x, double y, double z) {
return Double.compare(x, x()) == 0 && Double.compare(y, y()) == 0 && Double.compare(z, z()) == 0;
}
/**
@ -219,9 +229,7 @@ public interface Point {
* @return true if the two positions are similar
*/
default boolean samePoint(@NotNull Point point) {
return Double.compare(point.x(), x()) == 0 &&
Double.compare(point.y(), y()) == 0 &&
Double.compare(point.z(), z()) == 0;
return samePoint(point.x(), point.y(), point.z());
}
/**
@ -241,7 +249,20 @@ public interface Point {
* @return true if 'this' is in the same chunk as {@code point}
*/
default boolean sameChunk(@NotNull Point point) {
return ChunkUtils.getChunkCoordinate(x()) == ChunkUtils.getChunkCoordinate(point.x()) &&
ChunkUtils.getChunkCoordinate(z()) == ChunkUtils.getChunkCoordinate(point.z());
return chunkX() == point.chunkX() && chunkZ() == point.chunkZ();
}
default boolean sameBlock(int blockX, int blockY, int blockZ) {
return blockX() == blockX && blockY() == blockY && blockZ() == blockZ;
}
/**
* Gets if two points are in the same chunk.
*
* @param point the point to compare two
* @return true if 'this' is in the same chunk as {@code point}
*/
default boolean sameBlock(@NotNull Point point) {
return sameBlock(point.blockX(), point.blockY(), point.blockZ());
}
}

View File

@ -5,7 +5,6 @@ import net.minestom.server.utils.MathUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.function.DoubleUnaryOperator;
/**
@ -13,18 +12,11 @@ import java.util.function.DoubleUnaryOperator;
* <p>
* To become record and primitive.
*/
public final class Pos implements Point {
public record Pos(double x, double y, double z, float yaw, float pitch) implements Point {
public static final Pos ZERO = new Pos(0, 0, 0);
private final double x, y, z;
private final float yaw, pitch;
public Pos(double x, double y, double z, float yaw, float pitch) {
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
public Pos {
yaw = fixYaw(yaw);
}
public Pos(double x, double y, double z) {
@ -47,8 +39,7 @@ public final class Pos implements Point {
* @return the converted position
*/
public static @NotNull Pos fromPoint(@NotNull Point point) {
if (point instanceof Pos)
return (Pos) point;
if (point instanceof Pos pos) return pos;
return new Pos(point.x(), point.y(), point.z());
}
@ -108,7 +99,7 @@ public final class Pos implements Point {
@Contract(pure = true)
public @NotNull Pos withYaw(@NotNull DoubleUnaryOperator operator) {
return new Pos(x, y, z, (float) operator.applyAsDouble(yaw), pitch);
return withYaw((float) operator.applyAsDouble(yaw));
}
@Contract(pure = true)
@ -118,7 +109,7 @@ public final class Pos implements Point {
@Contract(pure = true)
public @NotNull Pos withPitch(@NotNull DoubleUnaryOperator operator) {
return new Pos(x, y, z, yaw, (float) operator.applyAsDouble(pitch));
return withPitch((float) operator.applyAsDouble(pitch));
}
/**
@ -148,24 +139,6 @@ public final class Pos implements Point {
xz * Math.cos(Math.toRadians(rotX)));
}
@Override
@Contract(pure = true)
public double x() {
return x;
}
@Override
@Contract(pure = true)
public double y() {
return y;
}
@Override
@Contract(pure = true)
public double z() {
return z;
}
/**
* Returns a new position based on this position fields.
*
@ -180,7 +153,7 @@ public final class Pos implements Point {
@Override
@Contract(pure = true)
public @NotNull Pos withX(@NotNull DoubleUnaryOperator operator) {
return new Pos(operator.applyAsDouble(x), y, z);
return new Pos(operator.applyAsDouble(x), y, z, yaw, pitch);
}
@Override
@ -192,7 +165,7 @@ public final class Pos implements Point {
@Override
@Contract(pure = true)
public @NotNull Pos withY(@NotNull DoubleUnaryOperator operator) {
return new Pos(x, operator.applyAsDouble(y), z);
return new Pos(x, operator.applyAsDouble(y), z, yaw, pitch);
}
@Override
@ -204,7 +177,7 @@ public final class Pos implements Point {
@Override
@Contract(pure = true)
public @NotNull Pos withZ(@NotNull DoubleUnaryOperator operator) {
return new Pos(x, y, operator.applyAsDouble(z));
return new Pos(x, y, operator.applyAsDouble(z), yaw, pitch);
}
@Override
@ -278,51 +251,30 @@ public final class Pos implements Point {
return (Pos) Point.super.relative(face);
}
@Contract(pure = true)
public float yaw() {
return yaw;
}
@Contract(pure = true)
public float pitch() {
return pitch;
}
@Contract(pure = true)
public @NotNull Vec asVec() {
return new Vec(x, y, z);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pos pos = (Pos) o;
return Double.compare(pos.x, x) == 0 &&
Double.compare(pos.y, y) == 0 &&
Double.compare(pos.z, z) == 0 &&
Float.compare(pos.yaw, yaw) == 0 &&
Float.compare(pos.pitch, pitch) == 0;
}
@Override
public int hashCode() {
return Objects.hash(x, y, z, yaw, pitch);
}
@Override
public String toString() {
return "Pos{" +
"x=" + x +
", y=" + y +
", z=" + z +
", yaw=" + yaw +
", pitch=" + pitch +
'}';
}
@FunctionalInterface
public interface Operator {
@NotNull Pos apply(double x, double y, double z, float yaw, float pitch);
}
/**
* Fixes a yaw value that is not between -180.0F and 180.0F
* So for example -1355.0F becomes 85.0F and 225.0F becomes -135.0F
*
* @param yaw The possible "wrong" yaw
* @return a fixed yaw
*/
private static float fixYaw(float yaw) {
yaw = yaw % 360;
if (yaw < -180.0F) {
yaw += 360.0F;
} else if (yaw > 180.0F) {
yaw -= 360.0F;
}
return yaw;
}
}

View File

@ -5,7 +5,6 @@ import net.minestom.server.utils.MathUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.function.DoubleUnaryOperator;
/**
@ -13,27 +12,12 @@ import java.util.function.DoubleUnaryOperator;
* <p>
* To become record and primitive.
*/
public final class Vec implements Point {
public record Vec(double x, double y, double z) implements Point {
public static final Vec ZERO = new Vec(0);
public static final Vec ONE = new Vec(1);
public static final double EPSILON = 0.000001;
private final double x, y, z;
/**
* Creates a new vec with the 3 coordinates set.
*
* @param x the X coordinate
* @param y the Y coordinate
* @param z the Z coordinate
*/
public Vec(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
/**
* Creates a new vec with the [x;z] coordinates set. Y is set to 0.
*
@ -61,8 +45,7 @@ public final class Vec implements Point {
* @return the converted vector
*/
public static @NotNull Vec fromPoint(@NotNull Point point) {
if (point instanceof Vec)
return (Vec) point;
if (point instanceof Vec vec) return vec;
return new Vec(point.x(), point.y(), point.z());
}
@ -487,43 +470,6 @@ public final class Vec implements Point {
return lerp(target, interpolation.apply(alpha));
}
@Override
public double x() {
return x;
}
@Override
public double y() {
return y;
}
@Override
public double z() {
return z;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Vec vec = (Vec) o;
return Double.compare(vec.x, x) == 0 && Double.compare(vec.y, y) == 0 && Double.compare(vec.z, z) == 0;
}
@Override
public int hashCode() {
return Objects.hash(x, y, z);
}
@Override
public String toString() {
return "Vec{" +
"x=" + x +
", y=" + y +
", z=" + z +
'}';
}
@FunctionalInterface
public interface Operator {
/**

View File

@ -13,6 +13,7 @@ import java.util.Set;
* <p>
* See {@link DataImpl} for the default implementation.
*/
@Deprecated
public abstract class Data implements PublicCloneable<Data> {
public static final Data EMPTY = new Data() {

View File

@ -19,7 +19,7 @@ public interface DataContainer {
* meaning that this will be null if no data has been defined.
*
* @return the {@link Data} of this container, can be null
* @deprecated use the tag API https://wiki.minestom.com/feature/tags
* @deprecated use the tag API https://wiki.minestom.net/feature/tags
*/
@Deprecated
@Nullable Data getData();
@ -31,8 +31,8 @@ public interface DataContainer {
* on your use-case.
*
* @param data the new {@link Data} of this container, null to remove it
* @deprecated use the tag API https://wiki.minestom.com/feature/tags
* @deprecated use the tag API https://wiki.minestom.net/feature/tags
*/
@Deprecated
void setData(@Nullable Data data);
}
}

View File

@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* {@link Data} implementation which uses a {@link ConcurrentHashMap}.
*/
@Deprecated
public class DataImpl extends Data {
protected final ConcurrentHashMap<String, Object> data = new ConcurrentHashMap<>();

View File

@ -23,6 +23,7 @@ import java.util.UUID;
* A lot of types are already registered by default so you do not have to add all of them manually,
* you can verify if {@link #getDataType(Class)} returns null for the desired type, if it is then you will need to register it.
*/
@Deprecated
public final class DataManager {
private final Map<Class, DataType> dataTypeMap = new HashMap<>();

View File

@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
*
* @param <T> the type of the object
*/
@Deprecated
public abstract class DataType<T> {
/**

View File

@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull;
* <p>
* See {@link SerializableDataImpl} for the default implementation.
*/
@Deprecated
public abstract class SerializableData extends Data {
/**

View File

@ -19,6 +19,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* {@link SerializableData} implementation based on {@link DataImpl}.
*/
@Deprecated
public class SerializableDataImpl extends SerializableData {
protected static final DataManager DATA_MANAGER = MinecraftServer.getDataManager();

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,6 @@
package net.minestom.server.entity;
import com.extollit.gaming.ai.path.HydrazinePathFinder;
import net.minestom.server.attribute.Attribute;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.ai.EntityAI;
import net.minestom.server.entity.ai.EntityAIGroup;
@ -49,7 +48,7 @@ public class EntityCreature extends LivingEntity implements NavigableEntity, Ent
aiTick(time);
// Path finding
this.navigator.tick(getAttributeValue(Attribute.MOVEMENT_SPEED));
this.navigator.tick();
// Fire, item pickup, ...
super.update(time);

View File

@ -16,8 +16,7 @@ public enum EntitySpawnType {
packet.uuid = entity.getUuid();
packet.type = entity.getEntityType().id();
packet.position = entity.getPosition();
if (entity.getEntityMeta() instanceof ObjectDataProvider) {
ObjectDataProvider objectDataProvider = (ObjectDataProvider) entity.getEntityMeta();
if (entity.getEntityMeta() instanceof ObjectDataProvider objectDataProvider) {
packet.data = objectDataProvider.getObjectData();
if (objectDataProvider.requiresVelocityPacketAtSpawn()) {
final var velocity = entity.getVelocityForPacket();
@ -57,8 +56,7 @@ public enum EntitySpawnType {
SpawnExperienceOrbPacket packet = new SpawnExperienceOrbPacket();
packet.entityId = entity.getEntityId();
packet.position = entity.getPosition();
if (entity.getEntityMeta() instanceof ExperienceOrbMeta) {
ExperienceOrbMeta experienceOrbMeta = (ExperienceOrbMeta) entity.getEntityMeta();
if (entity.getEntityMeta() instanceof ExperienceOrbMeta experienceOrbMeta) {
packet.expCount = (short) experienceOrbMeta.getCount();
}
return packet;
@ -70,8 +68,7 @@ public enum EntitySpawnType {
SpawnPaintingPacket packet = new SpawnPaintingPacket();
packet.entityId = entity.getEntityId();
packet.entityUuid = entity.getUuid();
if (entity.getEntityMeta() instanceof PaintingMeta) {
PaintingMeta paintingMeta = (PaintingMeta) entity.getEntityMeta();
if (entity.getEntityMeta() instanceof PaintingMeta paintingMeta) {
packet.motive = paintingMeta.getMotive().ordinal();
packet.position = new Vec(
Math.max(0, (paintingMeta.getMotive().getWidth() >> 1) - 1),
@ -79,18 +76,10 @@ public enum EntitySpawnType {
0
);
switch (paintingMeta.getDirection()) {
case SOUTH:
packet.direction = 0;
break;
case WEST:
packet.direction = 1;
break;
case NORTH:
packet.direction = 2;
break;
case EAST:
packet.direction = 3;
break;
case SOUTH -> packet.direction = 0;
case WEST -> packet.direction = 1;
case NORTH -> packet.direction = 2;
case EAST -> packet.direction = 3;
}
} else {
packet.position = Vec.ZERO;

View File

@ -3,15 +3,13 @@ package net.minestom.server.entity;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
@ApiStatus.NonExtendable
public interface EntityType extends ProtocolObject, EntityTypeConstants {
public sealed interface EntityType extends ProtocolObject, EntityTypes permits EntityTypeImpl {
/**
* Returns the entity registry.
*

View File

@ -22,10 +22,7 @@ import net.minestom.server.entity.metadata.monster.raider.*;
import net.minestom.server.entity.metadata.monster.skeleton.SkeletonMeta;
import net.minestom.server.entity.metadata.monster.skeleton.StrayMeta;
import net.minestom.server.entity.metadata.monster.skeleton.WitherSkeletonMeta;
import net.minestom.server.entity.metadata.monster.zombie.DrownedMeta;
import net.minestom.server.entity.metadata.monster.zombie.ZombieMeta;
import net.minestom.server.entity.metadata.monster.zombie.ZombieVillagerMeta;
import net.minestom.server.entity.metadata.monster.zombie.ZombifiedPiglinMeta;
import net.minestom.server.entity.metadata.monster.zombie.*;
import net.minestom.server.entity.metadata.other.*;
import net.minestom.server.entity.metadata.villager.VillagerMeta;
import net.minestom.server.entity.metadata.villager.WanderingTraderMeta;
@ -45,7 +42,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
final class EntityTypeImpl implements EntityType {
record EntityTypeImpl(Registry.EntityEntry registry) implements EntityType {
private static final Registry.Container<EntityType> CONTAINER = new Registry.Container<>(Registry.Resource.ENTITIES,
(container, namespace, object) -> container.register(new EntityTypeImpl(Registry.entity(namespace, object, null))));
private static final Map<String, BiFunction<Entity, Metadata, EntityMeta>> ENTITY_META_SUPPLIER = createMetaMap();
@ -119,6 +116,7 @@ final class EntityTypeImpl implements EntityType {
supplier.put("minecraft:guardian", GuardianMeta::new);
supplier.put("minecraft:hoglin", HoglinMeta::new);
supplier.put("minecraft:horse", HorseMeta::new);
supplier.put("minecraft:husk", HuskMeta::new);
supplier.put("minecraft:illusioner", IllusionerMeta::new);
supplier.put("minecraft:iron_golem", IronGolemMeta::new);
supplier.put("minecraft:item", ItemEntityMeta::new);
@ -244,17 +242,6 @@ final class EntityTypeImpl implements EntityType {
return result;
}
private final Registry.EntityEntry registry;
EntityTypeImpl(Registry.EntityEntry registry) {
this.registry = registry;
}
@Override
public @NotNull Registry.EntityEntry registry() {
return registry;
}
@Override
public String toString() {
return name();

View File

@ -3,37 +3,44 @@ package net.minestom.server.entity;
import net.minestom.server.item.attribute.AttributeSlot;
import org.jetbrains.annotations.NotNull;
import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*;
public enum EquipmentSlot {
MAIN_HAND,
OFF_HAND,
BOOTS,
LEGGINGS,
CHESTPLATE,
HELMET;
MAIN_HAND(false, -1),
OFF_HAND(false, -1),
BOOTS(true, BOOTS_SLOT),
LEGGINGS(true, LEGGINGS_SLOT),
CHESTPLATE(true, CHESTPLATE_SLOT),
HELMET(true, HELMET_SLOT);
private final boolean armor;
private final int armorSlot;
EquipmentSlot(boolean armor, int armorSlot) {
this.armor = armor;
this.armorSlot = armorSlot;
}
public boolean isHand() {
return this == MAIN_HAND || this == OFF_HAND;
return !armor;
}
public boolean isArmor() {
return !isHand();
return armor;
}
public int armorSlot() {
return armorSlot;
}
public static EquipmentSlot fromAttributeSlot(@NotNull AttributeSlot attributeSlot) {
switch (attributeSlot) {
case MAINHAND:
return MAIN_HAND;
case OFFHAND:
return OFF_HAND;
case FEET:
return BOOTS;
case LEGS:
return LEGGINGS;
case CHEST:
return CHESTPLATE;
case HEAD:
return HELMET;
}
throw new IllegalStateException("Something weird happened");
return switch (attributeSlot) {
case MAINHAND -> MAIN_HAND;
case OFFHAND -> OFF_HAND;
case FEET -> BOOTS;
case LEGS -> LEGGINGS;
case CHEST -> CHESTPLATE;
case HEAD -> HELMET;
};
}
}

View File

@ -3,7 +3,7 @@ package net.minestom.server.entity;
import net.minestom.server.entity.metadata.item.ItemEntityMeta;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.entity.EntityItemMergeEvent;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.EntityTracker;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.StackingRule;
import net.minestom.server.utils.time.Cooldown;
@ -13,7 +13,6 @@ import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.time.temporal.TemporalUnit;
import java.util.Set;
/**
* Represents an item on the ground.
@ -71,47 +70,26 @@ public class ItemEntity extends Entity {
(mergeDelay == null || !Cooldown.hasCooldown(time, lastMergeCheck, mergeDelay))) {
this.lastMergeCheck = time;
final Chunk chunk = instance.getChunkAt(getPosition());
final Set<Entity> entities = instance.getChunkEntities(chunk);
for (Entity entity : entities) {
if (entity instanceof ItemEntity) {
this.instance.getEntityTracker().nearbyEntities(position, mergeRange,
EntityTracker.Target.ITEMS, itemEntity -> {
if (itemEntity == this) return;
if (!itemEntity.isPickable() || !itemEntity.isMergeable()) return;
if (getDistance(itemEntity) > mergeRange) return;
// Do not merge with itself
if (entity == this)
continue;
final ItemStack itemStackEntity = itemEntity.getItemStack();
final StackingRule stackingRule = itemStack.getStackingRule();
final boolean canStack = stackingRule.canBeStacked(itemStack, itemStackEntity);
final ItemEntity itemEntity = (ItemEntity) entity;
if (!itemEntity.isPickable() || !itemEntity.isMergeable())
continue;
// Too far, do not merge
if (getDistance(itemEntity) > mergeRange)
continue;
final ItemStack itemStackEntity = itemEntity.getItemStack();
final StackingRule stackingRule = itemStack.getStackingRule();
final boolean canStack = stackingRule.canBeStacked(itemStack, itemStackEntity);
if (!canStack)
continue;
final int totalAmount = stackingRule.getAmount(itemStack) + stackingRule.getAmount(itemStackEntity);
final boolean canApply = stackingRule.canApply(itemStack, totalAmount);
if (!canApply)
continue;
final ItemStack result = stackingRule.apply(itemStack, totalAmount);
EntityItemMergeEvent entityItemMergeEvent = new EntityItemMergeEvent(this, itemEntity, result);
EventDispatcher.callCancellable(entityItemMergeEvent, () -> {
setItemStack(entityItemMergeEvent.getResult());
itemEntity.remove();
if (!canStack) return;
final int totalAmount = stackingRule.getAmount(itemStack) + stackingRule.getAmount(itemStackEntity);
if (!stackingRule.canApply(itemStack, totalAmount)) return;
final ItemStack result = stackingRule.apply(itemStack, totalAmount);
EntityItemMergeEvent entityItemMergeEvent = new EntityItemMergeEvent(this, itemEntity, result);
EventDispatcher.callCancellable(entityItemMergeEvent, () -> {
setItemStack(entityItemMergeEvent.getResult());
itemEntity.remove();
});
});
}
}
}
}

View File

@ -15,8 +15,7 @@ import net.minestom.server.event.entity.EntityDeathEvent;
import net.minestom.server.event.entity.EntityFireEvent;
import net.minestom.server.event.item.EntityEquipEvent;
import net.minestom.server.event.item.PickupItemEvent;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.EntityTracker;
import net.minestom.server.inventory.EquipmentHandler;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.ConnectionState;
@ -201,32 +200,21 @@ public class LivingEntity extends Entity implements EquipmentHandler {
// Items picking
if (canPickupItem() && itemPickupCooldown.isReady(time)) {
itemPickupCooldown.refreshLastUpdate(time);
final Chunk chunk = getChunk(); // TODO check surrounding chunks
final Set<Entity> entities = instance.getChunkEntities(chunk);
for (Entity entity : entities) {
if (entity instanceof ItemEntity) {
// Do not pick up if not visible
if (this instanceof Player && !entity.isViewer((Player) this))
continue;
final ItemEntity itemEntity = (ItemEntity) entity;
if (!itemEntity.isPickable())
continue;
final BoundingBox itemBoundingBox = itemEntity.getBoundingBox();
if (expandedBoundingBox.intersect(itemBoundingBox)) {
if (itemEntity.shouldRemove() || itemEntity.isRemoveScheduled())
continue;
PickupItemEvent pickupItemEvent = new PickupItemEvent(this, itemEntity);
EventDispatcher.callCancellable(pickupItemEvent, () -> {
final ItemStack item = itemEntity.getItemStack();
sendPacketToViewersAndSelf(new CollectItemPacket(itemEntity.getEntityId(), getEntityId(), item.getAmount()));
entity.remove();
});
}
}
}
this.instance.getEntityTracker().nearbyEntities(position, expandedBoundingBox.getWidth(),
EntityTracker.Target.ITEMS, itemEntity -> {
if (this instanceof Player player && !itemEntity.isViewer(player)) return;
if (!itemEntity.isPickable()) return;
final BoundingBox itemBoundingBox = itemEntity.getBoundingBox();
if (expandedBoundingBox.intersect(itemBoundingBox)) {
if (itemEntity.shouldRemove() || itemEntity.isRemoveScheduled()) return;
PickupItemEvent pickupItemEvent = new PickupItemEvent(this, itemEntity);
EventDispatcher.callCancellable(pickupItemEvent, () -> {
final ItemStack item = itemEntity.getItemStack();
sendPacketToViewersAndSelf(new CollectItemPacket(itemEntity.getEntityId(), getEntityId(), item.getAmount()));
itemEntity.remove();
});
}
});
}
}
@ -355,8 +343,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
sendPacketToViewersAndSelf(new EntityAnimationPacket(getEntityId(), EntityAnimationPacket.Animation.TAKE_DAMAGE));
// Additional hearts support
if (this instanceof Player) {
final Player player = (Player) this;
if (this instanceof Player player) {
final float additionalHearts = player.getAdditionalHearts();
if (additionalHearts > 0) {
if (remainingDamage > additionalHearts) {
@ -477,8 +464,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
protected void onAttributeChanged(@NotNull AttributeInstance attributeInstance) {
if (attributeInstance.getAttribute().isShared()) {
boolean self = false;
if (this instanceof Player) {
Player player = (Player) this;
if (this instanceof Player player) {
PlayerConnection playerConnection = player.playerConnection;
// connection null during Player initialization (due to #super call)
self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY;
@ -531,17 +517,11 @@ public class LivingEntity extends Entity implements EquipmentHandler {
}
@Override
protected boolean addViewer0(@NotNull Player player) {
if (!super.addViewer0(player)) {
return false;
}
final PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(getEquipmentsPacket());
playerConnection.sendPacket(getPropertiesPacket());
if (getTeam() != null) {
playerConnection.sendPacket(getTeam().createTeamsCreationPacket());
}
return true;
public void updateNewViewer(@NotNull Player player) {
super.updateNewViewer(player);
player.sendPacket(getEquipmentsPacket());
player.sendPacket(getPropertiesPacket());
if (getTeam() != null) player.sendPacket(getTeam().createTeamsCreationPacket());
}
@Override
@ -682,20 +662,10 @@ public class LivingEntity extends Entity implements EquipmentHandler {
*/
public void setTeam(Team team) {
if (this.team == team) return;
String member;
if (this instanceof Player) {
Player player = (Player) this;
member = player.getUsername();
} else {
member = this.uuid.toString();
}
String member = this instanceof Player player ? player.getUsername() : uuid.toString();
if (this.team != null) {
this.team.removeMember(member);
}
this.team = team;
if (team != null) {
team.addMember(member);
@ -711,46 +681,6 @@ public class LivingEntity extends Entity implements EquipmentHandler {
return team;
}
/**
* Gets the line of sight of the entity.
*
* @param maxDistance The max distance to scan
* @return A list of {@link Point poiints} in this entities line of sight
*/
public List<Point> getLineOfSight(int maxDistance) {
List<Point> blocks = new ArrayList<>();
Iterator<Point> it = new BlockIterator(this, maxDistance);
while (it.hasNext()) {
final Point position = it.next();
if (!getInstance().getBlock(position).isAir()) blocks.add(position);
}
return blocks;
}
/**
* Checks whether the current entity has line of sight to the given one.
* If so, it doesn't mean that the given entity is IN line of sight of the current,
* but the current one can rotate so that it will be true.
*
* @param entity the entity to be checked.
* @return if the current entity has line of sight to the given one.
*/
public boolean hasLineOfSight(Entity entity) {
final var start = getPosition().asVec().add(0D, getEyeHeight(), 0D);
final var end = entity.getPosition().asVec().add(0D, getEyeHeight(), 0D);
final var direction = end.sub(start);
final int maxDistance = (int) Math.ceil(direction.length());
Iterator<Point> it = new BlockIterator(start, direction.normalize(), 0D, maxDistance);
while (it.hasNext()) {
Block block = getInstance().getBlock(it.next());
if (!block.isAir() && !block.isLiquid()) {
return false;
}
}
return true;
}
/**
* Gets the target (not-air) block position of the entity.
*

View File

@ -291,49 +291,28 @@ public class Metadata {
}
private static <T> Value<T> getCorrespondingNewEmptyValue(int type) {
switch (type) {
case TYPE_BYTE:
return (Value<T>) Byte((byte) 0);
case TYPE_VARINT:
return (Value<T>) VarInt(0);
case TYPE_FLOAT:
return (Value<T>) Float(0);
case TYPE_STRING:
return (Value<T>) String("");
case TYPE_CHAT:
return (Value<T>) Chat(Component.empty());
case TYPE_OPTCHAT:
return (Value<T>) OptChat(null);
case TYPE_SLOT:
return (Value<T>) Slot(ItemStack.AIR);
case TYPE_BOOLEAN:
return (Value<T>) Boolean(false);
case TYPE_ROTATION:
return (Value<T>) Rotation(Vec.ZERO);
case TYPE_POSITION:
return (Value<T>) Position(Vec.ZERO);
case TYPE_OPTPOSITION:
return (Value<T>) OptPosition(null);
case TYPE_DIRECTION:
return (Value<T>) Direction(Direction.DOWN);
case TYPE_OPTUUID:
return (Value<T>) OptUUID(null);
case TYPE_OPTBLOCKID:
return (Value<T>) OptBlockID(null);
case TYPE_NBT:
return (Value<T>) NBT(new NBTEnd());
case TYPE_PARTICLE:
throw new UnsupportedOperationException();
case TYPE_VILLAGERDATA:
return (Value<T>) VillagerData(0, 0, 0);
case TYPE_OPTVARINT:
return (Value<T>) OptVarInt(null);
case TYPE_POSE:
return (Value<T>) Pose(Entity.Pose.STANDING);
default:
throw new UnsupportedOperationException();
}
return switch (type) {
case TYPE_BYTE -> (Value<T>) Byte((byte) 0);
case TYPE_VARINT -> (Value<T>) VarInt(0);
case TYPE_FLOAT -> (Value<T>) Float(0);
case TYPE_STRING -> (Value<T>) String("");
case TYPE_CHAT -> (Value<T>) Chat(Component.empty());
case TYPE_OPTCHAT -> (Value<T>) OptChat(null);
case TYPE_SLOT -> (Value<T>) Slot(ItemStack.AIR);
case TYPE_BOOLEAN -> (Value<T>) Boolean(false);
case TYPE_ROTATION -> (Value<T>) Rotation(Vec.ZERO);
case TYPE_POSITION -> (Value<T>) Position(Vec.ZERO);
case TYPE_OPTPOSITION -> (Value<T>) OptPosition(null);
case TYPE_DIRECTION -> (Value<T>) Direction(Direction.DOWN);
case TYPE_OPTUUID -> (Value<T>) OptUUID(null);
case TYPE_OPTBLOCKID -> (Value<T>) OptBlockID(null);
case TYPE_NBT -> (Value<T>) NBT(new NBTEnd());
case TYPE_PARTICLE -> throw new UnsupportedOperationException();
case TYPE_VILLAGERDATA -> (Value<T>) VillagerData(0, 0, 0);
case TYPE_OPTVARINT -> (Value<T>) OptVarInt(null);
case TYPE_POSE -> (Value<T>) Pose(Entity.Pose.STANDING);
default -> throw new UnsupportedOperationException();
};
}
private static <T> Value<T> read(int type, BinaryReader reader) {

View File

@ -12,8 +12,8 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEvent.ShowEntity;
import net.kyori.adventure.text.event.HoverEventSource;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.title.Title;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.title.TitlePart;
import net.minestom.server.MinecraftServer;
import net.minestom.server.advancements.AdvancementTab;
import net.minestom.server.adventure.AdventurePacketConvertor;
@ -32,12 +32,14 @@ import net.minestom.server.entity.fakeplayer.FakePlayer;
import net.minestom.server.entity.metadata.PlayerMeta;
import net.minestom.server.entity.vehicle.PlayerVehicleInformation;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.GlobalHandles;
import net.minestom.server.event.inventory.InventoryOpenEvent;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.item.ItemUpdateStateEvent;
import net.minestom.server.event.item.PickupExperienceEvent;
import net.minestom.server.event.player.*;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.EntityTracker;
import net.minestom.server.instance.Instance;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.PlayerInventory;
@ -50,6 +52,7 @@ import net.minestom.server.message.Messenger;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.PlayerProvider;
import net.minestom.server.network.packet.FramedPacket;
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;
@ -63,13 +66,11 @@ import net.minestom.server.resourcepack.ResourcePack;
import net.minestom.server.scoreboard.BelowNameTag;
import net.minestom.server.scoreboard.Team;
import net.minestom.server.statistic.PlayerStatistic;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.TickUtils;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.entity.EntityUtils;
import net.minestom.server.utils.function.IntegerBiConsumer;
import net.minestom.server.utils.identity.NamedAndIdentified;
import net.minestom.server.utils.instance.InstanceUtils;
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
@ -85,9 +86,9 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
/**
@ -98,14 +99,14 @@ import java.util.function.UnaryOperator;
*/
public class Player extends LivingEntity implements CommandSender, Localizable, HoverEventSource<ShowEntity>, Identified, NamedAndIdentified {
private static final Component REMOVE_MESSAGE = Component.text("You have been removed from the server without reason.", NamedTextColor.RED);
private long lastKeepAlive;
private boolean answerKeepAlive;
private String username;
private Component usernameComponent;
protected final PlayerConnection playerConnection;
// All the entities that this player can see
protected final Set<Entity> viewableEntities = ConcurrentHashMap.newKeySet();
private int latency;
private Component displayName;
@ -113,8 +114,29 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
private DimensionType dimensionType;
private GameMode gameMode;
// Chunks that the player can view
protected final Set<Chunk> viewableChunks = ConcurrentHashMap.newKeySet();
final IntegerBiConsumer chunkAdder = (chunkX, chunkZ) -> {
// Load new chunks
this.instance.loadOptionalChunk(chunkX, chunkZ).thenAccept(chunk -> {
try {
if (chunk != null) {
chunk.sendChunk(this);
GlobalHandles.PLAYER_CHUNK_LOAD.call(new PlayerChunkLoadEvent(this, chunkX, chunkZ));
}
} catch (Exception e) {
MinecraftServer.getExceptionManager().handleException(e);
}
});
};
final IntegerBiConsumer chunkRemover = (chunkX, chunkZ) -> {
// Unload old chunks
final Instance instance = this.instance;
if (instance == null) return;
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
if (chunk != null) {
sendPacket(new UnloadChunkPacket(chunkX, chunkZ));
GlobalHandles.PLAYER_CHUNK_UNLOAD.call(new PlayerChunkUnloadEvent(this, chunkX, chunkZ));
}
};
private final AtomicInteger teleportId = new AtomicInteger();
private int receivedTeleportId;
@ -166,9 +188,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Vehicle
private final PlayerVehicleInformation vehicleInformation = new PlayerVehicleInformation();
// Tick related
private final PlayerTickEvent playerTickEvent = new PlayerTickEvent(this);
// Adventure
private Identity identity;
private final Pointers pointers;
@ -276,7 +295,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Recipes end
// Tags
this.playerConnection.sendPacket(TagsPacket.getRequiredTagsPacket());
this.playerConnection.sendPacket(TagsPacket.DEFAULT_TAGS);
// Some client updates
this.playerConnection.sendPacket(getPropertiesPacket()); // Send default properties
@ -309,23 +328,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Experience orb pickup
if (experiencePickupCooldown.isReady(time)) {
experiencePickupCooldown.refreshLastUpdate(time);
final Chunk chunk = getChunk(); // TODO check surrounding chunks
final Set<Entity> entities = instance.getChunkEntities(chunk);
for (Entity entity : entities) {
if (entity instanceof ExperienceOrb) {
final ExperienceOrb experienceOrb = (ExperienceOrb) entity;
final BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
if (expandedBoundingBox.intersect(itemBoundingBox)) {
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
continue;
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(experienceOrb);
EventDispatcher.callCancellable(pickupExperienceEvent, () -> {
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
entity.remove();
});
}
}
}
this.instance.getEntityTracker().nearbyEntities(position, expandedBoundingBox.getWidth(),
EntityTracker.Target.EXPERIENCE_ORBS, experienceOrb -> {
final BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
if (expandedBoundingBox.intersect(itemBoundingBox)) {
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
return;
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(this, experienceOrb);
EventDispatcher.callCancellable(pickupExperienceEvent, () -> {
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
experienceOrb.remove();
});
}
});
}
// Eating animation
@ -353,7 +368,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
}
// Tick event
EventDispatcher.call(playerTickEvent);
GlobalHandles.PLAYER_TICK.call(new PlayerTickEvent(this));
}
@Override
@ -420,6 +435,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
getPlayerConnection().sendPacket(respawnPacket);
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this);
EventDispatcher.call(respawnEvent);
triggerStatus((byte) (24 + permissionLevel)); // Set permission level
refreshIsDead(false);
// Runnable called when teleportation is successful (after loading and sending necessary chunk)
@ -448,9 +464,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
EventDispatcher.call(new PlayerDisconnectEvent(this));
super.remove();
this.packets.clear();
if (getOpenInventory() != null) {
getOpenInventory().removeViewer(this);
}
final Inventory currentInventory = getOpenInventory();
if (currentInventory != null) currentInventory.removeViewer(this);
MinecraftServer.getBossBarManager().removeAllBossBars(this);
// Advancement tabs cache
{
@ -461,24 +476,29 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
}
}
}
final Pos position = this.position;
final int chunkX = position.chunkX();
final int chunkZ = position.chunkZ();
// Clear all viewable entities
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
this.instance.getEntityTracker().visibleEntities(chunkX, chunkZ, EntityTracker.Target.ENTITIES,
trackingUpdate::remove);
// Clear all viewable chunks
this.viewableChunks.forEach(chunk -> chunk.removeViewer(this));
ChunkUtils.forChunksInRange(chunkX, chunkZ, MinecraftServer.getChunkViewDistance(), chunkRemover);
// Remove from the tab-list
PacketUtils.broadcastPacket(getRemovePlayerToList());
// Prevent the player from being stuck in loading screen, or just unable to interact with the server
// This should be considered as a bug, since the player will ultimately time out anyway.
if (playerConnection.isOnline()) kick(REMOVE_MESSAGE);
}
@Override
protected boolean removeViewer0(@NotNull Player player) {
if (player == this || !super.removeViewer0(player)) {
return false;
}
public void updateOldViewer(@NotNull Player player) {
super.updateOldViewer(player);
// Team
if (this.getTeam() != null && this.getTeam().getMembers().size() == 1) {// If team only contains "this" player
player.getPlayerConnection().sendPacket(this.getTeam().createTeamDestructionPacket());
player.sendPacket(this.getTeam().createTeamDestructionPacket());
}
return true;
}
@Override
@ -487,6 +507,12 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
super.sendPacketToViewersAndSelf(packet);
}
@Override
public void sendPacketToViewersAndSelf(@NotNull FramedPacket framedPacket) {
this.playerConnection.sendPacket(framedPacket);
super.sendPacketToViewersAndSelf(framedPacket);
}
/**
* Changes the player instance and load surrounding chunks if needed.
* <p>
@ -501,18 +527,37 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
final Instance currentInstance = this.instance;
Check.argCondition(currentInstance == 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 (e.g. SharedInstance)
if (!InstanceUtils.areLinked(currentInstance, instance) || !spawnPosition.sameChunk(this.position)) {
final boolean firstSpawn = currentInstance == null;
return instance.loadOptionalChunk(spawnPosition)
.thenRun(() -> spawnPlayer(instance, spawnPosition, firstSpawn,
!Objects.equals(dimensionType, instance.getDimensionType()), true));
} else {
if (InstanceUtils.areLinked(currentInstance, instance) && spawnPosition.sameChunk(this.position)) {
// 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
return AsyncUtils.VOID_FUTURE
.thenRun(() -> spawnPlayer(instance, spawnPosition, false, false, false));
spawnPlayer(instance, spawnPosition, null, false, false, false);
return AsyncUtils.VOID_FUTURE;
}
// Must update the player chunks
final boolean dimensionChange = !Objects.equals(dimensionType, instance.getDimensionType());
final Thread runThread = Thread.currentThread();
final Consumer<Instance> runnable = (i) -> spawnPlayer(i, spawnPosition,
currentInstance,
currentInstance == null, dimensionChange, true);
// Wait for all surrounding chunks to load
List<CompletableFuture<Chunk>> futures = new ArrayList<>();
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(),
(chunkX, chunkZ) -> futures.add(instance.loadOptionalChunk(chunkX, chunkZ)));
return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new))
.thenCompose(unused -> {
if (runThread == Thread.currentThread()) {
runnable.accept(instance);
return AsyncUtils.VOID_FUTURE;
} else {
// Complete the future during the next instance tick
CompletableFuture<Void> future = new CompletableFuture<>();
instance.scheduleNextTick(i -> {
runnable.accept(i);
future.complete(null);
});
return future;
}
});
}
/**
@ -536,42 +581,42 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
* <p>
* UNSAFE: only called with {@link #setInstance(Instance, Pos)}.
*
* @param spawnPosition the position to teleport the player
* @param firstSpawn true if this is the player first spawn
* @param updateChunks true if chunks should be refreshed, false if the new instance shares the same
* chunks
* @param spawnPosition the position to teleport the player
* @param previousInstance the previous player instance, null if first spawn
* @param firstSpawn true if this is the player first spawn
* @param updateChunks true if chunks should be refreshed, false if the new instance shares the same
* chunks
*/
private void spawnPlayer(@NotNull Instance instance, @NotNull Pos spawnPosition,
@Nullable Instance previousInstance,
boolean firstSpawn, boolean dimensionChange, boolean updateChunks) {
final Set<Chunk> previousChunks = Set.copyOf(viewableChunks);
if (!firstSpawn) {
// Player instance changed, clear current viewable collections
previousChunks.forEach(chunk -> chunk.removeViewer(this));
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
if (updateChunks)
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(), chunkRemover);
}
if (previousInstance != null) {
previousInstance.getEntityTracker().visibleEntities(position,
EntityTracker.Target.ENTITIES, trackingUpdate::remove);
}
if (dimensionChange) sendDimension(instance.getDimensionType());
super.setInstance(instance, spawnPosition);
if (dimensionChange) {
sendDimension(instance.getDimensionType());
if (updateChunks) {
sendPacket(new UpdateViewPositionPacket(spawnPosition.chunkX(), spawnPosition.chunkZ()));
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(), chunkAdder);
}
if (updateChunks) {
// Warning: loop to remove once `refreshVisibleChunks` manage it
previousChunks.forEach(chunk ->
playerConnection.sendPacket(new UnloadChunkPacket(chunk.getChunkX(), chunk.getChunkZ())));
refreshVisibleChunks();
}
synchronizePosition(true); // So the player doesn't get stuck
if (dimensionChange || firstSpawn) {
synchronizePosition(true); // So the player doesn't get stuck
this.inventory.update();
}
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
EventDispatcher.call(spawnEvent);
this.playerConnection.flush();
EventDispatcher.call(new PlayerSpawnEvent(this, instance, firstSpawn));
}
/**
@ -596,14 +641,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
sendPluginMessage(channel, message.getBytes(StandardCharsets.UTF_8));
}
/**
* @deprecated Use {@link #sendMessage(Component)}
*/
@Deprecated
public void sendJsonMessage(@NotNull String json) {
this.sendMessage(GsonComponentSerializer.gson().deserialize(json));
}
@Override
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
Messenger.sendMessage(this, message, ChatPosition.fromMessageType(type), source.uuid());
@ -673,16 +710,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
}
@Override
public void showTitle(@NotNull Title title) {
playerConnection.sendPacket(new SetTitleTextPacket(title.title()));
playerConnection.sendPacket(new SetTitleSubTitlePacket(title.subtitle()));
final var times = title.times();
if (times != null) {
playerConnection.sendPacket(new SetTitleTimePacket(
TickUtils.fromDuration(times.fadeIn(), TickUtils.CLIENT_TICK_MS),
TickUtils.fromDuration(times.stay(), TickUtils.CLIENT_TICK_MS),
TickUtils.fromDuration(times.fadeOut(), TickUtils.CLIENT_TICK_MS)));
}
public <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value) {
playerConnection.sendPacket(AdventurePacketConvertor.createTitlePartPacket(part, value));
}
@Override
@ -690,20 +719,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
playerConnection.sendPacket(new ActionBarPacket(message));
}
/**
* Specifies the display time of a title.
*
* @param fadeIn ticks to spend fading in
* @param stay ticks to keep the title displayed
* @param fadeOut ticks to spend out, not when to start fading out
* @deprecated Use {@link #showTitle(Title)}. Note that this will overwrite the
* existing title. This is expected behavior and will be the case in 1.17.
*/
@Deprecated
public void sendTitleTime(int fadeIn, int stay, int fadeOut) {
playerConnection.sendPacket(new SetTitleTimePacket(fadeIn, stay, fadeOut));
}
@Override
public void resetTitle() {
playerConnection.sendPacket(new ClearTitlesPacket(true));
@ -1099,13 +1114,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
sendPacketToViewersAndSelf(getEquipmentsPacket());
getInventory().update();
{
// Send new chunks
final Chunk chunk = instance.getChunkAt(position);
Check.notNull(chunk, "Tried to interact with an unloaded chunk.");
refreshVisibleChunks(chunk);
}
}
/**
@ -1160,87 +1168,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
this.playerConnection.sendPacket(new SetExperiencePacket(exp, level, 0));
}
/**
* Called when the player changes chunk (move from one to another).
* Can also be used to refresh the list of chunks that the client should see based on {@link #getChunkRange()}.
* <p>
* It does remove and add the player from the chunks viewers list when removed or added.
* It also calls the events {@link PlayerChunkUnloadEvent} and {@link PlayerChunkLoadEvent}.
*
* @param newChunk the current/new player chunk (can be the current one)
*/
public void refreshVisibleChunks(@NotNull Chunk newChunk) {
// Previous chunks indexes
final long[] lastVisibleChunks = viewableChunks.stream().mapToLong(ChunkUtils::getChunkIndex).toArray();
// New chunks indexes
final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(newChunk.toPosition(), getChunkRange());
// Update client render distance
updateViewPosition(newChunk.getChunkX(), newChunk.getChunkZ());
// Unload old chunks
ArrayUtils.forDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks, chunkIndex -> {
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
//playerConnection.sendPacket(new UnloadChunkPacket(chunkX, chunkZ));
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
if (chunk != null) {
chunk.removeViewer(this);
}
});
// Load new chunks
ArrayUtils.forDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks, chunkIndex -> {
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
this.instance.loadOptionalChunk(chunkX, chunkZ).thenAccept(chunk -> {
if (chunk == null) {
// Cannot load chunk (auto load is not enabled)
return;
}
chunk.addViewer(this);
});
});
}
public void refreshVisibleChunks() {
final Chunk chunk = getChunk();
if (chunk != null) {
refreshVisibleChunks(chunk);
}
}
/**
* Refreshes the list of entities that the player should be able to see based
* on {@link MinecraftServer#getEntityViewDistance()} and {@link Entity#isAutoViewable()}.
*
* @param newChunk the new chunk of the player (can be the current one)
*/
public void refreshVisibleEntities(@NotNull Chunk newChunk) {
final int entityViewDistance = MinecraftServer.getEntityViewDistance();
final float maximalDistance = entityViewDistance * Chunk.CHUNK_SECTION_SIZE;
// Manage already viewable entities
this.viewableEntities.stream()
.filter(entity -> entity.getDistance(this) > maximalDistance)
.forEach(entity -> {
// Entity shouldn't be viewable anymore
if (isAutoViewable()) {
entity.removeViewer(this);
}
if (entity instanceof Player && entity.isAutoViewable()) {
removeViewer((Player) entity);
}
});
// Manage entities in unchecked chunks
EntityUtils.forEachRange(instance, newChunk.toPosition(), entityViewDistance, entity -> {
if (entity.isAutoViewable() && !entity.viewers.contains(this)) {
entity.addViewer(this);
}
if (entity instanceof Player && isAutoViewable() && !viewers.contains(entity)) {
addViewer((Player) entity);
}
});
}
/**
* Gets the player connection.
* <p>
@ -1248,11 +1175,25 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
*
* @return the player connection
*/
@NotNull
public PlayerConnection getPlayerConnection() {
public @NotNull PlayerConnection getPlayerConnection() {
return playerConnection;
}
/**
* Shortcut for {@link PlayerConnection#sendPacket(ServerPacket)}.
*
* @param packet the packet to send
*/
@ApiStatus.Experimental
public void sendPacket(@NotNull ServerPacket packet) {
this.playerConnection.sendPacket(packet);
}
@ApiStatus.Experimental
public void sendPacket(@NotNull FramedPacket framedPacket) {
this.playerConnection.sendPacket(framedPacket);
}
/**
* Gets if the player is online or not.
*
@ -1267,8 +1208,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
*
* @return the player settings
*/
@NotNull
public PlayerSettings getSettings() {
public @NotNull PlayerSettings getSettings() {
return settings;
}
@ -1281,8 +1221,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
return dimensionType;
}
@NotNull
public PlayerInventory getInventory() {
public @NotNull PlayerInventory getInventory() {
return inventory;
}
@ -1429,8 +1368,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
*
* @return the currently open inventory, null if there is not (player inventory is not detected)
*/
@Nullable
public Inventory getOpenInventory() {
public @Nullable Inventory getOpenInventory() {
return openInventory;
}
@ -1441,7 +1379,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
* @return true if the inventory has been opened/sent to the player, false otherwise (cancelled by event)
*/
public boolean openInventory(@NotNull Inventory inventory) {
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this);
EventDispatcher.callCancellable(inventoryOpenEvent, () -> {
@ -1451,7 +1388,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
}
Inventory newInventory = inventoryOpenEvent.getInventory();
if (newInventory == null) {
// just close the inventory
return;
@ -1463,9 +1399,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
playerConnection.sendPacket(openWindowPacket);
newInventory.addViewer(this);
this.openInventory = newInventory;
});
return !inventoryOpenEvent.isCancelled();
}
@ -1528,26 +1462,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
this.didCloseInventory = didCloseInventory;
}
/**
* Gets the player viewable chunks.
* <p>
* WARNING: adding or removing a chunk there will not load/unload it,
* use {@link Chunk#addViewer(Player)} or {@link Chunk#removeViewer(Player)}.
*
* @return a {@link Set} containing all the chunks that the player sees
*/
public Set<Chunk> getViewableChunks() {
return viewableChunks;
}
/**
* Sends a {@link UpdateViewPositionPacket} to the player.
*
* @param chunkX the chunk X
* @param chunkZ the chunk Z
*/
public void updateViewPosition(int chunkX, int chunkZ) {
playerConnection.sendPacket(new UpdateViewPositionPacket(chunkX, chunkZ));
public int getNextTeleportId() {
return teleportId.incrementAndGet();
}
public int getLastSentTeleportId() {
@ -1569,7 +1485,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@ApiStatus.Internal
protected void synchronizePosition(boolean includeSelf) {
if (includeSelf) {
playerConnection.sendPacket(new PlayerPositionAndLookPacket(position, (byte) 0x00, teleportId.incrementAndGet(), false));
playerConnection.sendPacket(new PlayerPositionAndLookPacket(position, (byte) 0x00, getNextTeleportId(), false));
}
super.synchronizePosition(includeSelf);
}
@ -1643,6 +1559,15 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
refreshAbilities();
}
@Override
public void setSneaking(boolean sneaking) {
if (isFlying()) { //If we are flying, don't set the players pose to sneaking as this can clip them through blocks
this.entityMeta.setSneaking(sneaking);
} else {
super.setSneaking(sneaking);
}
}
/**
* Gets if the player is currently flying.
*
@ -1658,7 +1583,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
* @param flying should the player fly
*/
public void setFlying(boolean flying) {
this.flying = flying;
refreshFlying(flying);
refreshAbilities();
}
@ -1671,6 +1596,17 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
* @see #setFlying(boolean) instead
*/
public void refreshFlying(boolean flying) {
//When the player starts or stops flying, their pose needs to change
if (this.flying != flying) {
Pose pose = getPose();
if (this.isSneaking() && pose == Pose.STANDING) {
setPose(Pose.SNEAKING);
} else if (pose == Pose.SNEAKING) {
setPose(Pose.STANDING);
}
}
this.flying = flying;
}
@ -1892,15 +1828,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
this.vehicleInformation.refresh(sideways, forward, jump, unmount);
}
/**
* @return the chunk range of the viewers,
* which is {@link MinecraftServer#getChunkViewDistance()} or {@link PlayerSettings#getViewDistance()}
* based on which one is the lowest
*/
public int getChunkRange() {
return Math.min(getSettings().viewDistance, MinecraftServer.getChunkViewDistance());
}
/**
* Gets the last sent keep alive id.
*
@ -1920,8 +1847,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
*
* @return a {@link PlayerInfoPacket} to add the player
*/
@NotNull
protected PlayerInfoPacket getAddPlayerToList() {
protected @NotNull PlayerInfoPacket getAddPlayerToList() {
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER);
PlayerInfoPacket.AddPlayer addPlayer =
@ -1930,11 +1856,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Skin support
if (skin != null) {
final String textures = skin.getTextures();
final String signature = skin.getSignature();
PlayerInfoPacket.AddPlayer.Property prop =
new PlayerInfoPacket.AddPlayer.Property("textures", textures, signature);
new PlayerInfoPacket.AddPlayer.Property("textures", skin.textures(), skin.signature());
addPlayer.properties.add(prop);
}
@ -1947,14 +1870,9 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
*
* @return a {@link PlayerInfoPacket} to remove the player
*/
@NotNull
protected PlayerInfoPacket getRemovePlayerToList() {
protected @NotNull PlayerInfoPacket getRemovePlayerToList() {
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER);
PlayerInfoPacket.RemovePlayer removePlayer =
new PlayerInfoPacket.RemovePlayer(getUuid());
playerInfoPacket.playerInfos.add(removePlayer);
playerInfoPacket.playerInfos.add(new PlayerInfoPacket.RemovePlayer(getUuid()));
return playerInfoPacket;
}
@ -1982,9 +1900,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
connection.sendPacket(new EntityHeadLookPacket(getEntityId(), position.yaw()));
}
@NotNull
@Override
public ItemStack getItemInMainHand() {
public @NotNull ItemStack getItemInMainHand() {
return inventory.getItemInMainHand();
}
@ -1993,9 +1910,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
inventory.setItemInMainHand(itemStack);
}
@NotNull
@Override
public ItemStack getItemInOffHand() {
public @NotNull ItemStack getItemInOffHand() {
return inventory.getItemInOffHand();
}
@ -2004,9 +1920,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
inventory.setItemInOffHand(itemStack);
}
@NotNull
@Override
public ItemStack getHelmet() {
public @NotNull ItemStack getHelmet() {
return inventory.getHelmet();
}
@ -2015,9 +1930,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
inventory.setHelmet(itemStack);
}
@NotNull
@Override
public ItemStack getChestplate() {
public @NotNull ItemStack getChestplate() {
return inventory.getChestplate();
}
@ -2026,9 +1940,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
inventory.setChestplate(itemStack);
}
@NotNull
@Override
public ItemStack getLeggings() {
public @NotNull ItemStack getLeggings() {
return inventory.getLeggings();
}
@ -2037,9 +1950,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
inventory.setLeggings(itemStack);
}
@NotNull
@Override
public ItemStack getBoots() {
public @NotNull ItemStack getBoots() {
return inventory.getBoots();
}
@ -2050,7 +1962,9 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@Override
public Locale getLocale() {
return settings.locale == null ? null : Locale.forLanguageTag(settings.locale);
final String locale = settings.locale;
if (locale == null) return null;
return Locale.forLanguageTag(locale.replace("_", "-"));
}
/**
@ -2114,16 +2028,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
RIGHT
}
/**
* @deprecated See {@link ChatMessageType}
*/
@Deprecated
public enum ChatMode {
ENABLED,
COMMANDS_ONLY,
HIDDEN
}
public class PlayerSettings {
private String locale;
@ -2155,17 +2059,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
return viewDistance;
}
/**
* Gets the player chat mode.
*
* @return the player chat mode
* @deprecated Use {@link #getChatMessageType()}
*/
@Deprecated
public ChatMode getChatMode() {
return ChatMode.values()[chatMessageType.ordinal()];
}
/**
* Gets the messages this player wants to receive.
*
@ -2211,9 +2104,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
*/
public void refresh(String locale, byte viewDistance, ChatMessageType chatMessageType, boolean chatColors,
byte displayedSkinParts, MainHand mainHand) {
final boolean viewDistanceChanged = this.viewDistance != viewDistance;
this.locale = locale;
this.viewDistance = viewDistance;
this.chatMessageType = chatMessageType;
@ -2223,11 +2113,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// TODO: Use the metadata object here
metadata.setIndex((byte) 17, Metadata.Byte(displayedSkinParts));
// Client changed his view distance in the settings
if (viewDistanceChanged) {
refreshVisibleChunks();
}
}
}

View File

@ -4,26 +4,17 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minestom.server.utils.mojang.MojangUtils;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* Contains all the data required to store a skin.
* <p>
* Can be applied to a player with {@link Player#setSkin(PlayerSkin)}
* or in the linked event {@link net.minestom.server.event.player.PlayerSkinInitEvent}.
*/
public class PlayerSkin {
private final String textures;
private final String signature;
public PlayerSkin(String textures, String signature) {
this.textures = textures;
this.signature = signature;
}
public record PlayerSkin(String textures, String signature) {
/**
* Gets a skin from a Mojang UUID.
@ -31,16 +22,14 @@ public class PlayerSkin {
* @param uuid Mojang UUID
* @return a player skin based on the UUID, null if not found
*/
@Nullable
public static PlayerSkin fromUuid(@NotNull String uuid) {
@Blocking
public static @Nullable 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;
if (!name.equals("textures")) continue;
final String textureValue = propertyObject.get("value").getAsString();
final String signatureValue = propertyObject.get("signature").getAsString();
return new PlayerSkin(textureValue, signatureValue);
@ -54,8 +43,8 @@ public class PlayerSkin {
* @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) {
@Blocking
public static @Nullable 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
@ -63,49 +52,18 @@ public class PlayerSkin {
}
/**
* Gets the skin textures value.
*
* @return the textures value
* @deprecated use {@link #textures()}
*/
@Deprecated
public String getTextures() {
return textures;
}
/**
* Gets the skin signature.
*
* @return the skin signature
* @deprecated use {@link #signature()}
*/
@Deprecated
public String getSignature() {
return signature;
}
@Override
public String toString() {
return "PlayerSkin{" +
"textures='" + textures + '\'' +
", signature='" + signature + '\'' +
'}';
}
/**
* {@inheritDoc}
*/
@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);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hash(textures, signature);
}
}

View File

@ -198,6 +198,7 @@ public class CombinedAttackGoal extends GoalSelector {
if (pathPosition != null) {
navigator.setPathTo(null);
}
this.entityCreature.lookAt(target);
return;
}
// Otherwise going to the target.

View File

@ -1,6 +1,7 @@
package net.minestom.server.entity.ai.goal;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.ai.GoalSelector;
@ -15,6 +16,8 @@ public class FollowTargetGoal extends GoalSelector {
private boolean forceEnd = false;
private Point lastTargetPos;
private Entity target;
/**
* Creates a follow target goal object.
*
@ -28,8 +31,14 @@ public class FollowTargetGoal extends GoalSelector {
@Override
public boolean shouldStart() {
return entityCreature.getTarget() != null &&
entityCreature.getTarget().getPosition().distance(entityCreature.getPosition()) >= 2;
Entity target = entityCreature.getTarget();
if (target == null) target = findTarget();
if (target == null) return false;
final boolean result = target.getPosition().distance(entityCreature.getPosition()) >= 2;
if (result) {
this.target = target;
}
return result;
}
@Override
@ -37,23 +46,22 @@ public class FollowTargetGoal extends GoalSelector {
lastUpdateTime = 0;
forceEnd = false;
lastTargetPos = null;
final Entity target = entityCreature.getTarget();
if (target != null) {
Navigator navigator = entityCreature.getNavigator();
lastTargetPos = target.getPosition();
if (lastTargetPos.distance(entityCreature.getPosition()) < 2) {
forceEnd = true;
navigator.setPathTo(null);
return;
}
if (navigator.getPathPosition() == null ||
(!navigator.getPathPosition().samePoint(lastTargetPos))) {
navigator.setPathTo(lastTargetPos);
} else {
forceEnd = true;
}
if (target == null) {
// No defined target
this.forceEnd = true;
return;
}
this.entityCreature.setTarget(target);
Navigator navigator = entityCreature.getNavigator();
this.lastTargetPos = target.getPosition();
if (lastTargetPos.distance(entityCreature.getPosition()) < 2) {
// Target is too far
this.forceEnd = true;
navigator.setPathTo(null);
return;
}
if (navigator.getPathPosition() == null || !navigator.getPathPosition().samePoint(lastTargetPos)) {
navigator.setPathTo(lastTargetPos);
} else {
forceEnd = true;
}
@ -66,7 +74,7 @@ public class FollowTargetGoal extends GoalSelector {
pathDuration.toMillis() + lastUpdateTime > time) {
return;
}
final var targetPos = entityCreature.getTarget() != null ? entityCreature.getTarget().getPosition() : null;
final Pos targetPos = entityCreature.getTarget() != null ? entityCreature.getTarget().getPosition() : null;
if (targetPos != null && !targetPos.samePoint(lastTargetPos)) {
this.lastUpdateTime = time;
this.lastTargetPos = targetPos;
@ -79,11 +87,12 @@ public class FollowTargetGoal extends GoalSelector {
final Entity target = entityCreature.getTarget();
return forceEnd ||
target == null ||
target.isRemoved() ||
target.getPosition().distance(entityCreature.getPosition()) < 2;
}
@Override
public void end() {
entityCreature.getNavigator().setPathTo(null);
this.entityCreature.getNavigator().setPathTo(null);
}
}

View File

@ -81,6 +81,7 @@ public class MeleeAttackGoal extends GoalSelector {
// Attack the target entity
if (entityCreature.getDistance(target) <= range) {
entityCreature.lookAt(target);
if (!Cooldown.hasCooldown(time, lastHit, delay)) {
entityCreature.attack(target, true);
this.lastHit = time;

View File

@ -6,8 +6,8 @@ import net.minestom.server.entity.ai.GoalSelector;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class RandomStrollGoal extends GoalSelector {
@ -15,6 +15,7 @@ public class RandomStrollGoal extends GoalSelector {
private final int radius;
private final List<Vec> closePositions;
private final Random random = new Random();
private long lastStroll;
@ -31,8 +32,11 @@ public class RandomStrollGoal extends GoalSelector {
@Override
public void start() {
Collections.shuffle(closePositions);
for (var position : closePositions) {
int remainingAttempt = closePositions.size();
while (remainingAttempt-- > 0) {
final int index = random.nextInt(closePositions.size());
final Vec position = closePositions.get(index);
final var target = entityCreature.getPosition().add(position);
final boolean result = entityCreature.getNavigator().setPathTo(target);
if (result) {

View File

@ -124,6 +124,7 @@ public class RangedAttackGoal extends GoalSelector {
if (pathPosition != null) {
navigator.setPathTo(null);
}
this.entityCreature.lookAt(target);
return;
}
final var targetPosition = target.getPosition();

View File

@ -21,6 +21,7 @@ public class ClosestEntityTarget extends TargetSelector {
private final float range;
private final Class<? extends LivingEntity>[] entitiesTarget;
@SafeVarargs
public ClosestEntityTarget(@NotNull EntityCreature entityCreature, float range,
@NotNull Class<? extends LivingEntity>... entitiesTarget) {
super(entityCreature);

View File

@ -22,20 +22,15 @@ public class LastEntityDamagerTarget extends TargetSelector {
@Override
public Entity findTarget() {
final DamageType damageType = entityCreature.getLastDamageSource();
if (!(damageType instanceof EntityDamage)) {
if (!(damageType instanceof EntityDamage entityDamage)) {
// No damager recorded, return null
return null;
}
final EntityDamage entityDamage = (EntityDamage) damageType;
final Entity entity = entityDamage.getSource();
if (entity.isRemoved()) {
// Entity not valid
return null;
}
// Check range
return entityCreature.getDistance(entity) < range ? entity : null;
}

View File

@ -2,7 +2,6 @@ package net.minestom.server.entity.fakeplayer;
import com.extollit.gaming.ai.path.HydrazinePathFinder;
import net.minestom.server.MinecraftServer;
import net.minestom.server.attribute.Attribute;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.NavigableEntity;
@ -118,9 +117,8 @@ public class FakePlayer extends Player implements NavigableEntity {
@Override
public void update(long time) {
super.update(time);
// Path finding
this.navigator.tick(getAttributeValue(Attribute.MOVEMENT_SPEED));
this.navigator.tick();
}
@Override
@ -131,12 +129,10 @@ public class FakePlayer extends Player implements NavigableEntity {
}
@Override
protected boolean addViewer0(@NotNull Player player) {
final boolean result = super.addViewer0(player);
if (result) {
handleTabList(player.getPlayerConnection());
}
return result;
public void updateNewViewer(@NotNull Player player) {
player.getPlayerConnection().sendPacket(getAddPlayerToList());
handleTabList(player.getPlayerConnection());
super.updateNewViewer(player);
}
/**

View File

@ -25,9 +25,11 @@ public class FakePlayerOption {
* WARNING: this can't be changed halfway.
*
* @param registered should the fake player be registered internally
* @return this instance, allowing for chained method calls
*/
public void setRegistered(boolean registered) {
public FakePlayerOption setRegistered(boolean registered) {
this.registered = registered;
return this;
}
/**
@ -45,8 +47,10 @@ public class FakePlayerOption {
* WARNING: this can't be changed halfway.
*
* @param inTabList should the player be in the tab-list
* @return this instance, allowing for chained method calls
*/
public void setInTabList(boolean inTabList) {
public FakePlayerOption setInTabList(boolean inTabList) {
this.inTabList = inTabList;
return this;
}
}

View File

@ -24,15 +24,9 @@ public class PufferfishMeta extends AbstractFishMeta {
private void updateBoundingBox(State state) {
switch (state) {
case UNPUFFED:
setBoundingBox(.35D, .35D);
break;
case SEMI_PUFFED:
setBoundingBox(.5D, .5D);
break;
default:
setBoundingBox(.7D, .7D);
break;
case UNPUFFED -> setBoundingBox(.35D, .35D);
case SEMI_PUFFED -> setBoundingBox(.5D, .5D);
default -> setBoundingBox(.7D, .7D);
}
}

View File

@ -14,6 +14,7 @@ import net.minestom.server.instance.Instance;
import net.minestom.server.instance.WorldBorder;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.position.PositionUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -22,11 +23,9 @@ import org.jetbrains.annotations.Nullable;
/**
* Necessary object for all {@link NavigableEntity}.
*/
public class Navigator {
public final class Navigator {
private final PFPathingEntity pathingEntity;
private HydrazinePathFinder pathFinder;
private IPath path;
private Point pathPosition;
private final Entity entity;
@ -88,30 +87,24 @@ public class Navigator {
// Tried to set path to the same target position
return false;
}
final Instance instance = entity.getInstance();
if (pathFinder == null) {
// Unexpected error
return false;
}
pathFinder.reset();
this.pathFinder.reset();
if (point == null) {
return false;
}
// Can't path with a null instance.
if (instance == null) {
return false;
}
// Can't path outside the world border
final WorldBorder worldBorder = instance.getWorldBorder();
if (!worldBorder.isInside(point)) {
return false;
}
// Can't path in an unloaded chunk
final Chunk chunk = instance.getChunkAt(point);
if (!ChunkUtils.isLoaded(chunk)) {
@ -126,11 +119,9 @@ public class Navigator {
point.y(),
point.z(),
pathOptions);
this.path = path;
final boolean success = path != null;
this.pathPosition = success ? point : null;
return success;
}
@ -141,57 +132,16 @@ public class Navigator {
return setPathTo(position, true);
}
public synchronized void tick(float speed) {
// No pathfinding tick for dead entities
@ApiStatus.Internal
public synchronized void tick() {
if (pathPosition == null) return; // No path
if (entity instanceof LivingEntity && ((LivingEntity) entity).isDead())
return;
if (pathPosition != null) {
IPath path = pathFinder.updatePathFor(pathingEntity);
this.path = path;
if (path != null) {
final Point targetPosition = pathingEntity.getTargetPosition();
if (targetPosition != null) {
moveTowards(targetPosition, speed);
}
} else {
if (pathPosition != null) {
this.pathPosition = null;
pathFinder.reset();
}
}
return; // No pathfinding tick for dead entities
if (pathFinder.updatePathFor(pathingEntity) == null) {
reset();
}
}
/**
* Gets the pathing entity.
* <p>
* Used by the pathfinder.
*
* @return the pathing entity
*/
@NotNull
public PFPathingEntity getPathingEntity() {
return pathingEntity;
}
/**
* Gets the assigned pathfinder.
* <p>
* Can be null if the navigable element hasn't been assigned to an {@link Instance} yet.
*
* @return the current pathfinder, null if none
*/
@Nullable
public HydrazinePathFinder getPathFinder() {
return pathFinder;
}
public void setPathFinder(@Nullable HydrazinePathFinder pathFinder) {
this.pathFinder = pathFinder;
}
/**
* Gets the target pathfinder position.
*
@ -201,28 +151,22 @@ public class Navigator {
return pathPosition;
}
/**
* Changes the position this element is trying to reach.
*
* @param pathPosition the new current path position
* @deprecated Please use {@link #setPathTo(Point)}
*/
@Deprecated
public void setPathPosition(@Nullable Point pathPosition) {
this.pathPosition = pathPosition;
}
@Nullable
public IPath getPath() {
return path;
}
public void setPath(@Nullable IPath path) {
this.path = path;
}
@NotNull
public Entity getEntity() {
public @NotNull Entity getEntity() {
return entity;
}
@ApiStatus.Internal
public @NotNull PFPathingEntity getPathingEntity() {
return pathingEntity;
}
@ApiStatus.Internal
public void setPathFinder(@Nullable HydrazinePathFinder pathFinder) {
this.pathFinder = pathFinder;
}
private void reset() {
this.pathPosition = null;
this.pathFinder.reset();
}
}

View File

@ -9,15 +9,15 @@ import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.LivingEntity;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
public class PFPathingEntity implements IPathingEntity {
@ApiStatus.Internal
public final class PFPathingEntity implements IPathingEntity {
private final Navigator navigator;
private final Entity entity;
private float searchRange;
private Point targetPosition;
// Capacities
private boolean fireResistant;
@ -37,10 +37,6 @@ public class PFPathingEntity implements IPathingEntity {
this.searchRange = getAttributeValue(Attribute.FOLLOW_RANGE);
}
public Point getTargetPosition() {
return targetPosition;
}
@Override
public int age() {
return (int) entity.getAliveTicks();
@ -194,7 +190,8 @@ public class PFPathingEntity implements IPathingEntity {
@Override
public void moveTo(Vec3d position, Passibility passibility, Gravitation gravitation) {
this.targetPosition = new Vec(position.x, position.y, position.z);
final Point targetPosition = new Vec(position.x, position.y, position.z);
this.navigator.moveTowards(targetPosition, getAttributeValue(Attribute.MOVEMENT_SPEED));
final double entityY = entity.getPosition().y();
if (entityY < targetPosition.y()) {
this.navigator.jump(1);

View File

@ -4,12 +4,16 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.event.trait.CancellableEvent;
import org.jetbrains.annotations.NotNull;
public class EventDispatcher {
public final class EventDispatcher {
public static void call(@NotNull Event event) {
MinecraftServer.getGlobalEventHandler().call(event);
}
public static <E extends Event> ListenerHandle<E> getHandle(@NotNull Class<E> handleType) {
return MinecraftServer.getGlobalEventHandler().getHandle(handleType);
}
public static void callCancellable(@NotNull CancellableEvent event, @NotNull Runnable successCallback) {
MinecraftServer.getGlobalEventHandler().callCancellable(event, successCallback);
}

View File

@ -19,7 +19,7 @@ import java.util.function.Predicate;
*/
public interface EventListener<T extends Event> {
@NotNull Class<T> getEventType();
@NotNull Class<T> eventType();
@NotNull Result run(@NotNull T event);
@ -122,7 +122,7 @@ public interface EventListener<T extends Event> {
final var handler = this.handler;
return new EventListener<>() {
@Override
public @NotNull Class<T> getEventType() {
public @NotNull Class<T> eventType() {
return eventType;
}

View File

@ -23,8 +23,7 @@ import java.util.function.Predicate;
*
* @param <T> The event type accepted by this node
*/
@ApiStatus.NonExtendable
public interface EventNode<T extends Event> {
public sealed interface EventNode<T extends Event> permits EventNodeImpl {
/**
* Creates an event node which accepts any event type with no filtering.
@ -182,15 +181,24 @@ public interface EventNode<T extends Event> {
}
/**
* Executes the given event on this node. The event must pass all conditions before
* it will be forwarded to the listeners.
* <p>
* Calling an event on a node will execute all child nodes, however, an event may be
* called anywhere on the event graph and it will propagate down from there only.
* Calls an event starting from this node.
*
* @param event the event to execute
* @param event the event to call
*/
void call(@NotNull T event);
default void call(@NotNull T event) {
//noinspection unchecked
getHandle((Class<T>) event.getClass()).call(event);
}
/**
* Gets the handle of an event type.
*
* @param handleType the handle type
* @param <E> the event type
* @return the handle linked to {@code handleType}
*/
@ApiStatus.Experimental
<E extends T> @NotNull ListenerHandle<E> getHandle(@NotNull Class<E> handleType);
/**
* Execute a cancellable event with a callback to execute if the event is successful.
@ -287,7 +295,9 @@ public interface EventNode<T extends Event> {
*
* @param name The node name to filter for
*/
void removeChildren(@NotNull String name);
default void removeChildren(@NotNull String name) {
removeChildren(name, getEventType());
}
/**
* Directly adds a child node to this node.
@ -318,9 +328,24 @@ public interface EventNode<T extends Event> {
@Contract(value = "_ -> this")
@NotNull EventNode<T> removeListener(@NotNull EventListener<? extends T> listener);
/**
* Maps a specific object to a node.
* <p>
* Be aware that such structure have huge performance penalty as they will
* always require a map lookup. Use only at last resort.
*
* @param node the node to map
* @param value the mapped value
*/
@ApiStatus.Experimental
void map(@NotNull EventNode<? extends T> node, @NotNull Object value);
/**
* Undo {@link #map(EventNode, Object)}
*
* @param value the value to unmap
* @return true if the value has been unmapped, false if nothing happened
*/
@ApiStatus.Experimental
boolean unmap(@NotNull Object value);

View File

@ -1,6 +1,7 @@
package net.minestom.server.event;
import net.minestom.server.MinecraftServer;
import net.minestom.server.event.trait.RecursiveEvent;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@ -10,19 +11,17 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.IntUnaryOperator;
import java.util.stream.Collectors;
class EventNodeImpl<T extends Event> implements EventNode<T> {
non-sealed class EventNodeImpl<T extends Event> implements EventNode<T> {
private static final Object GLOBAL_CHILD_LOCK = new Object();
private final Object lock = new Object();
private final Map<Class<? extends T>, Handle<T>> handleMap = new ConcurrentHashMap<>();
private final Map<Class<? extends T>, ListenerEntry<T>> listenerMap = new ConcurrentHashMap<>();
private final Set<EventNode<T>> children = new CopyOnWriteArraySet<>();
private final Map<Object, ListenerEntry<T>> mappedNodeCache = new WeakHashMap<>();
private final Set<EventNodeImpl<T>> children = new CopyOnWriteArraySet<>();
private final Map<Object, EventNodeImpl<T>> mappedNodeCache = new WeakHashMap<>();
private final String name;
private final EventFilter<T, ?> filter;
@ -31,9 +30,9 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
private volatile int priority;
private volatile EventNodeImpl<? super T> parent;
protected EventNodeImpl(@NotNull String name,
@NotNull EventFilter<T, ?> filter,
@Nullable BiPredicate<T, Object> predicate) {
EventNodeImpl(@NotNull String name,
@NotNull EventFilter<T, ?> filter,
@Nullable BiPredicate<T, Object> predicate) {
this.name = name;
this.filter = filter;
this.predicate = predicate;
@ -41,35 +40,16 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
}
@Override
public void call(@NotNull T event) {
final var eventClass = event.getClass();
if (!eventType.isAssignableFrom(eventClass)) return; // Invalid event type
// Conditions
if (predicate != null) {
try {
final var value = filter.getHandler(event);
if (!predicate.test(event, value)) return;
} catch (Exception e) {
MinecraftServer.getExceptionManager().handleException(e);
return;
}
}
// Process listeners list
final var entry = listenerMap.get(eventClass);
if (entry == null) return; // No listener nor children
entry.call(event);
// Process children
if (entry.childCount > 0) {
this.children.stream()
.sorted(Comparator.comparing(EventNode::getPriority))
.forEach(child -> child.call(event));
}
public <E extends T> @NotNull ListenerHandle<E> getHandle(@NotNull Class<E> handleType) {
//noinspection unchecked
return (ListenerHandle<E>) handleMap.computeIfAbsent(handleType,
aClass -> new Handle<>(this, (Class<T>) aClass));
}
@Override
public <E extends T> @NotNull List<EventNode<E>> findChildren(@NotNull String name, Class<E> eventType) {
if (children.isEmpty()) return Collections.emptyList();
synchronized (GLOBAL_CHILD_LOCK) {
if (children.isEmpty()) return Collections.emptyList();
List<EventNode<E>> result = new ArrayList<>();
for (EventNode<T> child : children) {
if (equals(child, name, eventType)) {
@ -115,23 +95,15 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
}
}
@Override
public void removeChildren(@NotNull String name) {
removeChildren(name, eventType);
}
@Override
public @NotNull EventNode<T> addChild(@NotNull EventNode<? extends T> child) {
synchronized (GLOBAL_CHILD_LOCK) {
final var childImpl = (EventNodeImpl<? extends T>) child;
Check.stateCondition(childImpl.parent != null, "Node already has a parent");
Check.stateCondition(Objects.equals(parent, child), "Cannot have a child as parent");
final boolean result = this.children.add((EventNodeImpl<T>) childImpl);
if (result) {
childImpl.parent = this;
// Increase listener count
propagateNode(childImpl, IntUnaryOperator.identity());
}
if (!children.add((EventNodeImpl<T>) childImpl)) return this; // Couldn't add the child (already present?)
childImpl.parent = this;
childImpl.invalidateEventsFor(this);
}
return this;
}
@ -139,13 +111,11 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
@Override
public @NotNull EventNode<T> removeChild(@NotNull EventNode<? extends T> child) {
synchronized (GLOBAL_CHILD_LOCK) {
final boolean result = this.children.remove(child);
if (result) {
final var childImpl = (EventNodeImpl<? extends T>) child;
childImpl.parent = null;
// Decrease listener count
propagateNode(childImpl, count -> -count);
}
final var childImpl = (EventNodeImpl<? extends T>) child;
final boolean result = this.children.remove(childImpl);
if (!result) return this; // Child not found
childImpl.parent = null;
childImpl.invalidateEventsFor(this);
}
return this;
}
@ -153,10 +123,10 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
@Override
public @NotNull EventNode<T> addListener(@NotNull EventListener<? extends T> listener) {
synchronized (GLOBAL_CHILD_LOCK) {
final var eventType = listener.getEventType();
var entry = getEntry(eventType);
final var eventType = listener.eventType();
ListenerEntry<T> entry = getEntry(eventType);
entry.listeners.add((EventListener<T>) listener);
propagateToParent(eventType, 1);
invalidateEvent(eventType);
}
return this;
}
@ -164,50 +134,36 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
@Override
public @NotNull EventNode<T> removeListener(@NotNull EventListener<? extends T> listener) {
synchronized (GLOBAL_CHILD_LOCK) {
final var eventType = listener.getEventType();
var entry = listenerMap.get(eventType);
if (entry == null) return this;
var listeners = entry.listeners;
final boolean removed = listeners.remove(listener);
if (removed) propagateToParent(eventType, -1);
final var eventType = listener.eventType();
ListenerEntry<T> entry = listenerMap.get(eventType);
if (entry == null) return this; // There is no listener with such type
if (entry.listeners.remove(listener)) invalidateEvent(eventType);
}
return this;
}
@Override
public void map(@NotNull EventNode<? extends T> node, @NotNull Object value) {
final var nodeImpl = (EventNodeImpl<? extends T>) node;
final var valueType = value.getClass();
synchronized (GLOBAL_CHILD_LOCK) {
nodeImpl.listenerMap.forEach((type, listenerEntry) -> {
final var entry = getEntry(type);
final boolean correct = entry.filters.stream().anyMatch(eventFilter -> {
final var handlerType = eventFilter.handlerType();
return handlerType != null && handlerType.isAssignableFrom(valueType);
});
Check.stateCondition(!correct, "The node filter {0} is not compatible with type {1}", nodeImpl.eventType, valueType);
synchronized (mappedNodeCache) {
entry.mappedNode.put(value, (EventNode<T>) nodeImpl);
mappedNodeCache.put(value, entry);
// TODO propagate
}
});
final var nodeImpl = (EventNodeImpl<? extends T>) node;
Check.stateCondition(nodeImpl.parent != null, "Node already has a parent");
Check.stateCondition(Objects.equals(parent, nodeImpl), "Cannot map to self");
EventNodeImpl<T> previous = this.mappedNodeCache.put(value, (EventNodeImpl<T>) nodeImpl);
if (previous != null) previous.parent = null;
nodeImpl.parent = this;
nodeImpl.invalidateEventsFor(this);
}
}
@Override
public boolean unmap(@NotNull Object value) {
synchronized (GLOBAL_CHILD_LOCK) {
synchronized (mappedNodeCache) {
var entry = mappedNodeCache.remove(value);
if (entry == null) return false;
final EventNode<T> previousNode = entry.mappedNode.remove(value);
if (previousNode != null) {
// TODO propagate
return true;
}
return false;
}
final var mappedNode = this.mappedNodeCache.remove(value);
if (mappedNode == null) return false; // Mapped node not found
final var childImpl = (EventNodeImpl<? extends T>) mappedNode;
childImpl.parent = null;
childImpl.invalidateEventsFor(this);
return true;
}
}
@ -215,9 +171,9 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
public void register(@NotNull EventBinding<? extends T> binding) {
synchronized (GLOBAL_CHILD_LOCK) {
for (var eventType : binding.eventTypes()) {
var entry = getEntry((Class<? extends T>) eventType);
ListenerEntry<T> entry = getEntry((Class<? extends T>) eventType);
final boolean added = entry.bindingConsumers.add((Consumer<T>) binding.consumer(eventType));
if (added) propagateToParent((Class<? extends T>) eventType, 1);
if (added) invalidateEvent((Class<? extends T>) eventType);
}
}
}
@ -226,10 +182,10 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
public void unregister(@NotNull EventBinding<? extends T> binding) {
synchronized (GLOBAL_CHILD_LOCK) {
for (var eventType : binding.eventTypes()) {
var entry = listenerMap.get(eventType);
ListenerEntry<T> entry = listenerMap.get(eventType);
if (entry == null) return;
final boolean removed = entry.bindingConsumers.remove(binding.consumer(eventType));
if (removed) propagateToParent((Class<? extends T>) eventType, -1);
if (removed) invalidateEvent((Class<? extends T>) eventType);
}
}
}
@ -260,103 +216,224 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
return parent;
}
private void propagateChildCountChange(Class<? extends T> eventClass, int count) {
var entry = getEntry(eventClass);
final int result = ListenerEntry.CHILD_UPDATER.addAndGet(entry, count);
if (result == 0 && entry.listeners.isEmpty()) {
this.listenerMap.remove(eventClass);
} else if (result < 0) {
throw new IllegalStateException("Something wrong happened, listener count: " + result);
private void invalidateEventsFor(EventNodeImpl<? super T> node) {
for (Class<? extends T> eventType : listenerMap.keySet()) {
node.invalidateEvent(eventType);
}
if (parent != null) {
parent.propagateChildCountChange(eventClass, count);
// TODO bindings?
for (EventNodeImpl<T> child : children) {
child.invalidateEventsFor(node);
}
}
private void propagateToParent(Class<? extends T> eventClass, int count) {
final var parent = this.parent;
if (parent != null) {
synchronized (parent.lock) {
parent.propagateChildCountChange(eventClass, count);
}
}
}
private void propagateNode(EventNodeImpl<? extends T> child, IntUnaryOperator operator) {
synchronized (lock) {
final var listeners = child.listenerMap;
listeners.forEach((eventClass, eventListeners) -> {
final var entry = listeners.get(eventClass);
if (entry == null) return;
final int childCount = entry.listeners.size() + entry.childCount;
propagateChildCountChange(eventClass, operator.applyAsInt(childCount));
});
}
private void invalidateEvent(Class<? extends T> eventClass) {
forTargetEvents(eventClass, type -> {
Handle<? super T> handle = handleMap.get(type);
if (handle != null) handle.updated = false;
});
final EventNodeImpl<? super T> parent = this.parent;
if (parent != null) parent.invalidateEvent(eventClass);
}
private ListenerEntry<T> getEntry(Class<? extends T> type) {
return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>(this, (Class<T>) aClass));
return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>());
}
private static boolean equals(EventNode<?> node, String name, Class<?> eventType) {
final boolean nameCheck = node.getName().equals(name);
final boolean typeCheck = eventType.isAssignableFrom(((EventNodeImpl<?>) node).eventType);
return nameCheck && typeCheck;
return node.getName().equals(name) && eventType.isAssignableFrom((node.getEventType()));
}
private static void forTargetEvents(Class<?> type, Consumer<Class<?>> consumer) {
consumer.accept(type);
// Recursion
if (RecursiveEvent.class.isAssignableFrom(type)) {
final Class<?> superclass = type.getSuperclass();
if (superclass != null && RecursiveEvent.class.isAssignableFrom(superclass)) {
forTargetEvents(superclass, consumer);
}
}
}
private static class ListenerEntry<T extends Event> {
private static final List<EventFilter<? extends Event, ?>> FILTERS = List.of(
EventFilter.ENTITY,
EventFilter.ITEM, EventFilter.INSTANCE,
EventFilter.INVENTORY, EventFilter.BLOCK);
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater<ListenerEntry> CHILD_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount");
final EventNodeImpl<T> node;
final List<EventFilter<?, ?>> filters;
final List<EventListener<T>> listeners = new CopyOnWriteArrayList<>();
final Set<Consumer<T>> bindingConsumers = new CopyOnWriteArraySet<>();
final Map<Object, EventNode<T>> mappedNode = new WeakHashMap<>();
volatile int childCount;
}
ListenerEntry(EventNodeImpl<T> node, Class<T> eventType) {
static final class Handle<E extends Event> implements ListenerHandle<E> {
private final EventNodeImpl<E> node;
private final Class<E> eventType;
private Consumer<E> listener = null;
private volatile boolean updated;
Handle(EventNodeImpl<E> node, Class<E> eventType) {
this.node = node;
this.filters = FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(eventType)).collect(Collectors.toList());
this.eventType = eventType;
}
void call(T event) {
// Event interfaces
if (!bindingConsumers.isEmpty()) {
for (var consumer : bindingConsumers) {
consumer.accept(event);
}
@Override
public void call(@NotNull E event) {
final Consumer<E> listener = updatedListener();
if (listener == null) return;
try {
listener.accept(event);
} catch (Throwable e) {
MinecraftServer.getExceptionManager().handleException(e);
}
// Mapped listeners
if (!mappedNode.isEmpty()) {
synchronized (node.mappedNodeCache) {
// Check mapped listeners for each individual event handler
for (var filter : filters) {
final var handler = filter.castHandler(event);
final var map = mappedNode.get(handler);
if (map != null) map.call(event);
}
}
}
@Override
public boolean hasListener() {
return updatedListener() != null;
}
@Nullable Consumer<E> updatedListener() {
if (updated) return listener;
synchronized (GLOBAL_CHILD_LOCK) {
if (updated) return listener;
final Consumer<E> listener = createConsumer();
this.listener = listener;
this.updated = true;
return listener;
}
// Basic listeners
if (!listeners.isEmpty()) {
for (EventListener<T> listener : listeners) {
EventListener.Result result;
try {
result = listener.run(event);
} catch (Exception e) {
result = EventListener.Result.EXCEPTION;
MinecraftServer.getExceptionManager().handleException(e);
}
if (result == EventListener.Result.EXPIRED) {
listeners.remove(listener);
}
private @Nullable Consumer<E> createConsumer() {
// Standalone listeners
List<Consumer<E>> listeners = new ArrayList<>();
forTargetEvents(eventType, type -> {
final ListenerEntry<E> entry = node.listenerMap.get(type);
if (entry != null) {
final Consumer<E> result = listenersConsumer(entry);
if (result != null) listeners.add(result);
}
});
final Consumer<E>[] listenersArray = listeners.toArray(Consumer[]::new);
// Mapped
final Consumer<E> mappedListener = mappedConsumer();
// Children
final Consumer<E>[] childrenListeners = node.children.stream()
.filter(child -> child.eventType.isAssignableFrom(eventType)) // Invalid event type
.sorted(Comparator.comparing(EventNode::getPriority))
.map(child -> ((Handle<E>) child.getHandle(eventType)).updatedListener())
.filter(Objects::nonNull)
.toArray(Consumer[]::new);
// Empty check
final BiPredicate<E, Object> predicate = node.predicate;
final EventFilter<E, ?> filter = node.filter;
final boolean hasPredicate = predicate != null;
final boolean hasListeners = listenersArray.length > 0;
final boolean hasMap = mappedListener != null;
final boolean hasChildren = childrenListeners.length > 0;
if (listenersArray.length == 0 && mappedListener == null && childrenListeners.length == 0) {
// No listener
return null;
}
return e -> {
// Filtering
if (hasPredicate) {
final Object value = filter.getHandler(e);
if (!predicate.test(e, value)) return;
}
// Normal listeners
if (hasListeners) {
for (Consumer<E> listener : listenersArray) {
listener.accept(e);
}
}
// Mapped nodes
if (hasMap) mappedListener.accept(e);
// Children
if (hasChildren) {
for (Consumer<E> childHandle : childrenListeners) {
childHandle.accept(e);
}
}
};
}
/**
* Create a consumer calling all listeners from {@link EventNode#addListener(EventListener)} and
* {@link EventNode#register(EventBinding)}.
* <p>
* Most computation should ideally be done outside the consumers as a one-time cost.
*/
private @Nullable Consumer<E> listenersConsumer(@NotNull ListenerEntry<E> entry) {
final EventListener<E>[] listenersCopy = entry.listeners.toArray(EventListener[]::new);
final Consumer<E>[] bindingsCopy = entry.bindingConsumers.toArray(Consumer[]::new);
final boolean listenersEmpty = listenersCopy.length == 0;
final boolean bindingsEmpty = bindingsCopy.length == 0;
if (listenersEmpty && bindingsEmpty) return null;
if (bindingsEmpty && listenersCopy.length == 1) {
// Only one normal listener
final EventListener<E> listener = listenersCopy[0];
return e -> callListener(listener, e);
}
// Worse case scenario, try to run everything
return e -> {
if (!listenersEmpty) {
for (EventListener<E> listener : listenersCopy) {
callListener(listener, e);
}
}
if (!bindingsEmpty) {
for (Consumer<E> eConsumer : bindingsCopy) {
eConsumer.accept(e);
}
}
};
}
/**
* Create a consumer handling {@link EventNode#map(EventNode, Object)}.
* The goal is to limit the amount of map lookup.
*/
private @Nullable Consumer<E> mappedConsumer() {
final var mappedNodeCache = node.mappedNodeCache;
if (mappedNodeCache.isEmpty()) return null;
Set<EventFilter<E, ?>> filters = new HashSet<>(mappedNodeCache.size());
Map<Object, Handle<E>> handlers = new HashMap<>(mappedNodeCache.size());
// Retrieve all filters used to retrieve potential handlers
for (var mappedEntry : mappedNodeCache.entrySet()) {
final EventNodeImpl<E> mappedNode = mappedEntry.getValue();
final Handle<E> handle = (Handle<E>) mappedNode.getHandle(eventType);
if (!handle.hasListener()) continue; // Implicit update
filters.add(mappedNode.filter);
handlers.put(mappedEntry.getKey(), handle);
}
// If at least one mapped node listen to this handle type,
// loop through them and forward to mapped node if there is a match
if (filters.isEmpty()) return null;
final EventFilter<E, ?>[] filterList = filters.toArray(EventFilter[]::new);
final BiConsumer<EventFilter<E, ?>, E> mapper = (filter, event) -> {
final Object handler = filter.castHandler(event);
final Handle<E> handle = handlers.get(handler);
if (handle != null) handle.call(event);
};
if (filterList.length == 1) {
final var firstFilter = filterList[0];
// Common case where there is only one filter
return event -> mapper.accept(firstFilter, event);
} else if (filterList.length == 2) {
final var firstFilter = filterList[0];
final var secondFilter = filterList[1];
return event -> {
mapper.accept(firstFilter, event);
mapper.accept(secondFilter, event);
};
} else {
return event -> {
for (var filter : filterList) {
mapper.accept(filter, event);
}
};
}
}
void callListener(@NotNull EventListener<E> listener, E event) {
EventListener.Result result = listener.run(event);
if (result == EventListener.Result.EXPIRED) {
node.removeListener(listener);
this.updated = false;
}
}
}

View File

@ -0,0 +1,28 @@
package net.minestom.server.event;
import net.minestom.server.MinecraftServer;
import net.minestom.server.event.entity.EntityTickEvent;
import net.minestom.server.event.instance.InstanceChunkLoadEvent;
import net.minestom.server.event.instance.InstanceTickEvent;
import net.minestom.server.event.inventory.InventoryItemChangeEvent;
import net.minestom.server.event.inventory.PlayerInventoryItemChangeEvent;
import net.minestom.server.event.player.*;
import org.jetbrains.annotations.ApiStatus;
/**
* Contains handles to the main node {@link MinecraftServer#getGlobalEventHandler()}
* (compatible with {@link EventDispatcher}).
*/
@ApiStatus.Internal
public final class GlobalHandles {
public static final ListenerHandle<PlayerTickEvent> PLAYER_TICK = EventDispatcher.getHandle(PlayerTickEvent.class);
public static final ListenerHandle<PlayerPacketEvent> PLAYER_PACKET = EventDispatcher.getHandle(PlayerPacketEvent.class);
public static final ListenerHandle<PlayerMoveEvent> PLAYER_MOVE = EventDispatcher.getHandle(PlayerMoveEvent.class);
public static final ListenerHandle<EntityTickEvent> ENTITY_TICK = EventDispatcher.getHandle(EntityTickEvent.class);
public static final ListenerHandle<InstanceTickEvent> INSTANCE_TICK = EventDispatcher.getHandle(InstanceTickEvent.class);
public static final ListenerHandle<PlayerChunkLoadEvent> PLAYER_CHUNK_LOAD = EventDispatcher.getHandle(PlayerChunkLoadEvent.class);
public static final ListenerHandle<PlayerChunkUnloadEvent> PLAYER_CHUNK_UNLOAD = EventDispatcher.getHandle(PlayerChunkUnloadEvent.class);
public static final ListenerHandle<InstanceChunkLoadEvent> INSTANCE_CHUNK_LOAD = EventDispatcher.getHandle(InstanceChunkLoadEvent.class);
public static final ListenerHandle<InventoryItemChangeEvent> INVENTORY_ITEM_CHANGE_EVENT = EventDispatcher.getHandle(InventoryItemChangeEvent.class);
public static final ListenerHandle<PlayerInventoryItemChangeEvent> PLAYER_INVENTORY_ITEM_CHANGE_EVENT = EventDispatcher.getHandle(PlayerInventoryItemChangeEvent.class);
}

View File

@ -0,0 +1,37 @@
package net.minestom.server.event;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Represents a key to a listenable event, retrievable from {@link EventNode#getHandle(Class)}.
* Useful to avoid map lookups.
* <p>
* It is recommended to store instances of this class in {@code static final} fields.
*
* @param <E> the event type
*/
@ApiStatus.Experimental
public sealed interface ListenerHandle<E extends Event> permits EventNodeImpl.Handle {
/**
* Calls the given event.
* Will try to fast exit the execution when possible if {@link #hasListener()} return {@code false}.
* <p>
* Anonymous and subclasses are not supported, events must have the exact type {@code E}.
*
* @param event the event to call
*/
void call(@NotNull E event);
/**
* Gets if any listener has been registered for the given handle.
* May trigger an update if the cached data is not correct.
* <p>
* Useful if you are able to avoid expensive computation in the case where
* the event is unused. Be aware that {@link #call(Event)}
* has similar optimization built-in.
*
* @return true if the event has 1 or more listeners
*/
boolean hasListener();
}

View File

@ -1,14 +1,14 @@
package net.minestom.server.event.entity;
import net.minestom.server.entity.Entity;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called when a player does a left click on an entity or with
* {@link net.minestom.server.entity.EntityCreature#attack(Entity)}.
*/
public class EntityAttackEvent implements EntityEvent {
public class EntityAttackEvent implements EntityInstanceEvent {
private final Entity entity;
private final Entity target;

View File

@ -4,13 +4,13 @@ import net.minestom.server.entity.Entity;
import net.minestom.server.entity.LivingEntity;
import net.minestom.server.entity.damage.DamageType;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called with {@link LivingEntity#damage(DamageType, float)}.
*/
public class EntityDamageEvent implements EntityEvent, CancellableEvent {
public class EntityDamageEvent implements EntityInstanceEvent, CancellableEvent {
private final Entity entity;
private final DamageType damageType;

View File

@ -1,10 +1,10 @@
package net.minestom.server.event.entity;
import net.minestom.server.entity.Entity;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import org.jetbrains.annotations.NotNull;
public class EntityDeathEvent implements EntityEvent {
public class EntityDeathEvent implements EntityInstanceEvent {
// TODO cause
private final Entity entity;

View File

@ -2,13 +2,13 @@ package net.minestom.server.event.entity;
import net.minestom.server.entity.Entity;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.time.temporal.TemporalUnit;
public class EntityFireEvent implements EntityEvent, CancellableEvent {
public class EntityFireEvent implements EntityInstanceEvent, CancellableEvent {
private final Entity entity;
private Duration duration;

View File

@ -3,16 +3,16 @@ package net.minestom.server.event.entity;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.ItemEntity;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Called when two {@link ItemEntity} are merging their {@link ItemStack} together to form a sole entity.
*/
public class EntityItemMergeEvent implements EntityEvent, CancellableEvent {
public class EntityItemMergeEvent implements EntityInstanceEvent, CancellableEvent {
private Entity entity;
private final Entity entity;
private final ItemEntity merged;
private ItemStack result;
@ -31,9 +31,8 @@ public class EntityItemMergeEvent implements EntityEvent, CancellableEvent {
*
* @return the source ItemEntity
*/
@NotNull
@Override
public ItemEntity getEntity() {
public @NotNull ItemEntity getEntity() {
return (ItemEntity) entity;
}
@ -44,8 +43,7 @@ public class EntityItemMergeEvent implements EntityEvent, CancellableEvent {
*
* @return the merged ItemEntity
*/
@NotNull
public ItemEntity getMerged() {
public @NotNull ItemEntity getMerged() {
return merged;
}
@ -54,8 +52,7 @@ public class EntityItemMergeEvent implements EntityEvent, CancellableEvent {
*
* @return the item stack
*/
@NotNull
public ItemStack getResult() {
public @NotNull ItemStack getResult() {
return result;
}

View File

@ -1,11 +1,11 @@
package net.minestom.server.event.entity;
import net.minestom.server.entity.Entity;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import net.minestom.server.potion.Potion;
import org.jetbrains.annotations.NotNull;
public class EntityPotionAddEvent implements EntityEvent {
public class EntityPotionAddEvent implements EntityInstanceEvent {
private final Entity entity;
private final Potion potion;

View File

@ -1,11 +1,11 @@
package net.minestom.server.event.entity;
import net.minestom.server.entity.Entity;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import net.minestom.server.potion.Potion;
import org.jetbrains.annotations.NotNull;
public class EntityPotionRemoveEvent implements EntityEvent {
public class EntityPotionRemoveEvent implements EntityInstanceEvent {
private final Entity entity;
private final Potion potion;

View File

@ -4,13 +4,13 @@ import net.minestom.server.coordinate.Point;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityProjectile;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called with {@link EntityProjectile#shoot(Point, double, double)}
*/
public class EntityShootEvent implements EntityEvent, CancellableEvent {
public class EntityShootEvent implements EntityInstanceEvent, CancellableEvent {
private final Entity entity;
private final Entity projectile;

View File

@ -1,14 +1,14 @@
package net.minestom.server.event.entity;
import net.minestom.server.entity.Entity;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
/**
* Called when a new instance is set for an entity.
*/
public class EntitySpawnEvent implements EntityEvent {
public class EntitySpawnEvent implements EntityInstanceEvent {
private final Entity entity;
private final Instance spawnInstance;

View File

@ -1,14 +1,14 @@
package net.minestom.server.event.entity;
import net.minestom.server.entity.Entity;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.EntityInstanceEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called when an entity ticks itself.
* Same event instance used for all tick events for the same entity.
*/
public class EntityTickEvent implements EntityEvent {
public class EntityTickEvent implements EntityInstanceEvent {
private final Entity entity;

Some files were not shown because too many files have changed in this diff Show More