mirror of https://github.com/Minestom/Minestom.git
Merge branch 'master' into area
This commit is contained in:
commit
9a390aadf1
|
@ -4,7 +4,7 @@
|
||||||
[![license](https://img.shields.io/github/license/Minestom/Minestom?style=for-the-badge&color=b2204c)](../LICENSE)
|
[![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)
|
[![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/)
|
[![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)
|
[![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.
|
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
|
# Usage
|
||||||
An example of how to use the Minestom library is available [here](/src/test/java/demo).
|
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?
|
# 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.
|
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.
|
||||||
|
|
|
@ -11,10 +11,10 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 17
|
||||||
- name: Run java checkstyle
|
- name: Run java checkstyle
|
||||||
uses: nikitasavinov/checkstyle-action@0.3.1
|
uses: nikitasavinov/checkstyle-action@0.3.1
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -11,10 +11,10 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 17
|
||||||
- name: Build javadoc
|
- name: Build javadoc
|
||||||
run: gradle javadoc
|
run: gradle javadoc
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,11 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 16
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
distribution: 'adopt'
|
distribution: 'zulu'
|
||||||
java-version: 16
|
java-version: 17
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
- name: Setup gradle cache
|
- name: Setup gradle cache
|
||||||
|
|
|
@ -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"
|
33
build.gradle
33
build.gradle
|
@ -3,14 +3,14 @@ import org.gradle.internal.os.OperatingSystem
|
||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.5.0'
|
id 'org.jetbrains.kotlin.jvm' version '1.5.31'
|
||||||
id 'checkstyle'
|
//id 'checkstyle'
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'net.minestom.server'
|
group 'net.minestom.server'
|
||||||
version '1.0'
|
version '1.0'
|
||||||
|
|
||||||
sourceCompatibility = 11
|
sourceCompatibility = 17
|
||||||
project.ext.lwjglVersion = "3.2.3"
|
project.ext.lwjglVersion = "3.2.3"
|
||||||
|
|
||||||
switch (OperatingSystem.current()) {
|
switch (OperatingSystem.current()) {
|
||||||
|
@ -55,10 +55,10 @@ allprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkstyle {
|
//checkstyle {
|
||||||
toolVersion "8.42"
|
// toolVersion "8.42"
|
||||||
configFile file("${projectDir}/minestom_checks.xml")
|
// configFile file("${projectDir}/minestom_checks.xml")
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -100,17 +100,17 @@ tasks.withType(Zip).configureEach {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Junit Testing Framework
|
// Junit Testing Framework
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||||
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.7.2')
|
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.1')
|
||||||
|
|
||||||
// Only here to ensure J9 module support for extensions and our classloaders
|
// 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
|
// 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
|
// 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
|
// Noise library for terrain generation
|
||||||
// https://jitpack.io/#Articdive/Jnoise
|
// https://jitpack.io/#Articdive/Jnoise
|
||||||
|
@ -126,10 +126,13 @@ dependencies {
|
||||||
// https://mvnrepository.com/artifact/org.jline/jline-terminal-jansi
|
// https://mvnrepository.com/artifact/org.jline/jline-terminal-jansi
|
||||||
implementation group: 'org.jline', name: 'jline-terminal-jansi', version: '3.20.0'
|
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
|
// 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
|
// Code modification
|
||||||
api "org.ow2.asm:asm:${asmVersion}"
|
api "org.ow2.asm:asm:${asmVersion}"
|
||||||
|
@ -172,7 +175,7 @@ dependencies {
|
||||||
lwjglApi "org.lwjgl:lwjgl-opengles"
|
lwjglApi "org.lwjgl:lwjgl-opengles"
|
||||||
lwjglApi "org.lwjgl:lwjgl-glfw"
|
lwjglApi "org.lwjgl:lwjgl-glfw"
|
||||||
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::$lwjglNatives"
|
||||||
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
|
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
|
||||||
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengles::$lwjglNatives"
|
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengles::$lwjglNatives"
|
||||||
|
|
|
@ -7,7 +7,7 @@ plugins {
|
||||||
group 'net.minestom.server'
|
group 'net.minestom.server'
|
||||||
version '1.0'
|
version '1.0'
|
||||||
|
|
||||||
sourceCompatibility = 1.11
|
sourceCompatibility = 17
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("net.minestom.codegen.Generators")
|
mainClass.set("net.minestom.codegen.Generators")
|
||||||
|
|
|
@ -17,15 +17,15 @@ public class Generators {
|
||||||
}
|
}
|
||||||
File outputFolder = new File(args[0]);
|
File outputFolder = new File(args[0]);
|
||||||
var generator = new CodeGenerator(outputFolder);
|
var generator = new CodeGenerator(outputFolder);
|
||||||
generator.generate(resource("blocks.json"), "net.minestom.server.instance.block", "Block", "BlockImpl", "BlockConstants");
|
generator.generate(resource("blocks.json"), "net.minestom.server.instance.block", "Block", "BlockImpl", "Blocks");
|
||||||
generator.generate(resource("items.json"), "net.minestom.server.item", "Material", "MaterialImpl", "MaterialConstants");
|
generator.generate(resource("items.json"), "net.minestom.server.item", "Material", "MaterialImpl", "Materials");
|
||||||
generator.generate(resource("entities.json"), "net.minestom.server.entity", "EntityType", "EntityTypeImpl", "EntityTypeConstants");
|
generator.generate(resource("entities.json"), "net.minestom.server.entity", "EntityType", "EntityTypeImpl", "EntityTypes");
|
||||||
generator.generate(resource("enchantments.json"), "net.minestom.server.item", "Enchantment", "EnchantmentImpl", "EnchantmentConstants");
|
generator.generate(resource("enchantments.json"), "net.minestom.server.item", "Enchantment", "EnchantmentImpl", "Enchantments");
|
||||||
generator.generate(resource("potion_effects.json"), "net.minestom.server.potion", "PotionEffect", "PotionEffectImpl", "PotionEffectConstants");
|
generator.generate(resource("potion_effects.json"), "net.minestom.server.potion", "PotionEffect", "PotionEffectImpl", "PotionEffects");
|
||||||
generator.generate(resource("potions.json"), "net.minestom.server.potion", "PotionType", "PotionTypeImpl", "PotionTypeConstants");
|
generator.generate(resource("potions.json"), "net.minestom.server.potion", "PotionType", "PotionTypeImpl", "PotionTypes");
|
||||||
generator.generate(resource("particles.json"), "net.minestom.server.particle", "Particle", "ParticleImpl", "ParticleConstants");
|
generator.generate(resource("particles.json"), "net.minestom.server.particle", "Particle", "ParticleImpl", "Particles");
|
||||||
generator.generate(resource("sounds.json"), "net.minestom.server.sound", "SoundEvent", "SoundEventImpl", "SoundEventConstants");
|
generator.generate(resource("sounds.json"), "net.minestom.server.sound", "SoundEvent", "SoundEventImpl", "SoundEvents");
|
||||||
generator.generate(resource("custom_statistics.json"), "net.minestom.server.statistic", "StatisticType", "StatisticTypeImpl", "StatisticTypeConstants");
|
generator.generate(resource("custom_statistics.json"), "net.minestom.server.statistic", "StatisticType", "StatisticTypeImpl", "StatisticTypes");
|
||||||
|
|
||||||
// Generate fluids
|
// Generate fluids
|
||||||
new FluidGenerator(resource("fluids.json"), outputFolder).generate();
|
new FluidGenerator(resource("fluids.json"), outputFolder).generate();
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# Update this version with every release. It is purely used for the code generator and data dependency.
|
# Update this version with every release. It is purely used for the code generator and data dependency.
|
||||||
mcVersion = 1.17
|
mcVersion = 1.17
|
||||||
|
|
||||||
asmVersion=9.0
|
asmVersion=9.2
|
||||||
mixinVersion=0.8.1
|
mixinVersion=0.8.4
|
||||||
hephaistosVersion=v1.1.8
|
hephaistosVersion=v1.1.8
|
||||||
kotlinVersion=1.5.0
|
kotlinVersion=1.5.31
|
||||||
adventureVersion=4.8.1
|
adventureVersion=4.9.3
|
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -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");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with 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
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
app_path=$0
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
# Need this for daisy-chained symlinks.
|
||||||
ls=`ls -ld "$PRG"`
|
while
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
[ -h "$app_path" ]
|
||||||
PRG="$link"
|
do
|
||||||
else
|
ls=$( ls -ld "$app_path" )
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
link=${ls#*' -> '}
|
||||||
fi
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
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.
|
# 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"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
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 [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
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."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
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.
|
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
@ -106,80 +140,95 @@ location of your Java installation."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
warn "Could not query maximum file descriptor limit"
|
||||||
fi
|
esac
|
||||||
ulimit -n $MAX_FD
|
case $MAX_FD in #(
|
||||||
if [ $? -ne 0 ] ; then
|
'' | soft) :;; #(
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
*)
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
else
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
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" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --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
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
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" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
jdk:
|
before_install:
|
||||||
- openjdk11
|
- source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||||
|
- sdk update
|
||||||
|
- sdk install java 17-open
|
||||||
|
- sdk use java 17-open
|
|
@ -4,7 +4,7 @@ package net.minestom.server.entity;
|
||||||
* Code autogenerated, do not edit!
|
* Code autogenerated, do not edit!
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface EntityTypeConstants {
|
interface EntityTypes {
|
||||||
EntityType AREA_EFFECT_CLOUD = EntityTypeImpl.get("minecraft:area_effect_cloud");
|
EntityType AREA_EFFECT_CLOUD = EntityTypeImpl.get("minecraft:area_effect_cloud");
|
||||||
|
|
||||||
EntityType ARMOR_STAND = EntityTypeImpl.get("minecraft:armor_stand");
|
EntityType ARMOR_STAND = EntityTypeImpl.get("minecraft:armor_stand");
|
|
@ -4,7 +4,7 @@ package net.minestom.server.instance.block;
|
||||||
* Code autogenerated, do not edit!
|
* Code autogenerated, do not edit!
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface BlockConstants {
|
interface Blocks {
|
||||||
Block AIR = BlockImpl.get("minecraft:air");
|
Block AIR = BlockImpl.get("minecraft:air");
|
||||||
|
|
||||||
Block STONE = BlockImpl.get("minecraft:stone");
|
Block STONE = BlockImpl.get("minecraft:stone");
|
|
@ -4,7 +4,7 @@ package net.minestom.server.item;
|
||||||
* Code autogenerated, do not edit!
|
* Code autogenerated, do not edit!
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface EnchantmentConstants {
|
interface Enchantments {
|
||||||
Enchantment PROTECTION = EnchantmentImpl.get("minecraft:protection");
|
Enchantment PROTECTION = EnchantmentImpl.get("minecraft:protection");
|
||||||
|
|
||||||
Enchantment FIRE_PROTECTION = EnchantmentImpl.get("minecraft:fire_protection");
|
Enchantment FIRE_PROTECTION = EnchantmentImpl.get("minecraft:fire_protection");
|
|
@ -4,7 +4,7 @@ package net.minestom.server.item;
|
||||||
* Code autogenerated, do not edit!
|
* Code autogenerated, do not edit!
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface MaterialConstants {
|
interface Materials {
|
||||||
Material AIR = MaterialImpl.get("minecraft:air");
|
Material AIR = MaterialImpl.get("minecraft:air");
|
||||||
|
|
||||||
Material STONE = MaterialImpl.get("minecraft:stone");
|
Material STONE = MaterialImpl.get("minecraft:stone");
|
|
@ -4,7 +4,7 @@ package net.minestom.server.particle;
|
||||||
* Code autogenerated, do not edit!
|
* Code autogenerated, do not edit!
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface ParticleConstants {
|
interface Particles {
|
||||||
Particle AMBIENT_ENTITY_EFFECT = ParticleImpl.get("minecraft:ambient_entity_effect");
|
Particle AMBIENT_ENTITY_EFFECT = ParticleImpl.get("minecraft:ambient_entity_effect");
|
||||||
|
|
||||||
Particle ANGRY_VILLAGER = ParticleImpl.get("minecraft:angry_villager");
|
Particle ANGRY_VILLAGER = ParticleImpl.get("minecraft:angry_villager");
|
|
@ -4,7 +4,7 @@ package net.minestom.server.potion;
|
||||||
* Code autogenerated, do not edit!
|
* Code autogenerated, do not edit!
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface PotionEffectConstants {
|
interface PotionEffects {
|
||||||
PotionEffect SPEED = PotionEffectImpl.get("minecraft:speed");
|
PotionEffect SPEED = PotionEffectImpl.get("minecraft:speed");
|
||||||
|
|
||||||
PotionEffect SLOWNESS = PotionEffectImpl.get("minecraft:slowness");
|
PotionEffect SLOWNESS = PotionEffectImpl.get("minecraft:slowness");
|
|
@ -4,7 +4,7 @@ package net.minestom.server.potion;
|
||||||
* Code autogenerated, do not edit!
|
* Code autogenerated, do not edit!
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface PotionTypeConstants {
|
interface PotionTypes {
|
||||||
PotionType EMPTY = PotionTypeImpl.get("minecraft:empty");
|
PotionType EMPTY = PotionTypeImpl.get("minecraft:empty");
|
||||||
|
|
||||||
PotionType WATER = PotionTypeImpl.get("minecraft:water");
|
PotionType WATER = PotionTypeImpl.get("minecraft:water");
|
|
@ -4,7 +4,7 @@ package net.minestom.server.sound;
|
||||||
* Code autogenerated, do not edit!
|
* Code autogenerated, do not edit!
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface SoundEventConstants {
|
interface SoundEvents {
|
||||||
SoundEvent AMBIENT_CAVE = SoundEventImpl.get("minecraft:ambient.cave");
|
SoundEvent AMBIENT_CAVE = SoundEventImpl.get("minecraft:ambient.cave");
|
||||||
|
|
||||||
SoundEvent AMBIENT_BASALT_DELTAS_ADDITIONS = SoundEventImpl.get("minecraft:ambient.basalt_deltas.additions");
|
SoundEvent AMBIENT_BASALT_DELTAS_ADDITIONS = SoundEventImpl.get("minecraft:ambient.basalt_deltas.additions");
|
|
@ -4,7 +4,7 @@ package net.minestom.server.statistic;
|
||||||
* Code autogenerated, do not edit!
|
* Code autogenerated, do not edit!
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface StatisticTypeConstants {
|
interface StatisticTypes {
|
||||||
StatisticType LEAVE_GAME = StatisticTypeImpl.get("minecraft:leave_game");
|
StatisticType LEAVE_GAME = StatisticTypeImpl.get("minecraft:leave_game");
|
||||||
|
|
||||||
StatisticType PLAY_TIME = StatisticTypeImpl.get("minecraft:play_time");
|
StatisticType PLAY_TIME = StatisticTypeImpl.get("minecraft:play_time");
|
|
@ -4,6 +4,7 @@ import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.map.Framebuffer;
|
import net.minestom.server.map.Framebuffer;
|
||||||
import net.minestom.server.map.MapColors;
|
import net.minestom.server.map.MapColors;
|
||||||
import net.minestom.server.timer.Task;
|
import net.minestom.server.timer.Task;
|
||||||
|
import net.minestom.server.utils.thread.ThreadBindingExecutor;
|
||||||
import org.lwjgl.BufferUtils;
|
import org.lwjgl.BufferUtils;
|
||||||
import org.lwjgl.PointerBuffer;
|
import org.lwjgl.PointerBuffer;
|
||||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||||
|
@ -27,6 +28,8 @@ public abstract class GLFWCapableBuffer {
|
||||||
private final ByteBuffer colorsBuffer;
|
private final ByteBuffer colorsBuffer;
|
||||||
private boolean onlyMapColors;
|
private boolean onlyMapColors;
|
||||||
|
|
||||||
|
private static ThreadBindingExecutor threadBindingPool;
|
||||||
|
|
||||||
protected GLFWCapableBuffer(int width, int height) {
|
protected GLFWCapableBuffer(int width, int height) {
|
||||||
this(width, height, GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
|
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.");
|
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() {
|
public GLFWCapableBuffer unbindContextFromThread() {
|
||||||
|
@ -80,14 +89,17 @@ public abstract class GLFWCapableBuffer {
|
||||||
return MinecraftServer.getSchedulerManager()
|
return MinecraftServer.getSchedulerManager()
|
||||||
.buildTask(new Runnable() {
|
.buildTask(new Runnable() {
|
||||||
private boolean first = true;
|
private boolean first = true;
|
||||||
|
private final Runnable subAction = () -> {
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if(first) {
|
if(first) {
|
||||||
changeRenderingThreadToCurrent();
|
changeRenderingThreadToCurrent();
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
render(rendering);
|
render(rendering);
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
threadBindingPool.execute(subAction);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.repeat(period)
|
.repeat(period)
|
||||||
|
|
|
@ -6,14 +6,12 @@ import net.minestom.server.command.CommandManager;
|
||||||
import net.minestom.server.data.DataManager;
|
import net.minestom.server.data.DataManager;
|
||||||
import net.minestom.server.data.DataType;
|
import net.minestom.server.data.DataType;
|
||||||
import net.minestom.server.data.SerializableData;
|
import net.minestom.server.data.SerializableData;
|
||||||
import net.minestom.server.entity.Player;
|
|
||||||
import net.minestom.server.event.GlobalEventHandler;
|
import net.minestom.server.event.GlobalEventHandler;
|
||||||
import net.minestom.server.exception.ExceptionManager;
|
import net.minestom.server.exception.ExceptionManager;
|
||||||
import net.minestom.server.extensions.Extension;
|
import net.minestom.server.extensions.Extension;
|
||||||
import net.minestom.server.extensions.ExtensionManager;
|
import net.minestom.server.extensions.ExtensionManager;
|
||||||
import net.minestom.server.fluid.Fluid;
|
import net.minestom.server.fluid.Fluid;
|
||||||
import net.minestom.server.gamedata.tags.TagManager;
|
import net.minestom.server.gamedata.tags.TagManager;
|
||||||
import net.minestom.server.instance.Chunk;
|
|
||||||
import net.minestom.server.instance.InstanceManager;
|
import net.minestom.server.instance.InstanceManager;
|
||||||
import net.minestom.server.instance.block.BlockManager;
|
import net.minestom.server.instance.block.BlockManager;
|
||||||
import net.minestom.server.instance.block.rule.BlockPlacementRule;
|
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.PacketProcessor;
|
||||||
import net.minestom.server.network.packet.server.play.PluginMessagePacket;
|
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.ServerDifficultyPacket;
|
||||||
import net.minestom.server.network.packet.server.play.UpdateViewDistancePacket;
|
|
||||||
import net.minestom.server.network.socket.Server;
|
import net.minestom.server.network.socket.Server;
|
||||||
import net.minestom.server.ping.ResponseDataConsumer;
|
import net.minestom.server.ping.ResponseDataConsumer;
|
||||||
import net.minestom.server.recipe.RecipeManager;
|
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.StorageLocation;
|
||||||
import net.minestom.server.storage.StorageManager;
|
import net.minestom.server.storage.StorageManager;
|
||||||
import net.minestom.server.terminal.MinestomTerminal;
|
import net.minestom.server.terminal.MinestomTerminal;
|
||||||
|
import net.minestom.server.thread.MinestomThreadPool;
|
||||||
import net.minestom.server.timer.SchedulerManager;
|
import net.minestom.server.timer.SchedulerManager;
|
||||||
import net.minestom.server.utils.MathUtils;
|
import net.minestom.server.utils.MathUtils;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.thread.MinestomThread;
|
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import net.minestom.server.world.Difficulty;
|
import net.minestom.server.world.Difficulty;
|
||||||
import net.minestom.server.world.DimensionTypeManager;
|
import net.minestom.server.world.DimensionTypeManager;
|
||||||
|
@ -118,10 +115,10 @@ public final class MinecraftServer {
|
||||||
// Data
|
// Data
|
||||||
private static boolean initialized;
|
private static boolean initialized;
|
||||||
private static boolean started;
|
private static boolean started;
|
||||||
private static boolean stopping;
|
private static volatile boolean stopping;
|
||||||
|
|
||||||
private static int chunkViewDistance = 8;
|
private static int chunkViewDistance = Integer.getInteger("minestom.chunk-view-distance", 8);
|
||||||
private static int entityViewDistance = 5;
|
private static int entityViewDistance = Integer.getInteger("minestom.entity-view-distance", 5);
|
||||||
private static int compressionThreshold = 256;
|
private static int compressionThreshold = 256;
|
||||||
private static boolean groupedPacket = true;
|
private static boolean groupedPacket = true;
|
||||||
private static boolean terminalEnabled = System.getProperty("minestom.terminal.disabled") == null;
|
private static boolean terminalEnabled = System.getProperty("minestom.terminal.disabled") == null;
|
||||||
|
@ -337,6 +334,7 @@ public final class MinecraftServer {
|
||||||
*
|
*
|
||||||
* @return the data manager
|
* @return the data manager
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static DataManager getDataManager() {
|
public static DataManager getDataManager() {
|
||||||
checkInitStatus(dataManager);
|
checkInitStatus(dataManager);
|
||||||
return dataManager;
|
return dataManager;
|
||||||
|
@ -446,20 +444,14 @@ public final class MinecraftServer {
|
||||||
*
|
*
|
||||||
* @param chunkViewDistance the new chunk view distance
|
* @param chunkViewDistance the new chunk view distance
|
||||||
* @throws IllegalArgumentException if {@code chunkViewDistance} is not between 2 and 32
|
* @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) {
|
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),
|
Check.argCondition(!MathUtils.isBetween(chunkViewDistance, 2, 32),
|
||||||
"The chunk view distance must be between 2 and 32");
|
"The chunk view distance must be between 2 and 32");
|
||||||
MinecraftServer.chunkViewDistance = chunkViewDistance;
|
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
|
* @param entityViewDistance the new entity view distance
|
||||||
* @throws IllegalArgumentException if {@code entityViewDistance} is not between 0 and 32
|
* @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) {
|
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),
|
Check.argCondition(!MathUtils.isBetween(entityViewDistance, 0, 32),
|
||||||
"The entity view distance must be between 0 and 32");
|
"The entity view distance must be between 0 and 32");
|
||||||
MinecraftServer.entityViewDistance = entityViewDistance;
|
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();
|
updateManager.start();
|
||||||
|
|
||||||
// Init & start the TCP server
|
// Init server
|
||||||
try {
|
try {
|
||||||
server.start(new InetSocketAddress(address, port));
|
server.init(new InetSocketAddress(address, port));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
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.");
|
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.");
|
LOGGER.info("Minestom server started successfully.");
|
||||||
|
|
||||||
if (terminalEnabled) {
|
if (terminalEnabled) {
|
||||||
MinestomTerminal.start();
|
MinestomTerminal.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop the server on SIGINT
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(MinecraftServer::stopCleanly));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops this server properly (saves if needed, kicking players, etc.)
|
* Stops this server properly (saves if needed, kicking players, etc.)
|
||||||
*/
|
*/
|
||||||
public static void stopCleanly() {
|
public static void stopCleanly() {
|
||||||
|
if (stopping) return;
|
||||||
stopping = true;
|
stopping = true;
|
||||||
LOGGER.info("Stopping Minestom server.");
|
LOGGER.info("Stopping Minestom server.");
|
||||||
extensionManager.unloadAllExtensions();
|
extensionManager.unloadAllExtensions();
|
||||||
|
@ -719,7 +713,7 @@ public final class MinecraftServer {
|
||||||
LOGGER.info("Shutting down all thread pools.");
|
LOGGER.info("Shutting down all thread pools.");
|
||||||
benchmarkManager.disable();
|
benchmarkManager.disable();
|
||||||
MinestomTerminal.stop();
|
MinestomTerminal.stop();
|
||||||
MinestomThread.shutdownAll();
|
MinestomThreadPool.shutdownAll();
|
||||||
LOGGER.info("Minestom server stopped successfully.");
|
LOGGER.info("Minestom server stopped successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package net.minestom.server;
|
package net.minestom.server;
|
||||||
|
|
||||||
import net.minestom.server.acquirable.Acquirable;
|
import net.minestom.server.acquirable.Acquirable;
|
||||||
import net.minestom.server.entity.Player;
|
|
||||||
import net.minestom.server.instance.Chunk;
|
import net.minestom.server.instance.Chunk;
|
||||||
import net.minestom.server.instance.Instance;
|
import net.minestom.server.instance.Instance;
|
||||||
import net.minestom.server.instance.InstanceManager;
|
import net.minestom.server.instance.InstanceManager;
|
||||||
import net.minestom.server.monitoring.TickMonitor;
|
import net.minestom.server.monitoring.TickMonitor;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import net.minestom.server.thread.SingleThreadProvider;
|
import net.minestom.server.thread.MinestomThread;
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -22,14 +23,13 @@ import java.util.function.LongConsumer;
|
||||||
/**
|
/**
|
||||||
* Manager responsible for the server ticks.
|
* Manager responsible for the server ticks.
|
||||||
* <p>
|
* <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 {
|
public final class UpdateManager {
|
||||||
|
|
||||||
private volatile boolean stopRequested;
|
private volatile boolean stopRequested;
|
||||||
|
|
||||||
// TODO make configurable
|
// 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> tickStartCallbacks = new ConcurrentLinkedQueue<>();
|
||||||
private final Queue<LongConsumer> tickEndCallbacks = 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.
|
* Starts the server loop in the update thread.
|
||||||
*/
|
*/
|
||||||
void start() {
|
void start() {
|
||||||
final ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
|
new TickSchedulerThread().start();
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a server tick and returns only once all the futures are completed.
|
* Gets the current {@link ThreadDispatcher}.
|
||||||
*
|
|
||||||
* @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}.
|
|
||||||
*
|
*
|
||||||
* @return the current thread provider
|
* @return the current thread provider
|
||||||
*/
|
*/
|
||||||
public @NotNull ThreadProvider getThreadProvider() {
|
public @NotNull ThreadDispatcher getThreadProvider() {
|
||||||
return threadProvider;
|
return threadDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the {@link ThreadProvider} that an instance has been created.
|
* Signals the {@link ThreadDispatcher} that an instance has been created.
|
||||||
* <p>
|
* <p>
|
||||||
* WARNING: should be automatically done by the {@link InstanceManager}.
|
* WARNING: should be automatically done by the {@link InstanceManager}.
|
||||||
*
|
*
|
||||||
* @param instance the instance
|
* @param instance the instance
|
||||||
*/
|
*/
|
||||||
public void signalInstanceCreate(Instance 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>
|
* <p>
|
||||||
* WARNING: should be automatically done by the {@link InstanceManager}.
|
* WARNING: should be automatically done by the {@link InstanceManager}.
|
||||||
*
|
*
|
||||||
* @param instance the instance
|
* @param instance the instance
|
||||||
*/
|
*/
|
||||||
public void signalInstanceDelete(Instance 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>
|
* <p>
|
||||||
* WARNING: should be automatically done by the {@link Instance} implementation.
|
* WARNING: should be automatically done by the {@link Instance} implementation.
|
||||||
*
|
*
|
||||||
* @param chunk the loaded chunk
|
* @param chunk the loaded chunk
|
||||||
*/
|
*/
|
||||||
public void signalChunkLoad(@NotNull Chunk 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>
|
* <p>
|
||||||
* WARNING: should be automatically done by the {@link Instance} implementation.
|
* WARNING: should be automatically done by the {@link Instance} implementation.
|
||||||
*
|
*
|
||||||
* @param chunk the unloaded chunk
|
* @param chunk the unloaded chunk
|
||||||
*/
|
*/
|
||||||
public void signalChunkUnload(@NotNull Chunk 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() {
|
public void stop() {
|
||||||
this.stopRequested = true;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ package net.minestom.server;
|
||||||
import net.kyori.adventure.audience.Audience;
|
import net.kyori.adventure.audience.Audience;
|
||||||
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
||||||
import net.minestom.server.entity.Player;
|
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.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -35,8 +37,7 @@ public interface Viewable {
|
||||||
*
|
*
|
||||||
* @return A Set containing all the element's viewers
|
* @return A Set containing all the element's viewers
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull Set<@NotNull Player> getViewers();
|
||||||
Set<Player> getViewers();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets if a player is seeing this viewable object.
|
* Gets if a player is seeing this viewable object.
|
||||||
|
@ -60,6 +61,13 @@ public interface Viewable {
|
||||||
PacketUtils.sendGroupedPacket(getViewers(), packet);
|
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.
|
* Sends multiple packets to all viewers.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -85,6 +93,11 @@ public interface Viewable {
|
||||||
sendPacketToViewers(packet);
|
sendPacketToViewers(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
default void sendPacketToViewersAndSelf(@NotNull FramedPacket framedPacket) {
|
||||||
|
sendPacketToViewers(framedPacket);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the result of {@link #getViewers()} as an Adventure Audience.
|
* Gets the result of {@link #getViewers()} as an Adventure Audience.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
package net.minestom.server.acquirable;
|
package net.minestom.server.acquirable;
|
||||||
|
|
||||||
import net.minestom.server.entity.Entity;
|
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.thread.TickThread;
|
||||||
import net.minestom.server.utils.async.AsyncUtils;
|
import net.minestom.server.utils.async.AsyncUtils;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -26,19 +24,12 @@ public interface Acquirable<T> {
|
||||||
* @return the entities ticked in the current thread
|
* @return the entities ticked in the current thread
|
||||||
*/
|
*/
|
||||||
static @NotNull Stream<@NotNull Entity> currentEntities() {
|
static @NotNull Stream<@NotNull Entity> currentEntities() {
|
||||||
return AcquirableImpl.ENTRIES.get().stream()
|
final Thread currentThread = Thread.currentThread();
|
||||||
.flatMap(chunkEntry -> chunkEntry.getEntities().stream());
|
if (currentThread instanceof TickThread) {
|
||||||
}
|
return ((TickThread) currentThread).entries().stream()
|
||||||
|
.flatMap(chunkEntry -> chunkEntry.entities().stream());
|
||||||
/**
|
}
|
||||||
* Mostly for internal use, external calls are unrecommended as they could lead
|
return Stream.empty();
|
||||||
* to unexpected behavior.
|
|
||||||
*
|
|
||||||
* @param entries the new chunk entries
|
|
||||||
*/
|
|
||||||
@ApiStatus.Internal
|
|
||||||
static void refreshEntries(@NotNull Collection<ThreadProvider.ChunkEntry> entries) {
|
|
||||||
AcquirableImpl.ENTRIES.set(entries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,8 +76,7 @@ public interface Acquirable<T> {
|
||||||
* @see #sync(Consumer) for auto-closeable capability
|
* @see #sync(Consumer) for auto-closeable capability
|
||||||
*/
|
*/
|
||||||
default @NotNull Acquired<T> lock() {
|
default @NotNull Acquired<T> lock() {
|
||||||
var optional = local();
|
return new Acquired<>(unwrap(), getHandler().getTickThread());
|
||||||
return optional.map(Acquired::local).orElseGet(() -> Acquired.locked(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,14 +90,19 @@ public interface Acquirable<T> {
|
||||||
* {@link Optional#empty()} otherwise
|
* {@link Optional#empty()} otherwise
|
||||||
*/
|
*/
|
||||||
default @NotNull Optional<T> local() {
|
default @NotNull Optional<T> local() {
|
||||||
final Thread currentThread = Thread.currentThread();
|
if (isLocal()) return Optional.of(unwrap());
|
||||||
final TickThread tickThread = getHandler().getTickThread();
|
|
||||||
if (Objects.equals(currentThread, tickThread)) {
|
|
||||||
return Optional.of(unwrap());
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
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.
|
* Locks the acquirable element, execute {@code consumer} synchronously and unlock the thread.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -117,7 +112,7 @@ public interface Acquirable<T> {
|
||||||
* @see #async(Consumer)
|
* @see #async(Consumer)
|
||||||
*/
|
*/
|
||||||
default void sync(@NotNull Consumer<T> consumer) {
|
default void sync(@NotNull Consumer<T> consumer) {
|
||||||
var acquired = lock();
|
Acquired<T> acquired = lock();
|
||||||
consumer.accept(acquired.get());
|
consumer.accept(acquired.get());
|
||||||
acquired.unlock();
|
acquired.unlock();
|
||||||
}
|
}
|
||||||
|
@ -153,22 +148,21 @@ public interface Acquirable<T> {
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
@NotNull Handler getHandler();
|
@NotNull Handler getHandler();
|
||||||
|
|
||||||
class Handler {
|
final class Handler {
|
||||||
|
private volatile ThreadDispatcher.ChunkEntry chunkEntry;
|
||||||
|
|
||||||
private volatile ThreadProvider.ChunkEntry chunkEntry;
|
public ThreadDispatcher.ChunkEntry getChunkEntry() {
|
||||||
|
|
||||||
public ThreadProvider.ChunkEntry getChunkEntry() {
|
|
||||||
return chunkEntry;
|
return chunkEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public void refreshChunkEntry(@NotNull ThreadProvider.ChunkEntry chunkEntry) {
|
public void refreshChunkEntry(@NotNull ThreadDispatcher.ChunkEntry chunkEntry) {
|
||||||
this.chunkEntry = chunkEntry;
|
this.chunkEntry = chunkEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TickThread getTickThread() {
|
public TickThread getTickThread() {
|
||||||
return chunkEntry != null ? chunkEntry.getThread() : null;
|
final ThreadDispatcher.ChunkEntry entry = this.chunkEntry;
|
||||||
|
return entry != null ? entry.thread() : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
package net.minestom.server.acquirable;
|
package net.minestom.server.acquirable;
|
||||||
|
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
|
||||||
import net.minestom.server.thread.TickThread;
|
import net.minestom.server.thread.TickThread;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
class AcquirableImpl<T> implements Acquirable<T> {
|
final class AcquirableImpl<T> implements Acquirable<T> {
|
||||||
|
static final AtomicLong WAIT_COUNTER_NANO = new AtomicLong();
|
||||||
protected static final ThreadLocal<Collection<ThreadProvider.ChunkEntry>> ENTRIES = ThreadLocal.withInitial(Collections::emptySet);
|
|
||||||
protected static final AtomicLong WAIT_COUNTER_NANO = new AtomicLong();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global lock used for synchronization.
|
* Global lock used for synchronization.
|
||||||
|
@ -38,41 +33,37 @@ class AcquirableImpl<T> implements Acquirable<T> {
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static @Nullable ReentrantLock enter(@Nullable Thread currentThread, @Nullable TickThread elementThread) {
|
static @Nullable ReentrantLock enter(@NotNull Thread currentThread, @Nullable TickThread elementThread) {
|
||||||
// Monitoring
|
if (elementThread == null) return null;
|
||||||
long time = System.nanoTime();
|
if (currentThread == elementThread) return null;
|
||||||
|
final ReentrantLock currentLock = currentThread instanceof TickThread ? ((TickThread) currentThread).lock() : null;
|
||||||
ReentrantLock currentLock;
|
final ReentrantLock targetLock = elementThread.lock();
|
||||||
{
|
if (targetLock.isHeldByCurrentThread()) return null;
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Monitoring
|
// 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) {
|
if (lock != null) {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
GLOBAL_LOCK.unlock();
|
||||||
}
|
}
|
||||||
GLOBAL_LOCK.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,47 +6,39 @@ import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
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 T value;
|
||||||
|
private final Thread owner;
|
||||||
private final boolean locked;
|
|
||||||
private final ReentrantLock lock;
|
private final ReentrantLock lock;
|
||||||
|
|
||||||
private boolean unlocked;
|
private boolean unlocked;
|
||||||
|
|
||||||
protected static <T> Acquired<T> local(@NotNull T value) {
|
Acquired(T value, TickThread tickThread) {
|
||||||
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) {
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.locked = locked;
|
this.owner = Thread.currentThread();
|
||||||
this.lock = locked ? AcquirableImpl.enter(currentThread, tickThread) : null;
|
this.lock = AcquirableImpl.enter(owner, tickThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull T get() {
|
public @NotNull T get() {
|
||||||
checkLock();
|
safeCheck();
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unlock() {
|
public void unlock() {
|
||||||
checkLock();
|
safeCheck();
|
||||||
this.unlocked = true;
|
this.unlocked = true;
|
||||||
if (!locked)
|
|
||||||
return;
|
|
||||||
AcquirableImpl.leave(lock);
|
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!");
|
Check.stateCondition(unlocked, "The acquired element has already been unlocked!");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,15 @@ import net.kyori.adventure.bossbar.BossBar;
|
||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
import net.kyori.adventure.sound.Sound;
|
import net.kyori.adventure.sound.Sound;
|
||||||
import net.kyori.adventure.sound.SoundStop;
|
import net.kyori.adventure.sound.SoundStop;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
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.entity.Entity;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
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.*;
|
||||||
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.sound.SoundEvent;
|
import net.minestom.server.sound.SoundEvent;
|
||||||
|
import net.minestom.server.utils.TickUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -141,10 +142,9 @@ public class AdventurePacketConvertor {
|
||||||
public static @NotNull ServerPacket createSoundPacket(@NotNull Sound sound, Sound.@NotNull Emitter emitter) {
|
public static @NotNull ServerPacket createSoundPacket(@NotNull Sound sound, Sound.@NotNull Emitter emitter) {
|
||||||
if (emitter == Sound.Emitter.self())
|
if (emitter == Sound.Emitter.self())
|
||||||
throw new IllegalArgumentException("you must replace instances of Emitter.self() before calling this method");
|
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");
|
throw new IllegalArgumentException("you can only call this method with entities");
|
||||||
|
|
||||||
final Entity entity = (Entity) emitter;
|
|
||||||
final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString());
|
final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString());
|
||||||
|
|
||||||
if (minestomSound != null) {
|
if (minestomSound != null) {
|
||||||
|
@ -206,4 +206,28 @@ public class AdventurePacketConvertor {
|
||||||
|
|
||||||
return packet;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,7 +12,6 @@ import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holder of custom audiences.
|
* Holder of custom audiences.
|
||||||
|
@ -97,7 +96,7 @@ public class AudienceRegistry {
|
||||||
if (this.isEmpty()) {
|
if (this.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
} else {
|
} 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
|
* @return the matching audience members
|
||||||
*/
|
*/
|
||||||
public @NotNull Iterable<? extends Audience> of(@NotNull Predicate<Audience> filter) {
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,16 @@ import net.kyori.adventure.identity.Identity;
|
||||||
import net.kyori.adventure.sound.Sound;
|
import net.kyori.adventure.sound.Sound;
|
||||||
import net.kyori.adventure.sound.SoundStop;
|
import net.kyori.adventure.sound.SoundStop;
|
||||||
import net.kyori.adventure.text.Component;
|
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.MinecraftServer;
|
||||||
import net.minestom.server.adventure.AdventurePacketConvertor;
|
import net.minestom.server.adventure.AdventurePacketConvertor;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.message.ChatPosition;
|
import net.minestom.server.message.ChatPosition;
|
||||||
import net.minestom.server.message.Messenger;
|
import net.minestom.server.message.Messenger;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
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 net.minestom.server.utils.PacketUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@ -47,6 +49,7 @@ public interface PacketGroupingAudience extends ForwardingAudience {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast a ServerPacket to all players of this audience
|
* Broadcast a ServerPacket to all players of this audience
|
||||||
|
*
|
||||||
* @param packet the packet to broadcast
|
* @param packet the packet to broadcast
|
||||||
*/
|
*/
|
||||||
default void sendGroupedPacket(ServerPacket packet) {
|
default void sendGroupedPacket(ServerPacket packet) {
|
||||||
|
@ -69,9 +72,8 @@ public interface PacketGroupingAudience extends ForwardingAudience {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void showTitle(@NotNull Title title) {
|
default <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value) {
|
||||||
sendGroupedPacket(new SetTitleTextPacket(title.title()));
|
sendGroupedPacket(AdventurePacketConvertor.createTitlePartPacket(part, value));
|
||||||
sendGroupedPacket(new SetTitleSubTitlePacket(title.subtitle()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,8 +118,6 @@ public interface PacketGroupingAudience extends ForwardingAudience {
|
||||||
sendGroupedPacket(AdventurePacketConvertor.createSoundStopPacket(stop));
|
sendGroupedPacket(AdventurePacketConvertor.createSoundStopPacket(stop));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default @NotNull Iterable<? extends Audience> audiences() {
|
default @NotNull Iterable<? extends Audience> audiences() {
|
||||||
return this.getPlayers();
|
return this.getPlayers();
|
||||||
|
|
|
@ -147,7 +147,7 @@ public class BossBarManager {
|
||||||
if (holders == null) {
|
if (holders == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
} else {
|
} else {
|
||||||
return holders.stream().map(holder -> holder.bar).collect(Collectors.toUnmodifiableList());
|
return holders.stream().map(holder -> holder.bar).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package net.minestom.server.adventure.provider;
|
package net.minestom.server.adventure.provider;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
@ -17,6 +15,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer {
|
final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer {
|
||||||
static final NBTLegacyHoverEventSerializer INSTANCE = new NBTLegacyHoverEventSerializer();
|
static final NBTLegacyHoverEventSerializer INSTANCE = new NBTLegacyHoverEventSerializer();
|
||||||
|
@ -33,8 +32,8 @@ final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer
|
||||||
try {
|
try {
|
||||||
// attempt the parse
|
// attempt the parse
|
||||||
final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw);
|
final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw);
|
||||||
if (!(nbt instanceof NBTCompound)) throw new IOException("contents were not a compound");
|
if (!(nbt instanceof NBTCompound contents)) throw new IOException("contents were not a compound");
|
||||||
final NBTCompound contents = (NBTCompound) nbt, tag = contents.getCompound(ITEM_TAG);
|
final NBTCompound tag = contents.getCompound(ITEM_TAG);
|
||||||
|
|
||||||
// create the event
|
// create the event
|
||||||
return HoverEvent.ShowItem.of(
|
return HoverEvent.ShowItem.of(
|
||||||
|
@ -53,9 +52,7 @@ final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw);
|
final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw);
|
||||||
if (!(nbt instanceof NBTCompound)) throw new IOException("contents were not a compound");
|
if (!(nbt instanceof NBTCompound contents)) throw new IOException("contents were not a compound");
|
||||||
|
|
||||||
final NBTCompound contents = (NBTCompound) nbt;
|
|
||||||
|
|
||||||
return HoverEvent.ShowEntity.of(
|
return HoverEvent.ShowEntity.of(
|
||||||
Key.key(Objects.requireNonNullElse(contents.getString(ENTITY_TYPE), "")),
|
Key.key(Objects.requireNonNullElse(contents.getString(ENTITY_TYPE), "")),
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package net.minestom.server.attribute;
|
package net.minestom.server.attribute;
|
||||||
|
|
||||||
import net.minestom.server.utils.UniqueIdUtils;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represent an attribute modifier.
|
* Represent an attribute modifier.
|
||||||
|
@ -24,7 +22,7 @@ public class AttributeModifier {
|
||||||
* @param operation the operation to apply this modifier with
|
* @param operation the operation to apply this modifier with
|
||||||
*/
|
*/
|
||||||
public AttributeModifier(@NotNull String name, float amount, @NotNull AttributeOperation operation) {
|
public AttributeModifier(@NotNull String name, float amount, @NotNull AttributeOperation operation) {
|
||||||
this(UniqueIdUtils.createRandomUUID(ThreadLocalRandom.current()), name, amount, operation);
|
this(UUID.randomUUID(), name, amount, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,13 +19,42 @@ public class BoundingBox {
|
||||||
private final Entity entity;
|
private final Entity entity;
|
||||||
private final double x, y, z;
|
private final double x, y, z;
|
||||||
|
|
||||||
private volatile Pos lastPosition;
|
private final CachedFace bottomFace = new CachedFace(() -> List.of(
|
||||||
private List<Vec> bottomFace;
|
new Vec(getMinX(), getMinY(), getMinZ()),
|
||||||
private List<Vec> topFace;
|
new Vec(getMaxX(), getMinY(), getMinZ()),
|
||||||
private List<Vec> leftFace;
|
new Vec(getMaxX(), getMinY(), getMaxZ()),
|
||||||
private List<Vec> rightFace;
|
new Vec(getMinX(), getMinY(), getMaxZ())
|
||||||
private List<Vec> frontFace;
|
));
|
||||||
private List<Vec> backFace;
|
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.
|
* 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) {
|
public boolean intersectWithBlock(int blockX, int blockY, int blockZ) {
|
||||||
final double offsetX = 1;
|
final double offsetX = 1;
|
||||||
final double maxX = (double) blockX + offsetX;
|
final double maxX = (double) blockX + offsetX;
|
||||||
|
|
||||||
final boolean checkX = getMinX() < maxX && getMaxX() > (double) blockX;
|
final boolean checkX = getMinX() < maxX && getMaxX() > (double) blockX;
|
||||||
if (!checkX)
|
if (!checkX) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
final double maxY = (double) blockY + 0.99999;
|
final double maxY = (double) blockY + 0.99999;
|
||||||
|
|
||||||
final boolean checkY = getMinY() < maxY && getMaxY() > (double) blockY;
|
final boolean checkY = getMinY() < maxY && getMaxY() > (double) blockY;
|
||||||
if (!checkY)
|
if (!checkY) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
final double offsetZ = 1;
|
final double offsetZ = 1;
|
||||||
final double maxZ = (double) blockZ + offsetZ;
|
final double maxZ = (double) blockZ + offsetZ;
|
||||||
|
|
||||||
// Z check
|
// Z check
|
||||||
return getMinZ() < maxZ && getMaxZ() > (double) blockZ;
|
return getMinZ() < maxZ && getMaxZ() > (double) blockZ;
|
||||||
}
|
}
|
||||||
|
@ -102,16 +127,106 @@ public class BoundingBox {
|
||||||
return intersectWithBlock(blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ());
|
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) {
|
public boolean intersect(double x, double y, double z) {
|
||||||
return (x >= getMinX() && x <= getMaxX()) &&
|
return (x >= getMinX() && x <= getMaxX()) &&
|
||||||
(y >= getMinY() && y <= getMaxY()) &&
|
(y >= getMinY() && y <= getMaxY()) &&
|
||||||
(z >= getMinZ() && z <= getMaxZ());
|
(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) {
|
public boolean intersect(@NotNull Point point) {
|
||||||
return intersect(point.x(), point.y(), point.z());
|
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.
|
* 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
|
* @param z the Z offset
|
||||||
* @return a new {@link BoundingBox} expanded
|
* @return a new {@link BoundingBox} expanded
|
||||||
*/
|
*/
|
||||||
@NotNull
|
public @NotNull BoundingBox expand(double x, double y, double z) {
|
||||||
public BoundingBox expand(double x, double y, double z) {
|
|
||||||
return new BoundingBox(entity, this.x + x, this.y + y, this.z + 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
|
* @param z the Z offset
|
||||||
* @return a new bounding box contracted
|
* @return a new bounding box contracted
|
||||||
*/
|
*/
|
||||||
@NotNull
|
public @NotNull BoundingBox contract(double x, double y, double z) {
|
||||||
public BoundingBox contract(double x, double y, double z) {
|
|
||||||
return new BoundingBox(entity, this.x - x, this.y - y, this.z - 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}
|
* @return the points at the bottom of the {@link BoundingBox}
|
||||||
*/
|
*/
|
||||||
public @NotNull List<Vec> getBottomFace() {
|
public @NotNull List<Vec> getBottomFace() {
|
||||||
this.bottomFace = get(bottomFace, () ->
|
return bottomFace.get();
|
||||||
List.of(new Vec(getMinX(), getMinY(), getMinZ()),
|
|
||||||
new Vec(getMaxX(), getMinY(), getMinZ()),
|
|
||||||
new Vec(getMaxX(), getMinY(), getMaxZ()),
|
|
||||||
new Vec(getMinX(), getMinY(), getMaxZ())));
|
|
||||||
return bottomFace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -239,12 +347,7 @@ public class BoundingBox {
|
||||||
* @return the points at the top of the {@link BoundingBox}
|
* @return the points at the top of the {@link BoundingBox}
|
||||||
*/
|
*/
|
||||||
public @NotNull List<Vec> getTopFace() {
|
public @NotNull List<Vec> getTopFace() {
|
||||||
this.topFace = get(topFace, () ->
|
return topFace.get();
|
||||||
List.of(new Vec(getMinX(), getMaxY(), getMinZ()),
|
|
||||||
new Vec(getMaxX(), getMaxY(), getMinZ()),
|
|
||||||
new Vec(getMaxX(), getMaxY(), getMaxZ()),
|
|
||||||
new Vec(getMinX(), getMaxY(), getMaxZ())));
|
|
||||||
return topFace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -253,12 +356,7 @@ public class BoundingBox {
|
||||||
* @return the points on the left face of the {@link BoundingBox}
|
* @return the points on the left face of the {@link BoundingBox}
|
||||||
*/
|
*/
|
||||||
public @NotNull List<Vec> getLeftFace() {
|
public @NotNull List<Vec> getLeftFace() {
|
||||||
this.leftFace = get(leftFace, () ->
|
return leftFace.get();
|
||||||
List.of(new Vec(getMinX(), getMinY(), getMinZ()),
|
|
||||||
new Vec(getMinX(), getMaxY(), getMinZ()),
|
|
||||||
new Vec(getMinX(), getMaxY(), getMaxZ()),
|
|
||||||
new Vec(getMinX(), getMinY(), getMaxZ())));
|
|
||||||
return leftFace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -267,12 +365,7 @@ public class BoundingBox {
|
||||||
* @return the points on the right face of the {@link BoundingBox}
|
* @return the points on the right face of the {@link BoundingBox}
|
||||||
*/
|
*/
|
||||||
public @NotNull List<Vec> getRightFace() {
|
public @NotNull List<Vec> getRightFace() {
|
||||||
this.rightFace = get(rightFace, () ->
|
return rightFace.get();
|
||||||
List.of(new Vec(getMaxX(), getMinY(), getMinZ()),
|
|
||||||
new Vec(getMaxX(), getMaxY(), getMinZ()),
|
|
||||||
new Vec(getMaxX(), getMaxY(), getMaxZ()),
|
|
||||||
new Vec(getMaxX(), getMinY(), getMaxZ())));
|
|
||||||
return rightFace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -281,12 +374,7 @@ public class BoundingBox {
|
||||||
* @return the points at the front of the {@link BoundingBox}
|
* @return the points at the front of the {@link BoundingBox}
|
||||||
*/
|
*/
|
||||||
public @NotNull List<Vec> getFrontFace() {
|
public @NotNull List<Vec> getFrontFace() {
|
||||||
this.frontFace = get(frontFace, () ->
|
return frontFace.get();
|
||||||
List.of(new Vec(getMinX(), getMinY(), getMinZ()),
|
|
||||||
new Vec(getMaxX(), getMinY(), getMinZ()),
|
|
||||||
new Vec(getMaxX(), getMaxY(), getMinZ()),
|
|
||||||
new Vec(getMinX(), getMaxY(), getMinZ())));
|
|
||||||
return frontFace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -295,12 +383,7 @@ public class BoundingBox {
|
||||||
* @return the points at the back of the {@link BoundingBox}
|
* @return the points at the back of the {@link BoundingBox}
|
||||||
*/
|
*/
|
||||||
public @NotNull List<Vec> getBackFace() {
|
public @NotNull List<Vec> getBackFace() {
|
||||||
this.backFace = get(backFace, () -> List.of(
|
return backFace.get();
|
||||||
new Vec(getMinX(), getMinY(), getMaxZ()),
|
|
||||||
new Vec(getMaxX(), getMinY(), getMaxZ()),
|
|
||||||
new Vec(getMaxX(), getMaxY(), getMaxZ()),
|
|
||||||
new Vec(getMinX(), getMaxY(), getMaxZ())));
|
|
||||||
return backFace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -315,13 +398,37 @@ public class BoundingBox {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NotNull List<Vec> get(@Nullable List<Vec> face, Supplier<? extends List<Vec>> vecSupplier) {
|
private enum Axis {
|
||||||
final var lastPos = this.lastPosition;
|
X, Y, Z
|
||||||
final var entityPos = entity.getPosition();
|
}
|
||||||
if (face != null && lastPos != null && lastPos.samePoint(entityPos)) {
|
|
||||||
return face;
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,10 @@ import net.minestom.server.instance.Chunk;
|
||||||
import net.minestom.server.instance.Instance;
|
import net.minestom.server.instance.Instance;
|
||||||
import net.minestom.server.instance.WorldBorder;
|
import net.minestom.server.instance.WorldBorder;
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import net.minestom.server.instance.block.BlockGetter;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class CollisionUtils {
|
public class CollisionUtils {
|
||||||
|
@ -75,7 +75,7 @@ public class CollisionUtils {
|
||||||
* @return result of the step
|
* @return result of the step
|
||||||
*/
|
*/
|
||||||
private static StepResult stepAxis(Instance instance, Chunk originChunk, Vec startPosition, Vec axis, double stepAmount, List<Vec> corners) {
|
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 double sign = Math.signum(stepAmount);
|
||||||
final int blockLength = (int) stepAmount;
|
final int blockLength = (int) stepAmount;
|
||||||
final double remainingLength = stepAmount - blockLength;
|
final double remainingLength = stepAmount - blockLength;
|
||||||
|
@ -94,7 +94,7 @@ public class CollisionUtils {
|
||||||
// find the corner which moved the least
|
// find the corner which moved the least
|
||||||
double smallestDisplacement = Double.POSITIVE_INFINITY;
|
double smallestDisplacement = Double.POSITIVE_INFINITY;
|
||||||
for (int i = 0; i < corners.size(); i++) {
|
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) {
|
if (displacement < smallestDisplacement) {
|
||||||
smallestDisplacement = displacement;
|
smallestDisplacement = displacement;
|
||||||
}
|
}
|
||||||
|
@ -111,27 +111,27 @@ public class CollisionUtils {
|
||||||
* @param corners the corners of the bounding box to consider
|
* @param corners the corners of the bounding box to consider
|
||||||
* @return true if found collision
|
* @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);
|
final double sign = Math.signum(amount);
|
||||||
for (int cornerIndex = 0; cornerIndex < corners.size(); cornerIndex++) {
|
for (int cornerIndex = 0; cornerIndex < corners.length; cornerIndex++) {
|
||||||
final Vec originalCorner = corners.get(cornerIndex);
|
final Vec originalCorner = corners[cornerIndex];
|
||||||
final Vec newCorner = originalCorner.add(axis.mul(amount));
|
final Vec newCorner = originalCorner.add(axis.mul(amount));
|
||||||
final Chunk chunk = ChunkUtils.retrieve(instance, originChunk, newCorner);
|
final Chunk chunk = ChunkUtils.retrieve(instance, originChunk, newCorner);
|
||||||
if (!ChunkUtils.isLoaded(chunk)) {
|
if (!ChunkUtils.isLoaded(chunk)) {
|
||||||
// Collision at chunk border
|
// Collision at chunk border
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
final Block block = chunk.getBlock(newCorner);
|
final Block block = chunk.getBlock(newCorner, BlockGetter.Condition.TYPE);
|
||||||
// TODO: block collision boxes
|
// TODO: block collision boxes
|
||||||
// TODO: for the moment, always consider a full block
|
// TODO: for the moment, always consider a full block
|
||||||
if (block.isSolid()) {
|
if (block != null && block.isSolid()) {
|
||||||
corners.set(cornerIndex, new Vec(
|
corners[cornerIndex] = new Vec(
|
||||||
Math.abs(axis.x()) > 10e-16 ? newCorner.blockX() - axis.x() * sign : originalCorner.x(),
|
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.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;
|
return true;
|
||||||
}
|
}
|
||||||
corners.set(cornerIndex, newCorner);
|
corners[cornerIndex] = newCorner;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -148,54 +148,28 @@ public class CollisionUtils {
|
||||||
@NotNull Pos currentPosition, @NotNull Pos newPosition) {
|
@NotNull Pos currentPosition, @NotNull Pos newPosition) {
|
||||||
final WorldBorder worldBorder = instance.getWorldBorder();
|
final WorldBorder worldBorder = instance.getWorldBorder();
|
||||||
final WorldBorder.CollisionAxis collisionAxis = worldBorder.getCollisionAxis(newPosition);
|
final WorldBorder.CollisionAxis collisionAxis = worldBorder.getCollisionAxis(newPosition);
|
||||||
switch (collisionAxis) {
|
return switch (collisionAxis) {
|
||||||
case NONE:
|
case NONE ->
|
||||||
// Apply velocity + gravity
|
// Apply velocity + gravity
|
||||||
return newPosition;
|
newPosition;
|
||||||
case BOTH:
|
case BOTH ->
|
||||||
// Apply Y velocity/gravity
|
// Apply Y velocity/gravity
|
||||||
return new Pos(currentPosition.x(), newPosition.y(), currentPosition.z());
|
new Pos(currentPosition.x(), newPosition.y(), currentPosition.z());
|
||||||
case X:
|
case X ->
|
||||||
// Apply Y/Z velocity/gravity
|
// Apply Y/Z velocity/gravity
|
||||||
return new Pos(currentPosition.x(), newPosition.y(), newPosition.z());
|
new Pos(currentPosition.x(), newPosition.y(), newPosition.z());
|
||||||
case Z:
|
case Z ->
|
||||||
// Apply X/Y velocity/gravity
|
// Apply X/Y velocity/gravity
|
||||||
return new Pos(newPosition.x(), newPosition.y(), currentPosition.z());
|
new Pos(newPosition.x(), newPosition.y(), currentPosition.z());
|
||||||
}
|
};
|
||||||
throw new IllegalStateException("Something weird happened...");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PhysicsResult {
|
public record PhysicsResult(Pos newPosition,
|
||||||
private final Pos newPosition;
|
Vec newVelocity,
|
||||||
private final Vec newVelocity;
|
boolean isOnGround) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class StepResult {
|
private record StepResult(Vec newPosition,
|
||||||
private final Vec newPosition;
|
boolean foundCollision) {
|
||||||
private final boolean foundCollision;
|
|
||||||
|
|
||||||
public StepResult(Vec newPosition, boolean foundCollision) {
|
|
||||||
this.newPosition = newPosition;
|
|
||||||
this.foundCollision = foundCollision;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class Color implements RGBLike {
|
||||||
* @param rgbLike the color
|
* @param rgbLike the color
|
||||||
*/
|
*/
|
||||||
public Color(RGBLike rgbLike) {
|
public Color(RGBLike rgbLike) {
|
||||||
this(rgbLike.red(), rgbLike.blue(), rgbLike.green());
|
this(rgbLike.red(), rgbLike.green(), rgbLike.blue());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
* @param commandName the command name to check
|
||||||
* @return true if the command does exist
|
* @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 sender the sender of the command
|
||||||
* @param command the raw command string (without the command prefix)
|
* @param command the raw command string (without the command prefix)
|
||||||
* @return the execution result
|
* @return the execution result
|
||||||
*/
|
*/
|
||||||
public @NotNull CommandResult execute(@NotNull CommandSender sender, @NotNull String command) {
|
public @NotNull CommandResult execute(@NotNull CommandSender sender, @NotNull String command) {
|
||||||
|
|
||||||
// Command event
|
// Command event
|
||||||
if (sender instanceof Player) {
|
if (sender instanceof Player player) {
|
||||||
Player player = (Player) sender;
|
|
||||||
|
|
||||||
PlayerCommandEvent playerCommandEvent = new PlayerCommandEvent(player, command);
|
PlayerCommandEvent playerCommandEvent = new PlayerCommandEvent(player, command);
|
||||||
EventDispatcher.call(playerCommandEvent);
|
EventDispatcher.call(playerCommandEvent);
|
||||||
|
|
||||||
if (playerCommandEvent.isCancelled())
|
if (playerCommandEvent.isCancelled())
|
||||||
return CommandResult.of(CommandResult.Type.CANCELLED, command);
|
return CommandResult.of(CommandResult.Type.CANCELLED, command);
|
||||||
|
|
||||||
command = playerCommandEvent.getCommand();
|
command = playerCommandEvent.getCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the command
|
// Process the command
|
||||||
final var result = dispatcher.execute(sender, command);
|
final CommandResult result = dispatcher.execute(sender, command);
|
||||||
if (result.getType() == CommandResult.Type.UNKNOWN) {
|
if (result.getType() == CommandResult.Type.UNKNOWN) {
|
||||||
if (unknownCommandCallback != null) {
|
if (unknownCommandCallback != null) {
|
||||||
this.unknownCommandCallback.apply(sender, command);
|
this.unknownCommandCallback.apply(sender, command);
|
||||||
|
@ -124,8 +118,8 @@ public final class CommandManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the command using a {@link ServerSender} to do not
|
* Executes the command using a {@link ServerSender}. This can be used
|
||||||
* print the command messages, and rely instead on the command return data.
|
* to run a silent command (nothing is printed to console).
|
||||||
*
|
*
|
||||||
* @see #execute(CommandSender, String)
|
* @see #execute(CommandSender, String)
|
||||||
*/
|
*/
|
||||||
|
@ -440,8 +434,7 @@ public final class CommandManager {
|
||||||
return literalNode;
|
return literalNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private @NotNull DeclareCommandsPacket.Node createMainNode(@NotNull String name, boolean executable) {
|
||||||
private DeclareCommandsPacket.Node createMainNode(@NotNull String name, boolean executable) {
|
|
||||||
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
|
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
|
||||||
literalNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL, executable, false, false);
|
literalNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL, executable, false, false);
|
||||||
literalNode.name = name;
|
literalNode.name = name;
|
||||||
|
|
|
@ -367,10 +367,8 @@ public class Command {
|
||||||
|
|
||||||
node = findNode.apply(node, Collections.singleton(literal));
|
node = findNode.apply(node, Collections.singleton(literal));
|
||||||
continue;
|
continue;
|
||||||
} else if (argument instanceof ArgumentWord) {
|
} else if (argument instanceof ArgumentWord argumentWord) {
|
||||||
ArgumentWord argumentWord = (ArgumentWord) argument;
|
|
||||||
if (argumentWord.hasRestrictions()) {
|
if (argumentWord.hasRestrictions()) {
|
||||||
|
|
||||||
addArguments.accept(node, arguments);
|
addArguments.accept(node, arguments);
|
||||||
arguments = new ArrayList<>();
|
arguments = new ArrayList<>();
|
||||||
|
|
||||||
|
|
|
@ -65,8 +65,7 @@ public class CommandDispatcher {
|
||||||
this.cache.invalidateAll();
|
this.cache.invalidateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
public @NotNull Set<Command> getCommands() {
|
||||||
public Set<Command> getCommands() {
|
|
||||||
return Collections.unmodifiableSet(commands);
|
return Collections.unmodifiableSet(commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,8 +75,7 @@ public class CommandDispatcher {
|
||||||
* @param commandName the command name
|
* @param commandName the command name
|
||||||
* @return the {@link Command} associated with the name, null if not any
|
* @return the {@link Command} associated with the name, null if not any
|
||||||
*/
|
*/
|
||||||
@Nullable
|
public @Nullable Command findCommand(@NotNull String commandName) {
|
||||||
public Command findCommand(@NotNull String commandName) {
|
|
||||||
commandName = commandName.toLowerCase();
|
commandName = commandName.toLowerCase();
|
||||||
return commandMap.getOrDefault(commandName, null);
|
return commandMap.getOrDefault(commandName, null);
|
||||||
}
|
}
|
||||||
|
@ -89,8 +87,7 @@ public class CommandDispatcher {
|
||||||
* @param commandString the command with the argument(s)
|
* @param commandString the command with the argument(s)
|
||||||
* @return the command result
|
* @return the command result
|
||||||
*/
|
*/
|
||||||
@NotNull
|
public @NotNull CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) {
|
||||||
public CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) {
|
|
||||||
CommandResult commandResult = parse(commandString);
|
CommandResult commandResult = parse(commandString);
|
||||||
ParsedCommand parsedCommand = commandResult.parsedCommand;
|
ParsedCommand parsedCommand = commandResult.parsedCommand;
|
||||||
if (parsedCommand != null) {
|
if (parsedCommand != null) {
|
||||||
|
@ -105,10 +102,8 @@ public class CommandDispatcher {
|
||||||
* @param commandString the command (containing the command name and the args if any)
|
* @param commandString the command (containing the command name and the args if any)
|
||||||
* @return the parsing result
|
* @return the parsing result
|
||||||
*/
|
*/
|
||||||
@NotNull
|
public @NotNull CommandResult parse(@NotNull String commandString) {
|
||||||
public CommandResult parse(@NotNull String commandString) {
|
|
||||||
commandString = commandString.trim();
|
commandString = commandString.trim();
|
||||||
|
|
||||||
// Verify if the result is cached
|
// Verify if the result is cached
|
||||||
{
|
{
|
||||||
final CommandResult cachedResult = cache.getIfPresent(commandString);
|
final CommandResult cachedResult = cache.getIfPresent(commandString);
|
||||||
|
@ -134,18 +129,15 @@ public class CommandDispatcher {
|
||||||
findParsedCommand(command, commandName, commandQueryResult.args, commandString, result);
|
findParsedCommand(command, commandName, commandQueryResult.args, commandString, result);
|
||||||
|
|
||||||
// Cache result
|
// Cache result
|
||||||
{
|
this.cache.put(commandString, result);
|
||||||
this.cache.put(commandString, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private @Nullable ParsedCommand findParsedCommand(@NotNull Command command,
|
||||||
private ParsedCommand findParsedCommand(@NotNull Command command,
|
@NotNull String commandName, @NotNull String[] args,
|
||||||
@NotNull String commandName, @NotNull String[] args,
|
@NotNull String commandString,
|
||||||
@NotNull String commandString,
|
@NotNull CommandResult result) {
|
||||||
@NotNull CommandResult result) {
|
|
||||||
final boolean hasArgument = args.length > 0;
|
final boolean hasArgument = args.length > 0;
|
||||||
|
|
||||||
// Search for subcommand
|
// Search for subcommand
|
||||||
|
@ -162,41 +154,37 @@ public class CommandDispatcher {
|
||||||
|
|
||||||
final String input = commandName + StringUtils.SPACE + String.join(StringUtils.SPACE, args);
|
final String input = commandName + StringUtils.SPACE + String.join(StringUtils.SPACE, args);
|
||||||
|
|
||||||
|
|
||||||
ParsedCommand parsedCommand = new ParsedCommand();
|
ParsedCommand parsedCommand = new ParsedCommand();
|
||||||
parsedCommand.command = command;
|
parsedCommand.command = command;
|
||||||
parsedCommand.commandString = commandString;
|
parsedCommand.commandString = commandString;
|
||||||
|
|
||||||
// The default executor should be used if no argument is provided
|
// The default executor should be used if no argument is provided
|
||||||
{
|
if (!hasArgument) {
|
||||||
if (!hasArgument) {
|
Optional<CommandSyntax> optionalSyntax = command.getSyntaxes()
|
||||||
Optional<CommandSyntax> optionalSyntax = command.getSyntaxes()
|
.stream()
|
||||||
.stream()
|
.filter(syntax -> syntax.getArguments().length == 0)
|
||||||
.filter(syntax -> syntax.getArguments().length == 0)
|
.findFirst();
|
||||||
.findFirst();
|
|
||||||
|
|
||||||
if (optionalSyntax.isPresent()) {
|
if (optionalSyntax.isPresent()) {
|
||||||
// Empty syntax found
|
// Empty syntax found
|
||||||
final CommandSyntax syntax = optionalSyntax.get();
|
final CommandSyntax syntax = optionalSyntax.get();
|
||||||
|
parsedCommand.syntax = syntax;
|
||||||
|
parsedCommand.executor = syntax.getExecutor();
|
||||||
|
parsedCommand.context = new CommandContext(input);
|
||||||
|
|
||||||
parsedCommand.syntax = syntax;
|
result.type = CommandResult.Type.SUCCESS;
|
||||||
parsedCommand.executor = syntax.getExecutor();
|
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);
|
parsedCommand.context = new CommandContext(input);
|
||||||
|
|
||||||
result.type = CommandResult.Type.SUCCESS;
|
result.type = CommandResult.Type.SUCCESS;
|
||||||
result.parsedCommand = parsedCommand;
|
result.parsedCommand = parsedCommand;
|
||||||
return 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();
|
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
|
||||||
// Contains all the fully validated syntaxes (we later find the one with the most amount of arguments)
|
// Contains all the fully validated syntaxes (we later find the one with the most amount of arguments)
|
||||||
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>(syntaxes.size());
|
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>(syntaxes.size());
|
||||||
|
|
||||||
// Contains all the syntaxes that are not fully correct, used to later, retrieve the "most correct syntax"
|
// 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
|
// Number of correct argument - The data about the failing argument
|
||||||
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
|
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
|
||||||
|
@ -233,29 +220,25 @@ public class CommandDispatcher {
|
||||||
result.parsedCommand = parsedCommand;
|
result.parsedCommand = parsedCommand;
|
||||||
return parsedCommand;
|
return parsedCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No all-correct syntax, find the closest one to use the argument callback
|
// No all-correct syntax, find the closest one to use the argument callback
|
||||||
{
|
if (!syntaxesSuggestions.isEmpty()) {
|
||||||
// Get closest valid syntax
|
final int max = syntaxesSuggestions.firstIntKey(); // number of correct arguments in the most correct syntax
|
||||||
if (!syntaxesSuggestions.isEmpty()) {
|
final CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max);
|
||||||
final int max = syntaxesSuggestions.firstIntKey(); // number of correct arguments in the most correct syntax
|
final CommandSyntax syntax = suggestionHolder.syntax;
|
||||||
final CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max);
|
final ArgumentSyntaxException argumentSyntaxException = suggestionHolder.argumentSyntaxException;
|
||||||
final CommandSyntax syntax = suggestionHolder.syntax;
|
final int argIndex = suggestionHolder.argIndex;
|
||||||
final ArgumentSyntaxException argumentSyntaxException = suggestionHolder.argumentSyntaxException;
|
|
||||||
final int argIndex = suggestionHolder.argIndex;
|
|
||||||
|
|
||||||
// Found the closest syntax with at least 1 correct argument
|
// Found the closest syntax with at least 1 correct argument
|
||||||
final Argument<?> argument = syntax.getArguments()[argIndex];
|
final Argument<?> argument = syntax.getArguments()[argIndex];
|
||||||
if (argument.hasErrorCallback()) {
|
if (argument.hasErrorCallback() && argumentSyntaxException != null) {
|
||||||
parsedCommand.callback = argument.getCallback();
|
parsedCommand.callback = argument.getCallback();
|
||||||
parsedCommand.argumentSyntaxException = argumentSyntaxException;
|
parsedCommand.argumentSyntaxException = argumentSyntaxException;
|
||||||
|
|
||||||
result.type = CommandResult.Type.INVALID_SYNTAX;
|
result.type = CommandResult.Type.INVALID_SYNTAX;
|
||||||
result.parsedCommand = parsedCommand;
|
result.parsedCommand = parsedCommand;
|
||||||
return parsedCommand;
|
return parsedCommand;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,23 +10,19 @@ public class CommandResult {
|
||||||
protected ParsedCommand parsedCommand;
|
protected ParsedCommand parsedCommand;
|
||||||
protected CommandData commandData;
|
protected CommandData commandData;
|
||||||
|
|
||||||
@NotNull
|
public @NotNull Type getType() {
|
||||||
public Type getType() {
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
public @NotNull String getInput() {
|
||||||
public String getInput() {
|
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public @Nullable ParsedCommand getParsedCommand() {
|
||||||
public ParsedCommand getParsedCommand() {
|
|
||||||
return parsedCommand;
|
return parsedCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public @Nullable CommandData getCommandData() {
|
||||||
public CommandData getCommandData() {
|
|
||||||
return commandData;
|
return commandData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,30 +31,25 @@ public class CommandResult {
|
||||||
* Command and syntax successfully found.
|
* Command and syntax successfully found.
|
||||||
*/
|
*/
|
||||||
SUCCESS,
|
SUCCESS,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command found, but the syntax is invalid.
|
* Command found, but the syntax is invalid.
|
||||||
* Executor sets to {@link Command#getDefaultExecutor()}.
|
* Executor sets to {@link Command#getDefaultExecutor()}.
|
||||||
*/
|
*/
|
||||||
INVALID_SYNTAX,
|
INVALID_SYNTAX,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command cancelled by an event listener.
|
* Command cancelled by an event listener.
|
||||||
*/
|
*/
|
||||||
CANCELLED,
|
CANCELLED,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command is not registered, it is also the default result type.
|
* Command is not registered, it is also the default result type.
|
||||||
*/
|
*/
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
public static @NotNull CommandResult of(@NotNull Type type, @NotNull String input) {
|
||||||
public static CommandResult of(@NotNull Type type, @NotNull String input) {
|
|
||||||
CommandResult result = new CommandResult();
|
CommandResult result = new CommandResult();
|
||||||
result.type = type;
|
result.type = type;
|
||||||
result.input = input;
|
result.input = input;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a {@link Command} ready to be executed (already parsed).
|
* Represents a {@link Command} ready to be executed (already parsed).
|
||||||
*/
|
*/
|
||||||
|
@ -35,16 +37,14 @@ public class ParsedCommand {
|
||||||
* @param source the command source
|
* @param source the command source
|
||||||
* @return the command data, null if none
|
* @return the command data, null if none
|
||||||
*/
|
*/
|
||||||
@Nullable
|
public @Nullable CommandData execute(@NotNull CommandSender source) {
|
||||||
public CommandData execute(@NotNull CommandSender source) {
|
|
||||||
// Global listener
|
// Global listener
|
||||||
command.globalListener(source, context, commandString);
|
command.globalListener(source, Objects.requireNonNullElseGet(context, () -> new CommandContext(commandString)), commandString);
|
||||||
// Command condition check
|
// Command condition check
|
||||||
final CommandCondition condition = command.getCondition();
|
final CommandCondition condition = command.getCondition();
|
||||||
if (condition != null) {
|
if (condition != null) {
|
||||||
final boolean result = condition.canUse(source, commandString);
|
final boolean result = condition.canUse(source, commandString);
|
||||||
if (!result)
|
if (!result) return null;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
// Condition is respected
|
// Condition is respected
|
||||||
if (executor != null) {
|
if (executor != null) {
|
||||||
|
@ -57,16 +57,16 @@ public class ParsedCommand {
|
||||||
context.retrieveDefaultValues(syntax.getDefaultValuesMap());
|
context.retrieveDefaultValues(syntax.getDefaultValuesMap());
|
||||||
try {
|
try {
|
||||||
executor.apply(source, context);
|
executor.apply(source, context);
|
||||||
} catch (Exception exception) {
|
} catch (Throwable throwable) {
|
||||||
MinecraftServer.getExceptionManager().handleException(exception);
|
MinecraftServer.getExceptionManager().handleException(throwable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The executor is probably the default one
|
// The executor is probably the default one
|
||||||
try {
|
try {
|
||||||
executor.apply(source, context);
|
executor.apply(source, context);
|
||||||
} catch (Exception exception) {
|
} catch (Throwable throwable) {
|
||||||
MinecraftServer.getExceptionManager().handleException(exception);
|
MinecraftServer.getExceptionManager().handleException(throwable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (callback != null && argumentSyntaxException != null) {
|
} else if (callback != null && argumentSyntaxException != null) {
|
||||||
|
@ -83,8 +83,7 @@ public class ParsedCommand {
|
||||||
return context.getReturnData();
|
return context.getReturnData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
public static @NotNull ParsedCommand withDefaultExecutor(@NotNull Command command, @NotNull String input) {
|
||||||
public static ParsedCommand withDefaultExecutor(@NotNull Command command, @NotNull String input) {
|
|
||||||
ParsedCommand parsedCommand = new ParsedCommand();
|
ParsedCommand parsedCommand = new ParsedCommand();
|
||||||
parsedCommand.command = command;
|
parsedCommand.command = command;
|
||||||
parsedCommand.commandString = input;
|
parsedCommand.commandString = input;
|
||||||
|
@ -92,5 +91,4 @@ public class ParsedCommand {
|
||||||
parsedCommand.context = new CommandContext(input);
|
parsedCommand.context = new CommandContext(input);
|
||||||
return parsedCommand;
|
return parsedCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ public class ArgumentBlockState extends Argument<Block> {
|
||||||
public static final int NO_BLOCK = 1;
|
public static final int NO_BLOCK = 1;
|
||||||
public static final int INVALID_BLOCK = 2;
|
public static final int INVALID_BLOCK = 2;
|
||||||
public static final int INVALID_PROPERTY = 3;
|
public static final int INVALID_PROPERTY = 3;
|
||||||
|
public static final int INVALID_PROPERTY_VALUE = 4;
|
||||||
|
|
||||||
public ArgumentBlockState(@NotNull String id) {
|
public ArgumentBlockState(@NotNull String id) {
|
||||||
super(id, true, false);
|
super(id, true, false);
|
||||||
|
@ -58,7 +59,11 @@ public class ArgumentBlockState extends Argument<Block> {
|
||||||
// Compute properties
|
// Compute properties
|
||||||
final String query = input.substring(nbtIndex);
|
final String query = input.substring(nbtIndex);
|
||||||
final var propertyMap = BlockUtils.parseProperties(query);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -245,7 +245,7 @@ public class ArgumentEntity extends Argument<EntityFinder> {
|
||||||
break;
|
break;
|
||||||
case "level":
|
case "level":
|
||||||
try {
|
try {
|
||||||
final IntRange level = ArgumentIntRange.staticParse(value);
|
final IntRange level = Argument.parse(new ArgumentIntRange(value));
|
||||||
entityFinder.setLevel(level);
|
entityFinder.setLevel(level);
|
||||||
} catch (ArgumentSyntaxException e) {
|
} catch (ArgumentSyntaxException e) {
|
||||||
throw new ArgumentSyntaxException("Invalid level number", input, INVALID_ARGUMENT_VALUE);
|
throw new ArgumentSyntaxException("Invalid level number", input, INVALID_ARGUMENT_VALUE);
|
||||||
|
@ -253,7 +253,7 @@ public class ArgumentEntity extends Argument<EntityFinder> {
|
||||||
break;
|
break;
|
||||||
case "distance":
|
case "distance":
|
||||||
try {
|
try {
|
||||||
final IntRange distance = ArgumentIntRange.staticParse(value);
|
final IntRange distance = Argument.parse(new ArgumentIntRange(value));
|
||||||
entityFinder.setDistance(distance);
|
entityFinder.setDistance(distance);
|
||||||
} catch (ArgumentSyntaxException e) {
|
} catch (ArgumentSyntaxException e) {
|
||||||
throw new ArgumentSyntaxException("Invalid level number", input, INVALID_ARGUMENT_VALUE);
|
throw new ArgumentSyntaxException("Invalid level number", input, INVALID_ARGUMENT_VALUE);
|
||||||
|
|
|
@ -1,66 +1,16 @@
|
||||||
package net.minestom.server.command.builder.arguments.minecraft;
|
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 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}.
|
* Represents an argument which will give you an {@link FloatRange}.
|
||||||
* <p>
|
* <p>
|
||||||
* Example: ..3, 3.., 5..10, 15
|
* Example: ..3, 3.., 5..10, 15
|
||||||
*/
|
*/
|
||||||
public class ArgumentFloatRange extends ArgumentRange<FloatRange> {
|
public class ArgumentFloatRange extends ArgumentRange<FloatRange, Float> {
|
||||||
|
|
||||||
public ArgumentFloatRange(String id) {
|
public ArgumentFloatRange(String id) {
|
||||||
super(id);
|
super(id, "minecraft:float_range", Float.MIN_VALUE, Float.MAX_VALUE, Float::parseFloat, FloatRange::new);
|
||||||
}
|
|
||||||
|
|
||||||
@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});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,76 +1,16 @@
|
||||||
package net.minestom.server.command.builder.arguments.minecraft;
|
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 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}.
|
* Represents an argument which will give you an {@link IntRange}.
|
||||||
* <p>
|
* <p>
|
||||||
* Example: ..3, 3.., 5..10, 15
|
* Example: ..3, 3.., 5..10, 15
|
||||||
*/
|
*/
|
||||||
public class ArgumentIntRange extends ArgumentRange<IntRange> {
|
public class ArgumentIntRange extends ArgumentRange<IntRange, Integer> {
|
||||||
|
|
||||||
public ArgumentIntRange(String id) {
|
public ArgumentIntRange(String id) {
|
||||||
super(id);
|
super(id, "minecraft:int_range", Integer.MIN_VALUE, Integer.MAX_VALUE, Integer::parseInt, IntRange::new);
|
||||||
}
|
|
||||||
|
|
||||||
@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});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,17 +1,81 @@
|
||||||
package net.minestom.server.command.builder.arguments.minecraft;
|
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.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}.
|
* Abstract class used by {@link ArgumentIntRange} and {@link ArgumentFloatRange}.
|
||||||
*
|
*
|
||||||
* @param <T> the type of the range
|
* @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;
|
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);
|
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});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class ArgumentEntityType extends ArgumentRegistry<EntityType> {
|
||||||
@Override
|
@Override
|
||||||
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
|
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
|
||||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, true);
|
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, true);
|
||||||
argumentNode.parser = "minecraft:entity_summon";
|
argumentNode.parser = "minecraft:resource_location";
|
||||||
argumentNode.suggestionsType = SuggestionType.SUMMONABLE_ENTITIES.getIdentifier();
|
argumentNode.suggestionsType = SuggestionType.SUMMONABLE_ENTITIES.getIdentifier();
|
||||||
|
|
||||||
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
|
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
|
||||||
|
|
|
@ -18,7 +18,8 @@ import java.util.regex.Pattern;
|
||||||
public class ArgumentNumber<T extends Number> extends Argument<T> {
|
public class ArgumentNumber<T extends Number> extends Argument<T> {
|
||||||
|
|
||||||
public static final int NOT_NUMBER_ERROR = 1;
|
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 boolean hasMin, hasMax;
|
||||||
protected T min, max;
|
protected T min, max;
|
||||||
|
@ -53,10 +54,10 @@ public class ArgumentNumber<T extends Number> extends Argument<T> {
|
||||||
|
|
||||||
// Check range
|
// Check range
|
||||||
if (hasMin && comparator.compare(value, min) < 0) {
|
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) {
|
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;
|
return value;
|
||||||
|
|
|
@ -3,7 +3,6 @@ package net.minestom.server.coordinate;
|
||||||
import net.minestom.server.instance.block.BlockFace;
|
import net.minestom.server.instance.block.BlockFace;
|
||||||
import net.minestom.server.utils.MathUtils;
|
import net.minestom.server.utils.MathUtils;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@ -15,8 +14,7 @@ import java.util.function.DoubleUnaryOperator;
|
||||||
* Can either be a {@link Pos} or {@link Vec}.
|
* Can either be a {@link Pos} or {@link Vec}.
|
||||||
* Interface will become {@code sealed} in the future.
|
* Interface will become {@code sealed} in the future.
|
||||||
*/
|
*/
|
||||||
@ApiStatus.NonExtendable
|
public sealed interface Point permits Vec, Pos {
|
||||||
public interface Point {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the X coordinate.
|
* Gets the X coordinate.
|
||||||
|
@ -72,6 +70,16 @@ public interface Point {
|
||||||
return (int) Math.floor(z());
|
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.
|
* Creates a point with a modified X coordinate based on its value.
|
||||||
*
|
*
|
||||||
|
@ -164,22 +172,35 @@ public interface Point {
|
||||||
|
|
||||||
@Contract(pure = true)
|
@Contract(pure = true)
|
||||||
default @NotNull Point relative(@NotNull BlockFace face) {
|
default @NotNull Point relative(@NotNull BlockFace face) {
|
||||||
switch (face) {
|
return switch (face) {
|
||||||
case BOTTOM:
|
case BOTTOM -> sub(0, 1, 0);
|
||||||
return sub(0, 1, 0);
|
case TOP -> add(0, 1, 0);
|
||||||
case TOP:
|
case NORTH -> sub(0, 0, 1);
|
||||||
return add(0, 1, 0);
|
case SOUTH -> add(0, 0, 1);
|
||||||
case NORTH:
|
case WEST -> sub(1, 0, 0);
|
||||||
return sub(0, 0, 1);
|
case EAST -> add(1, 0, 0);
|
||||||
case SOUTH:
|
};
|
||||||
return add(0, 0, 1);
|
}
|
||||||
case WEST:
|
|
||||||
return sub(1, 0, 0);
|
@Contract(pure = true)
|
||||||
case EAST:
|
default double distanceSquared(double x, double y, double z) {
|
||||||
return add(1, 0, 0);
|
return MathUtils.square(x() - x) + MathUtils.square(y() - y) + MathUtils.square(z() - z);
|
||||||
default: // should never be called
|
}
|
||||||
return this;
|
|
||||||
}
|
/**
|
||||||
|
* 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)
|
@Contract(pure = true)
|
||||||
default double distance(@NotNull Point point) {
|
default double distance(@NotNull Point point) {
|
||||||
return Math.sqrt(MathUtils.square(x() - point.x()) +
|
return distance(point.x(), point.y(), point.z());
|
||||||
MathUtils.square(y() - point.y()) +
|
|
||||||
MathUtils.square(z() - point.z()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
default boolean samePoint(double x, double y, double z) {
|
||||||
* Gets the squared distance between this point and another.
|
return Double.compare(x, x()) == 0 && Double.compare(y, y()) == 0 && Double.compare(z, z()) == 0;
|
||||||
*
|
|
||||||
* @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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -219,9 +229,7 @@ public interface Point {
|
||||||
* @return true if the two positions are similar
|
* @return true if the two positions are similar
|
||||||
*/
|
*/
|
||||||
default boolean samePoint(@NotNull Point point) {
|
default boolean samePoint(@NotNull Point point) {
|
||||||
return Double.compare(point.x(), x()) == 0 &&
|
return samePoint(point.x(), point.y(), point.z());
|
||||||
Double.compare(point.y(), y()) == 0 &&
|
|
||||||
Double.compare(point.z(), z()) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -241,7 +249,20 @@ public interface Point {
|
||||||
* @return true if 'this' is in the same chunk as {@code point}
|
* @return true if 'this' is in the same chunk as {@code point}
|
||||||
*/
|
*/
|
||||||
default boolean sameChunk(@NotNull Point point) {
|
default boolean sameChunk(@NotNull Point point) {
|
||||||
return ChunkUtils.getChunkCoordinate(x()) == ChunkUtils.getChunkCoordinate(point.x()) &&
|
return chunkX() == point.chunkX() && chunkZ() == point.chunkZ();
|
||||||
ChunkUtils.getChunkCoordinate(z()) == ChunkUtils.getChunkCoordinate(point.z());
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import net.minestom.server.utils.MathUtils;
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,18 +12,11 @@ import java.util.function.DoubleUnaryOperator;
|
||||||
* <p>
|
* <p>
|
||||||
* To become record and primitive.
|
* 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);
|
public static final Pos ZERO = new Pos(0, 0, 0);
|
||||||
|
|
||||||
private final double x, y, z;
|
public Pos {
|
||||||
private final float yaw, pitch;
|
yaw = fixYaw(yaw);
|
||||||
|
|
||||||
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(double x, double y, double z) {
|
public Pos(double x, double y, double z) {
|
||||||
|
@ -47,8 +39,7 @@ public final class Pos implements Point {
|
||||||
* @return the converted position
|
* @return the converted position
|
||||||
*/
|
*/
|
||||||
public static @NotNull Pos fromPoint(@NotNull Point point) {
|
public static @NotNull Pos fromPoint(@NotNull Point point) {
|
||||||
if (point instanceof Pos)
|
if (point instanceof Pos pos) return pos;
|
||||||
return (Pos) point;
|
|
||||||
return new Pos(point.x(), point.y(), point.z());
|
return new Pos(point.x(), point.y(), point.z());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +99,7 @@ public final class Pos implements Point {
|
||||||
|
|
||||||
@Contract(pure = true)
|
@Contract(pure = true)
|
||||||
public @NotNull Pos withYaw(@NotNull DoubleUnaryOperator operator) {
|
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)
|
@Contract(pure = true)
|
||||||
|
@ -118,7 +109,7 @@ public final class Pos implements Point {
|
||||||
|
|
||||||
@Contract(pure = true)
|
@Contract(pure = true)
|
||||||
public @NotNull Pos withPitch(@NotNull DoubleUnaryOperator operator) {
|
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)));
|
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.
|
* Returns a new position based on this position fields.
|
||||||
*
|
*
|
||||||
|
@ -180,7 +153,7 @@ public final class Pos implements Point {
|
||||||
@Override
|
@Override
|
||||||
@Contract(pure = true)
|
@Contract(pure = true)
|
||||||
public @NotNull Pos withX(@NotNull DoubleUnaryOperator operator) {
|
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
|
@Override
|
||||||
|
@ -192,7 +165,7 @@ public final class Pos implements Point {
|
||||||
@Override
|
@Override
|
||||||
@Contract(pure = true)
|
@Contract(pure = true)
|
||||||
public @NotNull Pos withY(@NotNull DoubleUnaryOperator operator) {
|
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
|
@Override
|
||||||
|
@ -204,7 +177,7 @@ public final class Pos implements Point {
|
||||||
@Override
|
@Override
|
||||||
@Contract(pure = true)
|
@Contract(pure = true)
|
||||||
public @NotNull Pos withZ(@NotNull DoubleUnaryOperator operator) {
|
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
|
@Override
|
||||||
|
@ -278,51 +251,30 @@ public final class Pos implements Point {
|
||||||
return (Pos) Point.super.relative(face);
|
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)
|
@Contract(pure = true)
|
||||||
public @NotNull Vec asVec() {
|
public @NotNull Vec asVec() {
|
||||||
return new Vec(x, y, z);
|
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
|
@FunctionalInterface
|
||||||
public interface Operator {
|
public interface Operator {
|
||||||
@NotNull Pos apply(double x, double y, double z, float yaw, float pitch);
|
@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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import net.minestom.server.utils.MathUtils;
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,27 +12,12 @@ import java.util.function.DoubleUnaryOperator;
|
||||||
* <p>
|
* <p>
|
||||||
* To become record and primitive.
|
* 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 ZERO = new Vec(0);
|
||||||
public static final Vec ONE = new Vec(1);
|
public static final Vec ONE = new Vec(1);
|
||||||
|
|
||||||
public static final double EPSILON = 0.000001;
|
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.
|
* 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
|
* @return the converted vector
|
||||||
*/
|
*/
|
||||||
public static @NotNull Vec fromPoint(@NotNull Point point) {
|
public static @NotNull Vec fromPoint(@NotNull Point point) {
|
||||||
if (point instanceof Vec)
|
if (point instanceof Vec vec) return vec;
|
||||||
return (Vec) point;
|
|
||||||
return new Vec(point.x(), point.y(), point.z());
|
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));
|
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
|
@FunctionalInterface
|
||||||
public interface Operator {
|
public interface Operator {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Set;
|
||||||
* <p>
|
* <p>
|
||||||
* See {@link DataImpl} for the default implementation.
|
* See {@link DataImpl} for the default implementation.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public abstract class Data implements PublicCloneable<Data> {
|
public abstract class Data implements PublicCloneable<Data> {
|
||||||
|
|
||||||
public static final Data EMPTY = new Data() {
|
public static final Data EMPTY = new Data() {
|
||||||
|
|
|
@ -19,7 +19,7 @@ public interface DataContainer {
|
||||||
* meaning that this will be null if no data has been defined.
|
* meaning that this will be null if no data has been defined.
|
||||||
*
|
*
|
||||||
* @return the {@link Data} of this container, can be null
|
* @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
|
@Deprecated
|
||||||
@Nullable Data getData();
|
@Nullable Data getData();
|
||||||
|
@ -31,8 +31,8 @@ public interface DataContainer {
|
||||||
* on your use-case.
|
* on your use-case.
|
||||||
*
|
*
|
||||||
* @param data the new {@link Data} of this container, null to remove it
|
* @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
|
@Deprecated
|
||||||
void setData(@Nullable Data data);
|
void setData(@Nullable Data data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
/**
|
/**
|
||||||
* {@link Data} implementation which uses a {@link ConcurrentHashMap}.
|
* {@link Data} implementation which uses a {@link ConcurrentHashMap}.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class DataImpl extends Data {
|
public class DataImpl extends Data {
|
||||||
|
|
||||||
protected final ConcurrentHashMap<String, Object> data = new ConcurrentHashMap<>();
|
protected final ConcurrentHashMap<String, Object> data = new ConcurrentHashMap<>();
|
||||||
|
|
|
@ -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,
|
* 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.
|
* 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 {
|
public final class DataManager {
|
||||||
|
|
||||||
private final Map<Class, DataType> dataTypeMap = new HashMap<>();
|
private final Map<Class, DataType> dataTypeMap = new HashMap<>();
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||||
*
|
*
|
||||||
* @param <T> the type of the object
|
* @param <T> the type of the object
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public abstract class DataType<T> {
|
public abstract class DataType<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull;
|
||||||
* <p>
|
* <p>
|
||||||
* See {@link SerializableDataImpl} for the default implementation.
|
* See {@link SerializableDataImpl} for the default implementation.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public abstract class SerializableData extends Data {
|
public abstract class SerializableData extends Data {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
/**
|
/**
|
||||||
* {@link SerializableData} implementation based on {@link DataImpl}.
|
* {@link SerializableData} implementation based on {@link DataImpl}.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class SerializableDataImpl extends SerializableData {
|
public class SerializableDataImpl extends SerializableData {
|
||||||
|
|
||||||
protected static final DataManager DATA_MANAGER = MinecraftServer.getDataManager();
|
protected static final DataManager DATA_MANAGER = MinecraftServer.getDataManager();
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,6 @@
|
||||||
package net.minestom.server.entity;
|
package net.minestom.server.entity;
|
||||||
|
|
||||||
import com.extollit.gaming.ai.path.HydrazinePathFinder;
|
import com.extollit.gaming.ai.path.HydrazinePathFinder;
|
||||||
import net.minestom.server.attribute.Attribute;
|
|
||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.entity.ai.EntityAI;
|
import net.minestom.server.entity.ai.EntityAI;
|
||||||
import net.minestom.server.entity.ai.EntityAIGroup;
|
import net.minestom.server.entity.ai.EntityAIGroup;
|
||||||
|
@ -49,7 +48,7 @@ public class EntityCreature extends LivingEntity implements NavigableEntity, Ent
|
||||||
aiTick(time);
|
aiTick(time);
|
||||||
|
|
||||||
// Path finding
|
// Path finding
|
||||||
this.navigator.tick(getAttributeValue(Attribute.MOVEMENT_SPEED));
|
this.navigator.tick();
|
||||||
|
|
||||||
// Fire, item pickup, ...
|
// Fire, item pickup, ...
|
||||||
super.update(time);
|
super.update(time);
|
||||||
|
|
|
@ -16,8 +16,7 @@ public enum EntitySpawnType {
|
||||||
packet.uuid = entity.getUuid();
|
packet.uuid = entity.getUuid();
|
||||||
packet.type = entity.getEntityType().id();
|
packet.type = entity.getEntityType().id();
|
||||||
packet.position = entity.getPosition();
|
packet.position = entity.getPosition();
|
||||||
if (entity.getEntityMeta() instanceof ObjectDataProvider) {
|
if (entity.getEntityMeta() instanceof ObjectDataProvider objectDataProvider) {
|
||||||
ObjectDataProvider objectDataProvider = (ObjectDataProvider) entity.getEntityMeta();
|
|
||||||
packet.data = objectDataProvider.getObjectData();
|
packet.data = objectDataProvider.getObjectData();
|
||||||
if (objectDataProvider.requiresVelocityPacketAtSpawn()) {
|
if (objectDataProvider.requiresVelocityPacketAtSpawn()) {
|
||||||
final var velocity = entity.getVelocityForPacket();
|
final var velocity = entity.getVelocityForPacket();
|
||||||
|
@ -57,8 +56,7 @@ public enum EntitySpawnType {
|
||||||
SpawnExperienceOrbPacket packet = new SpawnExperienceOrbPacket();
|
SpawnExperienceOrbPacket packet = new SpawnExperienceOrbPacket();
|
||||||
packet.entityId = entity.getEntityId();
|
packet.entityId = entity.getEntityId();
|
||||||
packet.position = entity.getPosition();
|
packet.position = entity.getPosition();
|
||||||
if (entity.getEntityMeta() instanceof ExperienceOrbMeta) {
|
if (entity.getEntityMeta() instanceof ExperienceOrbMeta experienceOrbMeta) {
|
||||||
ExperienceOrbMeta experienceOrbMeta = (ExperienceOrbMeta) entity.getEntityMeta();
|
|
||||||
packet.expCount = (short) experienceOrbMeta.getCount();
|
packet.expCount = (short) experienceOrbMeta.getCount();
|
||||||
}
|
}
|
||||||
return packet;
|
return packet;
|
||||||
|
@ -70,8 +68,7 @@ public enum EntitySpawnType {
|
||||||
SpawnPaintingPacket packet = new SpawnPaintingPacket();
|
SpawnPaintingPacket packet = new SpawnPaintingPacket();
|
||||||
packet.entityId = entity.getEntityId();
|
packet.entityId = entity.getEntityId();
|
||||||
packet.entityUuid = entity.getUuid();
|
packet.entityUuid = entity.getUuid();
|
||||||
if (entity.getEntityMeta() instanceof PaintingMeta) {
|
if (entity.getEntityMeta() instanceof PaintingMeta paintingMeta) {
|
||||||
PaintingMeta paintingMeta = (PaintingMeta) entity.getEntityMeta();
|
|
||||||
packet.motive = paintingMeta.getMotive().ordinal();
|
packet.motive = paintingMeta.getMotive().ordinal();
|
||||||
packet.position = new Vec(
|
packet.position = new Vec(
|
||||||
Math.max(0, (paintingMeta.getMotive().getWidth() >> 1) - 1),
|
Math.max(0, (paintingMeta.getMotive().getWidth() >> 1) - 1),
|
||||||
|
@ -79,18 +76,10 @@ public enum EntitySpawnType {
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
switch (paintingMeta.getDirection()) {
|
switch (paintingMeta.getDirection()) {
|
||||||
case SOUTH:
|
case SOUTH -> packet.direction = 0;
|
||||||
packet.direction = 0;
|
case WEST -> packet.direction = 1;
|
||||||
break;
|
case NORTH -> packet.direction = 2;
|
||||||
case WEST:
|
case EAST -> packet.direction = 3;
|
||||||
packet.direction = 1;
|
|
||||||
break;
|
|
||||||
case NORTH:
|
|
||||||
packet.direction = 2;
|
|
||||||
break;
|
|
||||||
case EAST:
|
|
||||||
packet.direction = 3;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
packet.position = Vec.ZERO;
|
packet.position = Vec.ZERO;
|
||||||
|
|
|
@ -3,15 +3,13 @@ package net.minestom.server.entity;
|
||||||
import net.minestom.server.registry.ProtocolObject;
|
import net.minestom.server.registry.ProtocolObject;
|
||||||
import net.minestom.server.registry.Registry;
|
import net.minestom.server.registry.Registry;
|
||||||
import net.minestom.server.utils.NamespaceID;
|
import net.minestom.server.utils.NamespaceID;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@ApiStatus.NonExtendable
|
public sealed interface EntityType extends ProtocolObject, EntityTypes permits EntityTypeImpl {
|
||||||
public interface EntityType extends ProtocolObject, EntityTypeConstants {
|
|
||||||
/**
|
/**
|
||||||
* Returns the entity registry.
|
* Returns the entity registry.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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.SkeletonMeta;
|
||||||
import net.minestom.server.entity.metadata.monster.skeleton.StrayMeta;
|
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.skeleton.WitherSkeletonMeta;
|
||||||
import net.minestom.server.entity.metadata.monster.zombie.DrownedMeta;
|
import net.minestom.server.entity.metadata.monster.zombie.*;
|
||||||
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.other.*;
|
import net.minestom.server.entity.metadata.other.*;
|
||||||
import net.minestom.server.entity.metadata.villager.VillagerMeta;
|
import net.minestom.server.entity.metadata.villager.VillagerMeta;
|
||||||
import net.minestom.server.entity.metadata.villager.WanderingTraderMeta;
|
import net.minestom.server.entity.metadata.villager.WanderingTraderMeta;
|
||||||
|
@ -45,7 +42,7 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiFunction;
|
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,
|
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))));
|
(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();
|
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:guardian", GuardianMeta::new);
|
||||||
supplier.put("minecraft:hoglin", HoglinMeta::new);
|
supplier.put("minecraft:hoglin", HoglinMeta::new);
|
||||||
supplier.put("minecraft:horse", HorseMeta::new);
|
supplier.put("minecraft:horse", HorseMeta::new);
|
||||||
|
supplier.put("minecraft:husk", HuskMeta::new);
|
||||||
supplier.put("minecraft:illusioner", IllusionerMeta::new);
|
supplier.put("minecraft:illusioner", IllusionerMeta::new);
|
||||||
supplier.put("minecraft:iron_golem", IronGolemMeta::new);
|
supplier.put("minecraft:iron_golem", IronGolemMeta::new);
|
||||||
supplier.put("minecraft:item", ItemEntityMeta::new);
|
supplier.put("minecraft:item", ItemEntityMeta::new);
|
||||||
|
@ -244,17 +242,6 @@ final class EntityTypeImpl implements EntityType {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Registry.EntityEntry registry;
|
|
||||||
|
|
||||||
EntityTypeImpl(Registry.EntityEntry registry) {
|
|
||||||
this.registry = registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Registry.EntityEntry registry() {
|
|
||||||
return registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name();
|
return name();
|
||||||
|
|
|
@ -3,37 +3,44 @@ package net.minestom.server.entity;
|
||||||
import net.minestom.server.item.attribute.AttributeSlot;
|
import net.minestom.server.item.attribute.AttributeSlot;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*;
|
||||||
|
|
||||||
public enum EquipmentSlot {
|
public enum EquipmentSlot {
|
||||||
MAIN_HAND,
|
MAIN_HAND(false, -1),
|
||||||
OFF_HAND,
|
OFF_HAND(false, -1),
|
||||||
BOOTS,
|
BOOTS(true, BOOTS_SLOT),
|
||||||
LEGGINGS,
|
LEGGINGS(true, LEGGINGS_SLOT),
|
||||||
CHESTPLATE,
|
CHESTPLATE(true, CHESTPLATE_SLOT),
|
||||||
HELMET;
|
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() {
|
public boolean isHand() {
|
||||||
return this == MAIN_HAND || this == OFF_HAND;
|
return !armor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isArmor() {
|
public boolean isArmor() {
|
||||||
return !isHand();
|
return armor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int armorSlot() {
|
||||||
|
return armorSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EquipmentSlot fromAttributeSlot(@NotNull AttributeSlot attributeSlot) {
|
public static EquipmentSlot fromAttributeSlot(@NotNull AttributeSlot attributeSlot) {
|
||||||
switch (attributeSlot) {
|
return switch (attributeSlot) {
|
||||||
case MAINHAND:
|
case MAINHAND -> MAIN_HAND;
|
||||||
return MAIN_HAND;
|
case OFFHAND -> OFF_HAND;
|
||||||
case OFFHAND:
|
case FEET -> BOOTS;
|
||||||
return OFF_HAND;
|
case LEGS -> LEGGINGS;
|
||||||
case FEET:
|
case CHEST -> CHESTPLATE;
|
||||||
return BOOTS;
|
case HEAD -> HELMET;
|
||||||
case LEGS:
|
};
|
||||||
return LEGGINGS;
|
|
||||||
case CHEST:
|
|
||||||
return CHESTPLATE;
|
|
||||||
case HEAD:
|
|
||||||
return HELMET;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Something weird happened");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package net.minestom.server.entity;
|
||||||
import net.minestom.server.entity.metadata.item.ItemEntityMeta;
|
import net.minestom.server.entity.metadata.item.ItemEntityMeta;
|
||||||
import net.minestom.server.event.EventDispatcher;
|
import net.minestom.server.event.EventDispatcher;
|
||||||
import net.minestom.server.event.entity.EntityItemMergeEvent;
|
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.ItemStack;
|
||||||
import net.minestom.server.item.StackingRule;
|
import net.minestom.server.item.StackingRule;
|
||||||
import net.minestom.server.utils.time.Cooldown;
|
import net.minestom.server.utils.time.Cooldown;
|
||||||
|
@ -13,7 +13,6 @@ import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.temporal.TemporalUnit;
|
import java.time.temporal.TemporalUnit;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an item on the ground.
|
* Represents an item on the ground.
|
||||||
|
@ -71,47 +70,26 @@ public class ItemEntity extends Entity {
|
||||||
(mergeDelay == null || !Cooldown.hasCooldown(time, lastMergeCheck, mergeDelay))) {
|
(mergeDelay == null || !Cooldown.hasCooldown(time, lastMergeCheck, mergeDelay))) {
|
||||||
this.lastMergeCheck = time;
|
this.lastMergeCheck = time;
|
||||||
|
|
||||||
final Chunk chunk = instance.getChunkAt(getPosition());
|
this.instance.getEntityTracker().nearbyEntities(position, mergeRange,
|
||||||
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
EntityTracker.Target.ITEMS, itemEntity -> {
|
||||||
for (Entity entity : entities) {
|
if (itemEntity == this) return;
|
||||||
if (entity instanceof ItemEntity) {
|
if (!itemEntity.isPickable() || !itemEntity.isMergeable()) return;
|
||||||
|
if (getDistance(itemEntity) > mergeRange) return;
|
||||||
|
|
||||||
// Do not merge with itself
|
final ItemStack itemStackEntity = itemEntity.getItemStack();
|
||||||
if (entity == this)
|
final StackingRule stackingRule = itemStack.getStackingRule();
|
||||||
continue;
|
final boolean canStack = stackingRule.canBeStacked(itemStack, itemStackEntity);
|
||||||
|
|
||||||
final ItemEntity itemEntity = (ItemEntity) entity;
|
if (!canStack) return;
|
||||||
if (!itemEntity.isPickable() || !itemEntity.isMergeable())
|
final int totalAmount = stackingRule.getAmount(itemStack) + stackingRule.getAmount(itemStackEntity);
|
||||||
continue;
|
if (!stackingRule.canApply(itemStack, totalAmount)) return;
|
||||||
|
final ItemStack result = stackingRule.apply(itemStack, totalAmount);
|
||||||
// Too far, do not merge
|
EntityItemMergeEvent entityItemMergeEvent = new EntityItemMergeEvent(this, itemEntity, result);
|
||||||
if (getDistance(itemEntity) > mergeRange)
|
EventDispatcher.callCancellable(entityItemMergeEvent, () -> {
|
||||||
continue;
|
setItemStack(entityItemMergeEvent.getResult());
|
||||||
|
itemEntity.remove();
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,7 @@ import net.minestom.server.event.entity.EntityDeathEvent;
|
||||||
import net.minestom.server.event.entity.EntityFireEvent;
|
import net.minestom.server.event.entity.EntityFireEvent;
|
||||||
import net.minestom.server.event.item.EntityEquipEvent;
|
import net.minestom.server.event.item.EntityEquipEvent;
|
||||||
import net.minestom.server.event.item.PickupItemEvent;
|
import net.minestom.server.event.item.PickupItemEvent;
|
||||||
import net.minestom.server.instance.Chunk;
|
import net.minestom.server.instance.EntityTracker;
|
||||||
import net.minestom.server.instance.block.Block;
|
|
||||||
import net.minestom.server.inventory.EquipmentHandler;
|
import net.minestom.server.inventory.EquipmentHandler;
|
||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
import net.minestom.server.network.ConnectionState;
|
import net.minestom.server.network.ConnectionState;
|
||||||
|
@ -201,32 +200,21 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||||
// Items picking
|
// Items picking
|
||||||
if (canPickupItem() && itemPickupCooldown.isReady(time)) {
|
if (canPickupItem() && itemPickupCooldown.isReady(time)) {
|
||||||
itemPickupCooldown.refreshLastUpdate(time);
|
itemPickupCooldown.refreshLastUpdate(time);
|
||||||
|
this.instance.getEntityTracker().nearbyEntities(position, expandedBoundingBox.getWidth(),
|
||||||
final Chunk chunk = getChunk(); // TODO check surrounding chunks
|
EntityTracker.Target.ITEMS, itemEntity -> {
|
||||||
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
if (this instanceof Player player && !itemEntity.isViewer(player)) return;
|
||||||
for (Entity entity : entities) {
|
if (!itemEntity.isPickable()) return;
|
||||||
if (entity instanceof ItemEntity) {
|
final BoundingBox itemBoundingBox = itemEntity.getBoundingBox();
|
||||||
// Do not pick up if not visible
|
if (expandedBoundingBox.intersect(itemBoundingBox)) {
|
||||||
if (this instanceof Player && !entity.isViewer((Player) this))
|
if (itemEntity.shouldRemove() || itemEntity.isRemoveScheduled()) return;
|
||||||
continue;
|
PickupItemEvent pickupItemEvent = new PickupItemEvent(this, itemEntity);
|
||||||
|
EventDispatcher.callCancellable(pickupItemEvent, () -> {
|
||||||
final ItemEntity itemEntity = (ItemEntity) entity;
|
final ItemStack item = itemEntity.getItemStack();
|
||||||
if (!itemEntity.isPickable())
|
sendPacketToViewersAndSelf(new CollectItemPacket(itemEntity.getEntityId(), getEntityId(), item.getAmount()));
|
||||||
continue;
|
itemEntity.remove();
|
||||||
|
});
|
||||||
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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,8 +343,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||||
sendPacketToViewersAndSelf(new EntityAnimationPacket(getEntityId(), EntityAnimationPacket.Animation.TAKE_DAMAGE));
|
sendPacketToViewersAndSelf(new EntityAnimationPacket(getEntityId(), EntityAnimationPacket.Animation.TAKE_DAMAGE));
|
||||||
|
|
||||||
// Additional hearts support
|
// Additional hearts support
|
||||||
if (this instanceof Player) {
|
if (this instanceof Player player) {
|
||||||
final Player player = (Player) this;
|
|
||||||
final float additionalHearts = player.getAdditionalHearts();
|
final float additionalHearts = player.getAdditionalHearts();
|
||||||
if (additionalHearts > 0) {
|
if (additionalHearts > 0) {
|
||||||
if (remainingDamage > additionalHearts) {
|
if (remainingDamage > additionalHearts) {
|
||||||
|
@ -477,8 +464,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||||
protected void onAttributeChanged(@NotNull AttributeInstance attributeInstance) {
|
protected void onAttributeChanged(@NotNull AttributeInstance attributeInstance) {
|
||||||
if (attributeInstance.getAttribute().isShared()) {
|
if (attributeInstance.getAttribute().isShared()) {
|
||||||
boolean self = false;
|
boolean self = false;
|
||||||
if (this instanceof Player) {
|
if (this instanceof Player player) {
|
||||||
Player player = (Player) this;
|
|
||||||
PlayerConnection playerConnection = player.playerConnection;
|
PlayerConnection playerConnection = player.playerConnection;
|
||||||
// connection null during Player initialization (due to #super call)
|
// connection null during Player initialization (due to #super call)
|
||||||
self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY;
|
self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY;
|
||||||
|
@ -531,17 +517,11 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean addViewer0(@NotNull Player player) {
|
public void updateNewViewer(@NotNull Player player) {
|
||||||
if (!super.addViewer0(player)) {
|
super.updateNewViewer(player);
|
||||||
return false;
|
player.sendPacket(getEquipmentsPacket());
|
||||||
}
|
player.sendPacket(getPropertiesPacket());
|
||||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
if (getTeam() != null) player.sendPacket(getTeam().createTeamsCreationPacket());
|
||||||
playerConnection.sendPacket(getEquipmentsPacket());
|
|
||||||
playerConnection.sendPacket(getPropertiesPacket());
|
|
||||||
if (getTeam() != null) {
|
|
||||||
playerConnection.sendPacket(getTeam().createTeamsCreationPacket());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -682,20 +662,10 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||||
*/
|
*/
|
||||||
public void setTeam(Team team) {
|
public void setTeam(Team team) {
|
||||||
if (this.team == team) return;
|
if (this.team == team) return;
|
||||||
|
String member = this instanceof Player player ? player.getUsername() : uuid.toString();
|
||||||
String member;
|
|
||||||
|
|
||||||
if (this instanceof Player) {
|
|
||||||
Player player = (Player) this;
|
|
||||||
member = player.getUsername();
|
|
||||||
} else {
|
|
||||||
member = this.uuid.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.team != null) {
|
if (this.team != null) {
|
||||||
this.team.removeMember(member);
|
this.team.removeMember(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.team = team;
|
this.team = team;
|
||||||
if (team != null) {
|
if (team != null) {
|
||||||
team.addMember(member);
|
team.addMember(member);
|
||||||
|
@ -711,46 +681,6 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||||
return team;
|
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.
|
* Gets the target (not-air) block position of the entity.
|
||||||
*
|
*
|
||||||
|
|
|
@ -291,49 +291,28 @@ public class Metadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> Value<T> getCorrespondingNewEmptyValue(int type) {
|
private static <T> Value<T> getCorrespondingNewEmptyValue(int type) {
|
||||||
switch (type) {
|
return switch (type) {
|
||||||
case TYPE_BYTE:
|
case TYPE_BYTE -> (Value<T>) Byte((byte) 0);
|
||||||
return (Value<T>) Byte((byte) 0);
|
case TYPE_VARINT -> (Value<T>) VarInt(0);
|
||||||
case TYPE_VARINT:
|
case TYPE_FLOAT -> (Value<T>) Float(0);
|
||||||
return (Value<T>) VarInt(0);
|
case TYPE_STRING -> (Value<T>) String("");
|
||||||
case TYPE_FLOAT:
|
case TYPE_CHAT -> (Value<T>) Chat(Component.empty());
|
||||||
return (Value<T>) Float(0);
|
case TYPE_OPTCHAT -> (Value<T>) OptChat(null);
|
||||||
case TYPE_STRING:
|
case TYPE_SLOT -> (Value<T>) Slot(ItemStack.AIR);
|
||||||
return (Value<T>) String("");
|
case TYPE_BOOLEAN -> (Value<T>) Boolean(false);
|
||||||
case TYPE_CHAT:
|
case TYPE_ROTATION -> (Value<T>) Rotation(Vec.ZERO);
|
||||||
return (Value<T>) Chat(Component.empty());
|
case TYPE_POSITION -> (Value<T>) Position(Vec.ZERO);
|
||||||
case TYPE_OPTCHAT:
|
case TYPE_OPTPOSITION -> (Value<T>) OptPosition(null);
|
||||||
return (Value<T>) OptChat(null);
|
case TYPE_DIRECTION -> (Value<T>) Direction(Direction.DOWN);
|
||||||
case TYPE_SLOT:
|
case TYPE_OPTUUID -> (Value<T>) OptUUID(null);
|
||||||
return (Value<T>) Slot(ItemStack.AIR);
|
case TYPE_OPTBLOCKID -> (Value<T>) OptBlockID(null);
|
||||||
case TYPE_BOOLEAN:
|
case TYPE_NBT -> (Value<T>) NBT(new NBTEnd());
|
||||||
return (Value<T>) Boolean(false);
|
case TYPE_PARTICLE -> throw new UnsupportedOperationException();
|
||||||
case TYPE_ROTATION:
|
case TYPE_VILLAGERDATA -> (Value<T>) VillagerData(0, 0, 0);
|
||||||
return (Value<T>) Rotation(Vec.ZERO);
|
case TYPE_OPTVARINT -> (Value<T>) OptVarInt(null);
|
||||||
case TYPE_POSITION:
|
case TYPE_POSE -> (Value<T>) Pose(Entity.Pose.STANDING);
|
||||||
return (Value<T>) Position(Vec.ZERO);
|
default -> throw new UnsupportedOperationException();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> Value<T> read(int type, BinaryReader reader) {
|
private static <T> Value<T> read(int type, BinaryReader reader) {
|
||||||
|
|
|
@ -12,8 +12,8 @@ import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.event.HoverEvent;
|
import net.kyori.adventure.text.event.HoverEvent;
|
||||||
import net.kyori.adventure.text.event.HoverEvent.ShowEntity;
|
import net.kyori.adventure.text.event.HoverEvent.ShowEntity;
|
||||||
import net.kyori.adventure.text.event.HoverEventSource;
|
import net.kyori.adventure.text.event.HoverEventSource;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.title.Title;
|
import net.kyori.adventure.title.TitlePart;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.advancements.AdvancementTab;
|
import net.minestom.server.advancements.AdvancementTab;
|
||||||
import net.minestom.server.adventure.AdventurePacketConvertor;
|
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.metadata.PlayerMeta;
|
||||||
import net.minestom.server.entity.vehicle.PlayerVehicleInformation;
|
import net.minestom.server.entity.vehicle.PlayerVehicleInformation;
|
||||||
import net.minestom.server.event.EventDispatcher;
|
import net.minestom.server.event.EventDispatcher;
|
||||||
|
import net.minestom.server.event.GlobalHandles;
|
||||||
import net.minestom.server.event.inventory.InventoryOpenEvent;
|
import net.minestom.server.event.inventory.InventoryOpenEvent;
|
||||||
import net.minestom.server.event.item.ItemDropEvent;
|
import net.minestom.server.event.item.ItemDropEvent;
|
||||||
import net.minestom.server.event.item.ItemUpdateStateEvent;
|
import net.minestom.server.event.item.ItemUpdateStateEvent;
|
||||||
import net.minestom.server.event.item.PickupExperienceEvent;
|
import net.minestom.server.event.item.PickupExperienceEvent;
|
||||||
import net.minestom.server.event.player.*;
|
import net.minestom.server.event.player.*;
|
||||||
import net.minestom.server.instance.Chunk;
|
import net.minestom.server.instance.Chunk;
|
||||||
|
import net.minestom.server.instance.EntityTracker;
|
||||||
import net.minestom.server.instance.Instance;
|
import net.minestom.server.instance.Instance;
|
||||||
import net.minestom.server.inventory.Inventory;
|
import net.minestom.server.inventory.Inventory;
|
||||||
import net.minestom.server.inventory.PlayerInventory;
|
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.ConnectionManager;
|
||||||
import net.minestom.server.network.ConnectionState;
|
import net.minestom.server.network.ConnectionState;
|
||||||
import net.minestom.server.network.PlayerProvider;
|
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.ClientPlayPacket;
|
||||||
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
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.BelowNameTag;
|
||||||
import net.minestom.server.scoreboard.Team;
|
import net.minestom.server.scoreboard.Team;
|
||||||
import net.minestom.server.statistic.PlayerStatistic;
|
import net.minestom.server.statistic.PlayerStatistic;
|
||||||
import net.minestom.server.utils.ArrayUtils;
|
|
||||||
import net.minestom.server.utils.MathUtils;
|
import net.minestom.server.utils.MathUtils;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.TickUtils;
|
|
||||||
import net.minestom.server.utils.async.AsyncUtils;
|
import net.minestom.server.utils.async.AsyncUtils;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
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.identity.NamedAndIdentified;
|
||||||
import net.minestom.server.utils.instance.InstanceUtils;
|
import net.minestom.server.utils.instance.InstanceUtils;
|
||||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||||
|
@ -85,9 +86,9 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.UnaryOperator;
|
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 {
|
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 long lastKeepAlive;
|
||||||
private boolean answerKeepAlive;
|
private boolean answerKeepAlive;
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
private Component usernameComponent;
|
private Component usernameComponent;
|
||||||
protected final PlayerConnection playerConnection;
|
protected final PlayerConnection playerConnection;
|
||||||
// All the entities that this player can see
|
|
||||||
protected final Set<Entity> viewableEntities = ConcurrentHashMap.newKeySet();
|
|
||||||
|
|
||||||
private int latency;
|
private int latency;
|
||||||
private Component displayName;
|
private Component displayName;
|
||||||
|
@ -113,8 +114,29 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
|
|
||||||
private DimensionType dimensionType;
|
private DimensionType dimensionType;
|
||||||
private GameMode gameMode;
|
private GameMode gameMode;
|
||||||
// Chunks that the player can view
|
final IntegerBiConsumer chunkAdder = (chunkX, chunkZ) -> {
|
||||||
protected final Set<Chunk> viewableChunks = ConcurrentHashMap.newKeySet();
|
// 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 final AtomicInteger teleportId = new AtomicInteger();
|
||||||
private int receivedTeleportId;
|
private int receivedTeleportId;
|
||||||
|
@ -166,9 +188,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
// Vehicle
|
// Vehicle
|
||||||
private final PlayerVehicleInformation vehicleInformation = new PlayerVehicleInformation();
|
private final PlayerVehicleInformation vehicleInformation = new PlayerVehicleInformation();
|
||||||
|
|
||||||
// Tick related
|
|
||||||
private final PlayerTickEvent playerTickEvent = new PlayerTickEvent(this);
|
|
||||||
|
|
||||||
// Adventure
|
// Adventure
|
||||||
private Identity identity;
|
private Identity identity;
|
||||||
private final Pointers pointers;
|
private final Pointers pointers;
|
||||||
|
@ -276,7 +295,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
// Recipes end
|
// Recipes end
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
this.playerConnection.sendPacket(TagsPacket.getRequiredTagsPacket());
|
this.playerConnection.sendPacket(TagsPacket.DEFAULT_TAGS);
|
||||||
|
|
||||||
// Some client updates
|
// Some client updates
|
||||||
this.playerConnection.sendPacket(getPropertiesPacket()); // Send default properties
|
this.playerConnection.sendPacket(getPropertiesPacket()); // Send default properties
|
||||||
|
@ -309,23 +328,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
// Experience orb pickup
|
// Experience orb pickup
|
||||||
if (experiencePickupCooldown.isReady(time)) {
|
if (experiencePickupCooldown.isReady(time)) {
|
||||||
experiencePickupCooldown.refreshLastUpdate(time);
|
experiencePickupCooldown.refreshLastUpdate(time);
|
||||||
final Chunk chunk = getChunk(); // TODO check surrounding chunks
|
this.instance.getEntityTracker().nearbyEntities(position, expandedBoundingBox.getWidth(),
|
||||||
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
EntityTracker.Target.EXPERIENCE_ORBS, experienceOrb -> {
|
||||||
for (Entity entity : entities) {
|
final BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
|
||||||
if (entity instanceof ExperienceOrb) {
|
if (expandedBoundingBox.intersect(itemBoundingBox)) {
|
||||||
final ExperienceOrb experienceOrb = (ExperienceOrb) entity;
|
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
|
||||||
final BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
|
return;
|
||||||
if (expandedBoundingBox.intersect(itemBoundingBox)) {
|
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(this, experienceOrb);
|
||||||
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
|
EventDispatcher.callCancellable(pickupExperienceEvent, () -> {
|
||||||
continue;
|
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
|
||||||
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(experienceOrb);
|
experienceOrb.remove();
|
||||||
EventDispatcher.callCancellable(pickupExperienceEvent, () -> {
|
});
|
||||||
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
|
}
|
||||||
entity.remove();
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eating animation
|
// Eating animation
|
||||||
|
@ -353,7 +368,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tick event
|
// Tick event
|
||||||
EventDispatcher.call(playerTickEvent);
|
GlobalHandles.PLAYER_TICK.call(new PlayerTickEvent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -420,6 +435,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
getPlayerConnection().sendPacket(respawnPacket);
|
getPlayerConnection().sendPacket(respawnPacket);
|
||||||
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this);
|
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this);
|
||||||
EventDispatcher.call(respawnEvent);
|
EventDispatcher.call(respawnEvent);
|
||||||
|
triggerStatus((byte) (24 + permissionLevel)); // Set permission level
|
||||||
refreshIsDead(false);
|
refreshIsDead(false);
|
||||||
|
|
||||||
// Runnable called when teleportation is successful (after loading and sending necessary chunk)
|
// 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));
|
EventDispatcher.call(new PlayerDisconnectEvent(this));
|
||||||
super.remove();
|
super.remove();
|
||||||
this.packets.clear();
|
this.packets.clear();
|
||||||
if (getOpenInventory() != null) {
|
final Inventory currentInventory = getOpenInventory();
|
||||||
getOpenInventory().removeViewer(this);
|
if (currentInventory != null) currentInventory.removeViewer(this);
|
||||||
}
|
|
||||||
MinecraftServer.getBossBarManager().removeAllBossBars(this);
|
MinecraftServer.getBossBarManager().removeAllBossBars(this);
|
||||||
// Advancement tabs cache
|
// 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
|
// 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
|
// Clear all viewable chunks
|
||||||
this.viewableChunks.forEach(chunk -> chunk.removeViewer(this));
|
ChunkUtils.forChunksInRange(chunkX, chunkZ, MinecraftServer.getChunkViewDistance(), chunkRemover);
|
||||||
// Remove from the tab-list
|
// Remove from the tab-list
|
||||||
PacketUtils.broadcastPacket(getRemovePlayerToList());
|
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
|
@Override
|
||||||
protected boolean removeViewer0(@NotNull Player player) {
|
public void updateOldViewer(@NotNull Player player) {
|
||||||
if (player == this || !super.removeViewer0(player)) {
|
super.updateOldViewer(player);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Team
|
// Team
|
||||||
if (this.getTeam() != null && this.getTeam().getMembers().size() == 1) {// If team only contains "this" player
|
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
|
@Override
|
||||||
|
@ -487,6 +507,12 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
super.sendPacketToViewersAndSelf(packet);
|
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.
|
* Changes the player instance and load surrounding chunks if needed.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -501,18 +527,37 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
|
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
|
||||||
final Instance currentInstance = this.instance;
|
final Instance currentInstance = this.instance;
|
||||||
Check.argCondition(currentInstance == instance, "Instance should be different than the current one");
|
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)) {
|
||||||
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 {
|
|
||||||
// The player already has the good version of all the chunks.
|
// 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
|
// We just need to refresh his entity viewing list and add him to the instance
|
||||||
return AsyncUtils.VOID_FUTURE
|
spawnPlayer(instance, spawnPosition, null, false, false, false);
|
||||||
.thenRun(() -> spawnPlayer(instance, spawnPosition, 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>
|
* <p>
|
||||||
* UNSAFE: only called with {@link #setInstance(Instance, Pos)}.
|
* UNSAFE: only called with {@link #setInstance(Instance, Pos)}.
|
||||||
*
|
*
|
||||||
* @param spawnPosition the position to teleport the player
|
* @param spawnPosition the position to teleport the player
|
||||||
* @param firstSpawn true if this is the player first spawn
|
* @param previousInstance the previous player instance, null if first spawn
|
||||||
* @param updateChunks true if chunks should be refreshed, false if the new instance shares the same
|
* @param firstSpawn true if this is the player first spawn
|
||||||
* chunks
|
* @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,
|
private void spawnPlayer(@NotNull Instance instance, @NotNull Pos spawnPosition,
|
||||||
|
@Nullable Instance previousInstance,
|
||||||
boolean firstSpawn, boolean dimensionChange, boolean updateChunks) {
|
boolean firstSpawn, boolean dimensionChange, boolean updateChunks) {
|
||||||
final Set<Chunk> previousChunks = Set.copyOf(viewableChunks);
|
|
||||||
if (!firstSpawn) {
|
if (!firstSpawn) {
|
||||||
// Player instance changed, clear current viewable collections
|
// Player instance changed, clear current viewable collections
|
||||||
previousChunks.forEach(chunk -> chunk.removeViewer(this));
|
if (updateChunks)
|
||||||
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
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);
|
super.setInstance(instance, spawnPosition);
|
||||||
|
|
||||||
if (dimensionChange) {
|
if (updateChunks) {
|
||||||
sendDimension(instance.getDimensionType());
|
sendPacket(new UpdateViewPositionPacket(spawnPosition.chunkX(), spawnPosition.chunkZ()));
|
||||||
|
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(), chunkAdder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateChunks) {
|
synchronizePosition(true); // So the player doesn't get stuck
|
||||||
// Warning: loop to remove once `refreshVisibleChunks` manage it
|
|
||||||
previousChunks.forEach(chunk ->
|
|
||||||
playerConnection.sendPacket(new UnloadChunkPacket(chunk.getChunkX(), chunk.getChunkZ())));
|
|
||||||
refreshVisibleChunks();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dimensionChange || firstSpawn) {
|
if (dimensionChange || firstSpawn) {
|
||||||
synchronizePosition(true); // So the player doesn't get stuck
|
|
||||||
this.inventory.update();
|
this.inventory.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
|
EventDispatcher.call(new PlayerSpawnEvent(this, instance, firstSpawn));
|
||||||
EventDispatcher.call(spawnEvent);
|
|
||||||
|
|
||||||
this.playerConnection.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -596,14 +641,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
sendPluginMessage(channel, message.getBytes(StandardCharsets.UTF_8));
|
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
|
@Override
|
||||||
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
|
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
|
||||||
Messenger.sendMessage(this, message, ChatPosition.fromMessageType(type), source.uuid());
|
Messenger.sendMessage(this, message, ChatPosition.fromMessageType(type), source.uuid());
|
||||||
|
@ -673,16 +710,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showTitle(@NotNull Title title) {
|
public <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value) {
|
||||||
playerConnection.sendPacket(new SetTitleTextPacket(title.title()));
|
playerConnection.sendPacket(AdventurePacketConvertor.createTitlePartPacket(part, value));
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -690,20 +719,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
playerConnection.sendPacket(new ActionBarPacket(message));
|
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
|
@Override
|
||||||
public void resetTitle() {
|
public void resetTitle() {
|
||||||
playerConnection.sendPacket(new ClearTitlesPacket(true));
|
playerConnection.sendPacket(new ClearTitlesPacket(true));
|
||||||
|
@ -1099,13 +1114,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
sendPacketToViewersAndSelf(getEquipmentsPacket());
|
sendPacketToViewersAndSelf(getEquipmentsPacket());
|
||||||
|
|
||||||
getInventory().update();
|
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));
|
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.
|
* Gets the player connection.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -1248,11 +1175,25 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
*
|
*
|
||||||
* @return the player connection
|
* @return the player connection
|
||||||
*/
|
*/
|
||||||
@NotNull
|
public @NotNull PlayerConnection getPlayerConnection() {
|
||||||
public PlayerConnection getPlayerConnection() {
|
|
||||||
return playerConnection;
|
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.
|
* Gets if the player is online or not.
|
||||||
*
|
*
|
||||||
|
@ -1267,8 +1208,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
*
|
*
|
||||||
* @return the player settings
|
* @return the player settings
|
||||||
*/
|
*/
|
||||||
@NotNull
|
public @NotNull PlayerSettings getSettings() {
|
||||||
public PlayerSettings getSettings() {
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1281,8 +1221,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
return dimensionType;
|
return dimensionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
public @NotNull PlayerInventory getInventory() {
|
||||||
public PlayerInventory getInventory() {
|
|
||||||
return inventory;
|
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)
|
* @return the currently open inventory, null if there is not (player inventory is not detected)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
public @Nullable Inventory getOpenInventory() {
|
||||||
public Inventory getOpenInventory() {
|
|
||||||
return openInventory;
|
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)
|
* @return true if the inventory has been opened/sent to the player, false otherwise (cancelled by event)
|
||||||
*/
|
*/
|
||||||
public boolean openInventory(@NotNull Inventory inventory) {
|
public boolean openInventory(@NotNull Inventory inventory) {
|
||||||
|
|
||||||
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this);
|
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this);
|
||||||
|
|
||||||
EventDispatcher.callCancellable(inventoryOpenEvent, () -> {
|
EventDispatcher.callCancellable(inventoryOpenEvent, () -> {
|
||||||
|
@ -1451,7 +1388,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
}
|
}
|
||||||
|
|
||||||
Inventory newInventory = inventoryOpenEvent.getInventory();
|
Inventory newInventory = inventoryOpenEvent.getInventory();
|
||||||
|
|
||||||
if (newInventory == null) {
|
if (newInventory == null) {
|
||||||
// just close the inventory
|
// just close the inventory
|
||||||
return;
|
return;
|
||||||
|
@ -1463,9 +1399,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
playerConnection.sendPacket(openWindowPacket);
|
playerConnection.sendPacket(openWindowPacket);
|
||||||
newInventory.addViewer(this);
|
newInventory.addViewer(this);
|
||||||
this.openInventory = newInventory;
|
this.openInventory = newInventory;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return !inventoryOpenEvent.isCancelled();
|
return !inventoryOpenEvent.isCancelled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1528,26 +1462,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
this.didCloseInventory = didCloseInventory;
|
this.didCloseInventory = didCloseInventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public int getNextTeleportId() {
|
||||||
* Gets the player viewable chunks.
|
return teleportId.incrementAndGet();
|
||||||
* <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 getLastSentTeleportId() {
|
public int getLastSentTeleportId() {
|
||||||
|
@ -1569,7 +1485,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
protected void synchronizePosition(boolean includeSelf) {
|
protected void synchronizePosition(boolean includeSelf) {
|
||||||
if (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);
|
super.synchronizePosition(includeSelf);
|
||||||
}
|
}
|
||||||
|
@ -1643,6 +1559,15 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
refreshAbilities();
|
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.
|
* 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
|
* @param flying should the player fly
|
||||||
*/
|
*/
|
||||||
public void setFlying(boolean flying) {
|
public void setFlying(boolean flying) {
|
||||||
this.flying = flying;
|
refreshFlying(flying);
|
||||||
refreshAbilities();
|
refreshAbilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1671,6 +1596,17 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
* @see #setFlying(boolean) instead
|
* @see #setFlying(boolean) instead
|
||||||
*/
|
*/
|
||||||
public void refreshFlying(boolean flying) {
|
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;
|
this.flying = flying;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1892,15 +1828,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
this.vehicleInformation.refresh(sideways, forward, jump, unmount);
|
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.
|
* 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
|
* @return a {@link PlayerInfoPacket} to add the player
|
||||||
*/
|
*/
|
||||||
@NotNull
|
protected @NotNull PlayerInfoPacket getAddPlayerToList() {
|
||||||
protected PlayerInfoPacket getAddPlayerToList() {
|
|
||||||
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER);
|
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER);
|
||||||
|
|
||||||
PlayerInfoPacket.AddPlayer addPlayer =
|
PlayerInfoPacket.AddPlayer addPlayer =
|
||||||
|
@ -1930,11 +1856,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
|
|
||||||
// Skin support
|
// Skin support
|
||||||
if (skin != null) {
|
if (skin != null) {
|
||||||
final String textures = skin.getTextures();
|
|
||||||
final String signature = skin.getSignature();
|
|
||||||
|
|
||||||
PlayerInfoPacket.AddPlayer.Property prop =
|
PlayerInfoPacket.AddPlayer.Property prop =
|
||||||
new PlayerInfoPacket.AddPlayer.Property("textures", textures, signature);
|
new PlayerInfoPacket.AddPlayer.Property("textures", skin.textures(), skin.signature());
|
||||||
addPlayer.properties.add(prop);
|
addPlayer.properties.add(prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1947,14 +1870,9 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
*
|
*
|
||||||
* @return a {@link PlayerInfoPacket} to remove the player
|
* @return a {@link PlayerInfoPacket} to remove the player
|
||||||
*/
|
*/
|
||||||
@NotNull
|
protected @NotNull PlayerInfoPacket getRemovePlayerToList() {
|
||||||
protected PlayerInfoPacket getRemovePlayerToList() {
|
|
||||||
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER);
|
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER);
|
||||||
|
playerInfoPacket.playerInfos.add(new PlayerInfoPacket.RemovePlayer(getUuid()));
|
||||||
PlayerInfoPacket.RemovePlayer removePlayer =
|
|
||||||
new PlayerInfoPacket.RemovePlayer(getUuid());
|
|
||||||
|
|
||||||
playerInfoPacket.playerInfos.add(removePlayer);
|
|
||||||
return playerInfoPacket;
|
return playerInfoPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1982,9 +1900,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
connection.sendPacket(new EntityHeadLookPacket(getEntityId(), position.yaw()));
|
connection.sendPacket(new EntityHeadLookPacket(getEntityId(), position.yaw()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStack getItemInMainHand() {
|
public @NotNull ItemStack getItemInMainHand() {
|
||||||
return inventory.getItemInMainHand();
|
return inventory.getItemInMainHand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1993,9 +1910,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
inventory.setItemInMainHand(itemStack);
|
inventory.setItemInMainHand(itemStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStack getItemInOffHand() {
|
public @NotNull ItemStack getItemInOffHand() {
|
||||||
return inventory.getItemInOffHand();
|
return inventory.getItemInOffHand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2004,9 +1920,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
inventory.setItemInOffHand(itemStack);
|
inventory.setItemInOffHand(itemStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStack getHelmet() {
|
public @NotNull ItemStack getHelmet() {
|
||||||
return inventory.getHelmet();
|
return inventory.getHelmet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2015,9 +1930,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
inventory.setHelmet(itemStack);
|
inventory.setHelmet(itemStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStack getChestplate() {
|
public @NotNull ItemStack getChestplate() {
|
||||||
return inventory.getChestplate();
|
return inventory.getChestplate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2026,9 +1940,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
inventory.setChestplate(itemStack);
|
inventory.setChestplate(itemStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStack getLeggings() {
|
public @NotNull ItemStack getLeggings() {
|
||||||
return inventory.getLeggings();
|
return inventory.getLeggings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2037,9 +1950,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
inventory.setLeggings(itemStack);
|
inventory.setLeggings(itemStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStack getBoots() {
|
public @NotNull ItemStack getBoots() {
|
||||||
return inventory.getBoots();
|
return inventory.getBoots();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2050,7 +1962,9 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Locale getLocale() {
|
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
|
RIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated See {@link ChatMessageType}
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public enum ChatMode {
|
|
||||||
ENABLED,
|
|
||||||
COMMANDS_ONLY,
|
|
||||||
HIDDEN
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PlayerSettings {
|
public class PlayerSettings {
|
||||||
|
|
||||||
private String locale;
|
private String locale;
|
||||||
|
@ -2155,17 +2059,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
return viewDistance;
|
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.
|
* 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,
|
public void refresh(String locale, byte viewDistance, ChatMessageType chatMessageType, boolean chatColors,
|
||||||
byte displayedSkinParts, MainHand mainHand) {
|
byte displayedSkinParts, MainHand mainHand) {
|
||||||
|
|
||||||
final boolean viewDistanceChanged = this.viewDistance != viewDistance;
|
|
||||||
|
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
this.viewDistance = viewDistance;
|
this.viewDistance = viewDistance;
|
||||||
this.chatMessageType = chatMessageType;
|
this.chatMessageType = chatMessageType;
|
||||||
|
@ -2223,11 +2113,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||||
|
|
||||||
// TODO: Use the metadata object here
|
// TODO: Use the metadata object here
|
||||||
metadata.setIndex((byte) 17, Metadata.Byte(displayedSkinParts));
|
metadata.setIndex((byte) 17, Metadata.Byte(displayedSkinParts));
|
||||||
|
|
||||||
// Client changed his view distance in the settings
|
|
||||||
if (viewDistanceChanged) {
|
|
||||||
refreshVisibleChunks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,26 +4,17 @@ import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import net.minestom.server.utils.mojang.MojangUtils;
|
import net.minestom.server.utils.mojang.MojangUtils;
|
||||||
|
import org.jetbrains.annotations.Blocking;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains all the data required to store a skin.
|
* Contains all the data required to store a skin.
|
||||||
* <p>
|
* <p>
|
||||||
* Can be applied to a player with {@link Player#setSkin(PlayerSkin)}
|
* Can be applied to a player with {@link Player#setSkin(PlayerSkin)}
|
||||||
* or in the linked event {@link net.minestom.server.event.player.PlayerSkinInitEvent}.
|
* or in the linked event {@link net.minestom.server.event.player.PlayerSkinInitEvent}.
|
||||||
*/
|
*/
|
||||||
public class PlayerSkin {
|
public record PlayerSkin(String textures, String signature) {
|
||||||
|
|
||||||
private final String textures;
|
|
||||||
private final String signature;
|
|
||||||
|
|
||||||
public PlayerSkin(String textures, String signature) {
|
|
||||||
this.textures = textures;
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a skin from a Mojang UUID.
|
* Gets a skin from a Mojang UUID.
|
||||||
|
@ -31,16 +22,14 @@ public class PlayerSkin {
|
||||||
* @param uuid Mojang UUID
|
* @param uuid Mojang UUID
|
||||||
* @return a player skin based on the UUID, null if not found
|
* @return a player skin based on the UUID, null if not found
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Blocking
|
||||||
public static PlayerSkin fromUuid(@NotNull String uuid) {
|
public static @Nullable PlayerSkin fromUuid(@NotNull String uuid) {
|
||||||
final JsonObject jsonObject = MojangUtils.fromUuid(uuid);
|
final JsonObject jsonObject = MojangUtils.fromUuid(uuid);
|
||||||
final JsonArray propertiesArray = jsonObject.get("properties").getAsJsonArray();
|
final JsonArray propertiesArray = jsonObject.get("properties").getAsJsonArray();
|
||||||
|
|
||||||
for (JsonElement jsonElement : propertiesArray) {
|
for (JsonElement jsonElement : propertiesArray) {
|
||||||
final JsonObject propertyObject = jsonElement.getAsJsonObject();
|
final JsonObject propertyObject = jsonElement.getAsJsonObject();
|
||||||
final String name = propertyObject.get("name").getAsString();
|
final String name = propertyObject.get("name").getAsString();
|
||||||
if (!name.equals("textures"))
|
if (!name.equals("textures")) continue;
|
||||||
continue;
|
|
||||||
final String textureValue = propertyObject.get("value").getAsString();
|
final String textureValue = propertyObject.get("value").getAsString();
|
||||||
final String signatureValue = propertyObject.get("signature").getAsString();
|
final String signatureValue = propertyObject.get("signature").getAsString();
|
||||||
return new PlayerSkin(textureValue, signatureValue);
|
return new PlayerSkin(textureValue, signatureValue);
|
||||||
|
@ -54,8 +43,8 @@ public class PlayerSkin {
|
||||||
* @param username the Minecraft username
|
* @param username the Minecraft username
|
||||||
* @return a skin based on a Minecraft username, null if not found
|
* @return a skin based on a Minecraft username, null if not found
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Blocking
|
||||||
public static PlayerSkin fromUsername(@NotNull String username) {
|
public static @Nullable PlayerSkin fromUsername(@NotNull String username) {
|
||||||
final JsonObject jsonObject = MojangUtils.fromUsername(username);
|
final JsonObject jsonObject = MojangUtils.fromUsername(username);
|
||||||
final String uuid = jsonObject.get("id").getAsString();
|
final String uuid = jsonObject.get("id").getAsString();
|
||||||
// Retrieve the skin data from the mojang uuid
|
// Retrieve the skin data from the mojang uuid
|
||||||
|
@ -63,49 +52,18 @@ public class PlayerSkin {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the skin textures value.
|
* @deprecated use {@link #textures()}
|
||||||
*
|
|
||||||
* @return the textures value
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public String getTextures() {
|
public String getTextures() {
|
||||||
return textures;
|
return textures;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the skin signature.
|
* @deprecated use {@link #signature()}
|
||||||
*
|
|
||||||
* @return the skin signature
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public String getSignature() {
|
public String getSignature() {
|
||||||
return signature;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,6 +198,7 @@ public class CombinedAttackGoal extends GoalSelector {
|
||||||
if (pathPosition != null) {
|
if (pathPosition != null) {
|
||||||
navigator.setPathTo(null);
|
navigator.setPathTo(null);
|
||||||
}
|
}
|
||||||
|
this.entityCreature.lookAt(target);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Otherwise going to the target.
|
// Otherwise going to the target.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package net.minestom.server.entity.ai.goal;
|
package net.minestom.server.entity.ai.goal;
|
||||||
|
|
||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.EntityCreature;
|
import net.minestom.server.entity.EntityCreature;
|
||||||
import net.minestom.server.entity.ai.GoalSelector;
|
import net.minestom.server.entity.ai.GoalSelector;
|
||||||
|
@ -15,6 +16,8 @@ public class FollowTargetGoal extends GoalSelector {
|
||||||
private boolean forceEnd = false;
|
private boolean forceEnd = false;
|
||||||
private Point lastTargetPos;
|
private Point lastTargetPos;
|
||||||
|
|
||||||
|
private Entity target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a follow target goal object.
|
* Creates a follow target goal object.
|
||||||
*
|
*
|
||||||
|
@ -28,8 +31,14 @@ public class FollowTargetGoal extends GoalSelector {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldStart() {
|
public boolean shouldStart() {
|
||||||
return entityCreature.getTarget() != null &&
|
Entity target = entityCreature.getTarget();
|
||||||
entityCreature.getTarget().getPosition().distance(entityCreature.getPosition()) >= 2;
|
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
|
@Override
|
||||||
|
@ -37,23 +46,22 @@ public class FollowTargetGoal extends GoalSelector {
|
||||||
lastUpdateTime = 0;
|
lastUpdateTime = 0;
|
||||||
forceEnd = false;
|
forceEnd = false;
|
||||||
lastTargetPos = null;
|
lastTargetPos = null;
|
||||||
final Entity target = entityCreature.getTarget();
|
if (target == null) {
|
||||||
if (target != null) {
|
// No defined target
|
||||||
Navigator navigator = entityCreature.getNavigator();
|
this.forceEnd = true;
|
||||||
|
return;
|
||||||
lastTargetPos = target.getPosition();
|
}
|
||||||
if (lastTargetPos.distance(entityCreature.getPosition()) < 2) {
|
this.entityCreature.setTarget(target);
|
||||||
forceEnd = true;
|
Navigator navigator = entityCreature.getNavigator();
|
||||||
navigator.setPathTo(null);
|
this.lastTargetPos = target.getPosition();
|
||||||
return;
|
if (lastTargetPos.distance(entityCreature.getPosition()) < 2) {
|
||||||
}
|
// Target is too far
|
||||||
|
this.forceEnd = true;
|
||||||
if (navigator.getPathPosition() == null ||
|
navigator.setPathTo(null);
|
||||||
(!navigator.getPathPosition().samePoint(lastTargetPos))) {
|
return;
|
||||||
navigator.setPathTo(lastTargetPos);
|
}
|
||||||
} else {
|
if (navigator.getPathPosition() == null || !navigator.getPathPosition().samePoint(lastTargetPos)) {
|
||||||
forceEnd = true;
|
navigator.setPathTo(lastTargetPos);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
forceEnd = true;
|
forceEnd = true;
|
||||||
}
|
}
|
||||||
|
@ -66,7 +74,7 @@ public class FollowTargetGoal extends GoalSelector {
|
||||||
pathDuration.toMillis() + lastUpdateTime > time) {
|
pathDuration.toMillis() + lastUpdateTime > time) {
|
||||||
return;
|
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)) {
|
if (targetPos != null && !targetPos.samePoint(lastTargetPos)) {
|
||||||
this.lastUpdateTime = time;
|
this.lastUpdateTime = time;
|
||||||
this.lastTargetPos = targetPos;
|
this.lastTargetPos = targetPos;
|
||||||
|
@ -79,11 +87,12 @@ public class FollowTargetGoal extends GoalSelector {
|
||||||
final Entity target = entityCreature.getTarget();
|
final Entity target = entityCreature.getTarget();
|
||||||
return forceEnd ||
|
return forceEnd ||
|
||||||
target == null ||
|
target == null ||
|
||||||
|
target.isRemoved() ||
|
||||||
target.getPosition().distance(entityCreature.getPosition()) < 2;
|
target.getPosition().distance(entityCreature.getPosition()) < 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void end() {
|
public void end() {
|
||||||
entityCreature.getNavigator().setPathTo(null);
|
this.entityCreature.getNavigator().setPathTo(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ public class MeleeAttackGoal extends GoalSelector {
|
||||||
|
|
||||||
// Attack the target entity
|
// Attack the target entity
|
||||||
if (entityCreature.getDistance(target) <= range) {
|
if (entityCreature.getDistance(target) <= range) {
|
||||||
|
entityCreature.lookAt(target);
|
||||||
if (!Cooldown.hasCooldown(time, lastHit, delay)) {
|
if (!Cooldown.hasCooldown(time, lastHit, delay)) {
|
||||||
entityCreature.attack(target, true);
|
entityCreature.attack(target, true);
|
||||||
this.lastHit = time;
|
this.lastHit = time;
|
||||||
|
|
|
@ -6,8 +6,8 @@ import net.minestom.server.entity.ai.GoalSelector;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class RandomStrollGoal extends GoalSelector {
|
public class RandomStrollGoal extends GoalSelector {
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ public class RandomStrollGoal extends GoalSelector {
|
||||||
|
|
||||||
private final int radius;
|
private final int radius;
|
||||||
private final List<Vec> closePositions;
|
private final List<Vec> closePositions;
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
private long lastStroll;
|
private long lastStroll;
|
||||||
|
|
||||||
|
@ -31,8 +32,11 @@ public class RandomStrollGoal extends GoalSelector {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
Collections.shuffle(closePositions);
|
int remainingAttempt = closePositions.size();
|
||||||
for (var position : closePositions) {
|
while (remainingAttempt-- > 0) {
|
||||||
|
final int index = random.nextInt(closePositions.size());
|
||||||
|
final Vec position = closePositions.get(index);
|
||||||
|
|
||||||
final var target = entityCreature.getPosition().add(position);
|
final var target = entityCreature.getPosition().add(position);
|
||||||
final boolean result = entityCreature.getNavigator().setPathTo(target);
|
final boolean result = entityCreature.getNavigator().setPathTo(target);
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|
|
@ -124,6 +124,7 @@ public class RangedAttackGoal extends GoalSelector {
|
||||||
if (pathPosition != null) {
|
if (pathPosition != null) {
|
||||||
navigator.setPathTo(null);
|
navigator.setPathTo(null);
|
||||||
}
|
}
|
||||||
|
this.entityCreature.lookAt(target);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final var targetPosition = target.getPosition();
|
final var targetPosition = target.getPosition();
|
||||||
|
|
|
@ -21,6 +21,7 @@ public class ClosestEntityTarget extends TargetSelector {
|
||||||
private final float range;
|
private final float range;
|
||||||
private final Class<? extends LivingEntity>[] entitiesTarget;
|
private final Class<? extends LivingEntity>[] entitiesTarget;
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
public ClosestEntityTarget(@NotNull EntityCreature entityCreature, float range,
|
public ClosestEntityTarget(@NotNull EntityCreature entityCreature, float range,
|
||||||
@NotNull Class<? extends LivingEntity>... entitiesTarget) {
|
@NotNull Class<? extends LivingEntity>... entitiesTarget) {
|
||||||
super(entityCreature);
|
super(entityCreature);
|
||||||
|
|
|
@ -22,20 +22,15 @@ public class LastEntityDamagerTarget extends TargetSelector {
|
||||||
@Override
|
@Override
|
||||||
public Entity findTarget() {
|
public Entity findTarget() {
|
||||||
final DamageType damageType = entityCreature.getLastDamageSource();
|
final DamageType damageType = entityCreature.getLastDamageSource();
|
||||||
|
if (!(damageType instanceof EntityDamage entityDamage)) {
|
||||||
if (!(damageType instanceof EntityDamage)) {
|
|
||||||
// No damager recorded, return null
|
// No damager recorded, return null
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final EntityDamage entityDamage = (EntityDamage) damageType;
|
|
||||||
final Entity entity = entityDamage.getSource();
|
final Entity entity = entityDamage.getSource();
|
||||||
|
|
||||||
if (entity.isRemoved()) {
|
if (entity.isRemoved()) {
|
||||||
// Entity not valid
|
// Entity not valid
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check range
|
// Check range
|
||||||
return entityCreature.getDistance(entity) < range ? entity : null;
|
return entityCreature.getDistance(entity) < range ? entity : null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package net.minestom.server.entity.fakeplayer;
|
||||||
|
|
||||||
import com.extollit.gaming.ai.path.HydrazinePathFinder;
|
import com.extollit.gaming.ai.path.HydrazinePathFinder;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.attribute.Attribute;
|
|
||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.entity.pathfinding.NavigableEntity;
|
import net.minestom.server.entity.pathfinding.NavigableEntity;
|
||||||
|
@ -118,9 +117,8 @@ public class FakePlayer extends Player implements NavigableEntity {
|
||||||
@Override
|
@Override
|
||||||
public void update(long time) {
|
public void update(long time) {
|
||||||
super.update(time);
|
super.update(time);
|
||||||
|
|
||||||
// Path finding
|
// Path finding
|
||||||
this.navigator.tick(getAttributeValue(Attribute.MOVEMENT_SPEED));
|
this.navigator.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -131,12 +129,10 @@ public class FakePlayer extends Player implements NavigableEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean addViewer0(@NotNull Player player) {
|
public void updateNewViewer(@NotNull Player player) {
|
||||||
final boolean result = super.addViewer0(player);
|
player.getPlayerConnection().sendPacket(getAddPlayerToList());
|
||||||
if (result) {
|
handleTabList(player.getPlayerConnection());
|
||||||
handleTabList(player.getPlayerConnection());
|
super.updateNewViewer(player);
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,9 +25,11 @@ public class FakePlayerOption {
|
||||||
* WARNING: this can't be changed halfway.
|
* WARNING: this can't be changed halfway.
|
||||||
*
|
*
|
||||||
* @param registered should the fake player be registered internally
|
* @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;
|
this.registered = registered;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,8 +47,10 @@ public class FakePlayerOption {
|
||||||
* WARNING: this can't be changed halfway.
|
* WARNING: this can't be changed halfway.
|
||||||
*
|
*
|
||||||
* @param inTabList should the player be in the tab-list
|
* @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;
|
this.inTabList = inTabList;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,15 +24,9 @@ public class PufferfishMeta extends AbstractFishMeta {
|
||||||
|
|
||||||
private void updateBoundingBox(State state) {
|
private void updateBoundingBox(State state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case UNPUFFED:
|
case UNPUFFED -> setBoundingBox(.35D, .35D);
|
||||||
setBoundingBox(.35D, .35D);
|
case SEMI_PUFFED -> setBoundingBox(.5D, .5D);
|
||||||
break;
|
default -> setBoundingBox(.7D, .7D);
|
||||||
case SEMI_PUFFED:
|
|
||||||
setBoundingBox(.5D, .5D);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setBoundingBox(.7D, .7D);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import net.minestom.server.instance.Instance;
|
||||||
import net.minestom.server.instance.WorldBorder;
|
import net.minestom.server.instance.WorldBorder;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import net.minestom.server.utils.position.PositionUtils;
|
import net.minestom.server.utils.position.PositionUtils;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@ -22,11 +23,9 @@ import org.jetbrains.annotations.Nullable;
|
||||||
/**
|
/**
|
||||||
* Necessary object for all {@link NavigableEntity}.
|
* Necessary object for all {@link NavigableEntity}.
|
||||||
*/
|
*/
|
||||||
public class Navigator {
|
public final class Navigator {
|
||||||
|
|
||||||
private final PFPathingEntity pathingEntity;
|
private final PFPathingEntity pathingEntity;
|
||||||
private HydrazinePathFinder pathFinder;
|
private HydrazinePathFinder pathFinder;
|
||||||
private IPath path;
|
|
||||||
private Point pathPosition;
|
private Point pathPosition;
|
||||||
|
|
||||||
private final Entity entity;
|
private final Entity entity;
|
||||||
|
@ -88,30 +87,24 @@ public class Navigator {
|
||||||
// Tried to set path to the same target position
|
// Tried to set path to the same target position
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Instance instance = entity.getInstance();
|
final Instance instance = entity.getInstance();
|
||||||
|
|
||||||
if (pathFinder == null) {
|
if (pathFinder == null) {
|
||||||
// Unexpected error
|
// Unexpected error
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
this.pathFinder.reset();
|
||||||
pathFinder.reset();
|
|
||||||
if (point == null) {
|
if (point == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't path with a null instance.
|
// Can't path with a null instance.
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't path outside the world border
|
// Can't path outside the world border
|
||||||
final WorldBorder worldBorder = instance.getWorldBorder();
|
final WorldBorder worldBorder = instance.getWorldBorder();
|
||||||
if (!worldBorder.isInside(point)) {
|
if (!worldBorder.isInside(point)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't path in an unloaded chunk
|
// Can't path in an unloaded chunk
|
||||||
final Chunk chunk = instance.getChunkAt(point);
|
final Chunk chunk = instance.getChunkAt(point);
|
||||||
if (!ChunkUtils.isLoaded(chunk)) {
|
if (!ChunkUtils.isLoaded(chunk)) {
|
||||||
|
@ -126,11 +119,9 @@ public class Navigator {
|
||||||
point.y(),
|
point.y(),
|
||||||
point.z(),
|
point.z(),
|
||||||
pathOptions);
|
pathOptions);
|
||||||
this.path = path;
|
|
||||||
|
|
||||||
final boolean success = path != null;
|
final boolean success = path != null;
|
||||||
this.pathPosition = success ? point : null;
|
this.pathPosition = success ? point : null;
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,57 +132,16 @@ public class Navigator {
|
||||||
return setPathTo(position, true);
|
return setPathTo(position, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void tick(float speed) {
|
@ApiStatus.Internal
|
||||||
// No pathfinding tick for dead entities
|
public synchronized void tick() {
|
||||||
|
if (pathPosition == null) return; // No path
|
||||||
if (entity instanceof LivingEntity && ((LivingEntity) entity).isDead())
|
if (entity instanceof LivingEntity && ((LivingEntity) entity).isDead())
|
||||||
return;
|
return; // No pathfinding tick for dead entities
|
||||||
|
if (pathFinder.updatePathFor(pathingEntity) == null) {
|
||||||
if (pathPosition != null) {
|
reset();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Gets the target pathfinder position.
|
||||||
*
|
*
|
||||||
|
@ -201,28 +151,22 @@ public class Navigator {
|
||||||
return pathPosition;
|
return pathPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public @NotNull Entity getEntity() {
|
||||||
* 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() {
|
|
||||||
return entity;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,15 @@ import net.minestom.server.coordinate.Point;
|
||||||
import net.minestom.server.coordinate.Vec;
|
import net.minestom.server.coordinate.Vec;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.LivingEntity;
|
import net.minestom.server.entity.LivingEntity;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class PFPathingEntity implements IPathingEntity {
|
@ApiStatus.Internal
|
||||||
|
public final class PFPathingEntity implements IPathingEntity {
|
||||||
private final Navigator navigator;
|
private final Navigator navigator;
|
||||||
private final Entity entity;
|
private final Entity entity;
|
||||||
|
|
||||||
private float searchRange;
|
private float searchRange;
|
||||||
private Point targetPosition;
|
|
||||||
|
|
||||||
// Capacities
|
// Capacities
|
||||||
private boolean fireResistant;
|
private boolean fireResistant;
|
||||||
|
@ -37,10 +37,6 @@ public class PFPathingEntity implements IPathingEntity {
|
||||||
this.searchRange = getAttributeValue(Attribute.FOLLOW_RANGE);
|
this.searchRange = getAttributeValue(Attribute.FOLLOW_RANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Point getTargetPosition() {
|
|
||||||
return targetPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int age() {
|
public int age() {
|
||||||
return (int) entity.getAliveTicks();
|
return (int) entity.getAliveTicks();
|
||||||
|
@ -194,7 +190,8 @@ public class PFPathingEntity implements IPathingEntity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveTo(Vec3d position, Passibility passibility, Gravitation gravitation) {
|
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();
|
final double entityY = entity.getPosition().y();
|
||||||
if (entityY < targetPosition.y()) {
|
if (entityY < targetPosition.y()) {
|
||||||
this.navigator.jump(1);
|
this.navigator.jump(1);
|
||||||
|
|
|
@ -4,12 +4,16 @@ import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.event.trait.CancellableEvent;
|
import net.minestom.server.event.trait.CancellableEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class EventDispatcher {
|
public final class EventDispatcher {
|
||||||
|
|
||||||
public static void call(@NotNull Event event) {
|
public static void call(@NotNull Event event) {
|
||||||
MinecraftServer.getGlobalEventHandler().call(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) {
|
public static void callCancellable(@NotNull CancellableEvent event, @NotNull Runnable successCallback) {
|
||||||
MinecraftServer.getGlobalEventHandler().callCancellable(event, successCallback);
|
MinecraftServer.getGlobalEventHandler().callCancellable(event, successCallback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import java.util.function.Predicate;
|
||||||
*/
|
*/
|
||||||
public interface EventListener<T extends Event> {
|
public interface EventListener<T extends Event> {
|
||||||
|
|
||||||
@NotNull Class<T> getEventType();
|
@NotNull Class<T> eventType();
|
||||||
|
|
||||||
@NotNull Result run(@NotNull T event);
|
@NotNull Result run(@NotNull T event);
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ public interface EventListener<T extends Event> {
|
||||||
final var handler = this.handler;
|
final var handler = this.handler;
|
||||||
return new EventListener<>() {
|
return new EventListener<>() {
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Class<T> getEventType() {
|
public @NotNull Class<T> eventType() {
|
||||||
return eventType;
|
return eventType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,7 @@ import java.util.function.Predicate;
|
||||||
*
|
*
|
||||||
* @param <T> The event type accepted by this node
|
* @param <T> The event type accepted by this node
|
||||||
*/
|
*/
|
||||||
@ApiStatus.NonExtendable
|
public sealed interface EventNode<T extends Event> permits EventNodeImpl {
|
||||||
public interface EventNode<T extends Event> {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an event node which accepts any event type with no filtering.
|
* 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
|
* Calls an event starting from this node.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* @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.
|
* 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
|
* @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.
|
* Directly adds a child node to this node.
|
||||||
|
@ -318,9 +328,24 @@ public interface EventNode<T extends Event> {
|
||||||
@Contract(value = "_ -> this")
|
@Contract(value = "_ -> this")
|
||||||
@NotNull EventNode<T> removeListener(@NotNull EventListener<? extends T> listener);
|
@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
|
@ApiStatus.Experimental
|
||||||
void map(@NotNull EventNode<? extends T> node, @NotNull Object value);
|
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
|
@ApiStatus.Experimental
|
||||||
boolean unmap(@NotNull Object value);
|
boolean unmap(@NotNull Object value);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package net.minestom.server.event;
|
package net.minestom.server.event;
|
||||||
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.event.trait.RecursiveEvent;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
@ -10,19 +11,17 @@ import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
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.BiPredicate;
|
||||||
import java.util.function.Consumer;
|
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 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 Map<Class<? extends T>, ListenerEntry<T>> listenerMap = new ConcurrentHashMap<>();
|
||||||
private final Set<EventNode<T>> children = new CopyOnWriteArraySet<>();
|
private final Set<EventNodeImpl<T>> children = new CopyOnWriteArraySet<>();
|
||||||
private final Map<Object, ListenerEntry<T>> mappedNodeCache = new WeakHashMap<>();
|
private final Map<Object, EventNodeImpl<T>> mappedNodeCache = new WeakHashMap<>();
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final EventFilter<T, ?> filter;
|
private final EventFilter<T, ?> filter;
|
||||||
|
@ -31,9 +30,9 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||||
private volatile int priority;
|
private volatile int priority;
|
||||||
private volatile EventNodeImpl<? super T> parent;
|
private volatile EventNodeImpl<? super T> parent;
|
||||||
|
|
||||||
protected EventNodeImpl(@NotNull String name,
|
EventNodeImpl(@NotNull String name,
|
||||||
@NotNull EventFilter<T, ?> filter,
|
@NotNull EventFilter<T, ?> filter,
|
||||||
@Nullable BiPredicate<T, Object> predicate) {
|
@Nullable BiPredicate<T, Object> predicate) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
this.predicate = predicate;
|
this.predicate = predicate;
|
||||||
|
@ -41,35 +40,16 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void call(@NotNull T event) {
|
public <E extends T> @NotNull ListenerHandle<E> getHandle(@NotNull Class<E> handleType) {
|
||||||
final var eventClass = event.getClass();
|
//noinspection unchecked
|
||||||
if (!eventType.isAssignableFrom(eventClass)) return; // Invalid event type
|
return (ListenerHandle<E>) handleMap.computeIfAbsent(handleType,
|
||||||
// Conditions
|
aClass -> new Handle<>(this, (Class<T>) aClass));
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <E extends T> @NotNull List<EventNode<E>> findChildren(@NotNull String name, Class<E> eventType) {
|
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) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
|
if (children.isEmpty()) return Collections.emptyList();
|
||||||
List<EventNode<E>> result = new ArrayList<>();
|
List<EventNode<E>> result = new ArrayList<>();
|
||||||
for (EventNode<T> child : children) {
|
for (EventNode<T> child : children) {
|
||||||
if (equals(child, name, eventType)) {
|
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
|
@Override
|
||||||
public @NotNull EventNode<T> addChild(@NotNull EventNode<? extends T> child) {
|
public @NotNull EventNode<T> addChild(@NotNull EventNode<? extends T> child) {
|
||||||
synchronized (GLOBAL_CHILD_LOCK) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
final var childImpl = (EventNodeImpl<? extends T>) child;
|
final var childImpl = (EventNodeImpl<? extends T>) child;
|
||||||
Check.stateCondition(childImpl.parent != null, "Node already has a parent");
|
Check.stateCondition(childImpl.parent != null, "Node already has a parent");
|
||||||
Check.stateCondition(Objects.equals(parent, child), "Cannot have a child as parent");
|
Check.stateCondition(Objects.equals(parent, child), "Cannot have a child as parent");
|
||||||
final boolean result = this.children.add((EventNodeImpl<T>) childImpl);
|
if (!children.add((EventNodeImpl<T>) childImpl)) return this; // Couldn't add the child (already present?)
|
||||||
if (result) {
|
childImpl.parent = this;
|
||||||
childImpl.parent = this;
|
childImpl.invalidateEventsFor(this);
|
||||||
// Increase listener count
|
|
||||||
propagateNode(childImpl, IntUnaryOperator.identity());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -139,13 +111,11 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||||
@Override
|
@Override
|
||||||
public @NotNull EventNode<T> removeChild(@NotNull EventNode<? extends T> child) {
|
public @NotNull EventNode<T> removeChild(@NotNull EventNode<? extends T> child) {
|
||||||
synchronized (GLOBAL_CHILD_LOCK) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
final boolean result = this.children.remove(child);
|
final var childImpl = (EventNodeImpl<? extends T>) child;
|
||||||
if (result) {
|
final boolean result = this.children.remove(childImpl);
|
||||||
final var childImpl = (EventNodeImpl<? extends T>) child;
|
if (!result) return this; // Child not found
|
||||||
childImpl.parent = null;
|
childImpl.parent = null;
|
||||||
// Decrease listener count
|
childImpl.invalidateEventsFor(this);
|
||||||
propagateNode(childImpl, count -> -count);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -153,10 +123,10 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||||
@Override
|
@Override
|
||||||
public @NotNull EventNode<T> addListener(@NotNull EventListener<? extends T> listener) {
|
public @NotNull EventNode<T> addListener(@NotNull EventListener<? extends T> listener) {
|
||||||
synchronized (GLOBAL_CHILD_LOCK) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
final var eventType = listener.getEventType();
|
final var eventType = listener.eventType();
|
||||||
var entry = getEntry(eventType);
|
ListenerEntry<T> entry = getEntry(eventType);
|
||||||
entry.listeners.add((EventListener<T>) listener);
|
entry.listeners.add((EventListener<T>) listener);
|
||||||
propagateToParent(eventType, 1);
|
invalidateEvent(eventType);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -164,50 +134,36 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||||
@Override
|
@Override
|
||||||
public @NotNull EventNode<T> removeListener(@NotNull EventListener<? extends T> listener) {
|
public @NotNull EventNode<T> removeListener(@NotNull EventListener<? extends T> listener) {
|
||||||
synchronized (GLOBAL_CHILD_LOCK) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
final var eventType = listener.getEventType();
|
final var eventType = listener.eventType();
|
||||||
var entry = listenerMap.get(eventType);
|
ListenerEntry<T> entry = listenerMap.get(eventType);
|
||||||
if (entry == null) return this;
|
if (entry == null) return this; // There is no listener with such type
|
||||||
var listeners = entry.listeners;
|
if (entry.listeners.remove(listener)) invalidateEvent(eventType);
|
||||||
final boolean removed = listeners.remove(listener);
|
|
||||||
if (removed) propagateToParent(eventType, -1);
|
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void map(@NotNull EventNode<? extends T> node, @NotNull Object value) {
|
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) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
nodeImpl.listenerMap.forEach((type, listenerEntry) -> {
|
final var nodeImpl = (EventNodeImpl<? extends T>) node;
|
||||||
final var entry = getEntry(type);
|
Check.stateCondition(nodeImpl.parent != null, "Node already has a parent");
|
||||||
final boolean correct = entry.filters.stream().anyMatch(eventFilter -> {
|
Check.stateCondition(Objects.equals(parent, nodeImpl), "Cannot map to self");
|
||||||
final var handlerType = eventFilter.handlerType();
|
EventNodeImpl<T> previous = this.mappedNodeCache.put(value, (EventNodeImpl<T>) nodeImpl);
|
||||||
return handlerType != null && handlerType.isAssignableFrom(valueType);
|
if (previous != null) previous.parent = null;
|
||||||
});
|
nodeImpl.parent = this;
|
||||||
Check.stateCondition(!correct, "The node filter {0} is not compatible with type {1}", nodeImpl.eventType, valueType);
|
nodeImpl.invalidateEventsFor(this);
|
||||||
synchronized (mappedNodeCache) {
|
|
||||||
entry.mappedNode.put(value, (EventNode<T>) nodeImpl);
|
|
||||||
mappedNodeCache.put(value, entry);
|
|
||||||
// TODO propagate
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean unmap(@NotNull Object value) {
|
public boolean unmap(@NotNull Object value) {
|
||||||
synchronized (GLOBAL_CHILD_LOCK) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
synchronized (mappedNodeCache) {
|
final var mappedNode = this.mappedNodeCache.remove(value);
|
||||||
var entry = mappedNodeCache.remove(value);
|
if (mappedNode == null) return false; // Mapped node not found
|
||||||
if (entry == null) return false;
|
final var childImpl = (EventNodeImpl<? extends T>) mappedNode;
|
||||||
final EventNode<T> previousNode = entry.mappedNode.remove(value);
|
childImpl.parent = null;
|
||||||
if (previousNode != null) {
|
childImpl.invalidateEventsFor(this);
|
||||||
// TODO propagate
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,9 +171,9 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||||
public void register(@NotNull EventBinding<? extends T> binding) {
|
public void register(@NotNull EventBinding<? extends T> binding) {
|
||||||
synchronized (GLOBAL_CHILD_LOCK) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
for (var eventType : binding.eventTypes()) {
|
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));
|
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) {
|
public void unregister(@NotNull EventBinding<? extends T> binding) {
|
||||||
synchronized (GLOBAL_CHILD_LOCK) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
for (var eventType : binding.eventTypes()) {
|
for (var eventType : binding.eventTypes()) {
|
||||||
var entry = listenerMap.get(eventType);
|
ListenerEntry<T> entry = listenerMap.get(eventType);
|
||||||
if (entry == null) return;
|
if (entry == null) return;
|
||||||
final boolean removed = entry.bindingConsumers.remove(binding.consumer(eventType));
|
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;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void propagateChildCountChange(Class<? extends T> eventClass, int count) {
|
private void invalidateEventsFor(EventNodeImpl<? super T> node) {
|
||||||
var entry = getEntry(eventClass);
|
for (Class<? extends T> eventType : listenerMap.keySet()) {
|
||||||
final int result = ListenerEntry.CHILD_UPDATER.addAndGet(entry, count);
|
node.invalidateEvent(eventType);
|
||||||
if (result == 0 && entry.listeners.isEmpty()) {
|
|
||||||
this.listenerMap.remove(eventClass);
|
|
||||||
} else if (result < 0) {
|
|
||||||
throw new IllegalStateException("Something wrong happened, listener count: " + result);
|
|
||||||
}
|
}
|
||||||
if (parent != null) {
|
// TODO bindings?
|
||||||
parent.propagateChildCountChange(eventClass, count);
|
for (EventNodeImpl<T> child : children) {
|
||||||
|
child.invalidateEventsFor(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void propagateToParent(Class<? extends T> eventClass, int count) {
|
private void invalidateEvent(Class<? extends T> eventClass) {
|
||||||
final var parent = this.parent;
|
forTargetEvents(eventClass, type -> {
|
||||||
if (parent != null) {
|
Handle<? super T> handle = handleMap.get(type);
|
||||||
synchronized (parent.lock) {
|
if (handle != null) handle.updated = false;
|
||||||
parent.propagateChildCountChange(eventClass, count);
|
});
|
||||||
}
|
final EventNodeImpl<? super T> parent = this.parent;
|
||||||
}
|
if (parent != null) parent.invalidateEvent(eventClass);
|
||||||
}
|
|
||||||
|
|
||||||
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 ListenerEntry<T> getEntry(Class<? extends T> type) {
|
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) {
|
private static boolean equals(EventNode<?> node, String name, Class<?> eventType) {
|
||||||
final boolean nameCheck = node.getName().equals(name);
|
return node.getName().equals(name) && eventType.isAssignableFrom((node.getEventType()));
|
||||||
final boolean typeCheck = eventType.isAssignableFrom(((EventNodeImpl<?>) node).eventType);
|
}
|
||||||
return nameCheck && typeCheck;
|
|
||||||
|
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 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 List<EventListener<T>> listeners = new CopyOnWriteArrayList<>();
|
||||||
final Set<Consumer<T>> bindingConsumers = new CopyOnWriteArraySet<>();
|
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.node = node;
|
||||||
this.filters = FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(eventType)).collect(Collectors.toList());
|
this.eventType = eventType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void call(T event) {
|
@Override
|
||||||
// Event interfaces
|
public void call(@NotNull E event) {
|
||||||
if (!bindingConsumers.isEmpty()) {
|
final Consumer<E> listener = updatedListener();
|
||||||
for (var consumer : bindingConsumers) {
|
if (listener == null) return;
|
||||||
consumer.accept(event);
|
try {
|
||||||
}
|
listener.accept(event);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
MinecraftServer.getExceptionManager().handleException(e);
|
||||||
}
|
}
|
||||||
// Mapped listeners
|
}
|
||||||
if (!mappedNode.isEmpty()) {
|
|
||||||
synchronized (node.mappedNodeCache) {
|
@Override
|
||||||
// Check mapped listeners for each individual event handler
|
public boolean hasListener() {
|
||||||
for (var filter : filters) {
|
return updatedListener() != null;
|
||||||
final var handler = filter.castHandler(event);
|
}
|
||||||
final var map = mappedNode.get(handler);
|
|
||||||
if (map != null) map.call(event);
|
@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) {
|
private @Nullable Consumer<E> createConsumer() {
|
||||||
EventListener.Result result;
|
// Standalone listeners
|
||||||
try {
|
List<Consumer<E>> listeners = new ArrayList<>();
|
||||||
result = listener.run(event);
|
forTargetEvents(eventType, type -> {
|
||||||
} catch (Exception e) {
|
final ListenerEntry<E> entry = node.listenerMap.get(type);
|
||||||
result = EventListener.Result.EXCEPTION;
|
if (entry != null) {
|
||||||
MinecraftServer.getExceptionManager().handleException(e);
|
final Consumer<E> result = listenersConsumer(entry);
|
||||||
}
|
if (result != null) listeners.add(result);
|
||||||
if (result == EventListener.Result.EXPIRED) {
|
}
|
||||||
listeners.remove(listener);
|
});
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
package net.minestom.server.event.entity;
|
package net.minestom.server.event.entity;
|
||||||
|
|
||||||
import net.minestom.server.entity.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;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a player does a left click on an entity or with
|
* Called when a player does a left click on an entity or with
|
||||||
* {@link net.minestom.server.entity.EntityCreature#attack(Entity)}.
|
* {@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 entity;
|
||||||
private final Entity target;
|
private final Entity target;
|
||||||
|
|
|
@ -4,13 +4,13 @@ import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.LivingEntity;
|
import net.minestom.server.entity.LivingEntity;
|
||||||
import net.minestom.server.entity.damage.DamageType;
|
import net.minestom.server.entity.damage.DamageType;
|
||||||
import net.minestom.server.event.trait.CancellableEvent;
|
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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called with {@link LivingEntity#damage(DamageType, float)}.
|
* 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 Entity entity;
|
||||||
private final DamageType damageType;
|
private final DamageType damageType;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package net.minestom.server.event.entity;
|
package net.minestom.server.event.entity;
|
||||||
|
|
||||||
import net.minestom.server.entity.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;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class EntityDeathEvent implements EntityEvent {
|
public class EntityDeathEvent implements EntityInstanceEvent {
|
||||||
|
|
||||||
// TODO cause
|
// TODO cause
|
||||||
private final Entity entity;
|
private final Entity entity;
|
||||||
|
|
|
@ -2,13 +2,13 @@ package net.minestom.server.event.entity;
|
||||||
|
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.event.trait.CancellableEvent;
|
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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.temporal.TemporalUnit;
|
import java.time.temporal.TemporalUnit;
|
||||||
|
|
||||||
public class EntityFireEvent implements EntityEvent, CancellableEvent {
|
public class EntityFireEvent implements EntityInstanceEvent, CancellableEvent {
|
||||||
|
|
||||||
private final Entity entity;
|
private final Entity entity;
|
||||||
private Duration duration;
|
private Duration duration;
|
||||||
|
|
|
@ -3,16 +3,16 @@ package net.minestom.server.event.entity;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.ItemEntity;
|
import net.minestom.server.entity.ItemEntity;
|
||||||
import net.minestom.server.event.trait.CancellableEvent;
|
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 net.minestom.server.item.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when two {@link ItemEntity} are merging their {@link ItemStack} together to form a sole entity.
|
* 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 final ItemEntity merged;
|
||||||
private ItemStack result;
|
private ItemStack result;
|
||||||
|
|
||||||
|
@ -31,9 +31,8 @@ public class EntityItemMergeEvent implements EntityEvent, CancellableEvent {
|
||||||
*
|
*
|
||||||
* @return the source ItemEntity
|
* @return the source ItemEntity
|
||||||
*/
|
*/
|
||||||
@NotNull
|
|
||||||
@Override
|
@Override
|
||||||
public ItemEntity getEntity() {
|
public @NotNull ItemEntity getEntity() {
|
||||||
return (ItemEntity) entity;
|
return (ItemEntity) entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +43,7 @@ public class EntityItemMergeEvent implements EntityEvent, CancellableEvent {
|
||||||
*
|
*
|
||||||
* @return the merged ItemEntity
|
* @return the merged ItemEntity
|
||||||
*/
|
*/
|
||||||
@NotNull
|
public @NotNull ItemEntity getMerged() {
|
||||||
public ItemEntity getMerged() {
|
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,8 +52,7 @@ public class EntityItemMergeEvent implements EntityEvent, CancellableEvent {
|
||||||
*
|
*
|
||||||
* @return the item stack
|
* @return the item stack
|
||||||
*/
|
*/
|
||||||
@NotNull
|
public @NotNull ItemStack getResult() {
|
||||||
public ItemStack getResult() {
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package net.minestom.server.event.entity;
|
package net.minestom.server.event.entity;
|
||||||
|
|
||||||
import net.minestom.server.entity.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 net.minestom.server.potion.Potion;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class EntityPotionAddEvent implements EntityEvent {
|
public class EntityPotionAddEvent implements EntityInstanceEvent {
|
||||||
|
|
||||||
private final Entity entity;
|
private final Entity entity;
|
||||||
private final Potion potion;
|
private final Potion potion;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package net.minestom.server.event.entity;
|
package net.minestom.server.event.entity;
|
||||||
|
|
||||||
import net.minestom.server.entity.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 net.minestom.server.potion.Potion;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class EntityPotionRemoveEvent implements EntityEvent {
|
public class EntityPotionRemoveEvent implements EntityInstanceEvent {
|
||||||
|
|
||||||
private final Entity entity;
|
private final Entity entity;
|
||||||
private final Potion potion;
|
private final Potion potion;
|
||||||
|
|
|
@ -4,13 +4,13 @@ import net.minestom.server.coordinate.Point;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.EntityProjectile;
|
import net.minestom.server.entity.EntityProjectile;
|
||||||
import net.minestom.server.event.trait.CancellableEvent;
|
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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called with {@link EntityProjectile#shoot(Point, double, double)}
|
* 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 entity;
|
||||||
private final Entity projectile;
|
private final Entity projectile;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package net.minestom.server.event.entity;
|
package net.minestom.server.event.entity;
|
||||||
|
|
||||||
import net.minestom.server.entity.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 net.minestom.server.instance.Instance;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a new instance is set for an entity.
|
* 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 Entity entity;
|
||||||
private final Instance spawnInstance;
|
private final Instance spawnInstance;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package net.minestom.server.event.entity;
|
package net.minestom.server.event.entity;
|
||||||
|
|
||||||
import net.minestom.server.entity.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;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an entity ticks itself.
|
* Called when an entity ticks itself.
|
||||||
* Same event instance used for all tick events for the same entity.
|
* 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;
|
private final Entity entity;
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue